From 1074a765e4c5a9edecc4aab6414111d2741ee28e Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Thu, 1 Aug 2024 14:14:10 -0700 Subject: [PATCH] fix(trace): do not place expect into unfinished api calls based on time (#31970) Fixes https://github.com/microsoft/playwright/issues/31959 --- packages/playwright/src/index.ts | 1 + packages/playwright/src/worker/testInfo.ts | 7 ++- .../playwright-test/playwright.trace.spec.ts | 48 +++++++++++++++++++ 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/packages/playwright/src/index.ts b/packages/playwright/src/index.ts index 9a159c984a..6dbf290d66 100644 --- a/packages/playwright/src/index.ts +++ b/packages/playwright/src/index.ts @@ -261,6 +261,7 @@ const playwrightFixtures: Fixtures = ({ title: renderApiCall(apiName, params), apiName, params, + canNestByTime: true, }); userData.userObject = step; out.stepId = step.stepId; diff --git a/packages/playwright/src/worker/testInfo.ts b/packages/playwright/src/worker/testInfo.ts index 1e9e1f9c46..bca8e8d96b 100644 --- a/packages/playwright/src/worker/testInfo.ts +++ b/packages/playwright/src/worker/testInfo.ts @@ -33,7 +33,7 @@ export interface TestStepInternal { complete(result: { error?: Error, attachments?: Attachment[] }): void; stepId: string; title: string; - category: 'hook' | 'fixture' | 'test.step' | 'expect' | string; + category: 'hook' | 'fixture' | 'test.step' | 'expect' | 'attach' | string; location?: Location; boxedStack?: StackFrame[]; steps: TestStepInternal[]; @@ -44,6 +44,9 @@ export interface TestStepInternal { infectParentStepsWithError?: boolean; box?: boolean; isStage?: boolean; + // TODO: this сould be decided based on the category, but pw:api + // is from a different abstraction layer. + canNestByTime?: boolean; } export type TestStage = { @@ -252,7 +255,7 @@ export class TestInfoImpl implements TestInfo { parentStep = this._findLastStageStep(); } else { parentStep = zones.zoneData('stepZone'); - if (!parentStep && data.category !== 'test.step') { + if (!parentStep && data.canNestByTime) { // API steps (but not test.step calls) can be nested by time, instead of by stack. // However, do not nest chains of route.continue by checking the title. parentStep = this._findLastNonFinishedStep(step => step.title !== data.title); diff --git a/tests/playwright-test/playwright.trace.spec.ts b/tests/playwright-test/playwright.trace.spec.ts index 9a692245f0..642a3555ca 100644 --- a/tests/playwright-test/playwright.trace.spec.ts +++ b/tests/playwright-test/playwright.trace.spec.ts @@ -1178,3 +1178,51 @@ test('should record trace for manually created context in a failed test', async // Check console events to make sure that library trace is recorded. expect(trace.events).toContainEqual(expect.objectContaining({ type: 'console', text: 'from the page' })); }); + +test('should not nest top level expect into unfinished api calls ', { + annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/31959' } +}, async ({ runInlineTest, server }) => { + server.setRoute('/index', (req, res) => { + res.writeHead(200, { 'Content-Type': 'text/html' }); + res.end(`
Hello!
`); + }); + server.setRoute('/hang', () => {}); + const result = await runInlineTest({ + 'a.spec.ts': ` + import { test, expect } from '@playwright/test'; + test('pass', async ({ page }) => { + await page.route('**/api', async route => { + const response = await route.fetch({ url: '${server.PREFIX}/hang' }); + await route.fulfill({ response }); + }); + await page.goto('${server.PREFIX}/index'); + await expect(page.getByText('Hello!')).toBeVisible(); + await page.unrouteAll({ behavior: 'ignoreErrors' }); + }); + `, + }, { trace: 'on' }); + expect(result.exitCode).toBe(0); + expect(result.failed).toBe(0); + + const tracePath = test.info().outputPath('test-results', 'a-pass', 'trace.zip'); + const trace = await parseTrace(tracePath); + expect(trace.actionTree).toEqual([ + 'Before Hooks', + ' fixture: browser', + ' browserType.launch', + ' fixture: context', + ' browser.newContext', + ' fixture: page', + ' browserContext.newPage', + 'page.route', + 'page.goto', + ' route.fetch', + ' page.unrouteAll', + 'expect.toBeVisible', + 'After Hooks', + ' fixture: page', + ' fixture: context', + ]); +}); + +