diff --git a/packages/playwright-core/src/client/channelOwner.ts b/packages/playwright-core/src/client/channelOwner.ts index a5d753507b..140389c596 100644 --- a/packages/playwright-core/src/client/channelOwner.ts +++ b/packages/playwright-core/src/client/channelOwner.ts @@ -148,15 +148,15 @@ export abstract class ChannelOwner { return await this._wrapApiCall(async apiZone => { - const { apiName, frames, csi, callCookie, stepId } = apiZone.reported ? { apiName: undefined, csi: undefined, callCookie: undefined, frames: [], stepId: undefined } : apiZone; - apiZone.reported = true; - let currentStepId = stepId; - if (csi && apiName) { - const out: { stepId?: string } = {}; - csi.onApiCallBegin(apiName, params, frames, callCookie, out); - currentStepId = out.stepId; + const validatedParams = validator(params, '', { tChannelImpl: tChannelImplToWire, binary: this._connection.rawBuffers() ? 'buffer' : 'toBase64' }); + if (!apiZone.isInternal && !apiZone.reported) { + apiZone.params = params; + apiZone.reported = true; + logApiCall(this._logger, `=> ${apiZone.apiName} started`); + apiZone.csi?.onApiCallBegin(apiZone); + return await this._connection.sendMessageToServer(this, prop, validatedParams, apiZone.apiName, apiZone.frames, apiZone.stepId); } - return await this._connection.sendMessageToServer(this, prop, validator(params, '', { tChannelImpl: tChannelImplToWire, binary: this._connection.rawBuffers() ? 'buffer' : 'toBase64' }), apiName, frames, currentStepId); + return await this._connection.sendMessageToServer(this, prop, validatedParams, undefined, [], undefined); }); }; } @@ -170,9 +170,9 @@ export abstract class ChannelOwner(func: (apiZone: ApiZone) => Promise, isInternal?: boolean): Promise { const logger = this._logger; - const apiZone = zones.zoneData('apiZone'); - if (apiZone) - return await func(apiZone); + const existingApiZone = zones.zoneData('apiZone'); + if (existingApiZone) + return await func(existingApiZone); const stackTrace = captureLibraryStackTrace(); let apiName: string | undefined = stackTrace.apiName; @@ -180,26 +180,23 @@ export abstract class ChannelOwner('expectZone'); const stepId = expectZone?.stepId; - if (!isInternal && expectZone) + if (expectZone) apiName = expectZone.title; // If we are coming from the expectZone, there is no need to generate a new // step for the API call, since it will be generated by the expect itself. const csi = isInternal || expectZone ? undefined : this._instrumentation; - const callCookie: any = {}; + const apiZone: ApiZone = { apiName, frames, isInternal, reported: false, csi, userData: undefined, stepId }; try { - logApiCall(logger, `=> ${apiName} started`, isInternal); - const apiZone: ApiZone = { apiName, frames, isInternal, reported: false, csi, callCookie, stepId }; const result = await zones.run('apiZone', apiZone, async () => await func(apiZone)); - csi?.onApiCallEnd(callCookie); - logApiCall(logger, `<= ${apiName} succeeded`, isInternal); + csi?.onApiCallEnd(apiZone); + if (!isInternal) + logApiCall(logger, `<= ${apiName} succeeded`); return result; } catch (e) { const innerError = ((process.env.PWDEBUGIMPL || isUnderTest()) && e.stack) ? '\n\n' + e.stack : ''; @@ -210,8 +207,9 @@ export abstract class ChannelOwner; frames: channels.StackFrame[]; isInternal: boolean; reported: boolean; csi: ClientInstrumentation | undefined; - callCookie: any; + userData: any; stepId?: string; + error?: Error; }; diff --git a/packages/playwright-core/src/client/clientInstrumentation.ts b/packages/playwright-core/src/client/clientInstrumentation.ts index 55c787df05..e2e8c6678e 100644 --- a/packages/playwright-core/src/client/clientInstrumentation.ts +++ b/packages/playwright-core/src/client/clientInstrumentation.ts @@ -18,12 +18,22 @@ import type { StackFrame } from '@protocol/channels'; import type { BrowserContext } from './browserContext'; import type { APIRequestContext } from './fetch'; +// Instrumentation can mutate the data, for example change apiName or stepId. +export interface ApiCallData { + apiName: string; + params?: Record; + frames: StackFrame[]; + userData: any; + stepId?: string; + error?: Error; +} + export interface ClientInstrumentation { addListener(listener: ClientInstrumentationListener): void; removeListener(listener: ClientInstrumentationListener): void; removeAllListeners(): void; - onApiCallBegin(apiCall: string, params: Record, frames: StackFrame[], userData: any, out: { stepId?: string }): void; - onApiCallEnd(userData: any, error?: Error): void; + onApiCallBegin(apiCall: ApiCallData): void; + onApiCallEnd(apiCal: ApiCallData): void; onWillPause(options: { keepTestTimeout: boolean }): void; runAfterCreateBrowserContext(context: BrowserContext): Promise; @@ -33,8 +43,8 @@ export interface ClientInstrumentation { } export interface ClientInstrumentationListener { - onApiCallBegin?(apiName: string, params: Record, frames: StackFrame[], userData: any, out: { stepId?: string }): void; - onApiCallEnd?(userData: any, error?: Error): void; + onApiCallBegin?(apiCall: ApiCallData): void; + onApiCallEnd?(apiCall: ApiCallData): void; onWillPause?(options: { keepTestTimeout: boolean }): void; runAfterCreateBrowserContext?(context: BrowserContext): Promise; diff --git a/packages/playwright/src/index.ts b/packages/playwright/src/index.ts index 27f1e9320b..c117c695b6 100644 --- a/packages/playwright/src/index.ts +++ b/packages/playwright/src/index.ts @@ -23,7 +23,7 @@ import type { Fixtures, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWor import type { TestInfoImpl, TestStepInternal } from './worker/testInfo'; import { rootTestType } from './common/testType'; import type { ContextReuseMode } from './common/config'; -import type { ClientInstrumentation, ClientInstrumentationListener } from '../../playwright-core/src/client/clientInstrumentation'; +import type { ApiCallData, ClientInstrumentation, ClientInstrumentationListener } from '../../playwright-core/src/client/clientInstrumentation'; import { currentTestInfo } from './common/globals'; export { expect } from './matchers/expect'; export const _baseTest: TestType<{}, {}> = rootTestType.test; @@ -258,34 +258,33 @@ const playwrightFixtures: Fixtures = ({ const tracingGroupSteps: TestStepInternal[] = []; const csiListener: ClientInstrumentationListener = { - onApiCallBegin: (apiName: string, params: Record, frames: StackFrame[], userData: any, out: { stepId?: string }) => { - userData.apiName = apiName; + onApiCallBegin: (data: ApiCallData) => { const testInfo = currentTestInfo(); - if (!testInfo || apiName.includes('setTestIdAttribute') || apiName === 'tracing.groupEnd') + if (!testInfo || data.apiName.includes('setTestIdAttribute') || data.apiName === 'tracing.groupEnd') return; const step = testInfo._addStep({ - location: frames[0] as any, + location: data.frames[0], category: 'pw:api', - title: renderApiCall(apiName, params), - apiName, - params, + title: renderApiCall(data.apiName, data.params), + apiName: data.apiName, + params: data.params, }, tracingGroupSteps[tracingGroupSteps.length - 1]); - userData.step = step; - out.stepId = step.stepId; - if (apiName === 'tracing.group') + data.userData = step; + data.stepId = step.stepId; + if (data.apiName === 'tracing.group') tracingGroupSteps.push(step); }, - onApiCallEnd: (userData: any, error?: Error) => { + onApiCallEnd: (data: ApiCallData) => { // "tracing.group" step will end later, when "tracing.groupEnd" finishes. - if (userData.apiName === 'tracing.group') + if (data.apiName === 'tracing.group') return; - if (userData.apiName === 'tracing.groupEnd') { + if (data.apiName === 'tracing.groupEnd') { const step = tracingGroupSteps.pop(); - step?.complete({ error }); + step?.complete({ error: data.error }); return; } - const step = userData.step; - step?.complete({ error }); + const step = data.userData; + step?.complete({ error: data.error }); }, onWillPause: ({ keepTestTimeout }) => { if (!keepTestTimeout)