Skip to content

Commit 2fad1cf

Browse files
committed
feat(trace): split API requests into a separate network stream
the existing `.network` stream mixed browser traffic with `APIRequestContext` traffic record `_apiRequest` entries in `.api.network`, archive it as `trace.api.network`, and ingest it alongside `.network` bump the trace format to `9` so older viewers reject traces that have the new stream
1 parent 3689414 commit 2fad1cf

13 files changed

Lines changed: 368 additions & 29 deletions

File tree

.claude/skills/playwright-dev/trace_system_guide.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,8 @@ When a trace is recorded, it creates this structure in the traces directory:
297297
```
298298
traces-dir/
299299
├── <traceName>.trace # Main events (JSONL format)
300-
├── <traceName>.network # Network events (JSONL format)
300+
├── <traceName>.network # Browser network events (JSONL format)
301+
├── <traceName>.api.network # APIRequestContext events (JSONL format)
301302
├── <traceName>-chunk1.trace # Additional chunks (if multiple)
302303
├── <traceName>.stacks # Stack trace metadata (optional)
303304
└── resources/
@@ -306,7 +307,7 @@ traces-dir/
306307
```
307308

308309
### File Formats
309-
- **`.trace` and `.network`**: JSONL (JSON Lines) - one event per line
310+
- **`.trace`, `.network`, and `.api.network`**: JSONL (JSON Lines), one event per line
310311
- **`.zip`**: Optional archive containing all above files
311312
- **`resources/`**: Binary blobs indexed by SHA1 hash
312313

