diff --git a/docs/src/test-api/class-test.md b/docs/src/test-api/class-test.md index 9b56fbf2e6..ae734d2137 100644 --- a/docs/src/test-api/class-test.md +++ b/docs/src/test-api/class-test.md @@ -1579,6 +1579,46 @@ test('test', async ({ page }) => { }); ``` +**Custom Step Location** + +Allows the specification of a custom location for a test step. This is particularly useful when `test.step` is used within helper functions or other layers of abstraction, enabling more accurate pinpointing of the step's origin in the source code. + +```js +import { test, expect } from '@playwright/test'; +import { getStepLocation } from './helpers'; + +test('user login process with custom step location', async ({ page }) => { + const location = getStepLocation(); + await test.step(`Navigate to login page`, async () => { + await page.goto('https://example.com/login'); + }, { location }); + + await test.step(`Fill in login credentials and submit`, async () => { + await page.fill('input#username', 'testuser'); + await page.fill('input#password', 'testpass'); + await page.click('button[type="submit"]'); + }, { location }); + + await test.step(`Check if the profile is visible post-login`, async () => { + const profileVisible = await page.isVisible('.profile'); + expect(profileVisible).toBeTruthy(); + }, { location }); +}); + +function getStepLocation() { + const error = new Error(); + const stackInfo = error.stack.split('\n')[2]; // Adjust based on environment + const match = /at (.+):(\d+):(\d+)/.exec(stackInfo); + return { + file: match[1], + line: parseInt(match[2], 10), + column: parseInt(match[3], 10) + }; +} +``` + +This revised code demonstrates how to track and report the location of each step in a user login process, making debugging and tracing much easier in complex tests. The getStepLocation function is crafted to fetch the exact location where the test step is defined, which can be dynamically adjusted to fit different JavaScript environments. + **Decorator** You can use TypeScript method decorators to turn a method into a step. diff --git a/tests/playwright-test/test-step.spec.ts b/tests/playwright-test/test-step.spec.ts index f7538de3e9..0fa7b9ad00 100644 --- a/tests/playwright-test/test-step.spec.ts +++ b/tests/playwright-test/test-step.spec.ts @@ -1239,3 +1239,94 @@ fixture | fixture: page fixture | fixture: context `); }); + +test('test with custom location', async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'helper.ts': ` + import { test } from '@playwright/test'; + + export function getStepLocation() { + return { file: 'custom-file.ts', line: 99, column: 1 }; + } + + export async function customStep(test, title, action) { + const location = getStepLocation(); + return await test.step(title, action, { location }); + } + `, + 'playwright.config.ts': ` + module.exports = { + reporter: './reporter', + }; + `, + 'a.test.ts': ` + import { test } from '@playwright/test'; + import { customStep } from './helper'; + + test('test with custom location', async ({ page }) => { + await customStep(test, 'User logs in', async () => { + await page.goto('http://localhost:1234/login'); + await page.fill('input#username', 'testuser'); + await page.fill('input#password', 'testpass'); + await page.click('button[type="submit"]'); + await page.waitForSelector('.profile'); + const profileVisible = await page.isVisible('.profile'); + expect(profileVisible).toBeTruthy(); + }); + }); + ` + }, { reporter: '', workers: 1 }); + + expect(result.exitCode).toBe(0); + expect(result.outputLines).toEqual([ + 'hook |Before Hooks', + 'test.step |User logs in @ custom-file.ts:99', + 'test.step |↪ success: Profile is visible', + 'hook |After Hooks' + ]); +}); + +test('nested step test with custom locations', async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'helper.ts': ` + import { test } from '@playwright/test'; + + export function getStepLocation(line) { + return { file: 'nested-steps.ts', line: line, column: 1 }; + } + + export async function customStep(test, title, line, action) { + const location = getStepLocation(line); + return await test.step(title, action, { location }); + } + `, + 'playwright.config.ts': ` + module.exports = { + reporter: './reporter', + }; + `, + 'a.test.ts': ` + import { test } from '@playwright/test'; + import { customStep } from './helper'; + + test('nested step test with custom locations', async ({ page }) => { + await customStep(test, 'Outer step', 10, async () => { + await page.goto('http://localhost:1234'); + await customStep(test, 'Inner step', 20, async () => { + const buttonVisible = await page.isVisible('button#submit'); + expect(buttonVisible).toBeTruthy(); + }); + }); + }); + ` + }, { reporter: '', workers: 1 }); + + expect(result.exitCode).toBe(0); + expect(result.outputLines).toEqual([ + 'hook |Before Hooks', + 'test.step |Outer step @ nested-steps.ts:10', + 'test.step |Inner step @ nested-steps.ts:20', + 'test.step |↪ success: Button is visible', + 'hook |After Hooks' + ]); +});