feat(expect): introduce explicit default async expect timeout (#8071)

This commit is contained in:
Pavel Feldman 2021-08-07 22:08:56 -07:00 committed by GitHub
parent 755cf60496
commit 290f601dae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 37 additions and 32 deletions

View File

@ -118,7 +118,7 @@ export default config;
## property: Fixtures.actionTimeout
- type: <[int]>
Timeout for each action and expect in milliseconds. Defaults to 0 (no timeout).
Default timeout for each Playwright action in milliseconds, defaults to 0 (no timeout).
This is a default timeout for all Playwright actions, same as configured via [`method: Page.setDefaultTimeout`].

View File

@ -106,6 +106,7 @@ export default config;
## property: TestProject.expect
- type: <[Object]>
- `timeout` <[float]> Default timeout for async expect matchers in milliseconds, defaults to 5000ms.
- `toMatchSnapshot` <[Object]>
- `threshold` <[float]> Image matching threshold between zero (strict) and one (lax).

View File

@ -25,14 +25,14 @@ await expect(page.locator('.status')).toHaveText('Submitted');
Playwright Test will be re-testing the node with the selector `.status` until fetched Node has the `"Submitted"`
text. It will be re-fetching the node and checking it over and over, until the condition is met or until the timeout is
reached. You can either pass this timeout or configure it once via the [`property: Fixtures.actionTimeout`] value
reached. You can either pass this timeout or configure it once via the [`property: TestProject.expect`] value
in test config.
By default, the timeout for assertions is not set, so it'll wait forever, until the whole test times out.
## expect(locator).toBeChecked
- `options`
- `timeout`: <[number]> Time to retry assertion for, defaults to [`property: Fixtures.actionTimeout`].
- `timeout`: <[number]> Time to retry assertion for, defaults to `timeout` in [`property: TestProject.expect`].
Ensures [Locator] points to the checked input.
@ -43,7 +43,7 @@ await expect(locator).toBeChecked();
## expect(locator).toBeDisabled
- `options`
- `timeout`: <[number]> Time to retry assertion for, defaults to [`property: Fixtures.actionTimeout`].
- `timeout`: <[number]> Time to retry assertion for, defaults to `timeout` in [`property: TestProject.expect`].
Ensures [Locator] points to a disabled element.
@ -54,7 +54,7 @@ await expect(locator).toBeDisabled();
## expect(locator).toBeEditable
- `options`
- `timeout`: <[number]> Time to retry assertion for, defaults to [`property: Fixtures.actionTimeout`].
- `timeout`: <[number]> Time to retry assertion for, defaults to `timeout` in [`property: TestProject.expect`].
Ensures [Locator] points to an editable element.
@ -65,7 +65,7 @@ await expect(locator).toBeEditable();
## expect(locator).toBeEmpty
- `options`
- `timeout`: <[number]> Time to retry assertion for, defaults to [`property: Fixtures.actionTimeout`].
- `timeout`: <[number]> Time to retry assertion for, defaults to `timeout` in [`property: TestProject.expect`].
Ensures [Locator] points to an empty editable element or to a DOM node that has no text.
@ -76,7 +76,7 @@ await expect(locator).toBeEmpty();
## expect(locator).toBeEnabled
- `options`
- `timeout`: <[number]> Time to retry assertion for, defaults to [`property: Fixtures.actionTimeout`].
- `timeout`: <[number]> Time to retry assertion for, defaults to `timeout` in [`property: TestProject.expect`].
Ensures [Locator] points to an enabled element.
@ -87,7 +87,7 @@ await expect(locator).toBeEnabled();
## expect(locator).toBeFocused
- `options`
- `timeout`: <[number]> Time to retry assertion for, defaults to [`property: Fixtures.actionTimeout`].
- `timeout`: <[number]> Time to retry assertion for, defaults to `timeout` in [`property: TestProject.expect`].
Ensures [Locator] points to a focused DOM node.
@ -98,7 +98,7 @@ await expect(locator).toBeFocused();
## expect(locator).toBeHidden
- `options`
- `timeout`: <[number]> Time to retry assertion for, defaults to [`property: Fixtures.actionTimeout`].
- `timeout`: <[number]> Time to retry assertion for, defaults to `timeout` in [`property: TestProject.expect`].
Ensures [Locator] points to a hidden DOM node, which is the opposite of [visible](./actionability.md#visible).
@ -109,7 +109,7 @@ await expect(locator).toBeHidden();
## expect(locator).toBeVisible
- `options`
- `timeout`: <[number]> Time to retry assertion for, defaults to [`property: Fixtures.actionTimeout`].
- `timeout`: <[number]> Time to retry assertion for, defaults to `timeout` in [`property: TestProject.expect`].
Ensures [Locator] points to a [visible](./actionability.md#visible) DOM node.
@ -121,7 +121,7 @@ await expect(locator).toBeVisible();
## expect(locator).toContainText(text, options?)
- `text`: <[string]> Text to look for inside the element
- `options`
- `timeout`: <[number]> Time to wait for, defaults to [`property: Fixtures.actionTimeout`].
- `timeout`: <[number]> Time to wait for, defaults to `timeout` in [`property: TestProject.expect`].
- `useInnerText`: <[boolean]> Whether to use `element.innerText` instead of `element.textContent` when retrieving DOM node text.
Ensures [Locator] points to a selected option.
@ -135,7 +135,7 @@ await expect(locator).toContainText('substring');
- `name`: <[string]> Attribute name
- `value`: <[string]|[RegExp]> Attribute value
- `options`
- `timeout`: <[number]> Time to retry assertion for, defaults to [`property: Fixtures.actionTimeout`].
- `timeout`: <[number]> Time to retry assertion for, defaults to `timeout` in [`property: TestProject.expect`].
Ensures [Locator] points to an element with given attribute.
@ -147,7 +147,7 @@ await expect(locator).toHaveAttribute('type', 'text');
## expect(locator).toHaveClass(expected)
- `expected`: <[string] | [RegExp] | [Array]<[string]>>
- `options`
- `timeout`: <[number]> Time to retry assertion for, defaults to [`property: Fixtures.actionTimeout`].
- `timeout`: <[number]> Time to retry assertion for, defaults to `timeout` in [`property: TestProject.expect`].
Ensures [Locator] points to an element with given CSS class.
@ -166,7 +166,7 @@ await expect(locator).toHaveClass(['component', 'component selected', 'component
## expect(locator).toHaveCount(count)
- `count`: <[number]>
- `options`
- `timeout`: <[number]> Time to retry assertion for, defaults to [`property: Fixtures.actionTimeout`].
- `timeout`: <[number]> Time to retry assertion for, defaults to `timeout` in [`property: TestProject.expect`].
Ensures [Locator] resolves to an exact number of DOM nodes.
@ -179,7 +179,7 @@ await expect(list).toHaveCount(3);
- `name`: <[string]> CSS property name
- `value`: <[string]|[RegExp]> CSS property value
- `options`
- `timeout`: <[number]> Time to retry assertion for, defaults to [`property: Fixtures.actionTimeout`].
- `timeout`: <[number]> Time to retry assertion for, defaults to `timeout` in [`property: TestProject.expect`].
Ensures [Locator] resolves to an element with the given computed CSS style
@ -191,7 +191,7 @@ await expect(locator).toHaveCSS('display', 'flex');
## expect(locator).toHaveId(id)
- `id`: <[string]> Element id
- `options`
- `timeout`: <[number]> Time to retry assertion for, defaults to [`property: Fixtures.actionTimeout`].
- `timeout`: <[number]> Time to retry assertion for, defaults to `timeout` in [`property: TestProject.expect`].
Ensures [Locator] points to an element with the given DOM Node ID.
@ -204,7 +204,7 @@ await expect(locator).toHaveId('lastname');
- `name`: <[string]> Property name
- `value`: <[any]> Property value
- `options`
- `timeout`: <[number]> Time to retry assertion for, defaults to [`property: Fixtures.actionTimeout`].
- `timeout`: <[number]> Time to retry assertion for, defaults to `timeout` in [`property: TestProject.expect`].
Ensures [Locator] points to an element with given JavaScript property. Note that this property can be
of a primitive type as well as a plain serializable JavaScript object.
@ -217,7 +217,7 @@ await expect(locator).toHaveJSProperty('loaded', true);
## expect(locator).toHaveText(expected, options)
- `expected`: <[string] | [RegExp] | [Array]<[string]>>
- `options`
- `timeout`: <[number]> Time to retry assertion for, defaults to [`property: Fixtures.actionTimeout`].
- `timeout`: <[number]> Time to retry assertion for, defaults to `timeout` in [`property: TestProject.expect`].
- `useInnerText`: <[boolean]> Whether to use `element.innerText` instead of `element.textContent` when retrieving DOM node text.
Ensures [Locator] points to an element with the given text. You can use regular expressions for the value as well.
@ -237,7 +237,7 @@ await expect(locator).toHaveText(['Text 1', 'Text 2', 'Text 3']);
## expect(page).toHaveTitle(title)
- `title`: <[string] | [RegExp]>>
- `options`
- `timeout`: <[number]> Time to retry assertion for, defaults to [`property: Fixtures.actionTimeout`].
- `timeout`: <[number]> Time to retry assertion for, defaults to `timeout` in [`property: TestProject.expect`].
Ensures page has a given title.
@ -248,7 +248,7 @@ await expect(page).toHaveTitle(/.*checkout/);
## expect(page).toHaveURL(url)
- `url`: <[string] | [RegExp]>>
- `options`
- `timeout`: <[number]> Time to retry assertion for, defaults to [`property: Fixtures.actionTimeout`].
- `timeout`: <[number]> Time to retry assertion for, defaults to `timeout` in [`property: TestProject.expect`].
Ensures page is navigated to a given URL.
@ -259,7 +259,7 @@ await expect(page).toHaveURL(/.*checkout/);
## expect(locator).toHaveValue(value)
- `value`: <[string] | [RegExp]>>
- `options`
- `timeout`: <[number]> Time to retry assertion for, defaults to [`property: Fixtures.actionTimeout`].
- `timeout`: <[number]> Time to retry assertion for, defaults to `timeout` in [`property: TestProject.expect`].
Ensures [Locator] points to an element with the given input value. You can use regular expressions for the value as well.

View File

@ -14,7 +14,6 @@
* limitations under the License.
*/
import expectLibrary from 'expect';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
@ -191,7 +190,6 @@ export const test = _baseTest.extend<PlaywrightTestArgs & PlaywrightTestOptions,
};
context.setDefaultTimeout(actionTimeout || 0);
context.setDefaultNavigationTimeout(navigationTimeout || actionTimeout || 0);
expectLibrary.setState({ playwrightActionTimeout: actionTimeout } as any);
context.on('page', page => allPages.push(page));
if (captureTrace) {

View File

@ -44,7 +44,7 @@ export async function toBeTruthy<T>(
let pass = false;
// TODO: interrupt on timeout for nice message.
await pollUntilDeadline(this, async remainingTime => {
await pollUntilDeadline(testInfo, async remainingTime => {
received = await query(remainingTime);
pass = !!received;
return pass === !matcherOptions.isNot;

View File

@ -60,7 +60,7 @@ export async function toEqual<T>(
let pass = false;
// TODO: interrupt on timeout for nice message.
await pollUntilDeadline(this, async remainingTime => {
await pollUntilDeadline(testInfo, async remainingTime => {
received = await query(remainingTime);
pass = equals(received, expected, [iterableEquality]);
return pass === !matcherOptions.isNot;

View File

@ -70,7 +70,7 @@ export async function toMatchText(
let pass = false;
// TODO: interrupt on timeout for nice message.
await pollUntilDeadline(this, async remainingTime => {
await pollUntilDeadline(testInfo, async remainingTime => {
received = await query(remainingTime);
if (options.matchSubstring)
pass = received.includes(expected as string);

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
import type { Expect } from './types';
import type { TestInfoImpl } from './types';
import util from 'util';
import path from 'path';
import type { TestError, Location } from './types';
@ -71,9 +71,11 @@ export async function raceAgainstDeadline<T>(promise: Promise<T>, deadline: numb
return (new DeadlineRunner(promise, deadline)).result;
}
export async function pollUntilDeadline(state: ReturnType<Expect['getState']>, func: (remainingTime: number) => Promise<boolean>, pollTime: number | undefined, deadlinePromise: Promise<void>): Promise<void> {
const playwrightActionTimeout = (state as any).playwrightActionTimeout;
pollTime = pollTime === 0 ? 0 : pollTime || playwrightActionTimeout;
export async function pollUntilDeadline(testInfo: TestInfoImpl, func: (remainingTime: number) => Promise<boolean>, pollTime: number | undefined, deadlinePromise: Promise<void>): Promise<void> {
let defaultExpectTimeout = testInfo.project.expect?.timeout;
if (typeof defaultExpectTimeout === 'undefined')
defaultExpectTimeout = 5000;
pollTime = pollTime === 0 ? 0 : pollTime || defaultExpectTimeout;
const deadline = pollTime ? monotonicTime() + pollTime : 0;
let aborted = false;

View File

@ -161,7 +161,7 @@ test('should support toHaveURL', async ({ runInlineTest }) => {
test('should support respect actionTimeout', async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.js': `module.exports = { use: { actionTimeout: 1000 } }`,
'playwright.config.js': `module.exports = { expect: { timeout: 1000 } }`,
'a.test.ts': `
const { test } = pwt;

4
types/test.d.ts vendored
View File

@ -37,6 +37,8 @@ export type UpdateSnapshots = 'all' | 'none' | 'missing';
type FixtureDefine<TestArgs extends KeyValue = {}, WorkerArgs extends KeyValue = {}> = { test: TestType<TestArgs, WorkerArgs>, fixtures: Fixtures<{}, {}, TestArgs, WorkerArgs> };
type ExpectSettings = {
// Default timeout for async expect matchers in milliseconds, defaults to 5000ms.
timeout?: number;
toMatchSnapshot?: {
// Pixel match threshold.
threshold?: number
@ -2520,7 +2522,7 @@ export interface PlaywrightTestOptions {
*/
contextOptions: BrowserContextOptions;
/**
* Timeout for each action and expect in milliseconds. Defaults to 0 (no timeout).
* Default timeout for each Playwright action in milliseconds, defaults to 0 (no timeout).
*
* This is a default timeout for all Playwright actions, same as configured via
* [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout).

View File

@ -36,6 +36,8 @@ export type UpdateSnapshots = 'all' | 'none' | 'missing';
type FixtureDefine<TestArgs extends KeyValue = {}, WorkerArgs extends KeyValue = {}> = { test: TestType<TestArgs, WorkerArgs>, fixtures: Fixtures<{}, {}, TestArgs, WorkerArgs> };
type ExpectSettings = {
// Default timeout for async expect matchers in milliseconds, defaults to 5000ms.
timeout?: number;
toMatchSnapshot?: {
// Pixel match threshold.
threshold?: number