diff --git a/packages/playwright/src/index.ts b/packages/playwright/src/index.ts index 8b392afa37..130e731b5a 100644 --- a/packages/playwright/src/index.ts +++ b/packages/playwright/src/index.ts @@ -248,6 +248,11 @@ const playwrightFixtures: Fixtures = ({ }, { auto: 'all-hooks-included', title: 'context configuration', box: true } as any], _setupArtifacts: [async ({ playwright, screenshot }, use, testInfo) => { + // This fixture has a separate zero-timeout slot to ensure that artifact collection + // happens even after some fixtures or hooks time out. + // Now that default test timeout is known, we can replace zero with an actual value. + testInfo.setTimeout(testInfo.project.timeout); + const artifactsRecorder = new ArtifactsRecorder(playwright, tracing().artifactsDir(), screenshot); await artifactsRecorder.willStartTest(testInfo as TestInfoImpl); const csiListener: ClientInstrumentationListener = { @@ -297,7 +302,7 @@ const playwrightFixtures: Fixtures = ({ clientInstrumentation.removeListener(csiListener); await artifactsRecorder.didFinishTest(); - }, { auto: 'all-hooks-included', title: 'trace recording', box: true } as any], + }, { auto: 'all-hooks-included', title: 'trace recording', box: true, timeout: 0 } as any], _contextFactory: [async ({ browser, video, _reuseContext, _combinedContextOptions /** mitigate dep-via-auto lack of traceability */ }, use, testInfo) => { const testInfoImpl = testInfo as TestInfoImpl; diff --git a/tests/playwright-test/playwright.trace.spec.ts b/tests/playwright-test/playwright.trace.spec.ts index e4a1efe4d7..5ca08925b4 100644 --- a/tests/playwright-test/playwright.trace.spec.ts +++ b/tests/playwright-test/playwright.trace.spec.ts @@ -1225,4 +1225,44 @@ test('should not nest top level expect into unfinished api calls ', { ]); }); +test('should record trace after fixture teardown timeout', { + annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/30718' }, +}, async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'a.spec.ts': ` + import { test as base, expect } from '@playwright/test'; + const test = base.extend({ + fixture: async ({}, use) => { + await use('foo'); + await new Promise(() => {}); + }, + }); + // Note: it is important that "fixture" is last, so that it runs the teardown first. + test('fails', async ({ page, fixture }) => { + await page.evaluate(() => console.log('from the page')); + }); + `, + }, { trace: 'on', timeout: '3000' }, { DEBUG: 'pw:test' }); + expect(result.exitCode).toBe(1); + expect(result.failed).toBe(1); + const tracePath = test.info().outputPath('test-results', 'a-fails', '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', + ' fixture: fixture', + 'page.evaluate', + 'After Hooks', + ' fixture: fixture', + 'Worker Cleanup', + ' fixture: browser', + ]); + // Check console events to make sure that library trace is recorded. + expect(trace.events).toContainEqual(expect.objectContaining({ type: 'console', text: 'from the page' })); +});