Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
12 changes: 12 additions & 0 deletions docs/src/test-api/class-test.md
Original file line number Diff line number Diff line change
Expand Up @@ -1850,6 +1850,12 @@ Specifies a custom location for the step to be shown in test reports and trace v

Arbitrary serializable parameters describing the step. They are reported to the reporters as `testStep.params` and are shown in the trace viewer.

### option: Test.step.subtitle
* since: v1.64
- `subtitle` <[string]>

Step subtitle that complements the title, for example the target of the step. It is reported to the reporters as `testStep.subtitle` and is shown next to the title in test reports and the trace viewer.

## async method: Test.step.skip
* since: v1.50
- returns: <[void]>
Expand Down Expand Up @@ -1903,6 +1909,12 @@ Specifies a custom location for the step to be shown in test reports and trace v

Arbitrary serializable parameters describing the step. They are reported to the reporters as `testStep.params` and are shown in the trace viewer.

### option: Test.step.skip.subtitle
* since: v1.64
- `subtitle` <[string]>

Step subtitle that complements the title, for example the target of the step. It is reported to the reporters as `testStep.subtitle` and is shown next to the title in test reports and the trace viewer.

### option: Test.step.skip.timeout
* since: v1.50
- `timeout` <[float]>
Expand Down
9 changes: 7 additions & 2 deletions docs/src/test-reporter-api/class-teststep.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,15 +112,20 @@ User-friendly test step title, for example `Click` or `Navigate`.

User-friendly test step subtitle that complements the title, when available. For Playwright API
calls, it is the target locator or the navigation url. For example, a `Click` step has the clicked
locator as a subtitle. User interfaces typically render the subtitle next to the title or on a
separate line.
locator as a subtitle. [`method: Test.step`] steps carry the subtitle passed by the test author.
User interfaces typically render the subtitle next to the title or on a separate line.

```js
// title `Click`, subtitle `getByRole('button')`
await page.getByRole('button').click();

// title `Navigate`, subtitle `example.com/index.html`
await page.goto('https://example.com/index.html');

// title `Add to cart`, subtitle `SKU 42`
await test.step('Add to cart', async () => {
// ...
}, { subtitle: 'SKU 42' });
```

## method: TestStep.titlePath
Expand Down
4 changes: 2 additions & 2 deletions packages/playwright/src/common/testType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,12 +274,12 @@ export class TestTypeImpl {
suite._use.push({ fixtures, location });
}