@@ -415,6 +416,7 @@ type RecordingState = {
415416
options: TracerOptions,
416417
traceName: string,
417418
networkFile: string,
419+
apiNetworkFile: string,
418420
traceFile: string,
419421
tracesDir: string,
420422
resourcesDir: string,
@@ -457,10 +459,11 @@ async load(backend: TraceLoaderBackend, unzipProgress) {
457459
1. Find .trace files (ordinals: "0", "1", etc.)
458460
2. For each ordinal:
459461
a. Read ordinal.trace (events)
460-
b. Read ordinal.network (network events)
461-
c. Parse with TraceModernizer
462-
d. Read ordinal.stacks (if exists)
463-
e. Sort actions by startTime
462+
b. Read ordinal.network (browser network events)
463+
c. Read ordinal.api.network (APIRequestContext events)
464+
d. Parse with TraceModernizer
465+
e. Read ordinal.stacks (if exists)
466+
f. Sort actions by startTime
464467
3. Terminate incomplete actions
465468
4. Finalize snapshot storage
466469
5. Build resource content-type map

packages/isomorphic/trace/traceLoader.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ export class TraceLoader {
7070

7171
const network = await this._backend.readText(prefix + '.network') || '';
7272
modernizer.appendTrace(network);
73+
const apiNetwork = await this._backend.readText(prefix + '.api.network') || '';
74+
modernizer.appendTrace(apiNetwork);
7375
unzipProgress?.(++done, total);
7476

7577
contextEntry.actions = modernizer.actions().sort((a1, a2) => a1.startTime - a2.startTime);

packages/isomorphic/trace/traceModernizer.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import type * as traceV5 from './versions/traceV5';
2121
import type * as traceV6 from './versions/traceV6';
2222
import type * as traceV7 from './versions/traceV7';
2323
import type * as traceV8 from './versions/traceV8';
24+
import type * as traceV9 from './versions/traceV9';
2425
import type { ActionEntry, ContextEntry, PageEntry } from './entries';
2526
import type { SnapshotStorage } from './snapshotStorage';
2627

@@ -33,7 +34,8 @@ export class TraceVersionError extends Error {
3334

3435
// 6 => 10/2023 ~1.40
3536
// 7 => 05/2024 ~1.45
36-
const latestVersion: trace.VERSION = 8;
37+
// 8 => 05/2025 ~1.53
38+
const latestVersion: trace.VERSION = 9;
3739

3840
export class TraceModernizer {
3941
private _contextEntry: ContextEntry;
@@ -439,4 +441,8 @@ export class TraceModernizer {
439441
}
440442
return result;
441443
}
444+
445+
_modernize_8_to_9(events: traceV8.TraceEvent[]): traceV9.TraceEvent[] {
446+
return events;
447+
}
442448
}
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import type { Entry as ResourceSnapshot } from '@trace/har';
18+
19+
type Language = 'javascript' | 'python' | 'java' | 'csharp' | 'jsonl';
20+
type Point = { x: number, y: number };
21+
type Size = { width: number, height: number };
22+
23+
type StackFrame = {
24+
file: string,
25+
line: number,
26+
column: number,
27+
function?: string,
28+
};
29+
30+
type Binary = Buffer;
31+
32+
type SerializedValue = {
33+
n?: number,
34+
b?: boolean,
35+
s?: string,
36+
v?: 'null' | 'undefined' | 'NaN' | 'Infinity' | '-Infinity' | '-0',
37+
d?: string,
38+
u?: string,
39+
bi?: string,
40+
ta?: {
41+
b: Binary,
42+
k: 'i8' | 'ui8' | 'ui8c' | 'i16' | 'ui16' | 'i32' | 'ui32' | 'f32' | 'f64' | 'bi64' | 'bui64',
43+
},
44+
e?: {
45+
m: string,
46+
n: string,
47+
s: string,
48+
},
49+
r?: {
50+
p: string,
51+
f: string,
52+
},
53+
a?: SerializedValue[],
54+
o?: {
55+
k: string,
56+
v: SerializedValue,
57+
}[],
58+
h?: number,
59+
id?: number,
60+
ref?: number,
61+
};
62+
63+
type SerializedError = {
64+
error?: {
65+
message: string,
66+
name: string,
67+
stack?: string,
68+
},
69+
value?: SerializedValue,
70+
};
71+
72+
// Text node.
73+
type TextNodeSnapshot = string;
74+
// Subtree reference, "x snapshots ago, node #y". Could point to a text node.
75+
// Only nodes that are not references are counted, starting from zero, using post-order traversal.
76+
type SubtreeReferenceSnapshot = [ [number, number] ];
77+
// Node name, and optional attributes and child nodes.
78+
type NodeNameAttributesChildNodesSnapshot = [ string ] | [ string, Record<string, string>, ...NodeSnapshot[] ];
79+
80+
type NodeSnapshot =
81+
TextNodeSnapshot |
82+
SubtreeReferenceSnapshot |
83+
NodeNameAttributesChildNodesSnapshot;
84+
85+
type ResourceOverride = {
86+
url: string,
87+
sha1?: string,
88+
ref?: number
89+
};
90+
91+
type FrameSnapshot = {
92+
snapshotName?: string,
93+
callId: string,
94+
pageId: string,
95+
frameId: string,
96+
frameUrl: string,
97+
timestamp: number,
98+
wallTime?: number,
99+
collectionTime: number,
100+
doctype?: string,
101+
html: NodeSnapshot,
102+
resourceOverrides: ResourceOverride[],
103+
viewport: { width: number, height: number },
104+
isMainFrame: boolean,
105+
};
106+
107+
type BrowserContextEventOptions = {
108+
baseURL?: string,
109+
viewport?: Size,
110+
deviceScaleFactor?: number,
111+
isMobile?: boolean,
112+
userAgent?: string,
113+
};
114+
115+
export type ContextCreatedTraceEvent = {
116+
version: number,
117+
type: 'context-options',
118+
origin: 'testRunner' | 'library',
119+
browserName: string,
120+
channel?: string,
121+
platform: string,
122+
playwrightVersion?: string,
123+
wallTime: number,
124+
monotonicTime: number,
125+
title?: string,
126+
options: BrowserContextEventOptions,
127+
sdkLanguage?: Language,
128+
testIdAttributeName?: string,
129+
contextId?: string,
130+
testTimeout?: number,
131+
annotations?: TraceEventAnnotation[],
132+
};
133+
134+
export type ScreencastFrameTraceEvent = {
135+
type: 'screencast-frame',
136+
pageId: string,
137+
sha1: string,
138+
width: number,
139+
height: number,
140+
timestamp: number,
141+
frameSwapWallTime?: number,
142+
};
143+
144+
export type BeforeActionTraceEvent = {
145+
type: 'before',
146+
callId: string;
147+
startTime: number;
148+
title?: string;
149+
class: string;
150+
method: string;
151+
params: Record<string, any>;
152+
stepId?: string;
153+
beforeSnapshot?: string;
154+
stack?: StackFrame[];
155+
pageId?: string;
156+
parentId?: string;
157+
group?: string;
158+
};
159+
160+
export type InputActionTraceEvent = {
161+
type: 'input',
162+
callId: string;
163+
inputSnapshot?: string;
164+
point?: Point;
165+
};
166+
167+
export type AfterActionTraceEventAttachment = {
168+
name: string;
169+
contentType: string;
170+
path?: string;
171+
sha1?: string;
172+
base64?: string;
173+
};
174+
175+
export type TraceEventAnnotation = {
176+
type: string,
177+
description?: string
178+
};
179+
180+
export type AfterActionTraceEvent = {
181+
type: 'after',
182+
callId: string;
183+
endTime: number;
184+
afterSnapshot?: string;
185+
error?: SerializedError['error'];
186+
attachments?: AfterActionTraceEventAttachment[];
187+
annotations?: TraceEventAnnotation[];
188+
result?: any;
189+
point?: Point;
190+
};
191+
192+
export type LogTraceEvent = {
193+
type: 'log',
194+
callId: string;
195+
time: number;
196+
message: string;
197+
};
198+
199+
export type EventTraceEvent = {
200+
type: 'event',
201+
time: number;
202+
class: string;
203+
method: string;
204+
params: any;
205+
pageId?: string;
206+
};
207+
208+
export type ConsoleMessageTraceEvent = {
209+
type: 'console';
210+
time: number;
211+
pageId?: string;
212+
messageType: string,
213+
text: string,
214+
args?: { preview: string, value: any }[],
215+
location: {
216+
url: string,
217+
lineNumber: number,
218+
columnNumber: number,
219+
},
220+
};
221+
222+
export type ResourceSnapshotTraceEvent = {
223+
type: 'resource-snapshot',
224+
snapshot: ResourceSnapshot,
225+
};
226+
227+
export type FrameSnapshotTraceEvent = {
228+
type: 'frame-snapshot',
229+
snapshot: FrameSnapshot,
230+
};
231+
232+
export type ActionTraceEvent = {
233+
type: 'action',
234+
} & Omit<BeforeActionTraceEvent, 'type'>
235+
& Omit<AfterActionTraceEvent, 'type'>
236+
& Omit<InputActionTraceEvent, 'type'>;
237+
238+
export type StdioTraceEvent = {
239+
type: 'stdout' | 'stderr';
240+
timestamp: number;
241+
text?: string;
242+
base64?: string;
243+
};
244+
245+
export type ErrorTraceEvent = {
246+
type: 'error';
247+
message: string;
248+
stack?: StackFrame[];
249+
};
250+
251+
export type TraceEvent =
252+
ContextCreatedTraceEvent |
253+
ScreencastFrameTraceEvent |
254+
ActionTraceEvent |
255+
BeforeActionTraceEvent |
256+
InputActionTraceEvent |
257+
AfterActionTraceEvent |
258+
EventTraceEvent |
259+
LogTraceEvent |
260+
ConsoleMessageTraceEvent |
261+
ResourceSnapshotTraceEvent |
262+
FrameSnapshotTraceEvent |
263+
StdioTraceEvent |
264+
ErrorTraceEvent;

0 commit comments

Comments
 (0)