chore: move from apiName to title in tracing (#36003)

This commit is contained in:
Pavel Feldman 2025-05-19 16:01:45 -07:00 committed by GitHub
parent 3c888d59dd
commit 27ce0eb867
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 333 additions and 59 deletions

View File

@ -47,7 +47,7 @@ import type * as har from '@trace/har';
import type { FrameSnapshot } from '@trace/snapshot'; import type { FrameSnapshot } from '@trace/snapshot';
import type * as trace from '@trace/trace'; import type * as trace from '@trace/trace';
const version: trace.VERSION = 7; const version: trace.VERSION = 8;
export type TracerOptions = { export type TracerOptions = {
name?: string; name?: string;
@ -230,7 +230,7 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
type: 'before', type: 'before',
callId: metadata.id, callId: metadata.id,
startTime: metadata.startTime, startTime: metadata.startTime,
apiName: name, title: name,
class: 'Tracing', class: 'Tracing',
method: 'tracingGroup', method: 'tracingGroup',
params: { }, params: { },
@ -650,7 +650,10 @@ function createBeforeActionTraceEvent(metadata: CallMetadata, parentId?: string)
type: 'before', type: 'before',
callId: metadata.id, callId: metadata.id,
startTime: metadata.startTime, startTime: metadata.startTime,
apiName: metadata.apiName || metadata.type + '.' + metadata.method, // This will disappear for action trace events, their titles will be
// built based on the protocol metainfo. If I don't do this now,
// trace ill get frame.click instead of page.click in trace viewer.
title: metadata.apiName,
class: metadata.type, class: metadata.type,
method: metadata.method, method: metadata.method,
params: metadata.params, params: metadata.params,

View File

@ -329,7 +329,7 @@ export class TestInfoImpl implements TestInfo {
location: data.location, location: data.location,
}; };
this._onStepBegin(payload); this._onStepBegin(payload);
this._tracing.appendBeforeActionForStep(stepId, parentStep?.stepId, data.apiName || data.title, data.params, data.location ? [data.location] : []); this._tracing.appendBeforeActionForStep(stepId, parentStep?.stepId, { title: data.title, params: data.params, stack: data.location ? [data.location] : [] });
return step; return step;
} }
@ -425,7 +425,7 @@ export class TestInfoImpl implements TestInfo {
this._stepMap.get(stepId)!.attachmentIndices.push(index); this._stepMap.get(stepId)!.attachmentIndices.push(index);
} else { } else {
const callId = `attach@${createGuid()}`; const callId = `attach@${createGuid()}`;
this._tracing.appendBeforeActionForStep(callId, undefined, `Attach "${attachment.name}"`, undefined, []); this._tracing.appendBeforeActionForStep(callId, undefined, { title: `Attach "${attachment.name}"`, stack: [] });
this._tracing.appendAfterActionForStep(callId, undefined, [attachment]); this._tracing.appendAfterActionForStep(callId, undefined, [attachment]);
} }

View File

@ -31,7 +31,7 @@ import type EventEmitter from 'events';
export type Attachment = TestInfo['attachments'][0]; export type Attachment = TestInfo['attachments'][0];
export const testTraceEntryName = 'test.trace'; export const testTraceEntryName = 'test.trace';
const version: trace.VERSION = 7; const version: trace.VERSION = 8;
let traceOrdinal = 0; let traceOrdinal = 0;
type TraceFixtureValue = PlaywrightWorkerOptions['trace'] | undefined; type TraceFixtureValue = PlaywrightWorkerOptions['trace'] | undefined;
@ -267,7 +267,7 @@ export class TestTracing {
}); });
} }
appendBeforeActionForStep(callId: string, parentId: string | undefined, apiName: string, params: Record<string, any> | undefined, stack: StackFrame[]) { appendBeforeActionForStep(callId: string, parentId: string | undefined, options: { title: string, params?: Record<string, any>, stack: StackFrame[] }) {
this._appendTraceEvent({ this._appendTraceEvent({
type: 'before', type: 'before',
callId, callId,
@ -275,9 +275,9 @@ export class TestTracing {
startTime: monotonicTime(), startTime: monotonicTime(),
class: 'Test', class: 'Test',
method: 'step', method: 'step',
apiName, title: options.title,
params: Object.fromEntries(Object.entries(params || {}).map(([name, value]) => [name, generatePreview(value)])), params: Object.fromEntries(Object.entries(options.params || {}).map(([name, value]) => [name, generatePreview(value)])),
stack, stack: options.stack,
}); });
} }

View File

@ -20,6 +20,7 @@ import type * as traceV4 from './versions/traceV4';
import type * as traceV5 from './versions/traceV5'; import type * as traceV5 from './versions/traceV5';
import type * as traceV6 from './versions/traceV6'; import type * as traceV6 from './versions/traceV6';
import type * as traceV7 from './versions/traceV7'; import type * as traceV7 from './versions/traceV7';
import type * as traceV8 from './versions/traceV8';
import type { ActionEntry, ContextEntry, PageEntry } from '../types/entries'; import type { ActionEntry, ContextEntry, PageEntry } from '../types/entries';
import type { SnapshotStorage } from './snapshotStorage'; import type { SnapshotStorage } from './snapshotStorage';
@ -32,7 +33,7 @@ export class TraceVersionError extends Error {
// 6 => 10/2023 ~1.40 // 6 => 10/2023 ~1.40
// 7 => 05/2024 ~1.45 // 7 => 05/2024 ~1.45
const latestVersion: trace.VERSION = 7; const latestVersion: trace.VERSION = 8;
export class TraceModernizer { export class TraceModernizer {
private _contextEntry: ContextEntry; private _contextEntry: ContextEntry;
@ -409,4 +410,15 @@ export class TraceModernizer {
} }
return result; return result;
} }
_modernize_7_to_8(events: traceV7.TraceEvent[]): traceV8.TraceEvent[] {
const result: traceV8.TraceEvent[] = [];
for (const event of events) {
result.push(event);
if (event.type !== 'before' || !event.apiName)
continue;
(event as traceV8.BeforeActionTraceEvent).title = event.apiName;
}
return result;
}
} }

