From 9771b1ee74dd1969857dd246fdd0f4e29c78fce9 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Sat, 6 May 2023 10:25:32 -0700 Subject: [PATCH] chore: show steps for fixtures (#22860) Fixes https://github.com/microsoft/playwright/issues/22565 --- .../src/worker/fixtureRunner.ts | 35 ++++- .../playwright-test/src/worker/testInfo.ts | 9 +- .../playwright-test/src/worker/workerMain.ts | 2 + packages/trace-viewer/src/ui/modelUtil.ts | 9 +- .../playwright-test/playwright.reuse.spec.ts | 8 + .../playwright-test/playwright.trace.spec.ts | 18 +++ tests/playwright-test/reporter.spec.ts | 52 +++++- tests/playwright-test/test-step.spec.ts | 148 +++++++++++++++--- .../to-have-screenshot.spec.ts | 4 + .../ui-mode-test-progress.spec.ts | 2 + tests/playwright-test/ui-mode-trace.spec.ts | 7 +- 11 files changed, 257 insertions(+), 37 deletions(-) diff --git a/packages/playwright-test/src/worker/fixtureRunner.ts b/packages/playwright-test/src/worker/fixtureRunner.ts index 9f7935b0b7..c01093b8bb 100644 --- a/packages/playwright-test/src/worker/fixtureRunner.ts +++ b/packages/playwright-test/src/worker/fixtureRunner.ts @@ -16,7 +16,7 @@ import { formatLocation, debugTest } from '../util'; import { ManualPromise, zones } from 'playwright-core/lib/utils'; -import type { TestInfoImpl } from './testInfo'; +import type { TestInfoImpl, TestStepInternal } from './testInfo'; import type { FixtureDescription, TimeoutManager } from './timeoutManager'; import { fixtureParameterNames, type FixturePool, type FixtureRegistration, type FixtureScope } from '../common/fixtures'; import type { WorkerInfo } from '../../types/test'; @@ -74,6 +74,12 @@ class Fixture { } } + // Break the regustration function into before/after steps. Create these before/after stacks + // w/o scopes, and create single mutable step that will be converted into the after step. + const shouldGenerateStep = !this.registration.name.startsWith('_') && !this.registration.option && this.registration.scope === 'test'; + let mutableStepOnStack: TestStepInternal | undefined; + let afterStep: TestStepInternal | undefined; + let called = false; const useFuncStarted = new ManualPromise(); debugTest(`setup ${this.registration.name}`); @@ -85,7 +91,17 @@ class Fixture { this._useFuncFinished = new ManualPromise(); useFuncStarted.resolve(); await this._useFuncFinished; + + if (shouldGenerateStep) { + afterStep = testInfo._addStep({ + wallTime: Date.now(), + title: `fixture: ${this.registration.name}`, + category: 'fixture', + }, testInfo._afterHooksStep); + mutableStepOnStack!.stepId = afterStep.stepId; + } }; + const workerInfo: WorkerInfo = { config: testInfo.config, parallelIndex: testInfo.parallelIndex, workerIndex: testInfo.workerIndex, project: testInfo.project }; const info = this.registration.scope === 'worker' ? workerInfo : testInfo; testInfo._timeoutManager.setCurrentFixture(this._runnableDescription); @@ -99,7 +115,16 @@ class Fixture { }; try { const result = zones.preserve(async () => { - return await this.registration.fn(params, useFunc, info); + if (!shouldGenerateStep) + return await this.registration.fn(params, useFunc, info); + + await testInfo._runAsStep({ + title: `fixture: ${this.registration.name}`, + category: 'fixture', + }, async step => { + mutableStepOnStack = step; + return await this.registration.fn(params, useFunc, info); + }); }); if (result instanceof Promise) @@ -110,6 +135,12 @@ class Fixture { handleError(e); } await useFuncStarted; + if (shouldGenerateStep) { + mutableStepOnStack?.complete({}); + this._selfTeardownComplete?.finally(() => { + afterStep?.complete({}); + }); + } testInfo._timeoutManager.setCurrentFixture(undefined); } diff --git a/packages/playwright-test/src/worker/testInfo.ts b/packages/playwright-test/src/worker/testInfo.ts index e3c25a7a68..957f64d760 100644 --- a/packages/playwright-test/src/worker/testInfo.ts +++ b/packages/playwright-test/src/worker/testInfo.ts @@ -26,7 +26,7 @@ import type { Location } from '../../types/testReporter'; import { getContainedPath, normalizeAndSaveAttachment, sanitizeForFilePath, serializeError, trimLongString } from '../util'; import type * as trace from '@trace/trace'; -interface TestStepInternal { +export interface TestStepInternal { complete(result: { error?: Error | TestInfoError }): void; stepId: string; title: string; @@ -56,6 +56,8 @@ export class TestInfoImpl implements TestInfo { readonly _projectInternal: FullProjectInternal; readonly _configInternal: FullConfigInternal; readonly _steps: TestStepInternal[] = []; + _beforeHooksStep: TestStepInternal | undefined; + _afterHooksStep: TestStepInternal | undefined; // ------------ TestInfo fields ------------ readonly testId: string; @@ -212,9 +214,10 @@ export class TestInfoImpl implements TestInfo { } } - _addStep(data: Omit): TestStepInternal { + _addStep(data: Omit, parentStep?: TestStepInternal): TestStepInternal { const stepId = `${data.category}@${data.title}@${++this._lastStepId}`; - let parentStep = zones.zoneData('stepZone', captureRawStack()); + if (!parentStep) + parentStep = zones.zoneData('stepZone', captureRawStack()) || undefined; // For out-of-stack calls, locate the enclosing step. let isLaxParent = false; diff --git a/packages/playwright-test/src/worker/workerMain.ts b/packages/playwright-test/src/worker/workerMain.ts index 6293ab1257..6dbdf2388a 100644 --- a/packages/playwright-test/src/worker/workerMain.ts +++ b/packages/playwright-test/src/worker/workerMain.ts @@ -322,6 +322,7 @@ export class WorkerMain extends ProcessRunner { let testFunctionParams: object | null = null; await testInfo._runAsStep({ category: 'hook', title: 'Before Hooks' }, async step => { + testInfo._beforeHooksStep = step; // Note: wrap all preparation steps together, because failure/skip in any of them // prevents further setup and/or test from running. const beforeHooksError = await testInfo._runAndFailOnError(async () => { @@ -392,6 +393,7 @@ export class WorkerMain extends ProcessRunner { testInfo._timeoutManager.setCurrentRunnable({ type: 'afterEach', slot: afterHooksSlot }); } await testInfo._runAsStep({ category: 'hook', title: 'After Hooks' }, async step => { + testInfo._afterHooksStep = step; let firstAfterHooksError: TestInfoError | undefined; await testInfo._runWithTimeout(async () => { // Note: do not wrap all teardown steps together, because failure in any of them diff --git a/packages/trace-viewer/src/ui/modelUtil.ts b/packages/trace-viewer/src/ui/modelUtil.ts index b6943fbcfc..f18f35ae68 100644 --- a/packages/trace-viewer/src/ui/modelUtil.ts +++ b/packages/trace-viewer/src/ui/modelUtil.ts @@ -84,7 +84,7 @@ function indexModel(context: ContextEntry) { } function mergeActions(contexts: ContextEntry[]) { - const map = new Map(); + const map = new Map(); // Protocol call aka isPrimary contexts have startTime/endTime as server-side times. // Step aka non-isPrimary contexts have startTime/endTime are client-side times. @@ -95,7 +95,7 @@ function mergeActions(contexts: ContextEntry[]) { for (const context of primaryContexts) { for (const action of context.actions) - map.set(action.wallTime, action); + map.set(`${action.apiName}@${action.wallTime}`, action); if (!offset && context.actions.length) offset = context.actions[0].startTime - context.actions[0].wallTime; } @@ -110,7 +110,8 @@ function mergeActions(contexts: ContextEntry[]) { action.endTime = action.startTime + duration; } - const existing = map.get(action.wallTime); + const key = `${action.apiName}@${action.wallTime}`; + const existing = map.get(key); if (existing && existing.apiName === action.apiName) { if (action.error) existing.error = action.error; @@ -118,7 +119,7 @@ function mergeActions(contexts: ContextEntry[]) { existing.attachments = action.attachments; continue; } - map.set(action.wallTime, action); + map.set(key, action); } } diff --git a/tests/playwright-test/playwright.reuse.spec.ts b/tests/playwright-test/playwright.reuse.spec.ts index 381afb96d4..aea83b64f9 100644 --- a/tests/playwright-test/playwright.reuse.spec.ts +++ b/tests/playwright-test/playwright.reuse.spec.ts @@ -147,10 +147,14 @@ test('should reuse context with trace if mode=when-possible', async ({ runInline expect(trace1.apiNames).toEqual([ 'Before Hooks', 'browserType.launch', + 'fixture: context', + 'fixture: page', 'browserContext.newPage', 'page.setContent', 'page.click', 'After Hooks', + 'fixture: page', + 'fixture: context', 'tracing.stopChunk', ]); expect(trace1.traceModel.storage().snapshotsForTest().length).toBeGreaterThan(0); @@ -159,11 +163,15 @@ test('should reuse context with trace if mode=when-possible', async ({ runInline const trace2 = await parseTrace(testInfo.outputPath('test-results', 'reuse-two', 'trace.zip')); expect(trace2.apiNames).toEqual([ 'Before Hooks', + 'fixture: context', + 'fixture: page', 'expect.toBe', 'page.setContent', 'page.fill', 'locator.click', 'After Hooks', + 'fixture: page', + 'fixture: context', 'tracing.stopChunk', ]); expect(trace2.traceModel.storage().snapshotsForTest().length).toBeGreaterThan(0); diff --git a/tests/playwright-test/playwright.trace.spec.ts b/tests/playwright-test/playwright.trace.spec.ts index c4de3cb1cb..42123e9c95 100644 --- a/tests/playwright-test/playwright.trace.spec.ts +++ b/tests/playwright-test/playwright.trace.spec.ts @@ -89,16 +89,22 @@ test('should record api trace', async ({ runInlineTest, server }, testInfo) => { const trace1 = await parseTrace(testInfo.outputPath('test-results', 'a-pass', 'trace.zip')); expect(trace1.apiNames).toEqual([ 'Before Hooks', + 'fixture: request', 'apiRequest.newContext', 'tracing.start', 'browserType.launch', + 'fixture: context', 'browser.newContext', 'tracing.start', + 'fixture: page', 'browserContext.newPage', 'page.goto', 'apiRequestContext.get', 'After Hooks', + 'fixture: page', + 'fixture: context', 'browserContext.close', + 'fixture: request', 'tracing.stopChunk', 'apiRequestContext.dispose', ]); @@ -115,16 +121,22 @@ test('should record api trace', async ({ runInlineTest, server }, testInfo) => { expect(trace3.apiNames).toEqual([ 'Before Hooks', 'tracing.startChunk', + 'fixture: request', 'apiRequest.newContext', 'tracing.start', + 'fixture: context', 'browser.newContext', 'tracing.start', + 'fixture: page', 'browserContext.newPage', 'page.goto', 'apiRequestContext.get', 'expect.toBe', 'After Hooks', + 'fixture: page', + 'fixture: context', 'browserContext.close', + 'fixture: request', 'tracing.stopChunk', 'apiRequestContext.dispose', 'browser.close', @@ -317,16 +329,22 @@ test('should not override trace file in afterAll', async ({ runInlineTest, serve expect(trace1.apiNames).toEqual([ 'Before Hooks', 'browserType.launch', + 'fixture: context', 'browser.newContext', 'tracing.start', + 'fixture: page', 'browserContext.newPage', 'page.goto', 'After Hooks', + 'fixture: page', + 'fixture: context', 'browserContext.close', 'afterAll hook', + 'fixture: request', 'apiRequest.newContext', 'tracing.start', 'apiRequestContext.get', + 'fixture: request', 'tracing.stopChunk', 'apiRequestContext.dispose', 'browser.close', diff --git a/tests/playwright-test/reporter.spec.ts b/tests/playwright-test/reporter.spec.ts index fcfbdf4494..c8e3984c40 100644 --- a/tests/playwright-test/reporter.spec.ts +++ b/tests/playwright-test/reporter.spec.ts @@ -280,17 +280,25 @@ test('should report expect steps', async ({ runInlineTest }) => { `begin {\"title\":\"Before Hooks\",\"category\":\"hook\"}`, `begin {\"title\":\"browserType.launch\",\"category\":\"pw:api\"}`, `end {\"title\":\"browserType.launch\",\"category\":\"pw:api\"}`, + `begin {\"title\":\"fixture: context\",\"category\":\"fixture\"}`, `begin {\"title\":\"browser.newContext\",\"category\":\"pw:api\"}`, `end {\"title\":\"browser.newContext\",\"category\":\"pw:api\"}`, + `end {\"title\":\"fixture: context\",\"category\":\"fixture\",\"steps\":[{\"title\":\"browser.newContext\",\"category\":\"pw:api\"}]}`, + `begin {\"title\":\"fixture: page\",\"category\":\"fixture\"}`, `begin {\"title\":\"browserContext.newPage\",\"category\":\"pw:api\"}`, `end {\"title\":\"browserContext.newPage\",\"category\":\"pw:api\"}`, - `end {\"title\":\"Before Hooks\",\"category\":\"hook\",\"steps\":[{\"title\":\"browserType.launch\",\"category\":\"pw:api\"},{\"title\":\"browser.newContext\",\"category\":\"pw:api\"},{\"title\":\"browserContext.newPage\",\"category\":\"pw:api\"}]}`, + `end {\"title\":\"fixture: page\",\"category\":\"fixture\",\"steps\":[{\"title\":\"browserContext.newPage\",\"category\":\"pw:api\"}]}`, + `end {\"title\":\"Before Hooks\",\"category\":\"hook\",\"steps\":[{\"title\":\"browserType.launch\",\"category\":\"pw:api\"},{\"title\":\"fixture: context\",\"category\":\"fixture\",\"steps\":[{\"title\":\"browser.newContext\",\"category\":\"pw:api\"}]},{\"title\":\"fixture: page\",\"category\":\"fixture\",\"steps\":[{\"title\":\"browserContext.newPage\",\"category\":\"pw:api\"}]}]}`, `begin {\"title\":\"expect.not.toHaveTitle\",\"category\":\"expect\"}`, `end {\"title\":\"expect.not.toHaveTitle\",\"category\":\"expect\"}`, `begin {\"title\":\"After Hooks\",\"category\":\"hook\"}`, + `begin {\"title\":\"fixture: page\",\"category\":\"fixture\"}`, + `end {\"title\":\"fixture: page\",\"category\":\"fixture\"}`, + `begin {\"title\":\"fixture: context\",\"category\":\"fixture\"}`, + `end {\"title\":\"fixture: context\",\"category\":\"fixture\"}`, `begin {\"title\":\"browserContext.close\",\"category\":\"pw:api\"}`, `end {\"title\":\"browserContext.close\",\"category\":\"pw:api\"}`, - `end {\"title\":\"After Hooks\",\"category\":\"hook\",\"steps\":[{\"title\":\"browserContext.close\",\"category\":\"pw:api\"}]}`, + `end {\"title\":\"After Hooks\",\"category\":\"hook\",\"steps\":[{\"title\":\"fixture: page\",\"category\":\"fixture\"},{\"title\":\"fixture: context\",\"category\":\"fixture\"},{\"title\":\"browserContext.close\",\"category\":\"pw:api\"}]}`, ]); }); @@ -341,13 +349,19 @@ test('should report api steps', async ({ runInlineTest }) => { `begin {\"title\":\"Before Hooks\",\"category\":\"hook\"}`, `begin {\"title\":\"browserType.launch\",\"category\":\"pw:api\"}`, `end {\"title\":\"browserType.launch\",\"category\":\"pw:api\"}`, + `begin {\"title\":\"fixture: context\",\"category\":\"fixture\"}`, `begin {\"title\":\"browser.newContext\",\"category\":\"pw:api\"}`, `end {\"title\":\"browser.newContext\",\"category\":\"pw:api\"}`, + `end {\"title\":\"fixture: context\",\"category\":\"fixture\",\"steps\":[{\"title\":\"browser.newContext\",\"category\":\"pw:api\"}]}`, + `begin {\"title\":\"fixture: page\",\"category\":\"fixture\"}`, `begin {\"title\":\"browserContext.newPage\",\"category\":\"pw:api\"}`, `end {\"title\":\"browserContext.newPage\",\"category\":\"pw:api\"}`, + `end {\"title\":\"fixture: page\",\"category\":\"fixture\",\"steps\":[{\"title\":\"browserContext.newPage\",\"category\":\"pw:api\"}]}`, + `begin {\"title\":\"fixture: request\",\"category\":\"fixture\"}`, `begin {\"title\":\"apiRequest.newContext\",\"category\":\"pw:api\"}`, `end {\"title\":\"apiRequest.newContext\",\"category\":\"pw:api\"}`, - `end {\"title\":\"Before Hooks\",\"category\":\"hook\",\"steps\":[{\"title\":\"browserType.launch\",\"category\":\"pw:api\"},{\"title\":\"browser.newContext\",\"category\":\"pw:api\"},{\"title\":\"browserContext.newPage\",\"category\":\"pw:api\"},{\"title\":\"apiRequest.newContext\",\"category\":\"pw:api\"}]}`, + `end {\"title\":\"fixture: request\",\"category\":\"fixture\",\"steps\":[{\"title\":\"apiRequest.newContext\",\"category\":\"pw:api\"}]}`, + `end {\"title\":\"Before Hooks\",\"category\":\"hook\",\"steps\":[{\"title\":\"browserType.launch\",\"category\":\"pw:api\"},{\"title\":\"fixture: context\",\"category\":\"fixture\",\"steps\":[{\"title\":\"browser.newContext\",\"category\":\"pw:api\"}]},{\"title\":\"fixture: page\",\"category\":\"fixture\",\"steps\":[{\"title\":\"browserContext.newPage\",\"category\":\"pw:api\"}]},{\"title\":\"fixture: request\",\"category\":\"fixture\",\"steps\":[{\"title\":\"apiRequest.newContext\",\"category\":\"pw:api\"}]}]}`, `begin {\"title\":\"page.waitForNavigation\",\"category\":\"pw:api\"}`, `begin {\"title\":\"page.goto(data:text/html,)\",\"category\":\"pw:api\"}`, `end {\"title\":\"page.waitForNavigation\",\"category\":\"pw:api\",\"steps\":[{\"title\":\"page.goto(data:text/html,)\",\"category\":\"pw:api\"}]}`, @@ -361,11 +375,17 @@ test('should report api steps', async ({ runInlineTest }) => { `begin {"title":"apiRequestContext.get(http://localhost2)","category":"pw:api"}`, `end {"title":"apiRequestContext.get(http://localhost2)","category":"pw:api","error":{"message":"","stack":"","location":"","snippet":""}}`, `begin {\"title\":\"After Hooks\",\"category\":\"hook\"}`, + `begin {\"title\":\"fixture: request\",\"category\":\"fixture\"}`, `begin {\"title\":\"apiRequestContext.dispose\",\"category\":\"pw:api\"}`, `end {\"title\":\"apiRequestContext.dispose\",\"category\":\"pw:api\"}`, + `end {\"title\":\"fixture: request\",\"category\":\"fixture\",\"steps\":[{\"title\":\"apiRequestContext.dispose\",\"category\":\"pw:api\"}]}`, + `begin {\"title\":\"fixture: page\",\"category\":\"fixture\"}`, + `end {\"title\":\"fixture: page\",\"category\":\"fixture\"}`, + `begin {\"title\":\"fixture: context\",\"category\":\"fixture\"}`, + `end {\"title\":\"fixture: context\",\"category\":\"fixture\"}`, `begin {\"title\":\"browserContext.close\",\"category\":\"pw:api\"}`, `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\"}]}`, + `end {\"title\":\"After Hooks\",\"category\":\"hook\",\"steps\":[{\"title\":\"fixture: request\",\"category\":\"fixture\",\"steps\":[{\"title\":\"apiRequestContext.dispose\",\"category\":\"pw:api\"}]},{\"title\":\"fixture: page\",\"category\":\"fixture\"},{\"title\":\"fixture: context\",\"category\":\"fixture\"},{\"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\"}`, @@ -414,21 +434,29 @@ test('should report api step failure', async ({ runInlineTest }) => { `begin {\"title\":\"Before Hooks\",\"category\":\"hook\"}`, `begin {\"title\":\"browserType.launch\",\"category\":\"pw:api\"}`, `end {\"title\":\"browserType.launch\",\"category\":\"pw:api\"}`, + `begin {\"title\":\"fixture: context\",\"category\":\"fixture\"}`, `begin {\"title\":\"browser.newContext\",\"category\":\"pw:api\"}`, `end {\"title\":\"browser.newContext\",\"category\":\"pw:api\"}`, + `end {\"title\":\"fixture: context\",\"category\":\"fixture\",\"steps\":[{\"title\":\"browser.newContext\",\"category\":\"pw:api\"}]}`, + `begin {\"title\":\"fixture: page\",\"category\":\"fixture\"}`, `begin {\"title\":\"browserContext.newPage\",\"category\":\"pw:api\"}`, `end {\"title\":\"browserContext.newPage\",\"category\":\"pw:api\"}`, - `end {\"title\":\"Before Hooks\",\"category\":\"hook\",\"steps\":[{\"title\":\"browserType.launch\",\"category\":\"pw:api\"},{\"title\":\"browser.newContext\",\"category\":\"pw:api\"},{\"title\":\"browserContext.newPage\",\"category\":\"pw:api\"}]}`, + `end {\"title\":\"fixture: page\",\"category\":\"fixture\",\"steps\":[{\"title\":\"browserContext.newPage\",\"category\":\"pw:api\"}]}`, + `end {\"title\":\"Before Hooks\",\"category\":\"hook\",\"steps\":[{\"title\":\"browserType.launch\",\"category\":\"pw:api\"},{\"title\":\"fixture: context\",\"category\":\"fixture\",\"steps\":[{\"title\":\"browser.newContext\",\"category\":\"pw:api\"}]},{\"title\":\"fixture: page\",\"category\":\"fixture\",\"steps\":[{\"title\":\"browserContext.newPage\",\"category\":\"pw:api\"}]}]}`, `begin {\"title\":\"page.setContent\",\"category\":\"pw:api\"}`, `end {\"title\":\"page.setContent\",\"category\":\"pw:api\"}`, `begin {\"title\":\"page.click(input)\",\"category\":\"pw:api\"}`, `end {\"title\":\"page.click(input)\",\"category\":\"pw:api\",\"error\":{\"message\":\"page.click: Timeout 1ms exceeded.\\n=========================== logs ===========================\\nwaiting for locator('input')\\n============================================================\",\"stack\":\"\",\"location\":\"\",\"snippet\":\"\"}}`, `begin {\"title\":\"After Hooks\",\"category\":\"hook\"}`, + `begin {\"title\":\"fixture: page\",\"category\":\"fixture\"}`, + `end {\"title\":\"fixture: page\",\"category\":\"fixture\"}`, + `begin {\"title\":\"fixture: context\",\"category\":\"fixture\"}`, + `end {\"title\":\"fixture: context\",\"category\":\"fixture\"}`, `begin {\"title\":\"browserContext.close\",\"category\":\"pw:api\"}`, `end {\"title\":\"browserContext.close\",\"category\":\"pw:api\"}`, `begin {\"title\":\"browser.close\",\"category\":\"pw:api\"}`, `end {\"title\":\"browser.close\",\"category\":\"pw:api\"}`, - `end {\"title\":\"After Hooks\",\"category\":\"hook\",\"steps\":[{\"title\":\"browserContext.close\",\"category\":\"pw:api\"},{\"title\":\"browser.close\",\"category\":\"pw:api\"}]}`, + `end {\"title\":\"After Hooks\",\"category\":\"hook\",\"steps\":[{\"title\":\"fixture: page\",\"category\":\"fixture\"},{\"title\":\"fixture: context\",\"category\":\"fixture\"},{\"title\":\"browserContext.close\",\"category\":\"pw:api\"},{\"title\":\"browser.close\",\"category\":\"pw:api\"}]}`, ]); }); @@ -480,19 +508,27 @@ test('should show nice stacks for locators', async ({ runInlineTest }) => { `begin {"title":"Before Hooks","category":"hook"}`, `begin {\"title\":\"browserType.launch\",\"category\":\"pw:api\"}`, `end {\"title\":\"browserType.launch\",\"category\":\"pw:api\"}`, + `begin {\"title\":\"fixture: context\",\"category\":\"fixture\"}`, `begin {\"title\":\"browser.newContext\",\"category\":\"pw:api\"}`, `end {\"title\":\"browser.newContext\",\"category\":\"pw:api\"}`, + `end {\"title\":\"fixture: context\",\"category\":\"fixture\",\"steps\":[{\"title\":\"browser.newContext\",\"category\":\"pw:api\"}]}`, + `begin {\"title\":\"fixture: page\",\"category\":\"fixture\"}`, `begin {"title":"browserContext.newPage","category":"pw:api"}`, `end {"title":"browserContext.newPage","category":"pw:api"}`, - `end {\"title\":\"Before Hooks\",\"category\":\"hook\",\"steps\":[{\"title\":\"browserType.launch\",\"category\":\"pw:api\"},{\"title\":\"browser.newContext\",\"category\":\"pw:api\"},{\"title\":\"browserContext.newPage\",\"category\":\"pw:api\"}]}`, + `end {\"title\":\"fixture: page\",\"category\":\"fixture\",\"steps\":[{\"title\":\"browserContext.newPage\",\"category\":\"pw:api\"}]}`, + `end {\"title\":\"Before Hooks\",\"category\":\"hook\",\"steps\":[{\"title\":\"browserType.launch\",\"category\":\"pw:api\"},{\"title\":\"fixture: context\",\"category\":\"fixture\",\"steps\":[{\"title\":\"browser.newContext\",\"category\":\"pw:api\"}]},{\"title\":\"fixture: page\",\"category\":\"fixture\",\"steps\":[{\"title\":\"browserContext.newPage\",\"category\":\"pw:api\"}]}]}`, `begin {"title":"page.setContent","category":"pw:api"}`, `end {"title":"page.setContent","category":"pw:api"}`, `begin {"title":"locator.evaluate(button)","category":"pw:api"}`, `end {"title":"locator.evaluate(button)","category":"pw:api"}`, `begin {"title":"After Hooks","category":"hook"}`, + `begin {\"title\":\"fixture: page\",\"category\":\"fixture\"}`, + `end {\"title\":\"fixture: page\",\"category\":\"fixture\"}`, + `begin {\"title\":\"fixture: context\",\"category\":\"fixture\"}`, + `end {\"title\":\"fixture: context\",\"category\":\"fixture\"}`, `begin {"title":"browserContext.close","category":"pw:api"}`, `end {"title":"browserContext.close","category":"pw:api"}`, - `end {"title":"After Hooks","category":"hook","steps":[{"title":"browserContext.close","category":"pw:api"}]}`, + `end {\"title\":\"After Hooks\",\"category\":\"hook\",\"steps\":[{\"title\":\"fixture: page\",\"category\":\"fixture\"},{\"title\":\"fixture: context\",\"category\":\"fixture\"},{\"title\":\"browserContext.close\",\"category\":\"pw:api\"}]}`, ]); }); diff --git a/tests/playwright-test/test-step.spec.ts b/tests/playwright-test/test-step.spec.ts index 4dc0caf635..ad3f5eeb4c 100644 --- a/tests/playwright-test/test-step.spec.ts +++ b/tests/playwright-test/test-step.spec.ts @@ -100,12 +100,24 @@ test('should report api step hierarchy', async ({ runInlineTest }) => { title: 'browserType.launch', }, { - category: 'pw:api', - title: 'browser.newContext', + category: 'fixture', + title: 'fixture: context', + steps: [ + { + category: 'pw:api', + title: 'browser.newContext', + }, + ] }, { - category: 'pw:api', - title: 'browserContext.newPage', + category: 'fixture', + title: 'fixture: page', + steps: [ + { + category: 'pw:api', + title: 'browserContext.newPage', + }, + ] }, ], }, @@ -171,6 +183,14 @@ test('should report api step hierarchy', async ({ runInlineTest }) => { category: 'hook', title: 'After Hooks', steps: [ + { + category: 'fixture', + title: 'fixture: page', + }, + { + category: 'fixture', + title: 'fixture: context', + }, { category: 'pw:api', title: 'browserContext.close', @@ -255,12 +275,24 @@ test('should not report nested after hooks', async ({ runInlineTest }) => { title: 'browserType.launch', }, { - category: 'pw:api', - title: 'browser.newContext', + category: 'fixture', + title: 'fixture: context', + steps: [ + { + category: 'pw:api', + title: 'browser.newContext', + }, + ] }, { - category: 'pw:api', - title: 'browserContext.newPage', + category: 'fixture', + title: 'fixture: page', + steps: [ + { + category: 'pw:api', + title: 'browserContext.newPage', + }, + ] }, ], }, @@ -277,6 +309,14 @@ test('should not report nested after hooks', async ({ runInlineTest }) => { category: 'hook', title: 'After Hooks', steps: [ + { + category: 'fixture', + title: 'fixture: page', + }, + { + category: 'fixture', + title: 'fixture: context', + }, { category: 'pw:api', title: 'browserContext.close', @@ -332,16 +372,20 @@ test('should report test.step from fixtures', async ({ runInlineTest }) => { expect(result.exitCode).toBe(0); expect(result.outputLines).toEqual([ `begin Before Hooks`, + `begin fixture: foo`, `begin setup foo`, `end setup foo`, + `end fixture: foo`, `end Before Hooks`, `begin test step`, `begin inside foo`, `end inside foo`, `end test step`, `begin After Hooks`, + `begin fixture: foo`, `begin teardown foo`, `end teardown foo`, + `end fixture: foo`, `end After Hooks`, ]); }); @@ -374,12 +418,24 @@ test('should report expect step locations', async ({ runInlineTest }) => { title: 'browserType.launch', }, { - category: 'pw:api', - title: 'browser.newContext', + category: 'fixture', + title: 'fixture: context', + steps: [ + { + category: 'pw:api', + title: 'browser.newContext', + }, + ] }, { - category: 'pw:api', - title: 'browserContext.newPage', + category: 'fixture', + title: 'fixture: page', + steps: [ + { + category: 'pw:api', + title: 'browserContext.newPage', + }, + ] }, ], }, @@ -396,6 +452,14 @@ test('should report expect step locations', async ({ runInlineTest }) => { category: 'hook', title: 'After Hooks', steps: [ + { + category: 'fixture', + title: 'fixture: page', + }, + { + category: 'fixture', + title: 'fixture: context', + }, { category: 'pw:api', title: 'browserContext.close', @@ -622,13 +686,25 @@ test('should nest steps based on zones', async ({ runInlineTest }) => { category: 'pw:api' }, { - category: 'pw:api', - title: 'browser.newContext', + category: 'fixture', + title: 'fixture: context', + steps: [ + { + category: 'pw:api', + title: 'browser.newContext', + }, + ] }, { - title: 'browserContext.newPage', - category: 'pw:api' - } + category: 'fixture', + title: 'fixture: page', + steps: [ + { + category: 'pw:api', + title: 'browserContext.newPage', + }, + ] + }, ] }, { @@ -700,6 +776,14 @@ test('should nest steps based on zones', async ({ runInlineTest }) => { ], location: { file: 'a.test.ts', line: 'number', column: 'number' } }, + { + category: 'fixture', + title: 'fixture: page', + }, + { + category: 'fixture', + title: 'fixture: context', + }, { title: 'browserContext.close', category: 'pw:api' @@ -856,8 +940,26 @@ test('should nest page.continue insize page.goto steps', async ({ runInlineTest category: 'hook', steps: [ { title: 'browserType.launch', category: 'pw:api' }, - { title: 'browser.newContext', category: 'pw:api' }, - { title: 'browserContext.newPage', category: 'pw:api' }, + { + category: 'fixture', + title: 'fixture: context', + steps: [ + { + category: 'pw:api', + title: 'browser.newContext', + }, + ] + }, + { + category: 'fixture', + title: 'fixture: page', + steps: [ + { + category: 'pw:api', + title: 'browserContext.newPage', + }, + ] + }, ], }, { @@ -881,6 +983,14 @@ test('should nest page.continue insize page.goto steps', async ({ runInlineTest title: 'After Hooks', category: 'hook', steps: [ + { + category: 'fixture', + title: 'fixture: page', + }, + { + category: 'fixture', + title: 'fixture: context', + }, { title: 'browserContext.close', category: 'pw:api' }, ], }, diff --git a/tests/playwright-test/to-have-screenshot.spec.ts b/tests/playwright-test/to-have-screenshot.spec.ts index e83e75e7b6..133694c2c1 100644 --- a/tests/playwright-test/to-have-screenshot.spec.ts +++ b/tests/playwright-test/to-have-screenshot.spec.ts @@ -208,10 +208,14 @@ test('should report toHaveScreenshot step with expectation name in title', async expect(result.outputLines).toEqual([ `end browserType.launch`, `end browser.newContext`, + `end fixture: context`, `end browserContext.newPage`, + `end fixture: page`, `end Before Hooks`, `end expect.toHaveScreenshot(foo.png)`, `end expect.toHaveScreenshot(is-a-test-1.png)`, + `end fixture: page`, + `end fixture: context`, `end browserContext.close`, `end After Hooks`, ]); diff --git a/tests/playwright-test/ui-mode-test-progress.spec.ts b/tests/playwright-test/ui-mode-test-progress.spec.ts index 8b325a97c7..bdfd8da1d9 100644 --- a/tests/playwright-test/ui-mode-test-progress.spec.ts +++ b/tests/playwright-test/ui-mode-test-progress.spec.ts @@ -108,6 +108,8 @@ test('should update trace live', async ({ runUITest, server }) => { /page.gotohttp:\/\/localhost:\d+\/one.html/, /page.gotohttp:\/\/localhost:\d+\/two.html/, /After Hooks[\d.]+m?s/, + /fixture: page[\d.]+m?s/, + /fixture: context[\d.]+m?s/, /browserContext.close[\d.]+m?s/, ]); }); diff --git a/tests/playwright-test/ui-mode-trace.spec.ts b/tests/playwright-test/ui-mode-trace.spec.ts index d28de3bfe5..04d7724ccd 100644 --- a/tests/playwright-test/ui-mode-trace.spec.ts +++ b/tests/playwright-test/ui-mode-trace.spec.ts @@ -45,8 +45,9 @@ test('should merge trace events', async ({ runUITest, server }) => { /locator.clickgetByRole\('button'\)[\d.]+m?s/, /expect.toBe[\d.]+m?s/, /After Hooks[\d.]+m?s/, + /fixture: page[\d.]+m?s/, + /fixture: context[\d.]+m?s/, /browserContext.close[\d.]+m?s/, - ]); }); @@ -73,6 +74,8 @@ test('should merge web assertion events', async ({ runUITest }, testInfo) => { /page.setContent[\d.]+m?s/, /expect.toBeVisiblelocator\('button'\)[\d.]+m?s/, /After Hooks[\d.]+m?s/, + /fixture: page[\d.]+m?s/, + /fixture: context[\d.]+m?s/, /browserContext.close[\d.]+m?s/, ]); }); @@ -148,6 +151,8 @@ test('should show snapshots for sync assertions', async ({ runUITest, server }) /locator\.clickgetByRole\('button'\)[\d.]+m?s/, /expect\.toBe[\d.]+m?s/, /After Hooks[\d.]+m?s/, + /fixture: page[\d.]+m?s/, + /fixture: context[\d.]+m?s/, /browserContext.close[\d.]+m?s/, ]);