diff --git a/packages/playwright-test/src/testInfo.ts b/packages/playwright-test/src/testInfo.ts index d34637894d..73839717ad 100644 --- a/packages/playwright-test/src/testInfo.ts +++ b/packages/playwright-test/src/testInfo.ts @@ -214,6 +214,18 @@ export class TestInfoImpl implements TestInfo { this.errors.push(error); } + async _runAsStep(cb: () => Promise, stepInfo: Omit): Promise { + 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 } = {}) { diff --git a/packages/playwright-test/src/workerRunner.ts b/packages/playwright-test/src/workerRunner.ts index 8957f27d1f..6556d0f351 100644 --- a/packages/playwright-test/src/workerRunner.ts +++ b/packages/playwright-test/src/workerRunner.ts @@ -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; diff --git a/tests/playwright-test/reporter-html.spec.ts b/tests/playwright-test/reporter-html.spec.ts index 961e38b8f7..b9420f9d5d 100644 --- a/tests/playwright-test/reporter-html.spec.ts +++ b/tests/playwright-test/reporter-html.spec.ts @@ -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 }) => { diff --git a/tests/playwright-test/reporter.spec.ts b/tests/playwright-test/reporter.spec.ts index 68a830039b..427b0945ef 100644 --- a/tests/playwright-test/reporter.spec.ts +++ b/tests/playwright-test/reporter.spec.ts @@ -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\"}]}]}`, ]); });