feat: step timeout option

Fixes https://github.com/microsoft/playwright/issues/33475
This commit is contained in:
Yury Semikhatsky 2024-11-12 12:09:04 -08:00
parent 7073f80879
commit bba4dc67fc
5 changed files with 36 additions and 5 deletions

View file

@ -1767,6 +1767,12 @@ Whether to box the step in the report. Defaults to `false`. When the step is box
Specifies a custom location for the step to be shown in test reports and trace viewer. By default, location of the [`method: Test.step`] call is shown. Specifies a custom location for the step to be shown in test reports and trace viewer. By default, location of the [`method: Test.step`] call is shown.
### option: Test.step.timeout
* since: v1.50
- `timeout` <[float]>
Maximum time in milliseconds for the step to finish. Defaults to `0` (no timeout).
## method: Test.use ## method: Test.use
* since: v1.10 * since: v1.10

View file

@ -21,7 +21,7 @@ import { wrapFunctionWithLocation } from '../transform/transform';
import type { FixturesWithLocation } from './config'; import type { FixturesWithLocation } from './config';
import type { Fixtures, TestType, TestDetails } from '../../types/test'; import type { Fixtures, TestType, TestDetails } from '../../types/test';
import type { Location } from '../../types/testReporter'; import type { Location } from '../../types/testReporter';
import { getPackageManagerExecCommand, zones } from 'playwright-core/lib/utils'; import { getPackageManagerExecCommand, ManualPromise, zones } from 'playwright-core/lib/utils';
const testTypeSymbol = Symbol('testType'); const testTypeSymbol = Symbol('testType');
@ -256,19 +256,25 @@ export class TestTypeImpl {
suite._use.push({ fixtures, location }); suite._use.push({ fixtures, location });
} }
async _step<T>(title: string, body: () => Promise<T>, options: {box?: boolean, location?: Location } = {}): Promise<T> { async _step<T>(title: string, body: () => Promise<T>, options: {box?: boolean, location?: Location, timeout?: number } = {}): Promise<T> {
const testInfo = currentTestInfo(); const testInfo = currentTestInfo();
if (!testInfo) if (!testInfo)
throw new Error(`test.step() can only be called from a test`); throw new Error(`test.step() can only be called from a test`);
const step = testInfo._addStep({ category: 'test.step', title, location: options.location, box: options.box }); const step = testInfo._addStep({ category: 'test.step', title, location: options.location, box: options.box });
return await zones.run('stepZone', step, async () => { return await zones.run('stepZone', step, async () => {
const timeoutPromise = new ManualPromise<T>();
const timer = options.timeout
? setTimeout(() => timeoutPromise.reject(new StepTimeoutError(`Step timeout ${options.timeout}ms exceeded.`)), options.timeout)
: undefined;
try { try {
const result = await body(); const result = await Promise.race([body(), timeoutPromise]);
step.complete({}); step.complete({});
return result; return result;
} catch (error) { } catch (error) {
step.complete({ error }); step.complete({ error });
throw error; throw error;
} finally {
clearTimeout(timer);
} }
}); });
} }
@ -302,6 +308,8 @@ function validateTestDetails(details: TestDetails) {
return { annotations, tags }; return { annotations, tags };
} }
class StepTimeoutError extends Error {}
export const rootTestType = new TestTypeImpl([]); export const rootTestType = new TestTypeImpl([]);
export function mergeTests(...tests: TestType<any, any>[]) { export function mergeTests(...tests: TestType<any, any>[]) {

View file

@ -5536,7 +5536,7 @@ export interface TestType<TestArgs extends KeyValue, WorkerArgs extends KeyValue
* @param body Step body. * @param body Step body.
* @param options * @param options
*/ */
step<T>(title: string, body: () => T | Promise<T>, options?: { box?: boolean, location?: Location }): Promise<T>; step<T>(title: string, body: () => T | Promise<T>, options?: { box?: boolean, location?: Location, timeout?: number }): Promise<T>;
/** /**
* `expect` function can be used to create test assertions. Read more about [test assertions](https://playwright.dev/docs/test-assertions). * `expect` function can be used to create test assertions. Read more about [test assertions](https://playwright.dev/docs/test-assertions).
* *

View file

@ -386,6 +386,23 @@ test('should not pass arguments and return value from step', async ({ runInlineT
expect(result.output).toContain('v2 = 20'); expect(result.output).toContain('v2 = 20');
}); });
test('step timeout option', async ({ runInlineTest }) => {
const result = await runInlineTest({
'a.test.ts': `
import { test, expect } from '@playwright/test';
test('step with timeout', async () => {
await test.step('my step', async () => {
await new Promise(() => {});
}, { timeout: 100 });
});
`
}, { reporter: '', workers: 1 });
expect(result.exitCode).toBe(1);
expect(result.passed).toBe(0);
console.log(result.output);
expect(result.output).toContain('Error: Step timeout 100ms exceeded.');
});
test('should mark step as failed when soft expect fails', async ({ runInlineTest }) => { test('should mark step as failed when soft expect fails', async ({ runInlineTest }) => {
const result = await runInlineTest({ const result = await runInlineTest({
'reporter.ts': stepIndentReporter, 'reporter.ts': stepIndentReporter,

View file

@ -162,7 +162,7 @@ export interface TestType<TestArgs extends KeyValue, WorkerArgs extends KeyValue
afterAll(inner: (args: TestArgs & WorkerArgs, testInfo: TestInfo) => Promise<any> | any): void; afterAll(inner: (args: TestArgs & WorkerArgs, testInfo: TestInfo) => Promise<any> | any): void;
afterAll(title: string, inner: (args: TestArgs & WorkerArgs, testInfo: TestInfo) => Promise<any> | any): void; afterAll(title: string, inner: (args: TestArgs & WorkerArgs, testInfo: TestInfo) => Promise<any> | any): void;
use(fixtures: Fixtures<{}, {}, TestArgs, WorkerArgs>): void; use(fixtures: Fixtures<{}, {}, TestArgs, WorkerArgs>): void;
step<T>(title: string, body: () => T | Promise<T>, options?: { box?: boolean, location?: Location }): Promise<T>; step<T>(title: string, body: () => T | Promise<T>, options?: { box?: boolean, location?: Location, timeout?: number }): Promise<T>;
expect: Expect<{}>; expect: Expect<{}>;
extend<T extends KeyValue, W extends KeyValue = {}>(fixtures: Fixtures<T, W, TestArgs, WorkerArgs>): TestType<TestArgs & T, WorkerArgs & W>; extend<T extends KeyValue, W extends KeyValue = {}>(fixtures: Fixtures<T, W, TestArgs, WorkerArgs>): TestType<TestArgs & T, WorkerArgs & W>;
info(): TestInfo; info(): TestInfo;