View File

@ -0,0 +1,260 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { Entry as ResourceSnapshot } from '@trace/har';
type Language = 'javascript' | 'python' | 'java' | 'csharp' | 'jsonl';
type Point = { x: number, y: number };
type Size = { width: number, height: number };
type StackFrame = {
file: string,
line: number,
column: number,
function?: string,
};
type Binary = Buffer;
type SerializedValue = {
n?: number,
b?: boolean,
s?: string,
v?: 'null' | 'undefined' | 'NaN' | 'Infinity' | '-Infinity' | '-0',
d?: string,
u?: string,
bi?: string,
ta?: {
b: Binary,
k: 'i8' | 'ui8' | 'ui8c' | 'i16' | 'ui16' | 'i32' | 'ui32' | 'f32' | 'f64' | 'bi64' | 'bui64',
},
e?: {
m: string,
n: string,
s: string,
},
r?: {
p: string,
f: string,
},
a?: SerializedValue[],
o?: {
k: string,
v: SerializedValue,
}[],
h?: number,
id?: number,
ref?: number,
};
type SerializedError = {
error?: {
message: string,
name: string,
stack?: string,
},
value?: SerializedValue,
};
// Text node.
type TextNodeSnapshot = string;
// Subtree reference, "x snapshots ago, node #y". Could point to a text node.
// Only nodes that are not references are counted, starting from zero, using post-order traversal.
type SubtreeReferenceSnapshot = [ [number, number] ];
// Node name, and optional attributes and child nodes.
type NodeNameAttributesChildNodesSnapshot = [ string ] | [ string, Record<string, string>, ...NodeSnapshot[] ];
type NodeSnapshot =
TextNodeSnapshot |
SubtreeReferenceSnapshot |
NodeNameAttributesChildNodesSnapshot;
type ResourceOverride = {
url: string,
sha1?: string,
ref?: number
};
type FrameSnapshot = {
snapshotName?: string,
callId: string,
pageId: string,
frameId: string,
frameUrl: string,
timestamp: number,
wallTime?: number,
collectionTime: number,
doctype?: string,
html: NodeSnapshot,
resourceOverrides: ResourceOverride[],
viewport: { width: number, height: number },
isMainFrame: boolean,
};
type BrowserContextEventOptions = {
baseURL?: string,
viewport?: Size,
deviceScaleFactor?: number,
isMobile?: boolean,
userAgent?: string,
};
export type ContextCreatedTraceEvent = {
version: number,
type: 'context-options',
origin: 'testRunner' | 'library',
browserName: string,
channel?: string,
platform: string,
wallTime: number,
monotonicTime: number,
title?: string,
options: BrowserContextEventOptions,
sdkLanguage?: Language,
testIdAttributeName?: string,
contextId?: string,
};
export type ScreencastFrameTraceEvent = {
type: 'screencast-frame',
pageId: string,
sha1: string,
width: number,
height: number,
timestamp: number,
frameSwapWallTime?: number,
};
export type BeforeActionTraceEvent = {
type: 'before',
callId: string;
startTime: number;
title?: string;
class: string;
method: string;
params: Record<string, any>;
stepId?: string;
beforeSnapshot?: string;
stack?: StackFrame[];
pageId?: string;
parentId?: string;
};
export type InputActionTraceEvent = {
type: 'input',
callId: string;
inputSnapshot?: string;
point?: Point;
};
export type AfterActionTraceEventAttachment = {
name: string;
contentType: string;
path?: string;
sha1?: string;
base64?: string;
};
export type AfterActionTraceEventAnnotation = {
type: string,
description?: string
};
export type AfterActionTraceEvent = {
type: 'after',
callId: string;
endTime: number;
afterSnapshot?: string;
error?: SerializedError['error'];
attachments?: AfterActionTraceEventAttachment[];
annotations?: AfterActionTraceEventAnnotation[];
result?: any;
point?: Point;
};
export type LogTraceEvent = {
type: 'log',
callId: string;
time: number;
message: string;
};
export type EventTraceEvent = {
type: 'event',
time: number;
class: string;
method: string;
params: any;
pageId?: string;
};
export type ConsoleMessageTraceEvent = {
type: 'console';
time: number;
pageId?: string;
messageType: string,
text: string,
args?: { preview: string, value: any }[],
location: {
url: string,
lineNumber: number,
columnNumber: number,
},
};
export type ResourceSnapshotTraceEvent = {
type: 'resource-snapshot',
snapshot: ResourceSnapshot,
};
export type FrameSnapshotTraceEvent = {
type: 'frame-snapshot',
snapshot: FrameSnapshot,
};
export type ActionTraceEvent = {
type: 'action',
} & Omit<BeforeActionTraceEvent, 'type'>
& Omit<AfterActionTraceEvent, 'type'>
& Omit<InputActionTraceEvent, 'type'>;
export type StdioTraceEvent = {
type: 'stdout' | 'stderr';
timestamp: number;
text?: string;
base64?: string;
};
export type ErrorTraceEvent = {
type: 'error';
message: string;
stack?: StackFrame[];
};
export type TraceEvent =
ContextCreatedTraceEvent |
ScreencastFrameTraceEvent |
ActionTraceEvent |
BeforeActionTraceEvent |
InputActionTraceEvent |
AfterActionTraceEvent |
EventTraceEvent |
LogTraceEvent |
ConsoleMessageTraceEvent |
ResourceSnapshotTraceEvent |
FrameSnapshotTraceEvent |
StdioTraceEvent |
ErrorTraceEvent;

