feat(hooks): add a step per hook (#12867)

It is now possible to see which hooks were run in the html report.
This commit is contained in:
Dmitry Gozman 2022-03-17 19:33:01 -07:00 committed by GitHub
parent c7d6f96328
commit 3a009531b1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 79 additions and 7 deletions

View file

@ -214,6 +214,18 @@ export class TestInfoImpl implements TestInfo {
this.errors.push(error);
}
async _runAsStep<T>(cb: () => Promise<T>, stepInfo: Omit<TestStepInternal, 'complete'>): Promise<T> {
const step = this._addStep(stepInfo);
try {
const result = await cb();
step.complete();
return result;
} catch (e) {
step.complete(e instanceof SkipError ? undefined : serializeError(e));
throw e;
}
}
// ------------ TestInfo methods ------------
async attach(name: string, options: { path?: string, body?: string | Buffer, contentType?: string } = {}) {

View file

@ -418,7 +418,13 @@ export class WorkerRunner extends EventEmitter {
if (actualScope !== scope)
continue;
testInfo._timeoutManager.setCurrentRunnable({ type: modifier.type, location: modifier.location, slot: timeSlot });
const result = await this._fixtureRunner.resolveParametersAndRunFunction(modifier.fn, testInfo);
const result = await testInfo._runAsStep(() => this._fixtureRunner.resolveParametersAndRunFunction(modifier.fn, testInfo), {
category: 'hook',
title: `${modifier.type} modifier`,
canHaveChildren: true,
forceNoParent: false,
location: modifier.location,
});
if (result && extraAnnotations)
extraAnnotations.push({ type: modifier.type, description: modifier.description });
testInfo[modifier.type](!!result, modifier.description);
@ -437,7 +443,13 @@ export class WorkerRunner extends EventEmitter {
// Separate time slot for each "beforeAll" hook.
const timeSlot = { timeout: this._project.config.timeout, elapsed: 0 };
testInfo._timeoutManager.setCurrentRunnable({ type: 'beforeAll', location: hook.location, slot: timeSlot });
await this._fixtureRunner.resolveParametersAndRunFunction(hook.fn, testInfo);
await testInfo._runAsStep(() => this._fixtureRunner.resolveParametersAndRunFunction(hook.fn, testInfo), {
category: 'hook',
title: `${hook.type} hook`,
canHaveChildren: true,
forceNoParent: false,
location: hook.location,
});
} catch (e) {
// Always run all the hooks, and capture the first error.
beforeAllError = beforeAllError || e;
@ -459,7 +471,13 @@ export class WorkerRunner extends EventEmitter {
// Separate time slot for each "afterAll" hook.
const timeSlot = { timeout: this._project.config.timeout, elapsed: 0 };
testInfo._timeoutManager.setCurrentRunnable({ type: 'afterAll', location: hook.location, slot: timeSlot });
await this._fixtureRunner.resolveParametersAndRunFunction(hook.fn, testInfo);
await testInfo._runAsStep(() => this._fixtureRunner.resolveParametersAndRunFunction(hook.fn, testInfo), {
category: 'hook',
title: `${hook.type} hook`,
canHaveChildren: true,
forceNoParent: false,
location: hook.location,
});
});
firstError = firstError || afterAllError;
}
@ -472,7 +490,13 @@ export class WorkerRunner extends EventEmitter {
for (const hook of hooks) {
try {
testInfo._timeoutManager.setCurrentRunnable({ type, location: hook.location, slot: timeSlot });
await this._fixtureRunner.resolveParametersAndRunFunction(hook.fn, testInfo);
await testInfo._runAsStep(() => this._fixtureRunner.resolveParametersAndRunFunction(hook.fn, testInfo), {
category: 'hook',
title: `${hook.type} hook`,
canHaveChildren: true,
forceNoParent: false,
location: hook.location,
});
} catch (e) {
// Always run all the hooks, and capture the first error.
error = error || e;

View file

@ -392,13 +392,31 @@ test('should show multi trace source', async ({ runInlineTest, page, server, sho
await expect(page.locator('.source-line-running')).toContainText('request.get');
});
test('should show timed out steps', async ({ runInlineTest, page, showReport }) => {
test('should show timed out steps and hooks', async ({ runInlineTest, page, showReport }) => {
const result = await runInlineTest({
'playwright.config.js': `
module.exports = { timeout: 3000 };
`,
'a.test.js': `
const { test } = pwt;
test.beforeAll(() => {
console.log('beforeAll 1');
});
test.beforeAll(() => {
console.log('beforeAll 2');
});
test.beforeEach(() => {
console.log('beforeEach 1');
});
test.beforeEach(() => {
console.log('beforeEach 2');
});
test.afterEach(() => {
console.log('afterEach 1');
});
test.afterAll(() => {
console.log('afterAll 1');
});
test('fails', async ({ page }) => {
await test.step('outer step', async () => {
await test.step('inner step', async () => {
@ -416,6 +434,20 @@ test('should show timed out steps', async ({ runInlineTest, page, showReport })
await page.click('text=outer step');
await expect(page.locator('.tree-item:has-text("outer step") svg.color-text-danger')).toHaveCount(2);
await expect(page.locator('.tree-item:has-text("inner step") svg.color-text-danger')).toHaveCount(2);
await page.click('text=Before Hooks');
await expect(page.locator('.tree-item:has-text("Before Hooks") .tree-item')).toContainText([
/beforeAll hook/,
/beforeAll hook/,
/beforeEach hook/,
/beforeEach hook/,
]);
await page.locator('text=beforeAll hook').nth(1).click();
await expect(page.locator('text=console.log(\'beforeAll 2\');')).toBeVisible();
await page.click('text=After Hooks');
await expect(page.locator('.tree-item:has-text("After Hooks") .tree-item')).toContainText([
/afterEach hook/,
/afterAll hook/,
]);
});
test('should render annotations', async ({ runInlineTest, page, showReport }) => {

View file

@ -343,11 +343,13 @@ test('should report api steps', async ({ runInlineTest }) => {
`%% end {\"title\":\"browserContext.close\",\"category\":\"pw:api\"}`,
`%% end {\"title\":\"After Hooks\",\"category\":\"hook\",\"steps\":[{\"title\":\"apiRequestContext.dispose\",\"category\":\"pw:api\"},{\"title\":\"browserContext.close\",\"category\":\"pw:api\"}]}`,
`%% begin {\"title\":\"Before Hooks\",\"category\":\"hook\"}`,
`%% begin {\"title\":\"beforeAll hook\",\"category\":\"hook\"}`,
`%% begin {\"title\":\"browser.newPage\",\"category\":\"pw:api\"}`,
`%% end {\"title\":\"browser.newPage\",\"category\":\"pw:api\"}`,
`%% begin {\"title\":\"page.setContent\",\"category\":\"pw:api\"}`,
`%% end {\"title\":\"page.setContent\",\"category\":\"pw:api\"}`,
`%% end {\"title\":\"Before Hooks\",\"category\":\"hook\",\"steps\":[{\"title\":\"browser.newPage\",\"category\":\"pw:api\"},{\"title\":\"page.setContent\",\"category\":\"pw:api\"}]}`,
`%% end {\"title\":\"beforeAll hook\",\"category\":\"hook\",\"steps\":[{\"title\":\"browser.newPage\",\"category\":\"pw:api\"},{\"title\":\"page.setContent\",\"category\":\"pw:api\"}]}`,
`%% end {\"title\":\"Before Hooks\",\"category\":\"hook\",\"steps\":[{\"title\":\"beforeAll hook\",\"category\":\"hook\",\"steps\":[{\"title\":\"browser.newPage\",\"category\":\"pw:api\"},{\"title\":\"page.setContent\",\"category\":\"pw:api\"}]}]}`,
`%% begin {\"title\":\"page.click(button)\",\"category\":\"pw:api\"}`,
`%% end {\"title\":\"page.click(button)\",\"category\":\"pw:api\"}`,
`%% begin {\"title\":\"After Hooks\",\"category\":\"hook\"}`,
@ -357,9 +359,11 @@ test('should report api steps', async ({ runInlineTest }) => {
`%% begin {\"title\":\"page.click(button)\",\"category\":\"pw:api\"}`,
`%% end {\"title\":\"page.click(button)\",\"category\":\"pw:api\"}`,
`%% begin {\"title\":\"After Hooks\",\"category\":\"hook\"}`,
`%% begin {\"title\":\"afterAll hook\",\"category\":\"hook\"}`,
`%% begin {\"title\":\"page.close\",\"category\":\"pw:api\"}`,
`%% end {\"title\":\"page.close\",\"category\":\"pw:api\"}`,
`%% end {\"title\":\"After Hooks\",\"category\":\"hook\",\"steps\":[{\"title\":\"page.close\",\"category\":\"pw:api\"}]}`,
`%% end {\"title\":\"afterAll hook\",\"category\":\"hook\",\"steps\":[{\"title\":\"page.close\",\"category\":\"pw:api\"}]}`,
`%% end {\"title\":\"After Hooks\",\"category\":\"hook\",\"steps\":[{\"title\":\"afterAll hook\",\"category\":\"hook\",\"steps\":[{\"title\":\"page.close\",\"category\":\"pw:api\"}]}]}`,
]);
});