Skip to content

Commit 2e56946

Browse files
committed
chore: move modelUtil.ts into isomorphic/trace
1 parent 4fa8508 commit 2e56946

28 files changed

+614
-620
lines changed

packages/playwright-core/src/utils/isomorphic/trace/snapshotServer.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ import type { SnapshotRenderer } from './snapshotRenderer';
1919
import type { SnapshotStorage } from './snapshotStorage';
2020
import type { ResourceSnapshot } from '@trace/snapshot';
2121

22-
type Point = { x: number, y: number };
23-
2422
export class SnapshotServer {
2523
private _snapshotStorage: SnapshotStorage;
2624
private _resourceLoader: (sha1: string) => Promise<Blob | undefined>;
@@ -117,12 +115,6 @@ export class SnapshotServer {
117115
}
118116
}
119117

120-
declare global {
121-
interface Window {
122-
showSnapshot: (url: string, point?: Point) => Promise<void>;
123-
}
124-
}
125-
126118
function removeHash(url: string) {
127119
try {
128120
const u = new URL(url);
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
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 { parseClientSideCallMetadata } from '@isomorphic/traceUtils';
18+
19+
import { SnapshotStorage } from './snapshotStorage';
20+
import { TraceModernizer } from './traceModernizer';
21+
22+
import type { ContextEntry } from './entries';
23+
24+
export interface TraceLoaderBackend {
25+
entryNames(): Promise<string[]>;
26+
hasEntry(entryName: string): Promise<boolean>;
27+
readText(entryName: string): Promise<string | undefined>;
28+
readBlob(entryName: string): Promise<Blob | undefined>;
29+
isLive(): boolean;
30+
traceURL(): string;
31+
}
32+
33+
export class TraceLoader {
34+
contextEntries: ContextEntry[] = [];
35+
private _snapshotStorage: SnapshotStorage | undefined;
36+
private _backend!: TraceLoaderBackend;
37+
private _resourceToContentType = new Map<string, string>();
38+
39+
constructor() {
40+
}
41+
42+
async load(backend: TraceLoaderBackend, unzipProgress: (done: number, total: number) => void) {
43+
this._backend = backend;
44+
45+
const ordinals: string[] = [];
46+
let hasSource = false;
47+
for (const entryName of await this._backend.entryNames()) {
48+
const match = entryName.match(/(.+)\.trace$/);
49+
if (match)
50+
ordinals.push(match[1] || '');
51+
if (entryName.includes('src@'))
52+
hasSource = true;
53+
}
54+
if (!ordinals.length)
55+
throw new Error('Cannot find .trace file');
56+
57+
this._snapshotStorage = new SnapshotStorage();
58+
59+
// 3 * ordinals progress increments below.
60+
const total = ordinals.length * 3;
61+
let done = 0;
62+
for (const ordinal of ordinals) {
63+
const contextEntry = createEmptyContext();
64+
contextEntry.hasSource = hasSource;
65+
const modernizer = new TraceModernizer(contextEntry, this._snapshotStorage);
66+
67+
const trace = await this._backend.readText(ordinal + '.trace') || '';
68+
modernizer.appendTrace(trace);
69+
unzipProgress(++done, total);
70+
71+
const network = await this._backend.readText(ordinal + '.network') || '';
72+
modernizer.appendTrace(network);
73+
unzipProgress(++done, total);
74+
75+
contextEntry.actions = modernizer.actions().sort((a1, a2) => a1.startTime - a2.startTime);
76+
77+
if (!backend.isLive()) {
78+
// Terminate actions w/o after event gracefully.
79+
// This would close after hooks event that has not been closed because
80+
// the trace is usually saved before after hooks complete.
81+
for (const action of contextEntry.actions.slice().reverse()) {
82+
if (!action.endTime && !action.error) {
83+
for (const a of contextEntry.actions) {
84+
if (a.parentId === action.callId && action.endTime < a.endTime)
85+
action.endTime = a.endTime;
86+
}
87+
}
88+
}
89+
}
90+
91+
const stacks = await this._backend.readText(ordinal + '.stacks');
92+
if (stacks) {
93+
const callMetadata = parseClientSideCallMetadata(JSON.parse(stacks));
94+
for (const action of contextEntry.actions)
95+
action.stack = action.stack || callMetadata.get(action.callId);
96+
}
97+
unzipProgress(++done, total);
98+
99+
for (const resource of contextEntry.resources) {
100+
if (resource.request.postData?._sha1)
101+
this._resourceToContentType.set(resource.request.postData._sha1, stripEncodingFromContentType(resource.request.postData.mimeType));
102+
if (resource.response.content?._sha1)
103+
this._resourceToContentType.set(resource.response.content._sha1, stripEncodingFromContentType(resource.response.content.mimeType));
104+
}
105+
106+
this.contextEntries.push(contextEntry);
107+
}
108+
109+
this._snapshotStorage.finalize();
110+
}
111+
112+
async hasEntry(filename: string): Promise<boolean> {
113+
return this._backend.hasEntry(filename);
114+
}
115+
116+
async resourceForSha1(sha1: string): Promise<Blob | undefined> {
117+
const blob = await this._backend.readBlob('resources/' + sha1);
118+
const contentType = this._resourceToContentType.get(sha1);
119+
// "x-unknown" in the har means "no content type".
120+
if (!blob || contentType === undefined || contentType === 'x-unknown')
121+
return blob;
122+
return new Blob([blob], { type: contentType });
123+
}
124+
125+
storage(): SnapshotStorage {
126+
return this._snapshotStorage!;
127+
}
128+
}
129+
130+
function stripEncodingFromContentType(contentType: string) {
131+
const charset = contentType.match(/^(.*);\s*charset=.*$/);
132+
if (charset)
133+
return charset[1];
134+
return contentType;
135+
}
136+
137+
function createEmptyContext(): ContextEntry {
138+
return {
139+
origin: 'testRunner',
140+
startTime: Number.MAX_SAFE_INTEGER,
141+
wallTime: Number.MAX_SAFE_INTEGER,
142+
endTime: 0,
143+
browserName: '',
144+
options: {
145+
deviceScaleFactor: 1,
146+
isMobile: false,
147+
viewport: { width: 1280, height: 800 },
148+
},
149+
pages: [],
150+
resources: [],
151+
actions: [],
152+
events: [],
153+
errors: [],
154+
stdio: [],
155+
hasSource: false,
156+
contextId: '',
157+
};
158+
}

0 commit comments

Comments
 (0)