View File

@ -128,9 +128,10 @@ export const renderAction = (
time = 'Timed out'; time = 'Timed out';
else if (!isLive) else if (!isLive)
time = '-'; time = '-';
const title = action.title ?? action.class.toLowerCase() + '.' + action.method;
return <> return <>
<div className='action-title' title={action.apiName}> <div className='action-title' title={title}>
<span>{action.apiName}</span> <span>{title}</span>
{parameterString && {parameterString &&
(parameterString.type === 'locator' ? ( (parameterString.type === 'locator' ? (
<> <>

View File

@ -42,7 +42,7 @@ export const CallTab: React.FunctionComponent<{
return ( return (
<div className='call-tab'> <div className='call-tab'>
<div className='call-line'>{action.apiName}</div> <div className='call-line'>{action.title}</div>
<div className='call-section'>Time</div> <div className='call-section'>Time</div>
<DateTimeCallLine name='start:' value={startTime} /> <DateTimeCallLine name='start:' value={startTime} />
<DateTimeCallLine name='duration:' value={renderDuration(action)} /> <DateTimeCallLine name='duration:' value={renderDuration(action)} />

View File

@ -153,9 +153,9 @@ function indexModel(context: ContextEntry) {
} }
let lastNonRouteAction = undefined; let lastNonRouteAction = undefined;
for (let i = context.actions.length - 1; i >= 0; i--) { for (let i = context.actions.length - 1; i >= 0; i--) {
const action = context.actions[i] as any; const action = context.actions[i] as ActionTraceEvent;
action[nextInContextSymbol] = lastNonRouteAction; (action as any)[nextInContextSymbol] = lastNonRouteAction;
if (!action.apiName.includes('route.')) if (action.class !== 'Route')
lastNonRouteAction = action; lastNonRouteAction = action;
} }
for (const event of context.events) for (const event of context.events)
@ -249,7 +249,7 @@ function mergeActionsAndUpdateTimingSameTrace(contexts: ContextEntry[]): ActionT
for (const context of libraryContexts) { for (const context of libraryContexts) {
for (const action of context.actions) { for (const action of context.actions) {
const key = matchByStepId ? action.stepId! : `${action.apiName}@${(action as any).wallTime}`; const key = matchByStepId ? action.stepId! : `${action.title}@${(action as any).wallTime}`;
map.set(key, { ...action, context }); map.set(key, { ...action, context });
} }
} }
@ -265,7 +265,7 @@ function mergeActionsAndUpdateTimingSameTrace(contexts: ContextEntry[]): ActionT
const nonPrimaryIdToPrimaryId = new Map<string, string>(); const nonPrimaryIdToPrimaryId = new Map<string, string>();
for (const context of testRunnerContexts) { for (const context of testRunnerContexts) {
for (const action of context.actions) { for (const action of context.actions) {
const key = matchByStepId ? action.callId : `${action.apiName}@${(action as any).wallTime}`; const key = matchByStepId ? action.callId : `${action.title}@${(action as any).wallTime}`;
const existing = map.get(key); const existing = map.get(key);
if (existing) { if (existing) {
nonPrimaryIdToPrimaryId.set(action.callId, existing.callId); nonPrimaryIdToPrimaryId.set(action.callId, existing.callId);
@ -326,7 +326,7 @@ function monotonicTimeDeltaBetweenLibraryAndRunner(nonPrimaryContexts: ContextEn
for (const action of context.actions) { for (const action of context.actions) {
if (!action.startTime) if (!action.startTime)
continue; continue;
const key = matchByStepId ? action.callId! : `${action.apiName}@${(action as any).wallTime}`; const key = matchByStepId ? action.callId! : `${action.title}@${(action as any).wallTime}`;
const libraryAction = libraryActions.get(key); const libraryAction = libraryActions.get(key);
if (libraryAction) if (libraryAction)
return action.startTime - libraryAction.startTime; return action.startTime - libraryAction.startTime;
@ -436,6 +436,7 @@ const kRouteMethods = new Set([
'browsercontext.unroute', 'browsercontext.unroute',
'browsercontext.unrouteall', 'browsercontext.unrouteall',
]); ]);
{ {
// .NET adds async suffix. // .NET adds async suffix.
for (const method of [...kRouteMethods]) for (const method of [...kRouteMethods])
@ -449,6 +450,3 @@ const kRouteMethods = new Set([
]) ])
kRouteMethods.add(method); kRouteMethods.add(method);
} }
export function isRouteAction(action: ActionTraceEventInContext) {
return action.class === 'Route' || kRouteMethods.has(action.apiName.toLowerCase());
}

View File

@ -105,7 +105,7 @@ export const Workbench: React.FunctionComponent<{
// Select the last non-after hooks item. // Select the last non-after hooks item.
let index = model.actions.length - 1; let index = model.actions.length - 1;
for (let i = 0; i < model.actions.length; ++i) { for (let i = 0; i < model.actions.length; ++i) {
if (model.actions[i].apiName === 'After Hooks' && i) { if (model.actions[i].title === 'After Hooks' && i) {
index = i - 1; index = i - 1;
break; break;
} }

View File

@ -20,8 +20,8 @@ import type { Point, SerializedError, StackFrame } from '@protocol/channels';
export type Size = { width: number, height: number }; export type Size = { width: number, height: number };
// Make sure you add _modernize_N_to_N1(event: any) to traceModel.ts. // Make sure you add _modernize_N_to_N1(event: any) to traceModernizer.ts.
export type VERSION = 7; export type VERSION = 8;
export type BrowserContextEventOptions = { export type BrowserContextEventOptions = {
baseURL?: string, baseURL?: string,
@ -61,7 +61,7 @@ export type BeforeActionTraceEvent = {
type: 'before', type: 'before',
callId: string; callId: string;
startTime: number; startTime: number;
apiName: string; title?: string;
class: string; class: string;
method: string; method: string;
params: Record<string, any>; params: Record<string, any>;

View File

@ -151,13 +151,13 @@ export async function parseTraceRaw(file: string): Promise<{ events: any[], reso
return { return {
events, events,
resources, resources,
actions: actionObjects.map(a => a.apiName), actions: actionObjects.map(a => a.title ?? a.class.toLowerCase() + '.' + a.method),
actionObjects, actionObjects,
stacks, stacks,
}; };
} }
export async function parseTrace(file: string): Promise<{ resources: Map<string, Buffer>, events: (EventTraceEvent | ConsoleMessageTraceEvent)[], actions: ActionTraceEvent[], apiNames: string[], traceModel: TraceModel, model: MultiTraceModel, actionTree: string[], errors: string[] }> { export async function parseTrace(file: string): Promise<{ resources: Map<string, Buffer>, events: (EventTraceEvent | ConsoleMessageTraceEvent)[], actions: ActionTraceEvent[], titles: string[], traceModel: TraceModel, model: MultiTraceModel, actionTree: string[], errors: string[] }> {
const backend = new TraceBackend(file); const backend = new TraceBackend(file);
const traceModel = new TraceModel(); const traceModel = new TraceModel();
await traceModel.load(backend, () => {}); await traceModel.load(backend, () => {});
@ -165,13 +165,13 @@ export async function parseTrace(file: string): Promise<{ resources: Map<string,
const { rootItem } = buildActionTree(model.actions); const { rootItem } = buildActionTree(model.actions);
const actionTree: string[] = []; const actionTree: string[] = [];
const visit = (actionItem: ActionTreeItem, indent: string) => { const visit = (actionItem: ActionTreeItem, indent: string) => {
actionTree.push(`${indent}${actionItem.action?.apiName || actionItem.id}`); actionTree.push(`${indent}${actionItem.action?.title || actionItem.id}`);
for (const child of actionItem.children) for (const child of actionItem.children)
visit(child, indent + ' '); visit(child, indent + ' ');
}; };
rootItem.children.forEach(a => visit(a, '')); rootItem.children.forEach(a => visit(a, ''));
return { return {
apiNames: model.actions.map(a => a.apiName), titles: model.actions.map(a => a.title ?? a.class.toLowerCase() + '.' + a.method),
resources: backend.entries, resources: backend.entries,
actions: model.actions, actions: model.actions,
events: model.events, events: model.events,

View File

@ -64,7 +64,7 @@ test('should collect trace with resources, but no js', async ({ context, page, s
expect(script.snapshot.response.content._sha1).toBe(undefined); expect(script.snapshot.response.content._sha1).toBe(undefined);
}); });
test('should use the correct apiName for event driven callbacks', async ({ context, page, server }, testInfo) => { test('should use the correct title for event driven callbacks', async ({ context, page, server }, testInfo) => {
await context.tracing.start(); await context.tracing.start();
// route.* calls should not be included in the trace // route.* calls should not be included in the trace
await page.route('**/empty.html', route => route.continue()); await page.route('**/empty.html', route => route.continue());
@ -125,7 +125,7 @@ test('can call tracing.group/groupEnd at any time and auto-close', async ({ cont
const { events } = await parseTraceRaw(testInfo.outputPath('trace.zip')); const { events } = await parseTraceRaw(testInfo.outputPath('trace.zip'));
const groups = events.filter(e => e.method === 'tracingGroup'); const groups = events.filter(e => e.method === 'tracingGroup');
expect(groups).toHaveLength(1); expect(groups).toHaveLength(1);
expect(groups[0].apiName).toBe('actual'); expect(groups[0].title).toBe('actual');
expect(events.some(e => e.type === 'after' && e.callId === groups[0].callId)).toBe(true); expect(events.some(e => e.type === 'after' && e.callId === groups[0].callId)).toBe(true);
}); });
@ -135,7 +135,7 @@ test('should not include buffers in the trace', async ({ context, page, server }
await page.screenshot(); await page.screenshot();
await context.tracing.stop({ path: testInfo.outputPath('trace.zip') }); await context.tracing.stop({ path: testInfo.outputPath('trace.zip') });
const { actionObjects } = await parseTraceRaw(testInfo.outputPath('trace.zip')); const { actionObjects } = await parseTraceRaw(testInfo.outputPath('trace.zip'));
const screenshotEvent = actionObjects.find(a => a.apiName === 'page.screenshot'); const screenshotEvent = actionObjects.find(a => a.title === 'page.screenshot');
expect(screenshotEvent.beforeSnapshot).toBeTruthy(); expect(screenshotEvent.beforeSnapshot).toBeTruthy();
expect(screenshotEvent.afterSnapshot).toBeTruthy(); expect(screenshotEvent.afterSnapshot).toBeTruthy();
expect(screenshotEvent.result).toEqual({ expect(screenshotEvent.result).toEqual({
@ -166,7 +166,7 @@ test('should include context API requests', async ({ browserName, context, page,
await page.request.post(server.PREFIX + '/simple.json', { data: { foo: 'bar' } }); await page.request.post(server.PREFIX + '/simple.json', { data: { foo: 'bar' } });
await context.tracing.stop({ path: testInfo.outputPath('trace.zip') }); await context.tracing.stop({ path: testInfo.outputPath('trace.zip') });
const { events } = await parseTraceRaw(testInfo.outputPath('trace.zip')); const { events } = await parseTraceRaw(testInfo.outputPath('trace.zip'));
const postEvent = events.find(e => e.apiName === 'apiRequestContext.post'); const postEvent = events.find(e => e.title === 'apiRequestContext.post');
expect(postEvent).toBeTruthy(); expect(postEvent).toBeTruthy();
const harEntry = events.find(e => e.type === 'resource-snapshot'); const harEntry = events.find(e => e.type === 'resource-snapshot');
expect(harEntry).toBeTruthy(); expect(harEntry).toBeTruthy();
@ -484,7 +484,7 @@ test('should include interrupted actions', async ({ context, page, server }, tes
await context.close(); await context.close();
const { events } = await parseTraceRaw(testInfo.outputPath('trace.zip')); const { events } = await parseTraceRaw(testInfo.outputPath('trace.zip'));
const clickEvent = events.find(e => e.apiName === 'page.click'); const clickEvent = events.find(e => e.title === 'page.click');
expect(clickEvent).toBeTruthy(); expect(clickEvent).toBeTruthy();
}); });
@ -605,7 +605,7 @@ test('should hide internal stack frames', async ({ context, page }, testInfo) =>
await context.tracing.stop({ path: tracePath }); await context.tracing.stop({ path: tracePath });
const trace = await parseTraceRaw(tracePath); const trace = await parseTraceRaw(tracePath);
const actions = trace.actionObjects.filter(a => !a.apiName.startsWith('tracing.')); const actions = trace.actionObjects.filter(a => a.class !== 'Tracing');
expect(actions).toHaveLength(4); expect(actions).toHaveLength(4);
for (const action of actions) for (const action of actions)
expect(relativeStack(action, trace.stacks)).toEqual(['tracing.spec.ts']); expect(relativeStack(action, trace.stacks)).toEqual(['tracing.spec.ts']);
@ -626,7 +626,7 @@ test('should hide internal stack frames in expect', async ({ context, page }, te
await context.tracing.stop({ path: tracePath }); await context.tracing.stop({ path: tracePath });
const trace = await parseTraceRaw(tracePath); const trace = await parseTraceRaw(tracePath);
const actions = trace.actionObjects.filter(a => !a.apiName.startsWith('tracing.')); const actions = trace.actionObjects.filter(a => a.class !== 'Tracing');
expect(actions).toHaveLength(5); expect(actions).toHaveLength(5);
for (const action of actions) for (const action of actions)
expect(relativeStack(action, trace.stacks)).toEqual(['tracing.spec.ts']); expect(relativeStack(action, trace.stacks)).toEqual(['tracing.spec.ts']);
@ -813,7 +813,7 @@ test('should not emit after w/o before', async ({ browserType, mode }, testInfo)
return { return {
type: e.type, type: e.type,
callId: +e.callId.split('@')[1] - minCallId, callId: +e.callId.split('@')[1] - minCallId,
apiName: e.apiName, title: e.title,
}; };
} }
}; };
@ -826,17 +826,17 @@ test('should not emit after w/o before', async ({ browserType, mode }, testInfo)
{ {
type: 'before', type: 'before',
callId: expect.any(Number), callId: expect.any(Number),
apiName: 'page.evaluate' title: 'page.evaluate'
}, },
{ {
type: 'before', type: 'before',
callId: expect.any(Number), callId: expect.any(Number),
apiName: 'page.waitForEvent' title: 'page.waitForEvent'
}, },
{ {
type: 'after', type: 'after',
callId: expect.any(Number), callId: expect.any(Number),
apiName: undefined, title: undefined,
}, },
]); ]);
call1 = sanitized[0].callId; call1 = sanitized[0].callId;
@ -852,12 +852,12 @@ test('should not emit after w/o before', async ({ browserType, mode }, testInfo)
{ {
type: 'before', type: 'before',
callId: expect.any(Number), callId: expect.any(Number),
apiName: 'page.evaluateHandle' title: 'page.evaluateHandle'
}, },
{ {
type: 'after', type: 'after',
callId: expect.any(Number), callId: expect.any(Number),
apiName: undefined title: undefined
} }
]); ]);
call2before = sanitized[0].callId; call2before = sanitized[0].callId;

View File

@ -213,7 +213,7 @@ test('should record trace', async ({ runInlineTest }) => {
expect(fs.existsSync(test.info().outputPath('test-results', 'a-pass', 'trace.zip'))).toBe(false); expect(fs.existsSync(test.info().outputPath('test-results', 'a-pass', 'trace.zip'))).toBe(false);
const trace = await parseTrace(test.info().outputPath('test-results', 'a-fail', 'trace.zip')); const trace = await parseTrace(test.info().outputPath('test-results', 'a-fail', 'trace.zip'));
expect(trace.apiNames).toEqual([ expect(trace.titles).toEqual([
'Before Hooks', 'Before Hooks',
'fixture: context', 'fixture: context',
'browser.newContext', 'browser.newContext',

View File

@ -478,14 +478,14 @@ test('should reset tracing', async ({ runInlineTest }, testInfo) => {
expect(result.passed).toBe(2); expect(result.passed).toBe(2);
const trace1 = await parseTrace(traceFile1); const trace1 = await parseTrace(traceFile1);
expect(trace1.apiNames).toEqual([ expect(trace1.titles).toEqual([
'page.setContent', 'page.setContent',
'page.click', 'page.click',
]); ]);
expect(trace1.traceModel.storage().snapshotsForTest().length).toBeGreaterThan(0); expect(trace1.traceModel.storage().snapshotsForTest().length).toBeGreaterThan(0);
const trace2 = await parseTrace(traceFile2); const trace2 = await parseTrace(traceFile2);
expect(trace2.apiNames).toEqual([ expect(trace2.titles).toEqual([
'page.setContent', 'page.setContent',
'page.fill', 'page.fill',
'locator.click', 'locator.click',

View File

@ -465,7 +465,7 @@ test(`trace:retain-on-failure should create trace if context is closed before fa
}, { trace: 'retain-on-failure' }); }, { trace: 'retain-on-failure' });
const tracePath = test.info().outputPath('test-results', 'a-passing-test', 'trace.zip'); const tracePath = test.info().outputPath('test-results', 'a-passing-test', 'trace.zip');
const trace = await parseTrace(tracePath); const trace = await parseTrace(tracePath);
expect(trace.apiNames).toContain('page.goto'); expect(trace.titles).toContain('page.goto');
expect(result.failed).toBe(1); expect(result.failed).toBe(1);
}); });
@ -487,7 +487,7 @@ test(`trace:retain-on-failure should create trace if context is closed before fa
}, { trace: 'retain-on-failure' }); }, { trace: 'retain-on-failure' });
const tracePath = test.info().outputPath('test-results', 'a-passing-test', 'trace.zip'); const tracePath = test.info().outputPath('test-results', 'a-passing-test', 'trace.zip');
const trace = await parseTrace(tracePath); const trace = await parseTrace(tracePath);
expect(trace.apiNames).toContain('page.goto'); expect(trace.titles).toContain('page.goto');
expect(result.failed).toBe(1); expect(result.failed).toBe(1);
}); });
@ -507,7 +507,7 @@ test(`trace:retain-on-failure should create trace if request context is disposed
}, { trace: 'retain-on-failure' }); }, { trace: 'retain-on-failure' });
const tracePath = test.info().outputPath('test-results', 'a-passing-test', 'trace.zip'); const tracePath = test.info().outputPath('test-results', 'a-passing-test', 'trace.zip');
const trace = await parseTrace(tracePath); const trace = await parseTrace(tracePath);
expect(trace.apiNames).toContain('apiRequestContext.get'); expect(trace.titles).toContain('apiRequestContext.get');
expect(result.failed).toBe(1); expect(result.failed).toBe(1);
}); });
@ -528,7 +528,7 @@ test('should include attachments by default', async ({ runInlineTest, server },
expect(result.exitCode).toBe(0); expect(result.exitCode).toBe(0);
expect(result.passed).toBe(1); expect(result.passed).toBe(1);
const trace = await parseTrace(testInfo.outputPath('test-results', 'a-pass', 'trace.zip')); const trace = await parseTrace(testInfo.outputPath('test-results', 'a-pass', 'trace.zip'));
expect(trace.apiNames).toEqual([ expect(trace.titles).toEqual([
'Before Hooks', 'Before Hooks',
`attach "foo"`, `attach "foo"`,
'After Hooks', 'After Hooks',
@ -558,7 +558,7 @@ test('should opt out of attachments', async ({ runInlineTest, server }, testInfo
expect(result.exitCode).toBe(0); expect(result.exitCode).toBe(0);
expect(result.passed).toBe(1); expect(result.passed).toBe(1);
const trace = await parseTrace(testInfo.outputPath('test-results', 'a-pass', 'trace.zip')); const trace = await parseTrace(testInfo.outputPath('test-results', 'a-pass', 'trace.zip'));
expect(trace.apiNames).toEqual([ expect(trace.titles).toEqual([
'Before Hooks', 'Before Hooks',
`attach "foo"`, `attach "foo"`,
'After Hooks', 'After Hooks',
@ -729,7 +729,7 @@ test('should not throw when attachment is missing', async ({ runInlineTest }, te
expect(result.exitCode).toBe(0); expect(result.exitCode).toBe(0);
expect(result.passed).toBe(1); expect(result.passed).toBe(1);
const trace = await parseTrace(testInfo.outputPath('test-results', 'a-passes', 'trace.zip')); const trace = await parseTrace(testInfo.outputPath('test-results', 'a-passes', 'trace.zip'));
expect(trace.apiNames).toContain('Attach "screenshot"'); expect(trace.titles).toContain('Attach "screenshot"');
}); });
test('should not throw when screenshot on failure fails', async ({ runInlineTest, server }, testInfo) => { test('should not throw when screenshot on failure fails', async ({ runInlineTest, server }, testInfo) => {
@ -1102,7 +1102,7 @@ test('trace:retain-on-first-failure should create trace but only on first failur
const tracePath = test.info().outputPath('test-results', 'a-fail', 'trace.zip'); const tracePath = test.info().outputPath('test-results', 'a-fail', 'trace.zip');
const trace = await parseTrace(tracePath); const trace = await parseTrace(tracePath);
expect(trace.apiNames).toContain('page.goto'); expect(trace.titles).toContain('page.goto');
expect(result.failed).toBe(1); expect(result.failed).toBe(1);
}); });
@ -1119,7 +1119,7 @@ test('trace:retain-on-first-failure should create trace if context is closed bef
}, { trace: 'retain-on-first-failure' }); }, { trace: 'retain-on-first-failure' });
const tracePath = test.info().outputPath('test-results', 'a-fail', 'trace.zip'); const tracePath = test.info().outputPath('test-results', 'a-fail', 'trace.zip');
const trace = await parseTrace(tracePath); const trace = await parseTrace(tracePath);
expect(trace.apiNames).toContain('page.goto'); expect(trace.titles).toContain('page.goto');
expect(result.failed).toBe(1); expect(result.failed).toBe(1);
}); });
@ -1138,7 +1138,7 @@ test('trace:retain-on-first-failure should create trace if context is closed bef
}, { trace: 'retain-on-first-failure' }); }, { trace: 'retain-on-first-failure' });
const tracePath = test.info().outputPath('test-results', 'a-fail', 'trace.zip'); const tracePath = test.info().outputPath('test-results', 'a-fail', 'trace.zip');
const trace = await parseTrace(tracePath); const trace = await parseTrace(tracePath);
expect(trace.apiNames).toContain('page.goto'); expect(trace.titles).toContain('page.goto');
expect(result.failed).toBe(1); expect(result.failed).toBe(1);
}); });
@ -1155,7 +1155,7 @@ test('trace:retain-on-first-failure should create trace if request context is di
}, { trace: 'retain-on-first-failure' }); }, { trace: 'retain-on-first-failure' });
const tracePath = test.info().outputPath('test-results', 'a-fail', 'trace.zip'); const tracePath = test.info().outputPath('test-results', 'a-fail', 'trace.zip');
const trace = await parseTrace(tracePath); const trace = await parseTrace(tracePath);
expect(trace.apiNames).toContain('apiRequestContext.get'); expect(trace.titles).toContain('apiRequestContext.get');
expect(result.failed).toBe(1); expect(result.failed).toBe(1);
}); });
@ -1348,13 +1348,13 @@ test('should record trace snapshot for more obscure commands', async ({ runInlin
const snapshots = trace.traceModel.storage(); const snapshots = trace.traceModel.storage();
const snapshotFrameOrPageId = snapshots.snapshotsForTest()[0]; const snapshotFrameOrPageId = snapshots.snapshotsForTest()[0];
const countAction = trace.actions.find(a => a.apiName === 'locator.count'); const countAction = trace.actions.find(a => a.title === 'locator.count');
expect(countAction.beforeSnapshot).toBeTruthy(); expect(countAction.beforeSnapshot).toBeTruthy();
expect(countAction.afterSnapshot).toBeTruthy(); expect(countAction.afterSnapshot).toBeTruthy();
expect(snapshots.snapshotByName(snapshotFrameOrPageId, countAction.beforeSnapshot)).toBeTruthy(); expect(snapshots.snapshotByName(snapshotFrameOrPageId, countAction.beforeSnapshot)).toBeTruthy();
expect(snapshots.snapshotByName(snapshotFrameOrPageId, countAction.afterSnapshot)).toBeTruthy(); expect(snapshots.snapshotByName(snapshotFrameOrPageId, countAction.afterSnapshot)).toBeTruthy();
const boundingBoxAction = trace.actions.find(a => a.apiName === 'locator.boundingBox'); const boundingBoxAction = trace.actions.find(a => a.title === 'locator.boundingBox');
expect(boundingBoxAction.beforeSnapshot).toBeTruthy(); expect(boundingBoxAction.beforeSnapshot).toBeTruthy();
expect(boundingBoxAction.afterSnapshot).toBeTruthy(); expect(boundingBoxAction.afterSnapshot).toBeTruthy();
expect(snapshots.snapshotByName(snapshotFrameOrPageId, boundingBoxAction.beforeSnapshot)).toBeTruthy(); expect(snapshots.snapshotByName(snapshotFrameOrPageId, boundingBoxAction.beforeSnapshot)).toBeTruthy();