Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: step timeout option #33560

Merged
merged 3 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/src/test-api/class-test.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

### 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
* since: v1.10

Expand Down
14 changes: 11 additions & 3 deletions packages/playwright/src/common/testType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { wrapFunctionWithLocation } from '../transform/transform';
import type { FixturesWithLocation } from './config';
import type { Fixtures, TestType, TestDetails } from '../../types/test';
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');

Expand Down Expand Up @@ -256,19 +256,25 @@ export class TestTypeImpl {
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();
if (!testInfo)
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 });
return await zones.run('stepZone', step, async () => {
const timeoutPromise = new ManualPromise<T>();
yury-s marked this conversation as resolved.
Show resolved Hide resolved
const timer = options.timeout
? setTimeout(() => timeoutPromise.reject(new StepTimeoutError(`Step timeout ${options.timeout}ms exceeded.`)), options.timeout)
: undefined;
try {
const result = await body();
const result = await Promise.race([body(), timeoutPromise]);
step.complete({});
return result;
} catch (error) {
step.complete({ error });
throw error;
} finally {
clearTimeout(timer);
}
});
}
Expand Down Expand Up @@ -302,6 +308,8 @@ function validateTestDetails(details: TestDetails) {
return { annotations, tags };
}

class StepTimeoutError extends Error {}
yury-s marked this conversation as resolved.
Show resolved Hide resolved

export const rootTestType = new TestTypeImpl([]);

export function mergeTests(...tests: TestType<any, any>[]) {
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright/types/test.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5536,7 +5536,7 @@ export interface TestType<TestArgs extends KeyValue, WorkerArgs extends KeyValue
* @param body Step body.
* @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).
*
Expand Down
17 changes: 17 additions & 0 deletions tests/playwright-test/test-step.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,23 @@ test('should not pass arguments and return value from step', async ({ runInlineT
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 });
yury-s marked this conversation as resolved.
Show resolved Hide resolved
});
`
}, { 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 }) => {
const result = await runInlineTest({
'reporter.ts': stepIndentReporter,
Expand Down
2 changes: 1 addition & 1 deletion utils/generate_types/overrides-test.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(title: string, inner: (args: TestArgs & WorkerArgs, testInfo: TestInfo) => Promise<any> | any): 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<{}>;
extend<T extends KeyValue, W extends KeyValue = {}>(fixtures: Fixtures<T, W, TestArgs, WorkerArgs>): TestType<TestArgs & T, WorkerArgs & W>;
info(): TestInfo;
Expand Down