feat: conditional step.skip() (#34578)
This commit is contained in:
parent
da12af24c2
commit
a1451c75f8
|
@ -1751,7 +1751,7 @@ Step name.
|
|||
|
||||
### param: Test.step.body
|
||||
* since: v1.10
|
||||
- `body` <[function]\(\):[Promise]<[any]>>
|
||||
- `body` <[function]\([TestStepInfo]\):[Promise]<[any]>>
|
||||
|
||||
Step body.
|
||||
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
# class: TestStepInfo
|
||||
* since: v1.51
|
||||
* langs: js
|
||||
|
||||
`TestStepInfo` contains information about currently running test step. It is passed as an argument to the step function. `TestStepInfo` provides utilities to control test step execution.
|
||||
|
||||
```js
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test('basic test', async ({ page, browserName }, TestStepInfo) => {
|
||||
await test.step('check some behavior', async step => {
|
||||
await step.skip(browserName === 'webkit', 'The feature is not available in WebKit');
|
||||
// ... rest of the step code
|
||||
await page.check('input');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## method: TestStepInfo.skip#1
|
||||
* since: v1.51
|
||||
|
||||
Unconditionally skip the currently running step. Test step is immediately aborted. This is similar to [`method: Test.step.skip`].
|
||||
|
||||
## method: TestStepInfo.skip#2
|
||||
* since: v1.51
|
||||
|
||||
Conditionally skips the currently running step with an optional description. This is similar to [`method: Test.step.skip`].
|
||||
|
||||
### param: TestStepInfo.skip#2.condition
|
||||
* since: v1.51
|
||||
- `condition` <[boolean]>
|
||||
|
||||
A skip condition. Test step is skipped when the condition is `true`.
|
||||
|
||||
### param: TestStepInfo.skip#2.description
|
||||
* since: v1.51
|
||||
- `description` ?<[string]>
|
||||
|
||||
Optional description that will be reflected in a test report.
|
|
@ -50,6 +50,14 @@ Start time of this particular test step.
|
|||
|
||||
List of steps inside this step.
|
||||
|
||||
## property: TestStep.annotations
|
||||
* since: v1.51
|
||||
- type: <[Array]<[Object]>>
|
||||
- `type` <[string]> Annotation type, for example `'skip'`.
|
||||
- `description` ?<[string]> Optional description.
|
||||
|
||||
The list of annotations applicable to the current test step.
|
||||
|
||||
## property: TestStep.attachments
|
||||
* since: v1.50
|
||||
- type: <[Array]<[Object]>>
|
||||
|
|
|
@ -109,6 +109,7 @@ export type StepEndPayload = {
|
|||
wallTime: number; // milliseconds since unix epoch
|
||||
error?: TestInfoErrorImpl;
|
||||
suggestedRebaseline?: string;
|
||||
annotations: { type: string, description?: string }[];
|
||||
};
|
||||
|
||||
export type TestEntry = {
|
||||
|
|
|
@ -19,7 +19,7 @@ import { currentlyLoadingFileSuite, currentTestInfo, setCurrentlyLoadingFileSuit
|
|||
import { TestCase, Suite } from './test';
|
||||
import { wrapFunctionWithLocation } from '../transform/transform';
|
||||
import type { FixturesWithLocation } from './config';
|
||||
import type { Fixtures, TestType, TestDetails } from '../../types/test';
|
||||
import type { Fixtures, TestType, TestDetails, TestStepInfo } from '../../types/test';
|
||||
import type { Location } from '../../types/testReporter';
|
||||
import { getPackageManagerExecCommand, monotonicTime, raceAgainstDeadline, zones } from 'playwright-core/lib/utils';
|
||||
import { errors } from 'playwright-core';
|
||||
|
@ -258,22 +258,17 @@ export class TestTypeImpl {
|
|||
suite._use.push({ fixtures, location });
|
||||
}
|
||||
|
||||
async _step<T>(expectation: 'pass'|'skip', title: string, body: () => T | Promise<T>, options: {box?: boolean, location?: Location, timeout?: number } = {}): Promise<T> {
|
||||
async _step<T>(expectation: 'pass'|'skip', title: string, body: (step: TestStepInfo) => T | 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`);
|
||||
if (expectation === 'skip') {
|
||||
const step = testInfo._addStep({ category: 'test.step.skip', title, location: options.location, box: options.box });
|
||||
step.complete({});
|
||||
return undefined as T;
|
||||
}
|
||||
const step = testInfo._addStep({ category: 'test.step', title, location: options.location, box: options.box });
|
||||
return await zones.run('stepZone', step, async () => {
|
||||
try {
|
||||
let result: Awaited<ReturnType<typeof raceAgainstDeadline<T>>> | undefined = undefined;
|
||||
result = await raceAgainstDeadline(async () => {
|
||||
try {
|
||||
return await body();
|
||||
return await step.info._runStepBody(expectation === 'skip', body);
|
||||
} catch (e) {
|
||||
// If the step timed out, the test fixtures will tear down, which in turn
|
||||
// will abort unfinished actions in the step body. Record such errors here.
|
||||
|
|
|
@ -109,6 +109,7 @@ export type JsonTestStepEnd = {
|
|||
duration: number;
|
||||
error?: reporterTypes.TestError;
|
||||
attachments?: number[]; // index of JsonTestResultEnd.attachments
|
||||
annotations?: Annotation[];
|
||||
};
|
||||
|
||||
export type JsonFullResult = {
|
||||
|
@ -546,6 +547,10 @@ class TeleTestStep implements reporterTypes.TestStep {
|
|||
get attachments() {
|
||||
return this._endPayload?.attachments?.map(index => this._result.attachments[index]) ?? [];
|
||||
}
|
||||
|
||||
get annotations() {
|
||||
return this._endPayload?.annotations ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
export class TeleTestResult implements reporterTypes.TestResult {
|
||||
|
|
|
@ -517,9 +517,12 @@ class HtmlBuilder {
|
|||
|
||||
private _createTestStep(dedupedStep: DedupedStep, result: api.TestResult): TestStep {
|
||||
const { step, duration, count } = dedupedStep;
|
||||
const skipped = dedupedStep.step.category === 'test.step.skip';
|
||||
const skipped = dedupedStep.step.annotations?.find(a => a.type === 'skip');
|
||||
let title = step.title;
|
||||
if (skipped)
|
||||
title = `${title} (skipped${skipped.description ? ': ' + skipped.description : ''})`;
|
||||
const testStep: TestStep = {
|
||||
title: step.title,
|
||||
title,
|
||||
startTime: step.startTime.toISOString(),
|
||||
duration,
|
||||
steps: dedupeSteps(step.steps).map(s => this._createTestStep(s, result)),
|
||||
|
@ -532,7 +535,7 @@ class HtmlBuilder {
|
|||
location: this._relativeLocation(step.location),
|
||||
error: step.error?.message,
|
||||
count,
|
||||
skipped
|
||||
skipped: !!skipped,
|
||||
};
|
||||
if (step.location)
|
||||
this._stepsInFile.set(step.location.file, testStep);
|
||||
|
|
|
@ -257,6 +257,7 @@ export class TeleReporterEmitter implements ReporterV2 {
|
|||
duration: step.duration,
|
||||
error: step.error,
|
||||
attachments: step.attachments.map(a => result.attachments.indexOf(a)),
|
||||
annotations: step.annotations.length ? step.annotations : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -321,6 +321,7 @@ class JobDispatcher {
|
|||
duration: -1,
|
||||
steps: [],
|
||||
attachments: [],
|
||||
annotations: [],
|
||||
location: params.location,
|
||||
};
|
||||
steps.set(params.stepId, step);
|
||||
|
@ -345,6 +346,7 @@ class JobDispatcher {
|
|||
step.error = params.error;
|
||||
if (params.suggestedRebaseline)
|
||||
addSuggestedRebaseline(step.location!, params.suggestedRebaseline);
|
||||
step.annotations = params.annotations;
|
||||
steps.delete(params.stepId);
|
||||
this._reporter.onStepEnd?.(test, result, step);
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { captureRawStack, monotonicTime, zones, sanitizeForFilePath, stringifyStackFrames } from 'playwright-core/lib/utils';
|
||||
import type { TestInfo, TestStatus, FullProject } from '../../types/test';
|
||||
import type { TestInfo, TestStatus, FullProject, TestStepInfo } from '../../types/test';
|
||||
import type { AttachmentPayload, StepBeginPayload, StepEndPayload, TestInfoErrorImpl, WorkerInitParams } from '../common/ipc';
|
||||
import type { TestCase } from '../common/test';
|
||||
import { TimeoutManager, TimeoutManagerError, kMaxDeadline } from './timeoutManager';
|
||||
|
@ -31,6 +31,7 @@ import { testInfoError } from './util';
|
|||
|
||||
export interface TestStepInternal {
|
||||
complete(result: { error?: Error | unknown, suggestedRebaseline?: string }): void;
|
||||
info: TestStepInfoImpl
|
||||
attachmentIndices: number[];
|
||||
stepId: string;
|
||||
title: string;
|
||||
|
@ -244,7 +245,7 @@ export class TestInfoImpl implements TestInfo {
|
|||
?? this._findLastStageStep(this._steps); // If no parent step on stack, assume the current stage as parent.
|
||||
}
|
||||
|
||||
_addStep(data: Omit<TestStepInternal, 'complete' | 'stepId' | 'steps' | 'attachmentIndices'>, parentStep?: TestStepInternal): TestStepInternal {
|
||||
_addStep(data: Omit<TestStepInternal, 'complete' | 'stepId' | 'steps' | 'attachmentIndices' | 'info'>, parentStep?: TestStepInternal): TestStepInternal {
|
||||
const stepId = `${data.category}@${++this._lastStepId}`;
|
||||
|
||||
if (data.isStage) {
|
||||
|
@ -269,6 +270,7 @@ export class TestInfoImpl implements TestInfo {
|
|||
...data,
|
||||
steps: [],
|
||||
attachmentIndices,
|
||||
info: new TestStepInfoImpl(),
|
||||
complete: result => {
|
||||
if (step.endWallTime)
|
||||
return;
|
||||
|
@ -302,11 +304,12 @@ export class TestInfoImpl implements TestInfo {
|
|||
wallTime: step.endWallTime,
|
||||
error: step.error,
|
||||
suggestedRebaseline: result.suggestedRebaseline,
|
||||
annotations: step.info.annotations,
|
||||
};
|
||||
this._onStepEnd(payload);
|
||||
const errorForTrace = step.error ? { name: '', message: step.error.message || '', stack: step.error.stack } : undefined;
|
||||
const attachments = attachmentIndices.map(i => this.attachments[i]);
|
||||
this._tracing.appendAfterActionForStep(stepId, errorForTrace, attachments);
|
||||
this._tracing.appendAfterActionForStep(stepId, errorForTrace, attachments, step.info.annotations);
|
||||
}
|
||||
};
|
||||
const parentStepList = parentStep ? parentStep.steps : this._steps;
|
||||
|
@ -504,6 +507,34 @@ export class TestInfoImpl implements TestInfo {
|
|||
}
|
||||
}
|
||||
|
||||
export class TestStepInfoImpl implements TestStepInfo {
|
||||
annotations: Annotation[] = [];
|
||||
|
||||
async _runStepBody<T>(skip: boolean, body: (step: TestStepInfo) => T | Promise<T>) {
|
||||
if (skip) {
|
||||
this.annotations.push({ type: 'skip' });
|
||||
return undefined as T;
|
||||
}
|
||||
try {
|
||||
return await body(this);
|
||||
} catch (e) {
|
||||
if (e instanceof SkipError)
|
||||
return undefined as T;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
skip(...args: unknown[]) {
|
||||
// skip();
|
||||
// skip(condition: boolean, description: string);
|
||||
if (args.length > 0 && !args[0])
|
||||
return;
|
||||
const description = args[1] as (string|undefined);
|
||||
this.annotations.push({ type: 'skip', description });
|
||||
throw new SkipError(description);
|
||||
}
|
||||
}
|
||||
|
||||
export class SkipError extends Error {
|
||||
}
|
||||
|
||||
|
|
|
@ -252,19 +252,20 @@ export class TestTracing {
|
|||
parentId,
|
||||
startTime: monotonicTime(),
|
||||
class: 'Test',
|
||||
method: category,
|
||||
method: 'step',
|
||||
apiName,
|
||||
params: Object.fromEntries(Object.entries(params || {}).map(([name, value]) => [name, generatePreview(value)])),
|
||||
stack,
|
||||
});
|
||||
}
|
||||
|
||||
appendAfterActionForStep(callId: string, error?: SerializedError['error'], attachments: Attachment[] = []) {
|
||||
appendAfterActionForStep(callId: string, error?: SerializedError['error'], attachments: Attachment[] = [], annotations?: trace.AfterActionTraceEventAnnotation[]) {
|
||||
this._appendTraceEvent({
|
||||
type: 'after',
|
||||
callId,
|
||||
endTime: monotonicTime(),
|
||||
attachments: serializeAttachments(attachments),
|
||||
annotations,
|
||||
error,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -5811,7 +5811,7 @@ export interface TestType<TestArgs extends {}, WorkerArgs extends {}> {
|
|||
* @param body Step body.
|
||||
* @param options
|
||||
*/
|
||||
<T>(title: string, body: () => T | Promise<T>, options?: { box?: boolean, location?: Location, timeout?: number }): Promise<T>;
|
||||
<T>(title: string, body: (step: TestStepInfo) => T | Promise<T>, options?: { box?: boolean, location?: Location, timeout?: number }): Promise<T>;
|
||||
/**
|
||||
* Mark a test step as "skip" to temporarily disable its execution, useful for steps that are currently failing and
|
||||
* planned for a near-term fix. Playwright will not run the step.
|
||||
|
@ -5835,7 +5835,7 @@ export interface TestType<TestArgs extends {}, WorkerArgs extends {}> {
|
|||
* @param body Step body.
|
||||
* @param options
|
||||
*/
|
||||
skip(title: string, body: () => any | Promise<any>, options?: { box?: boolean, location?: Location, timeout?: number }): Promise<void>;
|
||||
skip(title: string, body: (step: TestStepInfo) => any | Promise<any>, options?: { box?: boolean, location?: Location, timeout?: number }): Promise<void>;
|
||||
}
|
||||
/**
|
||||
* `expect` function can be used to create test assertions. Read more about [test assertions](https://playwright.dev/docs/test-assertions).
|
||||
|
@ -9553,6 +9553,39 @@ export interface TestInfoError {
|
|||
value?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* `TestStepInfo` contains information about currently running test step. It is passed as an argument to the step
|
||||
* function. `TestStepInfo` provides utilities to control test step execution.
|
||||
*
|
||||
* ```js
|
||||
* import { test, expect } from '@playwright/test';
|
||||
*
|
||||
* test('basic test', async ({ page, browserName }, TestStepInfo) => {
|
||||
* await test.step('check some behavior', async step => {
|
||||
* await step.skip(browserName === 'webkit', 'The feature is not available in WebKit');
|
||||
* // ... rest of the step code
|
||||
* await page.check('input');
|
||||
* });
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
export interface TestStepInfo {
|
||||
/**
|
||||
* Unconditionally skip the currently running step. Test step is immediately aborted. This is similar to
|
||||
* [test.step.skip(title, body[, options])](https://playwright.dev/docs/api/class-test#test-step-skip).
|
||||
*/
|
||||
skip(): void;
|
||||
|
||||
/**
|
||||
* Conditionally skips the currently running step with an optional description. This is similar to
|
||||
* [test.step.skip(title, body[, options])](https://playwright.dev/docs/api/class-test#test-step-skip).
|
||||
* @param condition A skip condition. Test step is skipped when the condition is `true`.
|
||||
* @param description Optional description that will be reflected in a test report.
|
||||
*/
|
||||
skip(condition: boolean, description?: string): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* `WorkerInfo` contains information about the worker that is running tests and is available to worker-scoped
|
||||
* fixtures. `WorkerInfo` is a subset of [TestInfo](https://playwright.dev/docs/api/class-testinfo) that is available
|
||||
|
|
|
@ -691,6 +691,21 @@ export interface TestStep {
|
|||
*/
|
||||
titlePath(): Array<string>;
|
||||
|
||||
/**
|
||||
* The list of annotations applicable to the current test step.
|
||||
*/
|
||||
annotations: Array<{
|
||||
/**
|
||||
* Annotation type, for example `'skip'`.
|
||||
*/
|
||||
type: string;
|
||||
|
||||
/**
|
||||
* Optional description.
|
||||
*/
|
||||
description?: string;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* The list of files or buffers attached in the step execution through
|
||||
* [testInfo.attach(name[, options])](https://playwright.dev/docs/api/class-testinfo#test-info-attach).
|
||||
|
|
|
@ -126,6 +126,7 @@ export class TraceModernizer {
|
|||
existing!.result = event.result;
|
||||
existing!.error = event.error;
|
||||
existing!.attachments = event.attachments;
|
||||
existing!.annotations = event.annotations;
|
||||
if (event.point)
|
||||
existing!.point = event.point;
|
||||
break;
|
||||
|
|
|
@ -120,7 +120,7 @@ export const renderAction = (
|
|||
|
||||
const parameterString = actionParameterDisplayString(action, sdkLanguage || 'javascript');
|
||||
|
||||
const isSkipped = action.class === 'Test' && action.method === 'test.step.skip';
|
||||
const isSkipped = action.class === 'Test' && action.method === 'step' && action.annotations?.some(a => a.type === 'skip');
|
||||
let time: string = '';
|
||||
if (action.endTime)
|
||||
time = msToString(action.endTime - action.startTime);
|
||||
|
|
|
@ -258,6 +258,8 @@ function mergeActionsAndUpdateTimingSameTrace(contexts: ContextEntry[]): ActionT
|
|||
existing.error = action.error;
|
||||
if (action.attachments)
|
||||
existing.attachments = action.attachments;
|
||||
if (action.annotations)
|
||||
existing.annotations = action.annotations;
|
||||
if (action.parentId)
|
||||
existing.parentId = nonPrimaryIdToPrimaryId.get(action.parentId) ?? action.parentId;
|
||||
// For the events that are present in the test runner context, always take
|
||||
|
|
|
@ -86,6 +86,11 @@ export type AfterActionTraceEventAttachment = {
|
|||
base64?: string;
|
||||
};
|
||||
|
||||
export type AfterActionTraceEventAnnotation = {
|
||||
type: string,
|
||||
description?: string
|
||||
};
|
||||
|
||||
export type AfterActionTraceEvent = {
|
||||
type: 'after',
|
||||
callId: string;
|
||||
|
@ -93,6 +98,7 @@ export type AfterActionTraceEvent = {
|
|||
afterSnapshot?: string;
|
||||
error?: SerializedError['error'];
|
||||
attachments?: AfterActionTraceEventAttachment[];
|
||||
annotations?: AfterActionTraceEventAnnotation[];
|
||||
result?: any;
|
||||
point?: Point;
|
||||
};
|
||||
|
|
|
@ -780,10 +780,35 @@ for (const useIntermediateMergeReport of [true, false] as const) {
|
|||
|
||||
await showReport();
|
||||
await page.click('text=example');
|
||||
await page.click('text=skipped step title');
|
||||
await page.click('text=skipped step title (skipped)');
|
||||
await expect(page.getByTestId('test-snippet')).toContainText(`await test.step.skip('skipped step title', async () => {`);
|
||||
});
|
||||
|
||||
test('step title should inlclude skipped step description', async ({ runInlineTest, page, showReport }) => {
|
||||
const result = await runInlineTest({
|
||||
'playwright.config.js': `
|
||||
export default { testDir: './tests' };
|
||||
`,
|
||||
'tests/a.test.ts': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test('example', async ({}) => {
|
||||
await test.step('step title', async (step) => {
|
||||
expect(1).toBe(1);
|
||||
step.skip(true, 'conditional step.skip');
|
||||
});
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(1);
|
||||
|
||||
await showReport();
|
||||
await page.click('text=example');
|
||||
await page.click('text=step title (skipped: conditional step.skip)');
|
||||
await expect(page.getByTestId('test-snippet')).toContainText(`await test.step('step title', async (step) => {`);
|
||||
});
|
||||
|
||||
test('should render annotations', async ({ runInlineTest, page, showReport }) => {
|
||||
const result = await runInlineTest({
|
||||
'playwright.config.js': `
|
||||
|
|
|
@ -75,7 +75,9 @@ export default class MyReporter implements Reporter {
|
|||
let location = '';
|
||||
if (step.location)
|
||||
location = formatLocation(step.location);
|
||||
console.log(formatPrefix(step.category) + indent + step.title + location);
|
||||
const skip = step.annotations?.find(a => a.type === 'skip');
|
||||
const skipped = skip?.description ? ' (skipped: ' + skip.description + ')' : skip ? ' (skipped)' : '';
|
||||
console.log(formatPrefix(step.category) + indent + step.title + location + skipped);
|
||||
if (step.error) {
|
||||
const errorLocation = this.printErrorLocation ? formatLocation(step.error.location) : '';
|
||||
console.log(formatPrefix(step.category) + indent + '↪ error: ' + this.trimError(step.error.message!) + errorLocation);
|
||||
|
@ -362,18 +364,16 @@ hook |Worker Cleanup
|
|||
`);
|
||||
});
|
||||
|
||||
test('should not pass arguments and return value from step', async ({ runInlineTest }) => {
|
||||
test('should not pass return value from step', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
test('steps with return values', async ({ page }) => {
|
||||
const v1 = await test.step('my step', (...args) => {
|
||||
expect(args.length).toBe(0);
|
||||
const v1 = await test.step('my step', () => {
|
||||
return 10;
|
||||
});
|
||||
console.log('v1 = ' + v1);
|
||||
const v2 = await test.step('my step', async (...args) => {
|
||||
expect(args.length).toBe(0);
|
||||
const v2 = await test.step('my step', async () => {
|
||||
return new Promise(f => setTimeout(() => f(v1 + 10), 100));
|
||||
});
|
||||
console.log('v2 = ' + v2);
|
||||
|
@ -1549,9 +1549,9 @@ test('test.step.skip should work', async ({ runInlineTest }) => {
|
|||
expect(result.report.stats.unexpected).toBe(0);
|
||||
expect(stripAnsi(result.output)).toBe(`
|
||||
hook |Before Hooks
|
||||
test.step.skip|outer step 1 @ a.test.ts:4
|
||||
test.step |outer step 1 @ a.test.ts:4 (skipped)
|
||||
test.step |outer step 2 @ a.test.ts:11
|
||||
test.step.skip| inner step 2.1 @ a.test.ts:12
|
||||
test.step | inner step 2.1 @ a.test.ts:12 (skipped)
|
||||
test.step | inner step 2.2 @ a.test.ts:13
|
||||
expect | expect.toBe @ a.test.ts:14
|
||||
hook |After Hooks
|
||||
|
@ -1581,12 +1581,56 @@ test('skip test.step.skip body', async ({ runInlineTest }) => {
|
|||
expect(stripAnsi(result.output)).toBe(`
|
||||
hook |Before Hooks
|
||||
test.step |outer step 2 @ a.test.ts:5
|
||||
test.step.skip| inner step 2 @ a.test.ts:6
|
||||
test.step | inner step 2 @ a.test.ts:6 (skipped)
|
||||
expect |expect.toBe @ a.test.ts:10
|
||||
hook |After Hooks
|
||||
`);
|
||||
});
|
||||
|
||||
test('step.skip should work at runtime', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'reporter.ts': stepIndentReporter,
|
||||
'playwright.config.ts': `module.exports = { reporter: './reporter' };`,
|
||||
'a.test.ts': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
test('test', async ({ }) => {
|
||||
await test.step('outer step 1', async () => {
|
||||
await test.step('inner step 1.1', async (step) => {
|
||||
step.skip();
|
||||
});
|
||||
await test.step('inner step 1.2', async (step) => {
|
||||
step.skip(true, 'condition is true');
|
||||
});
|
||||
await test.step('inner step 1.3', async () => {});
|
||||
});
|
||||
await test.step('outer step 2', async () => {
|
||||
await test.step.skip('inner step 2.1', async () => {});
|
||||
await test.step('inner step 2.2', async () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
`
|
||||
}, { reporter: '' });
|
||||
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.report.stats.expected).toBe(1);
|
||||
expect(result.report.stats.unexpected).toBe(0);
|
||||
expect(stripAnsi(result.output)).toBe(`
|
||||
hook |Before Hooks
|
||||
test.step |outer step 1 @ a.test.ts:4
|
||||
test.step | inner step 1.1 @ a.test.ts:5 (skipped)
|
||||
test.step | inner step 1.2 @ a.test.ts:8 (skipped: condition is true)
|
||||
test.step | inner step 1.3 @ a.test.ts:11
|
||||
test.step |outer step 2 @ a.test.ts:13
|
||||
test.step | inner step 2.1 @ a.test.ts:14 (skipped)
|
||||
test.step | inner step 2.2 @ a.test.ts:15
|
||||
expect | expect.toBe @ a.test.ts:16
|
||||
hook |After Hooks
|
||||
`);
|
||||
});
|
||||
|
||||
|
||||
test('show api calls inside expects', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'reporter.ts': stepIndentReporter,
|
||||
|
|
|
@ -163,8 +163,8 @@ export interface TestType<TestArgs extends {}, WorkerArgs extends {}> {
|
|||
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, timeout?: number }): Promise<T>;
|
||||
skip(title: string, body: () => any | Promise<any>, options?: { box?: boolean, location?: Location, timeout?: number }): Promise<void>;
|
||||
<T>(title: string, body: (step: TestStepInfo) => T | Promise<T>, options?: { box?: boolean, location?: Location, timeout?: number }): Promise<T>;
|
||||
skip(title: string, body: (step: TestStepInfo) => any | Promise<any>, options?: { box?: boolean, location?: Location, timeout?: number }): Promise<void>;
|
||||
}
|
||||
expect: Expect<{}>;
|
||||
extend<T extends {}, W extends {} = {}>(fixtures: Fixtures<T, W, TestArgs, WorkerArgs>): TestType<TestArgs & T, WorkerArgs & W>;
|
||||
|
|
Loading…
Reference in New Issue