Skip to content

Commit 51fa0ff

Browse files
authored
Revert "chore: move artifacts recording to TestLifecycleInstrumentation (#30935)" (#31694)
This reverts commit ba5b460.
1 parent 80f44f3 commit 51fa0ff

File tree

8 files changed

+91
-244
lines changed

8 files changed

+91
-244
lines changed

packages/playwright-core/src/client/tracing.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,24 +34,23 @@ export class Tracing extends ChannelOwner<channels.TracingChannel> implements ap
3434
}
3535

3636
async start(options: { name?: string, title?: string, snapshots?: boolean, screenshots?: boolean, sources?: boolean, _live?: boolean } = {}) {
37-
await this._wrapApiCall(async () => {
38-
this._includeSources = !!options.sources;
37+
this._includeSources = !!options.sources;
38+
const traceName = await this._wrapApiCall(async () => {
3939
await this._channel.tracingStart({
4040
name: options.name,
4141
snapshots: options.snapshots,
4242
screenshots: options.screenshots,
4343
live: options._live,
4444
});
4545
const response = await this._channel.tracingStartChunk({ name: options.name, title: options.title });
46-
await this._startCollectingStacks(response.traceName);
46+
return response.traceName;
4747
}, true);
48+
await this._startCollectingStacks(traceName);
4849
}
4950

5051
async startChunk(options: { name?: string, title?: string } = {}) {
51-
await this._wrapApiCall(async () => {
52-
const { traceName } = await this._channel.tracingStartChunk(options);
53-
await this._startCollectingStacks(traceName);
54-
}, true);
52+
const { traceName } = await this._channel.tracingStartChunk(options);
53+
await this._startCollectingStacks(traceName);
5554
}
5655

5756
private async _startCollectingStacks(traceName: string) {

packages/playwright/src/common/globals.ts

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -42,19 +42,3 @@ export function setIsWorkerProcess() {
4242
export function isWorkerProcess() {
4343
return _isWorkerProcess;
4444
}
45-
46-
export interface TestLifecycleInstrumentation {
47-
onTestBegin?(): Promise<void>;
48-
onTestFunctionEnd?(): Promise<void>;
49-
onTestEnd?(): Promise<void>;
50-
}
51-
52-
let _testLifecycleInstrumentation: TestLifecycleInstrumentation | undefined;
53-
54-
export function setTestLifecycleInstrumentation(instrumentation: TestLifecycleInstrumentation | undefined) {
55-
_testLifecycleInstrumentation = instrumentation;
56-
}
57-
58-
export function testLifecycleInstrumentation() {
59-
return _testLifecycleInstrumentation;
60-
}

packages/playwright/src/index.ts

Lines changed: 61 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@
1616

1717
import * as fs from 'fs';
1818
import * as path from 'path';
19-
import type { APIRequestContext, BrowserContext, Browser, BrowserContextOptions, LaunchOptions, Page, Tracing, Video, PageScreenshotOptions } from 'playwright-core';
19+
import type { APIRequestContext, BrowserContext, Browser, BrowserContextOptions, LaunchOptions, Page, Tracing, Video } from 'playwright-core';
20+
import * as playwrightLibrary from 'playwright-core';
2021
import { createGuid, debugMode, addInternalStackPrefix, isString, asLocator, jsonStringifyForceASCII } from 'playwright-core/lib/utils';
2122
import type { Fixtures, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, ScreenshotMode, TestInfo, TestType, VideoMode } from '../types/test';
2223
import type { TestInfoImpl } from './worker/testInfo';
2324
import { rootTestType } from './common/testType';
2425
import type { ContextReuseMode } from './common/config';
2526
import type { ClientInstrumentation, ClientInstrumentationListener } from '../../playwright-core/src/client/clientInstrumentation';
26-
import { currentTestInfo, setTestLifecycleInstrumentation, type TestLifecycleInstrumentation } from './common/globals';
27+
import { currentTestInfo } from './common/globals';
2728
export { expect } from './matchers/expect';
2829
export const _baseTest: TestType<{}, {}> = rootTestType.test;
2930

@@ -44,12 +45,11 @@ if ((process as any)['__pw_initiator__']) {
4445
type TestFixtures = PlaywrightTestArgs & PlaywrightTestOptions & {
4546
_combinedContextOptions: BrowserContextOptions,
4647
_setupContextOptions: void;
48+
_setupArtifacts: void;
4749
_contextFactory: (options?: BrowserContextOptions) => Promise<BrowserContext>;
4850
};
4951

5052
type WorkerFixtures = PlaywrightWorkerArgs & PlaywrightWorkerOptions & {
51-
// Same as "playwright", but exposed so that our internal tests can override it.
52-
_playwrightImpl: PlaywrightWorkerArgs['playwright'];
5353
_browserOptions: LaunchOptions;
5454
_optionContextReuseMode: ContextReuseMode,
5555
_optionConnectOptions: PlaywrightWorkerOptions['connectOptions'],
@@ -59,14 +59,9 @@ type WorkerFixtures = PlaywrightWorkerArgs & PlaywrightWorkerOptions & {
5959
const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
6060
defaultBrowserType: ['chromium', { scope: 'worker', option: true }],
6161
browserName: [({ defaultBrowserType }, use) => use(defaultBrowserType), { scope: 'worker', option: true }],
62-
_playwrightImpl: [({}, use) => use(require('playwright-core')), { scope: 'worker' }],
63-
64-
playwright: [async ({ _playwrightImpl, screenshot }, use) => {
65-
await connector.setPlaywright(_playwrightImpl, screenshot);
66-
await use(_playwrightImpl);
67-
await connector.setPlaywright(undefined, screenshot);
62+
playwright: [async ({}, use) => {
63+
await use(require('playwright-core'));
6864
}, { scope: 'worker', _hideStep: true } as any],
69-
7065
headless: [({ launchOptions }, use) => use(launchOptions.headless ?? true), { scope: 'worker', option: true }],
7166
channel: [({ launchOptions }, use) => use(launchOptions.channel), { scope: 'worker', option: true }],
7267
launchOptions: [{}, { scope: 'worker', option: true }],
@@ -227,7 +222,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
227222

228223
_setupContextOptions: [async ({ playwright, _combinedContextOptions, actionTimeout, navigationTimeout, testIdAttribute }, use, testInfo) => {
229224
if (testIdAttribute)
230-
playwright.selectors.setTestIdAttribute(testIdAttribute);
225+
playwrightLibrary.selectors.setTestIdAttribute(testIdAttribute);
231226
testInfo.snapshotSuffix = process.platform;
232227
if (debugMode())
233228
testInfo.setTimeout(0);
@@ -248,6 +243,58 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
248243
}
249244
}, { auto: 'all-hooks-included', _title: 'context configuration' } as any],
250245

246+
_setupArtifacts: [async ({ playwright, screenshot }, use, testInfo) => {
247+
const artifactsRecorder = new ArtifactsRecorder(playwright, tracing().artifactsDir(), screenshot);
248+
await artifactsRecorder.willStartTest(testInfo as TestInfoImpl);
249+
const csiListener: ClientInstrumentationListener = {
250+
onApiCallBegin: (apiName: string, params: Record<string, any>, frames: StackFrame[], userData: any, out: { stepId?: string }) => {
251+
const testInfo = currentTestInfo();
252+
if (!testInfo || apiName.includes('setTestIdAttribute'))
253+
return { userObject: null };
254+
const step = testInfo._addStep({
255+
location: frames[0] as any,
256+
category: 'pw:api',
257+
title: renderApiCall(apiName, params),
258+
apiName,
259+
params,
260+
});
261+
userData.userObject = step;
262+
out.stepId = step.stepId;
263+
},
264+
onApiCallEnd: (userData: any, error?: Error) => {
265+
const step = userData.userObject;
266+
step?.complete({ error });
267+
},
268+
onWillPause: () => {
269+
currentTestInfo()?.setTimeout(0);
270+
},
271+
runAfterCreateBrowserContext: async (context: BrowserContext) => {
272+
await artifactsRecorder?.didCreateBrowserContext(context);
273+
const testInfo = currentTestInfo();
274+
if (testInfo)
275+
attachConnectedHeaderIfNeeded(testInfo, context.browser());
276+
},
277+
runAfterCreateRequestContext: async (context: APIRequestContext) => {
278+
await artifactsRecorder?.didCreateRequestContext(context);
279+
},
280+
runBeforeCloseBrowserContext: async (context: BrowserContext) => {
281+
await artifactsRecorder?.willCloseBrowserContext(context);
282+
},
283+
runBeforeCloseRequestContext: async (context: APIRequestContext) => {
284+
await artifactsRecorder?.willCloseRequestContext(context);
285+
},
286+
};
287+
288+
const clientInstrumentation = (playwright as any)._instrumentation as ClientInstrumentation;
289+
clientInstrumentation.addListener(csiListener);
290+
291+
await use();
292+
293+
clientInstrumentation.removeListener(csiListener);
294+
await artifactsRecorder.didFinishTest();
295+
296+
}, { auto: 'all-hooks-included', _title: 'trace recording' } as any],
297+
251298
_contextFactory: [async ({ browser, video, _reuseContext }, use, testInfo) => {
252299
const testInfoImpl = testInfo as TestInfoImpl;
253300
const videoMode = normalizeVideoMode(video);
@@ -424,7 +471,7 @@ class ArtifactsRecorder {
424471
private _playwright: Playwright;
425472
private _artifactsDir: string;
426473
private _screenshotMode: ScreenshotMode;
427-
private _screenshotOptions: { mode: ScreenshotMode } & Pick<PageScreenshotOptions, 'fullPage' | 'omitBackground'> | undefined;
474+
private _screenshotOptions: { mode: ScreenshotMode } & Pick<playwrightLibrary.PageScreenshotOptions, 'fullPage' | 'omitBackground'> | undefined;
428475
private _temporaryScreenshots: string[] = [];
429476
private _temporaryArtifacts: string[] = [];
430477
private _reusedContexts = new Set<BrowserContext>();
@@ -449,6 +496,7 @@ class ArtifactsRecorder {
449496

450497
async willStartTest(testInfo: TestInfoImpl) {
451498
this._testInfo = testInfo;
499+
testInfo._onDidFinishTestFunction = () => this.didFinishTestFunction();
452500

453501
// Since beforeAll(s), test and afterAll(s) reuse the same TestInfo, make sure we do not
454502
// overwrite previous screenshots.
@@ -630,101 +678,6 @@ function tracing() {
630678
return (test.info() as TestInfoImpl)._tracing;
631679
}
632680

633-
class InstrumentationConnector implements TestLifecycleInstrumentation, ClientInstrumentationListener {
634-
private _playwright: PlaywrightWorkerArgs['playwright'] | undefined;
635-
private _screenshot: ScreenshotOption = 'off';
636-
private _artifactsRecorder: ArtifactsRecorder | undefined;
637-
private _testIsRunning = false;
638-
639-
constructor() {
640-
setTestLifecycleInstrumentation(this);
641-
}
642-
643-
async setPlaywright(playwright: PlaywrightWorkerArgs['playwright'] | undefined, screenshot: ScreenshotOption) {
644-
if (this._playwright) {
645-
if (this._testIsRunning) {
646-
// When "playwright" is destroyed during a test, collect artifacts immediately.
647-
await this.onTestEnd();
648-
}
649-
const clientInstrumentation = (this._playwright as any)._instrumentation as ClientInstrumentation;
650-
clientInstrumentation.removeListener(this);
651-
}
652-
this._playwright = playwright;
653-
this._screenshot = screenshot;
654-
if (this._playwright) {
655-
const clientInstrumentation = (this._playwright as any)._instrumentation as ClientInstrumentation;
656-
clientInstrumentation.addListener(this);
657-
if (this._testIsRunning) {
658-
// When "playwright" is created during a test, wire it up immediately.
659-
await this.onTestBegin();
660-
}
661-
}
662-
}
663-
664-
async onTestBegin() {
665-
this._testIsRunning = true;
666-
if (this._playwright) {
667-
this._artifactsRecorder = new ArtifactsRecorder(this._playwright, tracing().artifactsDir(), this._screenshot);
668-
await this._artifactsRecorder.willStartTest(currentTestInfo() as TestInfoImpl);
669-
}
670-
}
671-
672-
async onTestFunctionEnd() {
673-
await this._artifactsRecorder?.didFinishTestFunction();
674-
}
675-
676-
async onTestEnd() {
677-
await this._artifactsRecorder?.didFinishTest();
678-
this._artifactsRecorder = undefined;
679-
this._testIsRunning = false;
680-
}
681-
682-
onApiCallBegin(apiName: string, params: Record<string, any>, frames: StackFrame[], userData: any, out: { stepId?: string }) {
683-
const testInfo = currentTestInfo();
684-
if (!testInfo || apiName.includes('setTestIdAttribute'))
685-
return { userObject: null };
686-
const step = testInfo._addStep({
687-
location: frames[0] as any,
688-
category: 'pw:api',
689-
title: renderApiCall(apiName, params),
690-
apiName,
691-
params,
692-
});
693-
userData.userObject = step;
694-
out.stepId = step.stepId;
695-
}
696-
697-
onApiCallEnd(userData: any, error?: Error) {
698-
const step = userData.userObject;
699-
step?.complete({ error });
700-
}
701-
702-
onWillPause() {
703-
currentTestInfo()?.setTimeout(0);
704-
}
705-
706-
async runAfterCreateBrowserContext(context: BrowserContext) {
707-
await this._artifactsRecorder?.didCreateBrowserContext(context);
708-
const testInfo = currentTestInfo();
709-
if (testInfo)
710-
attachConnectedHeaderIfNeeded(testInfo, context.browser());
711-
}
712-
713-
async runAfterCreateRequestContext(context: APIRequestContext) {
714-
await this._artifactsRecorder?.didCreateRequestContext(context);
715-
}
716-
717-
async runBeforeCloseBrowserContext(context: BrowserContext) {
718-
await this._artifactsRecorder?.willCloseBrowserContext(context);
719-
}
720-
721-
async runBeforeCloseRequestContext(context: APIRequestContext) {
722-
await this._artifactsRecorder?.willCloseRequestContext(context);
723-
}
724-
}
725-
726-
const connector = new InstrumentationConnector();
727-
728681
export const test = _baseTest.extend<TestFixtures, WorkerFixtures>(playwrightFixtures);
729682

730683
export { defineConfig } from './common/configLoader';

packages/playwright/src/worker/testInfo.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ export class TestInfoImpl implements TestInfo {
6868
readonly _projectInternal: FullProjectInternal;
6969
readonly _configInternal: FullConfigInternal;
7070
private readonly _steps: TestStepInternal[] = [];
71+
_onDidFinishTestFunction: (() => Promise<void>) | undefined;
7172
private readonly _stages: TestStage[] = [];
7273
_hasNonRetriableError = false;
7374
_hasUnhandledError = false;

packages/playwright/src/worker/workerMain.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import { colors } from 'playwright-core/lib/utilsBundle';
1818
import { debugTest, relativeFilePath, serializeError } from '../util';
1919
import { type TestBeginPayload, type TestEndPayload, type RunPayload, type DonePayload, type WorkerInitParams, type TeardownErrorsPayload, stdioChunkToParams } from '../common/ipc';
20-
import { setCurrentTestInfo, setIsWorkerProcess, testLifecycleInstrumentation } from '../common/globals';
20+
import { setCurrentTestInfo, setIsWorkerProcess } from '../common/globals';
2121
import { deserializeConfig } from '../common/configLoader';
2222
import type { Suite, TestCase } from '../common/test';
2323
import type { Annotation, FullConfigInternal, FullProjectInternal } from '../common/config';
@@ -304,11 +304,10 @@ export class WorkerMain extends ProcessRunner {
304304
if (this._lastRunningTests.length > 10)
305305
this._lastRunningTests.shift();
306306
let shouldRunAfterEachHooks = false;
307-
const tracingSlot = { timeout: this._project.project.timeout, elapsed: 0 };
308307

309308
testInfo._allowSkips = true;
310309
await testInfo._runAsStage({ title: 'setup and test' }, async () => {
311-
await testInfo._runAsStage({ title: 'start tracing', runnable: { type: 'test', slot: tracingSlot } }, async () => {
310+
await testInfo._runAsStage({ title: 'start tracing', runnable: { type: 'test' } }, async () => {
312311
// Ideally, "trace" would be an config-level option belonging to the
313312
// test runner instead of a fixture belonging to Playwright.
314313
// However, for backwards compatibility, we have to read it from a fixture today.
@@ -319,7 +318,6 @@ export class WorkerMain extends ProcessRunner {
319318
if (typeof traceFixtureRegistration.fn === 'function')
320319
throw new Error(`"trace" option cannot be a function`);
321320
await testInfo._tracing.startIfNeeded(traceFixtureRegistration.fn);
322-
await testLifecycleInstrumentation()?.onTestBegin?.();
323321
});
324322

325323
if (this._isStopped || isSkipped) {
@@ -374,10 +372,10 @@ export class WorkerMain extends ProcessRunner {
374372

375373
try {
376374
// Run "immediately upon test function finish" callback.
377-
await testInfo._runAsStage({ title: 'on-test-function-finish', runnable: { type: 'test', slot: tracingSlot } }, async () => {
378-
await testLifecycleInstrumentation()?.onTestFunctionEnd?.();
379-
});
375+
await testInfo._runAsStage({ title: 'on-test-function-finish', runnable: { type: 'test', slot: afterHooksSlot } }, async () => testInfo._onDidFinishTestFunction?.());
380376
} catch (error) {
377+
if (error instanceof TimeoutManagerError)
378+
didTimeoutInAfterHooks = true;
381379
firstAfterHooksError = firstAfterHooksError ?? error;
382380
}
383381

@@ -460,8 +458,8 @@ export class WorkerMain extends ProcessRunner {
460458
}).catch(() => {}); // Ignore the top-level error, it is already inside TestInfo.errors.
461459
}
462460

461+
const tracingSlot = { timeout: this._project.project.timeout, elapsed: 0 };
463462
await testInfo._runAsStage({ title: 'stop tracing', runnable: { type: 'test', slot: tracingSlot } }, async () => {
464-
await testLifecycleInstrumentation()?.onTestEnd?.();
465463
await testInfo._tracing.stopIfNeeded();
466464
}).catch(() => {}); // Ignore the top-level error, it is already inside TestInfo.errors.
467465

tests/config/testModeFixtures.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,11 @@ export type TestModeTestFixtures = {
3030
export type TestModeWorkerFixtures = {
3131
toImplInWorkerScope: (rpcObject?: any) => any;
3232
playwright: typeof import('@playwright/test');
33-
_playwrightImpl: typeof import('@playwright/test');
3433
};
3534

3635
export const testModeTest = test.extend<TestModeTestFixtures, TestModeWorkerOptions & TestModeWorkerFixtures>({
3736
mode: ['default', { scope: 'worker', option: true }],
38-
_playwrightImpl: [async ({ mode }, run) => {
37+
playwright: [async ({ mode }, run) => {
3938
const testMode = {
4039
'default': new DefaultTestMode(),
4140
'service': new DefaultTestMode(),

tests/playwright-test/playwright.artifacts.spec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,10 @@ test('should work with screenshot: on', async ({ runInlineTest }, testInfo) => {
151151
' test-finished-1.png',
152152
'artifacts-shared-shared-failing',
153153
' test-failed-1.png',
154+
' test-failed-2.png',
154155
'artifacts-shared-shared-passing',
155156
' test-finished-1.png',
157+
' test-finished-2.png',
156158
'artifacts-two-contexts',
157159
' test-finished-1.png',
158160
' test-finished-2.png',
@@ -183,6 +185,7 @@ test('should work with screenshot: only-on-failure', async ({ runInlineTest }, t
183185
' test-failed-1.png',
184186
'artifacts-shared-shared-failing',
185187
' test-failed-1.png',
188+
' test-failed-2.png',
186189
'artifacts-two-contexts-failing',
187190
' test-failed-1.png',
188191
' test-failed-2.png',

0 commit comments

Comments
 (0)