async _step<T>(expectation: 'pass'|'skip', title: string, body: (step: TestStepInfo) => T | Promise<T>, options: {box?: boolean, location?: Location, timeout?: number, params?: Record<string, any> } = {}): Promise<T> {
async _step<T>(expectation: 'pass'|'skip', title: string, body: (step: TestStepInfo) => T | Promise<T>, options: {box?: boolean, location?: Location, timeout?: number, params?: Record<string, any>, subtitle?: string } = {}): Promise<T> {
const testInfo = currentTestInfo();
if (!testInfo)
throw new Error(`test.step() can only be called from a test`);
await testInfo._onUserStepBegin?.(title);
const step = testInfo._addStep({ category: 'test.step', title, location: options.location, box: options.box, params: options.params });
const step = testInfo._addStep({ category: 'test.step', title, subtitle: options.subtitle, location: options.location, box: options.box, params: options.params });
return await currentZone().with('stepZone', step).run(async () => {
try {
let result: Awaited<ReturnType<typeof raceAgainstDeadline<T>>> | undefined = undefined;
Expand Down
4 changes: 2 additions & 2 deletions packages/playwright/types/test.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6727,7 +6727,7 @@ export interface TestType<TestArgs extends {}, WorkerArgs extends {}> {
* @param body Step body.
* @param options
*/
<T>(title: string, body: (step: TestStepInfo) => T | Promise<T>, options?: { box?: boolean, location?: Location, timeout?: number, params?: { [key: string]: any } }): Promise<T>;
<T>(title: string, body: (step: TestStepInfo) => T | Promise<T>, options?: { box?: boolean, location?: Location, timeout?: number, params?: { [key: string]: any }, subtitle?: string }): 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. See also
Expand Down Expand Up @@ -6755,7 +6755,7 @@ export interface TestType<TestArgs extends {}, WorkerArgs extends {}> {
* @param body Step body.
* @param options
*/
skip(title: string, body: (step: TestStepInfo) => any | Promise<any>, options?: { box?: boolean, location?: Location, timeout?: number, params?: { [key: string]: any } }): Promise<void>;
skip(title: string, body: (step: TestStepInfo) => any | Promise<any>, options?: { box?: boolean, location?: Location, timeout?: number, params?: { [key: string]: any }, subtitle?: string }): Promise<void>;
}
/**
* `expect` function can be used to create test assertions. Read more about [test assertions](https://playwright.dev/docs/test-assertions).
Expand Down
10 changes: 8 additions & 2 deletions packages/playwright/types/testReporter.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -921,15 +921,21 @@ export interface TestStep {

/**
* User-friendly test step subtitle that complements the title, when available. For Playwright API calls, it is the
* target locator or the navigation url. For example, a `Click` step has the clicked locator as a subtitle. User
* interfaces typically render the subtitle next to the title or on a separate line.
* target locator or the navigation url. For example, a `Click` step has the clicked locator as a subtitle.
* [test.step(title, body[, options])](https://playwright.dev/docs/api/class-test#test-step) steps carry the subtitle
* passed by the test author. User interfaces typically render the subtitle next to the title or on a separate line.
*
* ```js
* // title `Click`, subtitle `getByRole('button')`
* await page.getByRole('button').click();
*
* // title `Navigate`, subtitle `example.com/index.html`
* await page.goto('https://example.com/index.html');
*
* // title `Add to cart`, subtitle `SKU 42`
* await test.step('Add to cart', async () => {
* // ...
* }, { subtitle: 'SKU 42' });
* ```
*
*/
Expand Down
16 changes: 16 additions & 0 deletions tests/playwright-test/playwright.trace.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1553,3 +1553,19 @@ test('should record step params in trace', async ({ runInlineTest }, testInfo) =
expect(actionByTitle('my step').params).toEqual({ foo: 'bar' });
expect(actionByTitle('Expect "toBe"').params).toEqual({ expected: '1' });
});

test('should record step subtitle in trace', async ({ runInlineTest }, testInfo) => {
const result = await runInlineTest({
'a.spec.ts': `
import { test, expect } from '@playwright/test';
test('pass', async ({}) => {
await test.step('my step', async () => {}, { subtitle: 'my subtitle' });
});
`,
}, { trace: 'on' });

expect(result.exitCode).toBe(0);
expect(result.passed).toBe(1);
const trace = await parseTrace(testInfo.outputPath('test-results', 'a-pass', 'trace.zip'));
expect(trace.model.actions.find(a => a.title === 'my step')!.subtitle).toBe('my subtitle');
});
4 changes: 2 additions & 2 deletions tests/playwright-test/reporter-blob.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1277,7 +1277,7 @@ test('preserve step params', async ({ runInlineTest, mergeReports }) => {
import { test, expect } from '@playwright/test';
test('test 1', async ({ page }) => {
await page.goto('about:blank');
await test.step('my step', async () => {}, { params: { foo: 'bar', count: 7 } });
await test.step('my step', async () => {}, { subtitle: 'my subtitle', params: { foo: 'bar', count: 7 } });
});
`,
};
Expand All @@ -1286,7 +1286,7 @@ test('preserve step params', async ({ runInlineTest, mergeReports }) => {
expect(exitCode).toBe(0);
expect(outputLines).toEqual([
`Navigate about:blank | {"url":"about:blank"}`,
`my step | {"foo":"bar","count":7}`,
`my step my subtitle | {"foo":"bar","count":7}`,
]);
});

Expand Down
18 changes: 18 additions & 0 deletions tests/playwright-test/reporter-html.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,24 @@ for (const useIntermediateMergeReport of [true, false] as const) {
await expect(page.locator('.step-subtitle .step-title-highlight')).toHaveText(['#target']);
});

test('should render test.step subtitle', async ({ runInlineTest, page, showReport }) => {
const result = await runInlineTest({
'a.test.js': `
import { test, expect } from '@playwright/test';
test('has steps', async ({}) => {
await test.step('Add to cart', async () => {}, { subtitle: 'SKU 42' });
});
`,
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
expect(result.exitCode).toBe(0);
expect(result.passed).toBe(1);

await showReport();
await page.getByRole('link', { name: 'has steps' }).click();
await expect(page.locator('.step-title-container', { hasText: 'Add to cart' })).toHaveAttribute('aria-label', 'Add to cart SKU 42');
await expect(page.locator('.step-subtitle')).toHaveText('SKU 42');
});

test('should show step snippets from non-root', async ({ runInlineTest, page, showReport }) => {
const result = await runInlineTest({
'playwright.config.js': `
Expand Down
32 changes: 32 additions & 0 deletions tests/playwright-test/test-step.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1899,6 +1899,38 @@ test('should report step params', async ({ runInlineTest }) => {
]);
});

test('should report step subtitle', async ({ runInlineTest }) => {
const result = await runInlineTest({
'reporter.ts': `
import type { Reporter, TestCase, TestResult, TestStep } from '@playwright/test/reporter';
export default class MyReporter implements Reporter {
onStepEnd(test: TestCase, result: TestResult, step: TestStep) {
if (step.category === 'test.step')
console.log('%%' + step.title + ' | ' + step.subtitle);
}
}
`,
'playwright.config.ts': `
module.exports = { reporter: './reporter' };
`,
'a.test.ts': `
import { test, expect } from '@playwright/test';
test('pass', async ({}) => {
await test.step('my step', async () => {}, { subtitle: 'my subtitle' });
await test.step.skip('skipped step', async () => {}, { subtitle: 'skipped subtitle' });
await test.step('plain step', async () => {});
});
`
}, { reporter: '' });

expect(result.exitCode).toBe(0);
expect(result.outputLines).toEqual([
`my step | my subtitle`,
`skipped step | skipped subtitle`,
`plain step | undefined`,
]);
});

test('should report input step params', async ({ runInlineTest }) => {
const result = await runInlineTest({
'reporter.ts': `
Expand Down
4 changes: 2 additions & 2 deletions utils/generate_types/overrides-test.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,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: (step: TestStepInfo) => T | Promise<T>, options?: { box?: boolean, location?: Location, timeout?: number, params?: { [key: string]: any } }): Promise<T>;
skip(title: string, body: (step: TestStepInfo) => any | Promise<any>, options?: { box?: boolean, location?: Location, timeout?: number, params?: { [key: string]: any } }): Promise<void>;
<T>(title: string, body: (step: TestStepInfo) => T | Promise<T>, options?: { box?: boolean, location?: Location, timeout?: number, params?: { [key: string]: any }, subtitle?: string }): Promise<T>;
skip(title: string, body: (step: TestStepInfo) => any | Promise<any>, options?: { box?: boolean, location?: Location, timeout?: number, params?: { [key: string]: any }, subtitle?: string }): Promise<void>;
}
expect: Expect<{}>;
extend<T extends {}, W extends {} = {}>(fixtures: Fixtures<T, W, TestArgs, WorkerArgs>): TestType<TestArgs & T, WorkerArgs & W>;
Expand Down
Loading