mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
refactor(test): fix naming and add comments
This commit is contained in:
parent
423e799b5d
commit
281fd55e63
5 changed files with 250 additions and 149 deletions
|
@ -3,7 +3,7 @@ import { SignInMode, SignInIdentifier, ConnectorType } from '@logto/schemas';
|
||||||
import { updateSignInExperience } from '#src/api/sign-in-experience.js';
|
import { updateSignInExperience } from '#src/api/sign-in-experience.js';
|
||||||
import { demoAppUrl } from '#src/constants.js';
|
import { demoAppUrl } from '#src/constants.js';
|
||||||
import { clearConnectorsByTypes } from '#src/helpers/connector.js';
|
import { clearConnectorsByTypes } from '#src/helpers/connector.js';
|
||||||
import ExpectJourney from '#src/ui-helpers/expect-journey.js';
|
import ExpectFlows from '#src/ui-helpers/expect-flows.js';
|
||||||
|
|
||||||
const credentials = {
|
const credentials = {
|
||||||
username: 'test_bootstrap',
|
username: 'test_bootstrap',
|
||||||
|
@ -42,7 +42,7 @@ describe('smoke testing on the demo app', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to create a new account with a credential preset', async () => {
|
it('should be able to create a new account with a credential preset', async () => {
|
||||||
const journey = new ExpectJourney(await browser.newPage());
|
const journey = new ExpectFlows(await browser.newPage());
|
||||||
|
|
||||||
// Open the demo app and navigate to the register page
|
// Open the demo app and navigate to the register page
|
||||||
await journey.startWith(demoAppUrl, 'register');
|
await journey.startWith(demoAppUrl, 'register');
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { ConnectorType, SignInIdentifier, SignInMode } from '@logto/schemas';
|
||||||
import { updateSignInExperience } from '#src/api/sign-in-experience.js';
|
import { updateSignInExperience } from '#src/api/sign-in-experience.js';
|
||||||
import { demoAppUrl } from '#src/constants.js';
|
import { demoAppUrl } from '#src/constants.js';
|
||||||
import { clearConnectorsByTypes, setEmailConnector } from '#src/helpers/connector.js';
|
import { clearConnectorsByTypes, setEmailConnector } from '#src/helpers/connector.js';
|
||||||
import ExpectJourney from '#src/ui-helpers/expect-journey.js';
|
import ExpectFlows from '#src/ui-helpers/expect-flows.js';
|
||||||
import { waitFor } from '#src/utils.js';
|
import { waitFor } from '#src/utils.js';
|
||||||
|
|
||||||
describe('password policy', () => {
|
describe('password policy', () => {
|
||||||
|
@ -60,7 +60,7 @@ describe('password policy', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work for username + password', async () => {
|
it('should work for username + password', async () => {
|
||||||
const journey = new ExpectJourney(await browser.newPage(), { forgotPassword: true });
|
const journey = new ExpectFlows(await browser.newPage(), { forgotPassword: true });
|
||||||
|
|
||||||
// Open the demo app and navigate to the register page
|
// Open the demo app and navigate to the register page
|
||||||
await journey.startWith(demoAppUrl, 'register');
|
await journey.startWith(demoAppUrl, 'register');
|
||||||
|
@ -86,7 +86,7 @@ describe('password policy', () => {
|
||||||
verify: true,
|
verify: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const journey = new ExpectJourney(await browser.newPage(), { forgotPassword: true });
|
const journey = new ExpectFlows(await browser.newPage(), { forgotPassword: true });
|
||||||
|
|
||||||
// Open the demo app and navigate to the register page
|
// Open the demo app and navigate to the register page
|
||||||
await journey.startWith(demoAppUrl, 'register');
|
await journey.startWith(demoAppUrl, 'register');
|
||||||
|
@ -108,7 +108,7 @@ describe('password policy', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work for forgot password', async () => {
|
it('should work for forgot password', async () => {
|
||||||
const journey = new ExpectJourney(await browser.newPage(), { forgotPassword: true });
|
const journey = new ExpectFlows(await browser.newPage(), { forgotPassword: true });
|
||||||
|
|
||||||
// Open the demo app and navigate to the register page
|
// Open the demo app and navigate to the register page
|
||||||
await journey.startWith(demoAppUrl, 'sign-in');
|
await journey.startWith(demoAppUrl, 'sign-in');
|
||||||
|
|
186
packages/integration-tests/src/ui-helpers/expect-flows.ts
Normal file
186
packages/integration-tests/src/ui-helpers/expect-flows.ts
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
import { appendPath } from '@silverhand/essentials';
|
||||||
|
|
||||||
|
import { logtoUrl } from '#src/constants.js';
|
||||||
|
import { readVerificationCode } from '#src/helpers/index.js';
|
||||||
|
|
||||||
|
import ExpectPage from './expect-page.js';
|
||||||
|
|
||||||
|
const demoAppUrl = appendPath(new URL(logtoUrl), 'demo-app');
|
||||||
|
|
||||||
|
/** Remove the query string together with the `?` from a URL string. */
|
||||||
|
const stripQuery = (url: string) => url.split('?')[0];
|
||||||
|
|
||||||
|
export type FlowsType = 'sign-in' | 'register' | 'continue' | 'forgot-password';
|
||||||
|
|
||||||
|
export type FlowsPath =
|
||||||
|
| FlowsType
|
||||||
|
| `${FlowsType}/password`
|
||||||
|
| `${FlowsType}/verify`
|
||||||
|
| `${FlowsType}/verification-code`
|
||||||
|
| `forgot-password/reset`;
|
||||||
|
|
||||||
|
export type ExpectFlowsOptions = {
|
||||||
|
/** The URL of the flows endpoint. */
|
||||||
|
endpoint?: URL;
|
||||||
|
/**
|
||||||
|
* Whether the forgot password flow is enabled.
|
||||||
|
*
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
forgotPassword?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type OngoingFlows = {
|
||||||
|
type: FlowsType;
|
||||||
|
initialUrl: URL;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class that provides:
|
||||||
|
*
|
||||||
|
* - A set of methods to navigate to a specific page for a flows.
|
||||||
|
* - A set of methods to assert the state of a flows and its side effects.
|
||||||
|
*/
|
||||||
|
export default class ExpectFlows extends ExpectPage {
|
||||||
|
readonly options: Required<ExpectFlowsOptions>;
|
||||||
|
|
||||||
|
protected get flowsType() {
|
||||||
|
if (this.#ongoing === undefined) {
|
||||||
|
return this.throwNoOngoingFlowsError();
|
||||||
|
}
|
||||||
|
return this.#ongoing.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ongoing?: OngoingFlows;
|
||||||
|
|
||||||
|
constructor(thePage = global.page, options: ExpectFlowsOptions = {}) {
|
||||||
|
super(thePage);
|
||||||
|
this.options = {
|
||||||
|
endpoint: new URL(logtoUrl),
|
||||||
|
forgotPassword: false,
|
||||||
|
...options,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start flows with the given initial URL. Expect the initial URL is protected by Logto, and
|
||||||
|
* navigate to the flows sign-in page if unauthenticated.
|
||||||
|
*
|
||||||
|
* If the flows can be started, the instance will be marked as ongoing.
|
||||||
|
*
|
||||||
|
* @param initialUrl The initial URL to start the flows with.
|
||||||
|
* @param type The type of flows to expect. If it's `register`, it will try to click the "Create
|
||||||
|
* account" link on the sign-in page.
|
||||||
|
*/
|
||||||
|
async startWith(initialUrl = demoAppUrl, type: FlowsType = 'sign-in') {
|
||||||
|
await this.toStart(initialUrl);
|
||||||
|
this.toBeAt('sign-in');
|
||||||
|
|
||||||
|
if (type === 'register') {
|
||||||
|
await this.toClick('a', 'Create account');
|
||||||
|
this.toBeAt('register');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#ongoing = { type, initialUrl };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure the flows is ongoing and the page is at the initial URL; then try to click the "sign out"
|
||||||
|
* button (case-insensitive) and close the page.
|
||||||
|
*
|
||||||
|
* It will clear the ongoing flows if the flows is ended successfully.
|
||||||
|
*/
|
||||||
|
async verifyThenEnd() {
|
||||||
|
if (this.#ongoing === undefined) {
|
||||||
|
return this.throwNoOngoingFlowsError();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.toMatchUrl(this.#ongoing.initialUrl);
|
||||||
|
await this.toClick('div[role=button]', /sign out/i);
|
||||||
|
|
||||||
|
this.#ongoing = undefined;
|
||||||
|
await this.page.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert the page is at the given flows path.
|
||||||
|
*
|
||||||
|
* @param pathname The flows path to assert.
|
||||||
|
*/
|
||||||
|
toBeAt(pathname: FlowsPath) {
|
||||||
|
const stripped = stripQuery(this.page.url());
|
||||||
|
expect(stripped).toBe(this.buildFlowsUrl(pathname).href);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert the page is at the verification code page and fill the verification code input with the
|
||||||
|
* code from Logto database.
|
||||||
|
*
|
||||||
|
* @param type The type of flows to expect.
|
||||||
|
*/
|
||||||
|
async toCompleteVerification(type: FlowsType) {
|
||||||
|
this.toBeAt(`${type}/verification-code`);
|
||||||
|
const { code } = await readVerificationCode();
|
||||||
|
|
||||||
|
for (const [index, char] of code.split('').entries()) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await this.toFillInput(`passcode_${index}`, char);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill the password inputs with the given passwords. If the password is an array, the second
|
||||||
|
* element will be used to assert the error message; otherwise, the password is expected to be
|
||||||
|
* valid and the form will be submitted.
|
||||||
|
*
|
||||||
|
* @param passwords The passwords to fill.
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* In the following example, the first password is expected to be rejected with the error message
|
||||||
|
* "simple password" (case-insensitive), and the second password is expected to be accepted.
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* await journey.toFillPasswords(
|
||||||
|
* [credentials.pwnedPassword, 'simple password'],
|
||||||
|
* credentials.password,
|
||||||
|
* );
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
async toFillPasswords(
|
||||||
|
...passwords: Array<string | [password: string, errorMessage: string | RegExp]>
|
||||||
|
) {
|
||||||
|
for (const element of passwords) {
|
||||||
|
const [password, errorMessage] = Array.isArray(element) ? element : [element, undefined];
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await this.toFillForm(
|
||||||
|
this.options.forgotPassword
|
||||||
|
? { newPassword: password }
|
||||||
|
: {
|
||||||
|
newPassword: password,
|
||||||
|
confirmPassword: password,
|
||||||
|
},
|
||||||
|
{ submit: true, shouldNavigate: errorMessage === undefined }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (errorMessage === undefined) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
// Reject the password and assert the error message
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await this.toMatchAlert(
|
||||||
|
typeof errorMessage === 'string' ? new RegExp(errorMessage, 'i') : errorMessage
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Build a full flows URL from a pathname. */
|
||||||
|
protected buildFlowsUrl(pathname = '') {
|
||||||
|
return appendPath(this.options.endpoint, pathname);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected throwNoOngoingFlowsError() {
|
||||||
|
return this.throwError('The flows has not started yet. Use `startWith` to start the flows.');
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,143 +0,0 @@
|
||||||
import { appendPath } from '@silverhand/essentials';
|
|
||||||
|
|
||||||
import { logtoUrl } from '#src/constants.js';
|
|
||||||
import { readVerificationCode } from '#src/helpers/index.js';
|
|
||||||
|
|
||||||
import ExpectPage from './expect-page.js';
|
|
||||||
|
|
||||||
const demoAppUrl = appendPath(new URL(logtoUrl), 'demo-app');
|
|
||||||
|
|
||||||
/** Remove the query string together with the `?` from a URL string. */
|
|
||||||
const stripQuery = (url: string) => url.split('?')[0];
|
|
||||||
|
|
||||||
export type JourneyType = 'sign-in' | 'register' | 'continue' | 'forgot-password';
|
|
||||||
|
|
||||||
export type JourneyPath =
|
|
||||||
| JourneyType
|
|
||||||
| `${JourneyType}/password`
|
|
||||||
| `${JourneyType}/verify`
|
|
||||||
| `${JourneyType}/verification-code`
|
|
||||||
| `forgot-password/reset`;
|
|
||||||
|
|
||||||
export type ExpectJourneyOptions = {
|
|
||||||
/** The URL of the journey endpoint. */
|
|
||||||
endpoint?: URL;
|
|
||||||
/**
|
|
||||||
* Whether the forgot password flow is enabled.
|
|
||||||
*
|
|
||||||
* @default false
|
|
||||||
*/
|
|
||||||
forgotPassword?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
type OngoingJourney = {
|
|
||||||
type: JourneyType;
|
|
||||||
initialUrl: URL;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A class that provides:
|
|
||||||
*
|
|
||||||
* - A set of methods to navigate to a specific page for a journey.
|
|
||||||
* - A set of methods to assert the state of a journey and its side effects.
|
|
||||||
*/
|
|
||||||
export default class ExpectJourney extends ExpectPage {
|
|
||||||
readonly options: Required<ExpectJourneyOptions>;
|
|
||||||
|
|
||||||
protected get journeyType() {
|
|
||||||
if (this.#ongoing === undefined) {
|
|
||||||
return this.throwNoOngoingJourneyError();
|
|
||||||
}
|
|
||||||
return this.#ongoing.type;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ongoing?: OngoingJourney;
|
|
||||||
|
|
||||||
constructor(thePage = global.page, options: ExpectJourneyOptions = {}) {
|
|
||||||
super(thePage);
|
|
||||||
this.options = {
|
|
||||||
endpoint: new URL(logtoUrl),
|
|
||||||
forgotPassword: false,
|
|
||||||
...options,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async startWith(initialUrl = demoAppUrl, type: JourneyType = 'sign-in') {
|
|
||||||
await this.toStart(initialUrl);
|
|
||||||
this.toBeAt('sign-in');
|
|
||||||
|
|
||||||
if (type === 'register') {
|
|
||||||
await this.toClick('a', 'Create account');
|
|
||||||
this.toBeAt('register');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.#ongoing = { type, initialUrl };
|
|
||||||
}
|
|
||||||
|
|
||||||
async verifyThenEnd() {
|
|
||||||
if (this.#ongoing === undefined) {
|
|
||||||
return this.throwNoOngoingJourneyError();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.toMatchUrl(this.#ongoing.initialUrl);
|
|
||||||
await this.toClick('div[role=button]', /sign out/i);
|
|
||||||
|
|
||||||
this.#ongoing = undefined;
|
|
||||||
await this.page.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
toBeAt(mode: JourneyPath) {
|
|
||||||
const stripped = stripQuery(this.page.url());
|
|
||||||
expect(stripped).toBe(this.buildJourneyUrl(mode).href);
|
|
||||||
}
|
|
||||||
|
|
||||||
async toCompleteVerification(type: JourneyType) {
|
|
||||||
this.toBeAt(`${type}/verification-code`);
|
|
||||||
const { code } = await readVerificationCode();
|
|
||||||
|
|
||||||
for (const [index, char] of code.split('').entries()) {
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
await this.toFillInput(`passcode_${index}`, char);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async toFillPasswords(
|
|
||||||
...passwords: Array<string | [password: string, errorMessage: string | RegExp]>
|
|
||||||
) {
|
|
||||||
for (const element of passwords) {
|
|
||||||
const [password, errorMessage] = Array.isArray(element) ? element : [element, undefined];
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
await this.toFillForm(
|
|
||||||
this.options.forgotPassword
|
|
||||||
? { newPassword: password }
|
|
||||||
: {
|
|
||||||
newPassword: password,
|
|
||||||
confirmPassword: password,
|
|
||||||
},
|
|
||||||
{ submit: true, shouldNavigate: errorMessage === undefined }
|
|
||||||
);
|
|
||||||
|
|
||||||
if (errorMessage === undefined) {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
// Reject the password and assert the error message
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
await this.toMatchAlert(
|
|
||||||
typeof errorMessage === 'string' ? new RegExp(errorMessage, 'i') : errorMessage
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Build a full journey URL from a pathname. */
|
|
||||||
protected buildJourneyUrl(pathname = '') {
|
|
||||||
return appendPath(this.options.endpoint, pathname);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected throwNoOngoingJourneyError() {
|
|
||||||
return this.throwError(
|
|
||||||
'The journey has not started yet. Use `startWith` to start the journey.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,6 +2,7 @@ import { type ElementHandle, type Page } from 'puppeteer';
|
||||||
|
|
||||||
import { expectNavigation } from '#src/utils.js';
|
import { expectNavigation } from '#src/utils.js';
|
||||||
|
|
||||||
|
/** Error thrown by {@link ExpectPage}. */
|
||||||
class ExpectPageError extends Error {
|
class ExpectPageError extends Error {
|
||||||
constructor(
|
constructor(
|
||||||
message: string,
|
message: string,
|
||||||
|
@ -17,20 +18,43 @@ class ExpectPageError extends Error {
|
||||||
export default class ExpectPage {
|
export default class ExpectPage {
|
||||||
constructor(public readonly page = global.page) {}
|
constructor(public readonly page = global.page) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to the given URL and wait for the page to load. Assert that an element with ID `app`
|
||||||
|
* is present.
|
||||||
|
*
|
||||||
|
* @param initialUrl The URL to navigate to.
|
||||||
|
*/
|
||||||
async toStart(initialUrl: URL) {
|
async toStart(initialUrl: URL) {
|
||||||
await expectNavigation(this.page.goto(initialUrl.href), this.page);
|
await expectNavigation(this.page.goto(initialUrl.href), this.page);
|
||||||
await expect(this.page).toMatchElement('#app');
|
await expect(this.page).toMatchElement('#app');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Click on the element matching the given selector and text.
|
||||||
|
*
|
||||||
|
* @param selector The selector to match.
|
||||||
|
* @param text The text to match, if provided.
|
||||||
|
* @param shouldNavigate Whether the click should trigger a navigation. Defaults to `true`.
|
||||||
|
*/
|
||||||
async toClick(selector: string, text?: string | RegExp, shouldNavigate = true) {
|
async toClick(selector: string, text?: string | RegExp, shouldNavigate = true) {
|
||||||
const clicked = expect(this.page).toClick(selector, { text });
|
const clicked = expect(this.page).toClick(selector, { text });
|
||||||
return shouldNavigate ? expectNavigation(clicked, this.page) : clicked;
|
return shouldNavigate ? expectNavigation(clicked, this.page) : clicked;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Click on the `<button type="submit">` element on the page.
|
||||||
|
*
|
||||||
|
* @param shouldNavigate Whether the click should trigger a navigation. Defaults to `true`.
|
||||||
|
*/
|
||||||
async toClickSubmit(shouldNavigate = true) {
|
async toClickSubmit(shouldNavigate = true) {
|
||||||
return this.toClick('button[type=submit]', undefined, shouldNavigate);
|
return this.toClick('button[type=submit]', undefined, shouldNavigate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the `<form>` element on the page and submit it programmatically.
|
||||||
|
*
|
||||||
|
* @param shouldNavigate Whether the click should trigger a navigation. Defaults to `true`.
|
||||||
|
*/
|
||||||
async toSubmit(shouldNavigate = true) {
|
async toSubmit(shouldNavigate = true) {
|
||||||
const form = await expect(this.page).toMatchElement('form');
|
const form = await expect(this.page).toMatchElement('form');
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
@ -40,6 +64,16 @@ export default class ExpectPage {
|
||||||
return shouldNavigate ? expectNavigation(submitted, this.page) : submitted;
|
return shouldNavigate ? expectNavigation(submitted, this.page) : submitted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill an `<input>` with the given name with the given value and optionally submit the form.
|
||||||
|
*
|
||||||
|
* @param name The name of the input to fill.
|
||||||
|
* @param value The value to fill the input with.
|
||||||
|
* @param options Options to control the behavior of the method.
|
||||||
|
* @param options.submit Whether to submit the form after filling the input. Defaults to `false`.
|
||||||
|
* @param options.shouldNavigate Whether the submit should trigger a navigation. Defaults to `true`. Note that this
|
||||||
|
* option is ignored if `options.submit` is `false`.
|
||||||
|
*/
|
||||||
async toFillInput(
|
async toFillInput(
|
||||||
name: string,
|
name: string,
|
||||||
value: string,
|
value: string,
|
||||||
|
@ -51,6 +85,15 @@ export default class ExpectPage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill a `<form>` with the given values and optionally submit it.
|
||||||
|
*
|
||||||
|
* @param values The key-value object of values to fill the form with.
|
||||||
|
* @param options Options to control the behavior of the method.
|
||||||
|
* @param options.submit Whether to submit the form after filling the input. Defaults to `false`.
|
||||||
|
* @param options.shouldNavigate Whether the submit should trigger a navigation. Defaults to `true`. Note that this
|
||||||
|
* option is ignored if `options.submit` is `false`.
|
||||||
|
*/
|
||||||
async toFillForm(
|
async toFillForm(
|
||||||
values: Record<string, string>,
|
values: Record<string, string>,
|
||||||
options?: { submit: true; shouldNavigate?: boolean }
|
options?: { submit: true; shouldNavigate?: boolean }
|
||||||
|
@ -61,14 +104,29 @@ export default class ExpectPage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expect the page to match an element with `role="alert"` and optionally with the given text.
|
||||||
|
*
|
||||||
|
* @param text The text to match, if provided.
|
||||||
|
*/
|
||||||
async toMatchAlert(text?: string | RegExp): Promise<ElementHandle> {
|
async toMatchAlert(text?: string | RegExp): Promise<ElementHandle> {
|
||||||
return expect(this.page).toMatchElement('*[role=alert]', { text });
|
return expect(this.page).toMatchElement('*[role=alert]', { text });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expect the page's URL to match the given URL.
|
||||||
|
*
|
||||||
|
* @param url The URL to match.
|
||||||
|
*/
|
||||||
toMatchUrl(url: URL | string) {
|
toMatchUrl(url: URL | string) {
|
||||||
expect(this.page.url()).toBe(typeof url === 'string' ? url : url.href);
|
expect(this.page.url()).toBe(typeof url === 'string' ? url : url.href);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expect a toast to appear with the given text, then remove it immediately.
|
||||||
|
*
|
||||||
|
* @param text The text to match.
|
||||||
|
*/
|
||||||
async waitForToast(text: string | RegExp) {
|
async waitForToast(text: string | RegExp) {
|
||||||
const toast = await expect(this.page).toMatchElement(`.ReactModal__Content[class*=toast]`, {
|
const toast = await expect(this.page).toMatchElement(`.ReactModal__Content[class*=toast]`, {
|
||||||
text,
|
text,
|
||||||
|
|
Loading…
Reference in a new issue