docs: improve test.step documentation (#27535)

This commit is contained in:
Dmitry Gozman 2023-10-10 14:48:44 -07:00 committed by GitHub
parent 9edb811434
commit d11380e911
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 246 additions and 42 deletions

View File

@ -1331,7 +1331,7 @@ Optional description that will be reflected in a test report.
* since: v1.10
- returns: <[any]>
Declares a test step.
Declares a test step that is shown in the report.
**Usage**
@ -1342,6 +1342,14 @@ test('test', async ({ page }) => {
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
* since: v1.10
- `title` <[string]>
@ -1378,46 +1498,7 @@ Step body.
* since: v1.39
- `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.
```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.
});
```
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.
## method: Test.use
* since: v1.10

View File

@ -3318,7 +3318,7 @@ export interface TestType<TestArgs extends KeyValue, WorkerArgs extends KeyValue
*/
use(fixtures: Fixtures<{}, {}, TestArgs, WorkerArgs>): void;
/**
* Declares a test step.
* Declares a test step that is shown in the report.
*
* **Usage**
*
@ -3329,6 +3329,14 @@ export interface TestType<TestArgs extends KeyValue, WorkerArgs extends KeyValue
* 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 body Step body.
* @param options