diff --git a/packages/playwright-core/src/server/dispatchers/dispatcher.ts b/packages/playwright-core/src/server/dispatchers/dispatcher.ts index cc3820e21f..1b52ab5658 100644 --- a/packages/playwright-core/src/server/dispatchers/dispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/dispatcher.ts @@ -26,6 +26,7 @@ import { rewriteErrorMessage } from '../../utils/stackTrace'; import type { PlaywrightDispatcher } from './playwrightDispatcher'; import { eventsHelper } from '../..//utils/eventsHelper'; import type { RegisteredListener } from '../..//utils/eventsHelper'; +import type * as trace from '@trace/trace'; export const dispatcherSymbol = Symbol('dispatcher'); const metadataValidator = createMetadataValidator(); @@ -186,20 +187,15 @@ export class DispatcherConnection { private _sendMessageToClient(guid: string, type: string, method: string, params: any, sdkObject?: SdkObject) { if (sdkObject) { - const eventMetadata: CallMetadata = { - id: `event@${++lastEventId}`, - objectId: sdkObject?.guid, - pageId: sdkObject?.attribution?.page?.guid, - frameId: sdkObject?.attribution?.frame?.guid, - startTime: monotonicTime(), - endTime: 0, - type, + const event: trace.EventTraceEvent = { + type: 'event', + class: type, method, params: params || {}, - log: [], - snapshots: [] + time: monotonicTime(), + pageId: sdkObject?.attribution?.page?.guid, }; - sdkObject.instrumentation?.onEvent(sdkObject, eventMetadata); + sdkObject.instrumentation?.onEvent(sdkObject, event); } this.onmessage({ guid, method, params }); } @@ -330,5 +326,3 @@ function formatLogRecording(log: string[]): string { const rightLength = headerLength - header.length - leftLength; return `\n${'='.repeat(leftLength)}${header}${'='.repeat(rightLength)}\n${log.join('\n')}\n${'='.repeat(headerLength)}`; } - -let lastEventId = 0; diff --git a/packages/playwright-core/src/server/instrumentation.ts b/packages/playwright-core/src/server/instrumentation.ts index f8d22390ae..8f06a7afcd 100644 --- a/packages/playwright-core/src/server/instrumentation.ts +++ b/packages/playwright-core/src/server/instrumentation.ts @@ -35,6 +35,7 @@ export type Attribution = { import type { CallMetadata } from '@protocol/callMetadata'; export type { CallMetadata } from '@protocol/callMetadata'; +import type * as trace from '@trace/trace'; export const kTestSdkObjects = new WeakSet(); @@ -61,7 +62,7 @@ export interface Instrumentation { onBeforeInputAction(sdkObject: SdkObject, metadata: CallMetadata, element: ElementHandle): Promise; onCallLog(sdkObject: SdkObject, metadata: CallMetadata, logName: string, message: string): void; onAfterCall(sdkObject: SdkObject, metadata: CallMetadata): Promise; - onEvent(sdkObject: SdkObject, metadata: CallMetadata): void; + onEvent(sdkObject: SdkObject, event: trace.EventTraceEvent): void; onPageOpen(page: Page): void; onPageClose(page: Page): void; onBrowserOpen(browser: Browser): void; @@ -73,7 +74,7 @@ export interface InstrumentationListener { onBeforeInputAction?(sdkObject: SdkObject, metadata: CallMetadata, element: ElementHandle): Promise; onCallLog?(sdkObject: SdkObject, metadata: CallMetadata, logName: string, message: string): void; onAfterCall?(sdkObject: SdkObject, metadata: CallMetadata): Promise; - onEvent?(sdkObject: SdkObject, metadata: CallMetadata): void; + onEvent?(sdkObject: SdkObject, event: trace.EventTraceEvent): void; onPageOpen?(page: Page): void; onPageClose?(page: Page): void; onBrowserOpen?(browser: Browser): void; diff --git a/packages/playwright-core/src/server/trace/recorder/tracing.ts b/packages/playwright-core/src/server/trace/recorder/tracing.ts index 730e45ef0f..5d7195bb24 100644 --- a/packages/playwright-core/src/server/trace/recorder/tracing.ts +++ b/packages/playwright-core/src/server/trace/recorder/tracing.ts @@ -43,7 +43,7 @@ import type { SnapshotterBlob, SnapshotterDelegate } from './snapshotter'; import { Snapshotter } from './snapshotter'; import { yazl } from '../../../zipBundle'; -const version: VERSION = 3; +const version: VERSION = 4; export type TracerOptions = { name?: string; @@ -341,15 +341,25 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps } pendingCall.afterSnapshot = this._captureSnapshot('after', sdkObject, metadata); await pendingCall.afterSnapshot; - const event: trace.ActionTraceEvent = { type: 'action', metadata }; - this._appendTraceEvent(event); + const event = createActionTraceEvent(metadata); + if (event) + this._appendTraceEvent(event); this._pendingCalls.delete(metadata.id); } - onEvent(sdkObject: SdkObject, metadata: CallMetadata) { + onEvent(sdkObject: SdkObject, event: trace.EventTraceEvent) { if (!sdkObject.attribution.context) return; - const event: trace.ActionTraceEvent = { type: 'event', metadata }; + if (event.method === '__create__' && event.class === 'ConsoleMessage') { + const object: trace.ObjectTraceEvent = { + type: 'object', + class: event.class, + guid: event.params.guid, + initializer: event.params.initializer, + }; + this._appendTraceEvent(object); + return; + } this._appendTraceEvent(event); } @@ -467,3 +477,25 @@ function visitTraceEvent(object: any, sha1s: Set): any { export function shouldCaptureSnapshot(metadata: CallMetadata): boolean { return commandsWithTracingSnapshots.has(metadata.type + '.' + metadata.method); } + +function createActionTraceEvent(metadata: CallMetadata): trace.ActionTraceEvent | null { + if (metadata.internal || metadata.method.startsWith('tracing')) + return null; + return { + type: 'action', + callId: metadata.id, + startTime: metadata.startTime, + endTime: metadata.endTime, + apiName: metadata.apiName || metadata.type + '.' + metadata.method, + class: metadata.type, + method: metadata.method, + params: metadata.params, + wallTime: metadata.wallTime || Date.now(), + log: metadata.log, + snapshots: metadata.snapshots, + error: metadata.error?.error, + result: metadata.result, + point: metadata.point, + pageId: metadata.pageId, + }; +} diff --git a/packages/trace-viewer/src/entries.ts b/packages/trace-viewer/src/entries.ts index 2b9670d306..c55b4171b7 100644 --- a/packages/trace-viewer/src/entries.ts +++ b/packages/trace-viewer/src/entries.ts @@ -32,8 +32,8 @@ export type ContextEntry = { pages: PageEntry[]; resources: ResourceSnapshot[]; actions: trace.ActionTraceEvent[]; - events: trace.ActionTraceEvent[]; - objects: { [key: string]: any }; + events: trace.EventTraceEvent[]; + initializers: { [key: string]: any }; hasSource: boolean; }; @@ -60,7 +60,7 @@ export function createEmptyContext(): ContextEntry { resources: [], actions: [], events: [], - objects: {}, + initializers: {}, hasSource: false }; } diff --git a/packages/trace-viewer/src/traceModel.ts b/packages/trace-viewer/src/traceModel.ts index 7995450202..baef807ef1 100644 --- a/packages/trace-viewer/src/traceModel.ts +++ b/packages/trace-viewer/src/traceModel.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import type { CallMetadata } from '@protocol/callMetadata'; import type * as trace from '@trace/trace'; +import type * as traceV3 from './versions/traceV3'; import { parseClientSideCallMetadata } from '@trace/traceUtils'; import type zip from '@zip.js/zip.js'; // @ts-ignore @@ -87,7 +87,7 @@ export class TraceModel { await stacksEntry.getData!(writer); const metadataMap = parseClientSideCallMetadata(JSON.parse(await writer.getData())); for (const action of this.contextEntry.actions) - action.metadata.stack = action.metadata.stack || metadataMap.get(action.metadata.id); + action.stack = action.stack || metadataMap.get(action.callId); } this._build(); @@ -117,7 +117,7 @@ export class TraceModel { } private _build() { - this.contextEntry!.actions.sort((a1, a2) => a1.metadata.startTime - a2.metadata.startTime); + this.contextEntry!.actions.sort((a1, a2) => a1.startTime - a2.startTime); this.contextEntry!.resources = this._snapshotStorage!.resources(); } @@ -137,6 +137,8 @@ export class TraceModel { if (!line) return; const event = this._modernize(JSON.parse(line)); + if (!event) + return; switch (event.type) { case 'context-options': { this.contextEntry.browserName = event.browserName; @@ -153,22 +155,15 @@ export class TraceModel { break; } case 'action': { - const include = !isTracing(event.metadata) && (!event.metadata.internal || event.metadata.apiName); - if (include) { - if (!event.metadata.apiName) - event.metadata.apiName = event.metadata.type + '.' + event.metadata.method; - this.contextEntry!.actions.push(event); - } + this.contextEntry!.actions.push(event); break; } case 'event': { - const metadata = event.metadata; - if (metadata.pageId) { - if (metadata.method === '__create__') - this.contextEntry!.objects[metadata.params.guid] = metadata.params.initializer; - else - this.contextEntry!.events.push(event); - } + this.contextEntry!.events.push(event); + break; + } + case 'object': { + this.contextEntry!.initializers[event.guid] = event.initializer; break; } case 'resource-snapshot': @@ -178,9 +173,13 @@ export class TraceModel { this._snapshotStorage!.addFrameSnapshot(event.snapshot); break; } - if (event.type === 'action' || event.type === 'event') { - this.contextEntry!.startTime = Math.min(this.contextEntry!.startTime, event.metadata.startTime); - this.contextEntry!.endTime = Math.max(this.contextEntry!.endTime, event.metadata.endTime); + if (event.type === 'action') { + this.contextEntry!.startTime = Math.min(this.contextEntry!.startTime, event.startTime); + this.contextEntry!.endTime = Math.max(this.contextEntry!.endTime, event.endTime); + } + if (event.type === 'event') { + this.contextEntry!.startTime = Math.min(this.contextEntry!.startTime, event.time); + this.contextEntry!.endTime = Math.max(this.contextEntry!.endTime, event.time); } if (event.type === 'screencast-frame') { this.contextEntry!.startTime = Math.min(this.contextEntry!.startTime, event.timestamp); @@ -237,6 +236,54 @@ export class TraceModel { } return event; } + + _modernize_3_to_4(event: traceV3.TraceEvent): trace.TraceEvent | null { + if (event.type !== 'action') { + return event as traceV3.ContextCreatedTraceEvent | + traceV3.ScreencastFrameTraceEvent | + traceV3.ResourceSnapshotTraceEvent | + traceV3.FrameSnapshotTraceEvent; + } + + const metadata = event.metadata; + if (metadata.internal || metadata.method.startsWith('tracing')) + return null; + if (metadata.id.startsWith('event@')) { + if (metadata.method === '__create__' && metadata.type === 'ConsoleMessage') { + return { + type: 'object', + class: metadata.type, + guid: metadata.params.guid, + initializer: metadata.params.initializer, + }; + } + return { + type: 'event', + time: metadata.startTime, + class: metadata.type, + method: metadata.method, + params: metadata.params, + pageId: metadata.pageId, + }; + } + return { + type: 'action', + callId: metadata.id, + startTime: metadata.startTime, + endTime: metadata.endTime, + apiName: metadata.apiName || metadata.type + '.' + metadata.method, + class: metadata.type, + method: metadata.method, + params: metadata.params, + wallTime: metadata.wallTime || Date.now(), + log: metadata.log, + snapshots: metadata.snapshots, + error: metadata.error?.error, + result: metadata.result, + point: metadata.point, + pageId: metadata.pageId, + }; + } } export class PersistentSnapshotStorage extends BaseSnapshotStorage { @@ -254,7 +301,3 @@ export class PersistentSnapshotStorage extends BaseSnapshotStorage { return writer.getData(); } } - -function isTracing(metadata: CallMetadata): boolean { - return metadata.method.startsWith('tracing'); -} diff --git a/packages/trace-viewer/src/ui/actionList.tsx b/packages/trace-viewer/src/ui/actionList.tsx index 94989648e5..4fb935f062 100644 --- a/packages/trace-viewer/src/ui/actionList.tsx +++ b/packages/trace-viewer/src/ui/actionList.tsx @@ -45,8 +45,8 @@ export const ActionList: React.FC = ({ selectedItem={selectedAction} onSelected={(action: ActionTraceEvent) => onSelected(action)} onHighlighted={(action: ActionTraceEvent) => onHighlighted(action)} - itemKey={(action: ActionTraceEvent) => action.metadata.id} - itemType={(action: ActionTraceEvent) => action.metadata.error?.error?.message ? 'error' : undefined} + itemKey={(action: ActionTraceEvent) => action.callId} + itemType={(action: ActionTraceEvent) => action.error?.message ? 'error' : undefined} itemRender={(action: ActionTraceEvent) => renderAction(action, sdkLanguage, setSelectedTab)} showNoItemsMessage={true} >; @@ -57,17 +57,16 @@ const renderAction = ( sdkLanguage: Language | undefined, setSelectedTab: (tab: string) => void ) => { - const { metadata } = action; const { errors, warnings } = modelUtil.stats(action); - const locator = metadata.params.selector ? asLocator(sdkLanguage || 'javascript', metadata.params.selector) : undefined; + const locator = action.params.selector ? asLocator(sdkLanguage || 'javascript', action.params.selector) : undefined; return <>
- {metadata.apiName} + {action.apiName} {locator &&
{locator}
} - {metadata.method === 'goto' && metadata.params.url &&
{metadata.params.url}
} + {action.method === 'goto' && action.params.url &&
{action.params.url}
}
-
{metadata.endTime ? msToString(metadata.endTime - metadata.startTime) : 'Timed Out'}
+
{action.endTime ? msToString(action.endTime - action.startTime) : 'Timed Out'}
setSelectedTab('console')}> {!!errors &&
{errors}
} {!!warnings &&
{warnings}
} diff --git a/packages/trace-viewer/src/ui/callTab.tsx b/packages/trace-viewer/src/ui/callTab.tsx index 58874f8bab..8593d48779 100644 --- a/packages/trace-viewer/src/ui/callTab.tsx +++ b/packages/trace-viewer/src/ui/callTab.tsx @@ -14,7 +14,6 @@ * limitations under the License. */ -import type { CallMetadata } from '@protocol/callMetadata'; import type { SerializedValue } from '@protocol/channels'; import type { ActionTraceEvent } from '@trace/trace'; import { msToString } from '@web/uiUtils'; @@ -30,20 +29,20 @@ export const CallTab: React.FunctionComponent<{ }> = ({ action, sdkLanguage }) => { if (!action) return null; - const logs = action.metadata.log; - const error = action.metadata.error?.error?.message; - const params = { ...action.metadata.params }; + const logs = action.log; + const error = action.error?.message; + const params = { ...action.params }; // Strip down the waitForEventInfo data, we never need it. delete params.info; const paramKeys = Object.keys(params); - const wallTime = action.metadata.wallTime ? new Date(action.metadata.wallTime).toLocaleString() : null; - const duration = action.metadata.endTime ? msToString(action.metadata.endTime - action.metadata.startTime) : 'Timed Out'; + const wallTime = action.wallTime ? new Date(action.wallTime).toLocaleString() : null; + const duration = action.endTime ? msToString(action.endTime - action.startTime) : 'Timed Out'; return