mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
docs: improve test.step documentation (#27535)
This commit is contained in:
parent
9edb811434
commit
d11380e911
@ -1331,7 +1331,7 @@ Optional description that will be reflected in a test report.
|
|||||||
* since: v1.10
|
* since: v1.10
|
||||||
- returns: <[any]>
|
- returns: <[any]>
|
||||||
|
|
||||||
Declares a test step.
|
Declares a test step that is shown in the report.
|
||||||
|
|
||||||
**Usage**
|
**Usage**
|
||||||
|
|
||||||
@ -1342,6 +1342,14 @@ test('test', async ({ page }) => {
|
|||||||
await test.step('Log in', async () => {
|
await test.step('Log in', async () => {
|
||||||
// ...
|
// ...
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await test.step('Outer step', async () => {
|
||||||
|
// ...
|
||||||
|
// You can nest steps inside each other.
|
||||||
|
await test.step('Inner step', async () => {
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -1361,6 +1369,118 @@ test('test', async ({ page }) => {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Decorator**
|
||||||
|
|
||||||
|
You can use TypeScript method decorators to turn a method into a step.
|
||||||
|
Each call to the decorated method will show up as a step in the report.
|
||||||
|
|
||||||
|
```js
|
||||||
|
function step(target: Function, context: ClassMethodDecoratorContext) {
|
||||||
|
return function replacementMethod(...args: any) {
|
||||||
|
const name = this.constructor.name + '.' + (context.name as string);
|
||||||
|
return test.step(name, async () => {
|
||||||
|
return await target.call(this, ...args);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoginPage {
|
||||||
|
constructor(readonly page: Page) {}
|
||||||
|
|
||||||
|
@step
|
||||||
|
async login() {
|
||||||
|
const account = { username: 'Alice', password: 's3cr3t' };
|
||||||
|
await this.page.getByLabel('Username or email address').fill(account.username);
|
||||||
|
await this.page.getByLabel('Password').fill(account.password);
|
||||||
|
await this.page.getByRole('button', { name: 'Sign in' }).click();
|
||||||
|
await expect(this.page.getByRole('button', { name: 'View profile and more' })).toBeVisible();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test('example', async ({ page }) => {
|
||||||
|
const loginPage = new LoginPage(page);
|
||||||
|
await loginPage.login();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**Boxing**
|
||||||
|
|
||||||
|
When something inside a step fails, you would usually see the error pointing to the exact action that failed. For example, consider the following login step:
|
||||||
|
|
||||||
|
```js
|
||||||
|
async function login(page) {
|
||||||
|
await test.step('login', async () => {
|
||||||
|
const account = { username: 'Alice', password: 's3cr3t' };
|
||||||
|
await page.getByLabel('Username or email address').fill(account.username);
|
||||||
|
await page.getByLabel('Password').fill(account.password);
|
||||||
|
await page.getByRole('button', { name: 'Sign in' }).click();
|
||||||
|
await expect(page.getByRole('button', { name: 'View profile and more' })).toBeVisible();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
test('example', async ({ page }) => {
|
||||||
|
await page.goto('https://github.com/login');
|
||||||
|
await login(page);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
```txt
|
||||||
|
Error: Timed out 5000ms waiting for expect(locator).toBeVisible()
|
||||||
|
... error details omitted ...
|
||||||
|
|
||||||
|
8 | await page.getByRole('button', { name: 'Sign in' }).click();
|
||||||
|
> 9 | await expect(page.getByRole('button', { name: 'View profile and more' })).toBeVisible();
|
||||||
|
| ^
|
||||||
|
10 | });
|
||||||
|
```
|
||||||
|
|
||||||
|
As we see above, the test may fail with an error pointing inside the step. If you would like the error to highlight the "login" step instead of its internals, use the `box` option. An error inside a boxed step points to the step call site.
|
||||||
|
|
||||||
|
```js
|
||||||
|
async function login(page) {
|
||||||
|
await test.step('login', async () => {
|
||||||
|
// ...
|
||||||
|
}, { box: true }); // Note the "box" option here.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```txt
|
||||||
|
Error: Timed out 5000ms waiting for expect(locator).toBeVisible()
|
||||||
|
... error details omitted ...
|
||||||
|
|
||||||
|
14 | await page.goto('https://github.com/login');
|
||||||
|
> 15 | await login(page);
|
||||||
|
| ^
|
||||||
|
16 | });
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also create a TypeScript decorator for a boxed step, similar to a regular step decorator above:
|
||||||
|
|
||||||
|
```js
|
||||||
|
function boxedStep(target: Function, context: ClassMethodDecoratorContext) {
|
||||||
|
return function replacementMethod(...args: any) {
|
||||||
|
const name = this.constructor.name + '.' + (context.name as string);
|
||||||
|
return test.step(name, async () => {
|
||||||
|
return await target.call(this, ...args);
|
||||||
|
}, { box: true }); // Note the "box" option here.
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoginPage {
|
||||||
|
constructor(readonly page: Page) {}
|
||||||
|
|
||||||
|
@boxedStep
|
||||||
|
async login() {
|
||||||
|
// ....
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test('example', async ({ page }) => {
|
||||||
|
const loginPage = new LoginPage(page);
|
||||||
|
await loginPage.login(); // <-- Error will be reported on this line.
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
### param: Test.step.title
|
### param: Test.step.title
|
||||||
* since: v1.10
|
* since: v1.10
|
||||||
- `title` <[string]>
|
- `title` <[string]>
|
||||||
@ -1378,46 +1498,7 @@ Step body.
|
|||||||
* since: v1.39
|
* since: v1.39
|
||||||
- `box` <boolean>
|
- `box` <boolean>
|
||||||
|
|
||||||
Whether to box the step in the report. Defaults to `false`. When the step is boxed, errors thrown from the step internals point to the step call site.
|
Whether to box the step in the report. Defaults to `false`. When the step is boxed, errors thrown from the step internals point to the step call site. See below for more details.
|
||||||
|
|
||||||
```js
|
|
||||||
const assertGoodPage = async page => {
|
|
||||||
await test.step('assertGoodPage', async () => {
|
|
||||||
await expect(page.getByText('does-not-exist')).toBeVisible();
|
|
||||||
}, { box: true });
|
|
||||||
};
|
|
||||||
|
|
||||||
test('box', async ({ page }) => {
|
|
||||||
await assertGoodPage(page); // <-- Errors will be reported on this line.
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also use TypeScript method decorators to annotate method as a boxed step:
|
|
||||||
|
|
||||||
```js
|
|
||||||
function boxedStep(target: Function, context: ClassMethodDecoratorContext) {
|
|
||||||
return function replacementMethod(...args: any) {
|
|
||||||
const name = this.constructor.name + '.' + (context.name as string);
|
|
||||||
return test.step(name, async () => {
|
|
||||||
return await target.call(this, ...args);
|
|
||||||
}, { box: true });
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
class Pom {
|
|
||||||
constructor(readonly page: Page) {}
|
|
||||||
|
|
||||||
@boxedStep
|
|
||||||
async assertGoodPage() {
|
|
||||||
await expect(this.page.getByText('does-not-exist')).toBeVisible({ timeout: 1 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test('box', async ({ page }) => {
|
|
||||||
const pom = new Pom(page);
|
|
||||||
await pom.assertGoodPage(); // <-- Error will be reported on this line.
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## method: Test.use
|
## method: Test.use
|
||||||
* since: v1.10
|
* since: v1.10
|
||||||
|
|||||||
125
packages/playwright/types/test.d.ts
vendored
125
packages/playwright/types/test.d.ts
vendored
@ -3318,7 +3318,7 @@ export interface TestType<TestArgs extends KeyValue, WorkerArgs extends KeyValue
|
|||||||
*/
|
*/
|
||||||
use(fixtures: Fixtures<{}, {}, TestArgs, WorkerArgs>): void;
|
use(fixtures: Fixtures<{}, {}, TestArgs, WorkerArgs>): void;
|
||||||
/**
|
/**
|
||||||
* Declares a test step.
|
* Declares a test step that is shown in the report.
|
||||||
*
|
*
|
||||||
* **Usage**
|
* **Usage**
|
||||||
*
|
*
|
||||||
@ -3329,6 +3329,14 @@ export interface TestType<TestArgs extends KeyValue, WorkerArgs extends KeyValue
|
|||||||
* await test.step('Log in', async () => {
|
* await test.step('Log in', async () => {
|
||||||
* // ...
|
* // ...
|
||||||
* });
|
* });
|
||||||
|
*
|
||||||
|
* await test.step('Outer step', async () => {
|
||||||
|
* // ...
|
||||||
|
* // You can nest steps inside each other.
|
||||||
|
* await test.step('Inner step', async () => {
|
||||||
|
* // ...
|
||||||
|
* });
|
||||||
|
* });
|
||||||
* });
|
* });
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
@ -3348,6 +3356,121 @@ export interface TestType<TestArgs extends KeyValue, WorkerArgs extends KeyValue
|
|||||||
* });
|
* });
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
|
* **Decorator**
|
||||||
|
*
|
||||||
|
* You can use TypeScript method decorators to turn a method into a step. Each call to the decorated method will show
|
||||||
|
* up as a step in the report.
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* function step(target: Function, context: ClassMethodDecoratorContext) {
|
||||||
|
* return function replacementMethod(...args: any) {
|
||||||
|
* const name = this.constructor.name + '.' + (context.name as string);
|
||||||
|
* return test.step(name, async () => {
|
||||||
|
* return await target.call(this, ...args);
|
||||||
|
* });
|
||||||
|
* };
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* class LoginPage {
|
||||||
|
* constructor(readonly page: Page) {}
|
||||||
|
*
|
||||||
|
* @step
|
||||||
|
* async login() {
|
||||||
|
* const account = { username: 'Alice', password: 's3cr3t' };
|
||||||
|
* await this.page.getByLabel('Username or email address').fill(account.username);
|
||||||
|
* await this.page.getByLabel('Password').fill(account.password);
|
||||||
|
* await this.page.getByRole('button', { name: 'Sign in' }).click();
|
||||||
|
* await expect(this.page.getByRole('button', { name: 'View profile and more' })).toBeVisible();
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* test('example', async ({ page }) => {
|
||||||
|
* const loginPage = new LoginPage(page);
|
||||||
|
* await loginPage.login();
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* **Boxing**
|
||||||
|
*
|
||||||
|
* When something inside a step fails, you would usually see the error pointing to the exact action that failed. For
|
||||||
|
* example, consider the following login step:
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* async function login(page) {
|
||||||
|
* await test.step('login', async () => {
|
||||||
|
* const account = { username: 'Alice', password: 's3cr3t' };
|
||||||
|
* await page.getByLabel('Username or email address').fill(account.username);
|
||||||
|
* await page.getByLabel('Password').fill(account.password);
|
||||||
|
* await page.getByRole('button', { name: 'Sign in' }).click();
|
||||||
|
* await expect(page.getByRole('button', { name: 'View profile and more' })).toBeVisible();
|
||||||
|
* });
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* test('example', async ({ page }) => {
|
||||||
|
* await page.goto('https://github.com/login');
|
||||||
|
* await login(page);
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ```txt
|
||||||
|
* Error: Timed out 5000ms waiting for expect(locator).toBeVisible()
|
||||||
|
* ... error details omitted ...
|
||||||
|
*
|
||||||
|
* 8 | await page.getByRole('button', { name: 'Sign in' }).click();
|
||||||
|
* > 9 | await expect(page.getByRole('button', { name: 'View profile and more' })).toBeVisible();
|
||||||
|
* | ^
|
||||||
|
* 10 | });
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* As we see above, the test may fail with an error pointing inside the step. If you would like the error to highlight
|
||||||
|
* the "login" step instead of its internals, use the `box` option. An error inside a boxed step points to the step
|
||||||
|
* call site.
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* async function login(page) {
|
||||||
|
* await test.step('login', async () => {
|
||||||
|
* // ...
|
||||||
|
* }, { box: true }); // Note the "box" option here.
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ```txt
|
||||||
|
* Error: Timed out 5000ms waiting for expect(locator).toBeVisible()
|
||||||
|
* ... error details omitted ...
|
||||||
|
*
|
||||||
|
* 14 | await page.goto('https://github.com/login');
|
||||||
|
* > 15 | await login(page);
|
||||||
|
* | ^
|
||||||
|
* 16 | });
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* You can also create a TypeScript decorator for a boxed step, similar to a regular step decorator above:
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* function boxedStep(target: Function, context: ClassMethodDecoratorContext) {
|
||||||
|
* return function replacementMethod(...args: any) {
|
||||||
|
* const name = this.constructor.name + '.' + (context.name as string);
|
||||||
|
* return test.step(name, async () => {
|
||||||
|
* return await target.call(this, ...args);
|
||||||
|
* }, { box: true }); // Note the "box" option here.
|
||||||
|
* };
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* class LoginPage {
|
||||||
|
* constructor(readonly page: Page) {}
|
||||||
|
*
|
||||||
|
* @boxedStep
|
||||||
|
* async login() {
|
||||||
|
* // ....
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* test('example', async ({ page }) => {
|
||||||
|
* const loginPage = new LoginPage(page);
|
||||||
|
* await loginPage.login(); // <-- Error will be reported on this line.
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*
|
||||||
* @param title Step name.
|
* @param title Step name.
|
||||||
* @param body Step body.
|
* @param body Step body.
|
||||||
* @param options
|
* @param options
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user