@pedropaulovc/playwright-core 1.59.0-next.1 → 1.59.0-next.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/cli/program.js
CHANGED
|
@@ -39,7 +39,7 @@ var import_driver = require("./driver");
|
|
|
39
39
|
var import_server = require("../server");
|
|
40
40
|
var import_utils = require("../utils");
|
|
41
41
|
var import_traceViewer = require("../server/trace/viewer/traceViewer");
|
|
42
|
-
var
|
|
42
|
+
var import_src = require("../../../../trace-exporter/src");
|
|
43
43
|
var import_utils2 = require("../utils");
|
|
44
44
|
var import_ascii = require("../server/utils/ascii");
|
|
45
45
|
var import_utilsBundle = require("../utilsBundle");
|
|
@@ -297,7 +297,7 @@ Examples:
|
|
|
297
297
|
$ show-trace https://example.com/trace.zip`);
|
|
298
298
|
import_utilsBundle.program.command("export-trace <trace>").description("export trace to LLM-friendly markdown files").option("-o, --output <dir>", "output directory", "./trace-export").action(async function(trace, options) {
|
|
299
299
|
const traceFile = import_path.default.resolve(trace);
|
|
300
|
-
await (0,
|
|
300
|
+
await (0, import_src.exportTraceToMarkdown)(traceFile, {
|
|
301
301
|
outputDir: import_path.default.resolve(options.output)
|
|
302
302
|
}).catch(logErrorAndExit);
|
|
303
303
|
console.log(`Trace exported to ${options.output}`);
|
package/package.json
CHANGED
|
@@ -1,974 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __export = (target, all) => {
|
|
9
|
-
for (var name in all)
|
|
10
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
-
};
|
|
12
|
-
var __copyProps = (to, from, except, desc) => {
|
|
13
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
-
for (let key of __getOwnPropNames(from))
|
|
15
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
-
}
|
|
18
|
-
return to;
|
|
19
|
-
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
-
var traceExporter_exports = {};
|
|
30
|
-
__export(traceExporter_exports, {
|
|
31
|
-
exportTraceToMarkdown: () => exportTraceToMarkdown
|
|
32
|
-
});
|
|
33
|
-
module.exports = __toCommonJS(traceExporter_exports);
|
|
34
|
-
var import_fs = __toESM(require("fs"));
|
|
35
|
-
var import_path = __toESM(require("path"));
|
|
36
|
-
var import_zipFile = require("../../utils/zipFile");
|
|
37
|
-
var import_stringUtils = require("../../../utils/isomorphic/stringUtils");
|
|
38
|
-
async function exportTraceToMarkdown(traceFile, options) {
|
|
39
|
-
const context = await parseTrace(traceFile);
|
|
40
|
-
const outputDir = options.outputDir;
|
|
41
|
-
const assetsDir = import_path.default.join(outputDir, "assets");
|
|
42
|
-
const screenshotsDir = import_path.default.join(assetsDir, "screenshots");
|
|
43
|
-
const snapshotsDir = import_path.default.join(assetsDir, "snapshots");
|
|
44
|
-
await import_fs.default.promises.mkdir(outputDir, { recursive: true });
|
|
45
|
-
await import_fs.default.promises.mkdir(screenshotsDir, { recursive: true });
|
|
46
|
-
await import_fs.default.promises.mkdir(snapshotsDir, { recursive: true });
|
|
47
|
-
const assetMap = await extractAssets(traceFile, context, outputDir);
|
|
48
|
-
const files = [
|
|
49
|
-
{ name: "README.md", content: generateReadmeMarkdown() },
|
|
50
|
-
{ name: "index.md", content: generateIndexMarkdown(context, traceFile) },
|
|
51
|
-
{ name: "metadata.md", content: generateMetadataMarkdown(context) },
|
|
52
|
-
{ name: "timeline.md", content: generateTimelineMarkdown(context.actions, assetMap, buildStepSnapshotMap(context.actions)) },
|
|
53
|
-
{ name: "errors.md", content: generateErrorsMarkdown(context.errors, context.actions) },
|
|
54
|
-
{ name: "console.md", content: generateConsoleMarkdown(context.events) },
|
|
55
|
-
{ name: "network.md", content: generateNetworkMarkdown(context.resources) }
|
|
56
|
-
];
|
|
57
|
-
for (const file of files)
|
|
58
|
-
await import_fs.default.promises.writeFile(import_path.default.join(outputDir, file.name), file.content);
|
|
59
|
-
}
|
|
60
|
-
async function parseTrace(traceFile) {
|
|
61
|
-
const zipFile = new import_zipFile.ZipFile(traceFile);
|
|
62
|
-
const entries = await zipFile.entries();
|
|
63
|
-
const context = {
|
|
64
|
-
browserName: "Unknown",
|
|
65
|
-
wallTime: 0,
|
|
66
|
-
startTime: Number.MAX_SAFE_INTEGER,
|
|
67
|
-
endTime: 0,
|
|
68
|
-
options: {},
|
|
69
|
-
actions: [],
|
|
70
|
-
events: [],
|
|
71
|
-
errors: [],
|
|
72
|
-
resources: [],
|
|
73
|
-
pages: [],
|
|
74
|
-
snapshots: [],
|
|
75
|
-
networkResourceMap: /* @__PURE__ */ new Map()
|
|
76
|
-
};
|
|
77
|
-
const actionMap = /* @__PURE__ */ new Map();
|
|
78
|
-
const pageMap = /* @__PURE__ */ new Map();
|
|
79
|
-
const traceEntries = entries.filter((name) => name.endsWith(".trace"));
|
|
80
|
-
const networkEntries = entries.filter((name) => name.endsWith(".network"));
|
|
81
|
-
for (const entryName of [...traceEntries, ...networkEntries]) {
|
|
82
|
-
const content = await zipFile.read(entryName);
|
|
83
|
-
const lines = content.toString("utf-8").split("\n");
|
|
84
|
-
for (const line of lines) {
|
|
85
|
-
if (!line.trim())
|
|
86
|
-
continue;
|
|
87
|
-
try {
|
|
88
|
-
const event = JSON.parse(line);
|
|
89
|
-
processTraceEvent(event, context, actionMap, pageMap);
|
|
90
|
-
} catch {
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
context.actions = [...actionMap.values()].sort((a, b) => a.startTime - b.startTime);
|
|
95
|
-
context.pages = [...pageMap.values()];
|
|
96
|
-
for (const action of context.actions) {
|
|
97
|
-
if (action.endTime > context.endTime)
|
|
98
|
-
context.endTime = action.endTime;
|
|
99
|
-
}
|
|
100
|
-
zipFile.close();
|
|
101
|
-
return context;
|
|
102
|
-
}
|
|
103
|
-
function processTraceEvent(event, context, actionMap, pageMap) {
|
|
104
|
-
switch (event.type) {
|
|
105
|
-
case "context-options":
|
|
106
|
-
context.browserName = event.browserName || "Unknown";
|
|
107
|
-
context.channel = event.channel;
|
|
108
|
-
context.title = event.title;
|
|
109
|
-
context.platform = event.platform;
|
|
110
|
-
context.playwrightVersion = event.playwrightVersion;
|
|
111
|
-
context.wallTime = event.wallTime || 0;
|
|
112
|
-
context.startTime = event.monotonicTime || 0;
|
|
113
|
-
context.sdkLanguage = event.sdkLanguage;
|
|
114
|
-
context.options = event.options || {};
|
|
115
|
-
break;
|
|
116
|
-
case "before":
|
|
117
|
-
actionMap.set(event.callId, {
|
|
118
|
-
callId: event.callId,
|
|
119
|
-
class: event.class,
|
|
120
|
-
method: event.method,
|
|
121
|
-
params: event.params || {},
|
|
122
|
-
startTime: event.startTime || 0,
|
|
123
|
-
endTime: 0,
|
|
124
|
-
log: [],
|
|
125
|
-
stack: event.stack,
|
|
126
|
-
beforeSnapshot: event.beforeSnapshot,
|
|
127
|
-
pageId: event.pageId,
|
|
128
|
-
parentId: event.parentId,
|
|
129
|
-
title: event.title,
|
|
130
|
-
group: event.group,
|
|
131
|
-
stepId: event.stepId
|
|
132
|
-
});
|
|
133
|
-
break;
|
|
134
|
-
case "after":
|
|
135
|
-
const action = actionMap.get(event.callId);
|
|
136
|
-
if (action) {
|
|
137
|
-
action.endTime = event.endTime || action.startTime;
|
|
138
|
-
action.error = event.error;
|
|
139
|
-
action.result = event.result;
|
|
140
|
-
action.afterSnapshot = event.afterSnapshot;
|
|
141
|
-
}
|
|
142
|
-
break;
|
|
143
|
-
case "log":
|
|
144
|
-
const logAction = actionMap.get(event.callId);
|
|
145
|
-
if (logAction) {
|
|
146
|
-
logAction.log.push({
|
|
147
|
-
time: event.time || 0,
|
|
148
|
-
message: event.message || ""
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
break;
|
|
152
|
-
case "console":
|
|
153
|
-
context.events.push({
|
|
154
|
-
type: "console",
|
|
155
|
-
time: event.time || 0,
|
|
156
|
-
messageType: event.messageType,
|
|
157
|
-
text: event.text,
|
|
158
|
-
location: event.location
|
|
159
|
-
});
|
|
160
|
-
break;
|
|
161
|
-
case "error":
|
|
162
|
-
context.errors.push({
|
|
163
|
-
message: event.message || "Unknown error",
|
|
164
|
-
stack: event.stack
|
|
165
|
-
});
|
|
166
|
-
break;
|
|
167
|
-
case "resource-snapshot":
|
|
168
|
-
if (event.snapshot) {
|
|
169
|
-
const url = event.snapshot.request?.url || "";
|
|
170
|
-
const sha1 = event.snapshot.response?.content?._sha1;
|
|
171
|
-
context.resources.push({
|
|
172
|
-
request: {
|
|
173
|
-
method: event.snapshot.request?.method || "GET",
|
|
174
|
-
url
|
|
175
|
-
},
|
|
176
|
-
response: {
|
|
177
|
-
status: event.snapshot.response?.status || 0,
|
|
178
|
-
content: event.snapshot.response?.content,
|
|
179
|
-
_failureText: event.snapshot.response?._failureText
|
|
180
|
-
}
|
|
181
|
-
});
|
|
182
|
-
if (url && sha1)
|
|
183
|
-
context.networkResourceMap.set(url, sha1);
|
|
184
|
-
}
|
|
185
|
-
break;
|
|
186
|
-
case "screencast-frame":
|
|
187
|
-
let page = pageMap.get(event.pageId);
|
|
188
|
-
if (!page) {
|
|
189
|
-
page = { pageId: event.pageId, screencastFrames: [] };
|
|
190
|
-
pageMap.set(event.pageId, page);
|
|
191
|
-
}
|
|
192
|
-
page.screencastFrames.push({
|
|
193
|
-
sha1: event.sha1,
|
|
194
|
-
timestamp: event.timestamp || 0
|
|
195
|
-
});
|
|
196
|
-
break;
|
|
197
|
-
case "frame-snapshot":
|
|
198
|
-
if (event.snapshot) {
|
|
199
|
-
context.snapshots.push({
|
|
200
|
-
snapshotName: event.snapshot.snapshotName || "",
|
|
201
|
-
callId: event.snapshot.callId || "",
|
|
202
|
-
pageId: event.snapshot.pageId || "",
|
|
203
|
-
frameId: event.snapshot.frameId || "",
|
|
204
|
-
frameUrl: event.snapshot.frameUrl || "",
|
|
205
|
-
html: event.snapshot.html,
|
|
206
|
-
timestamp: event.snapshot.timestamp || 0,
|
|
207
|
-
resourceOverrides: event.snapshot.resourceOverrides || [],
|
|
208
|
-
doctype: event.snapshot.doctype,
|
|
209
|
-
viewport: event.snapshot.viewport
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
break;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
async function extractAssets(traceFile, context, outputDir) {
|
|
216
|
-
const assetMap = /* @__PURE__ */ new Map();
|
|
217
|
-
const zipFile = new import_zipFile.ZipFile(traceFile);
|
|
218
|
-
const entries = await zipFile.entries();
|
|
219
|
-
const resourcesDir = import_path.default.join(outputDir, "assets", "resources");
|
|
220
|
-
await import_fs.default.promises.mkdir(resourcesDir, { recursive: true });
|
|
221
|
-
const snapshotsByFrame = /* @__PURE__ */ new Map();
|
|
222
|
-
for (const snapshot of context.snapshots) {
|
|
223
|
-
let frameSnapshots = snapshotsByFrame.get(snapshot.frameId);
|
|
224
|
-
if (!frameSnapshots) {
|
|
225
|
-
frameSnapshots = [];
|
|
226
|
-
snapshotsByFrame.set(snapshot.frameId, frameSnapshots);
|
|
227
|
-
}
|
|
228
|
-
frameSnapshots.push(snapshot);
|
|
229
|
-
}
|
|
230
|
-
const neededSha1s = /* @__PURE__ */ new Set();
|
|
231
|
-
for (const snapshot of context.snapshots) {
|
|
232
|
-
const frameSnapshots = snapshotsByFrame.get(snapshot.frameId) || [];
|
|
233
|
-
const snapshotIndex = frameSnapshots.indexOf(snapshot);
|
|
234
|
-
for (const override of snapshot.resourceOverrides) {
|
|
235
|
-
if (override.sha1) {
|
|
236
|
-
neededSha1s.add(override.sha1);
|
|
237
|
-
} else if (override.ref !== void 0 && snapshotIndex >= 0) {
|
|
238
|
-
const refIndex = snapshotIndex - override.ref;
|
|
239
|
-
if (refIndex >= 0 && refIndex < frameSnapshots.length) {
|
|
240
|
-
const refSnapshot = frameSnapshots[refIndex];
|
|
241
|
-
const refOverride = refSnapshot.resourceOverrides.find((o) => o.url === override.url);
|
|
242
|
-
if (refOverride?.sha1)
|
|
243
|
-
neededSha1s.add(refOverride.sha1);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
for (const page of context.pages) {
|
|
249
|
-
for (const frame of page.screencastFrames)
|
|
250
|
-
neededSha1s.add(frame.sha1);
|
|
251
|
-
}
|
|
252
|
-
for (const sha1 of context.networkResourceMap.values())
|
|
253
|
-
neededSha1s.add(sha1);
|
|
254
|
-
for (const sha1 of neededSha1s) {
|
|
255
|
-
const resourcePath = `resources/${sha1}`;
|
|
256
|
-
if (!entries.includes(resourcePath))
|
|
257
|
-
continue;
|
|
258
|
-
try {
|
|
259
|
-
const buffer = await zipFile.read(resourcePath);
|
|
260
|
-
const fullPath = import_path.default.join(resourcesDir, sha1);
|
|
261
|
-
await import_fs.default.promises.writeFile(fullPath, buffer);
|
|
262
|
-
assetMap.set(sha1, `./assets/resources/${sha1}`);
|
|
263
|
-
} catch {
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
const snapshotsDir = import_path.default.join(outputDir, "assets", "snapshots");
|
|
267
|
-
await import_fs.default.promises.mkdir(snapshotsDir, { recursive: true });
|
|
268
|
-
for (const snapshot of context.snapshots) {
|
|
269
|
-
if (!snapshot.html || !snapshot.snapshotName)
|
|
270
|
-
continue;
|
|
271
|
-
try {
|
|
272
|
-
const frameSnapshots = snapshotsByFrame.get(snapshot.frameId) || [];
|
|
273
|
-
const snapshotIndex = frameSnapshots.indexOf(snapshot);
|
|
274
|
-
const renderer = new ExportSnapshotRenderer(frameSnapshots, snapshotIndex, context.networkResourceMap);
|
|
275
|
-
const html = renderer.render();
|
|
276
|
-
for (const sha1 of renderer.getUsedSha1s()) {
|
|
277
|
-
if (!assetMap.has(sha1)) {
|
|
278
|
-
const resourcePath = `resources/${sha1}`;
|
|
279
|
-
if (entries.includes(resourcePath)) {
|
|
280
|
-
try {
|
|
281
|
-
const buffer = await zipFile.read(resourcePath);
|
|
282
|
-
const fullPath2 = import_path.default.join(resourcesDir, sha1);
|
|
283
|
-
await import_fs.default.promises.writeFile(fullPath2, buffer);
|
|
284
|
-
assetMap.set(sha1, `./assets/resources/${sha1}`);
|
|
285
|
-
} catch {
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
const safeName = snapshot.snapshotName.replace(/[^a-zA-Z0-9@_-]/g, "_");
|
|
291
|
-
const relativePath = `assets/snapshots/${safeName}.html`;
|
|
292
|
-
const fullPath = import_path.default.join(outputDir, relativePath);
|
|
293
|
-
await import_fs.default.promises.writeFile(fullPath, html);
|
|
294
|
-
assetMap.set(snapshot.snapshotName, `./${relativePath}`);
|
|
295
|
-
} catch {
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
zipFile.close();
|
|
299
|
-
return assetMap;
|
|
300
|
-
}
|
|
301
|
-
function generateReadmeMarkdown() {
|
|
302
|
-
return `# Playwright Trace Export
|
|
303
|
-
|
|
304
|
-
This folder contains a Playwright trace exported to LLM-friendly markdown format.
|
|
305
|
-
|
|
306
|
-
## Contents
|
|
307
|
-
|
|
308
|
-
- **index.md** - Overview with test status and error summary
|
|
309
|
-
- **timeline.md** - Step-by-step action timeline with links to DOM snapshots
|
|
310
|
-
- **metadata.md** - Browser and environment information
|
|
311
|
-
- **errors.md** - Full error details with stack traces
|
|
312
|
-
- **console.md** - Browser console output
|
|
313
|
-
- **network.md** - HTTP request log
|
|
314
|
-
|
|
315
|
-
## Viewing DOM Snapshots
|
|
316
|
-
|
|
317
|
-
The exported DOM snapshots include CSS and can be viewed in a browser. Since snapshots use relative paths, you need to serve them via HTTP:
|
|
318
|
-
|
|
319
|
-
\`\`\`bash
|
|
320
|
-
# Using npx serve
|
|
321
|
-
npx serve
|
|
322
|
-
|
|
323
|
-
# Or using Python
|
|
324
|
-
python -m http.server 8000
|
|
325
|
-
\`\`\`
|
|
326
|
-
|
|
327
|
-
Then open the snapshot URLs from \`timeline.md\`, for example:
|
|
328
|
-
- http://localhost:3000/assets/snapshots/after@call@123.html (npx serve)
|
|
329
|
-
- http://localhost:8000/assets/snapshots/after@call@123.html (Python)
|
|
330
|
-
|
|
331
|
-
## Loading Snapshots with Playwright
|
|
332
|
-
|
|
333
|
-
You can load exported snapshots into Playwright for automated DOM inspection:
|
|
334
|
-
|
|
335
|
-
\`\`\`js
|
|
336
|
-
const { chromium } = require('playwright');
|
|
337
|
-
|
|
338
|
-
(async () => {
|
|
339
|
-
const browser = await chromium.launch();
|
|
340
|
-
const page = await browser.newPage();
|
|
341
|
-
|
|
342
|
-
// Serve the export directory first, then:
|
|
343
|
-
await page.goto('http://localhost:3000/assets/snapshots/after@call@123.html');
|
|
344
|
-
|
|
345
|
-
// Inspect the DOM
|
|
346
|
-
const title = await page.title();
|
|
347
|
-
const buttons = await page.locator('button').all();
|
|
348
|
-
console.log(\`Page has \${buttons.length} buttons\`);
|
|
349
|
-
|
|
350
|
-
await browser.close();
|
|
351
|
-
})();
|
|
352
|
-
\`\`\`
|
|
353
|
-
|
|
354
|
-
This is useful for LLM-based analysis where the AI can navigate and inspect the captured page state.
|
|
355
|
-
`;
|
|
356
|
-
}
|
|
357
|
-
function generateIndexMarkdown(context, traceFile) {
|
|
358
|
-
const title = context.title || "Trace Export";
|
|
359
|
-
const duration = Math.round(context.endTime - context.startTime);
|
|
360
|
-
const actionCount = context.actions.length;
|
|
361
|
-
const errorCount = context.errors.length + context.actions.filter((a) => a.error).length;
|
|
362
|
-
const hasErrors = errorCount > 0;
|
|
363
|
-
const errorSummary = collectErrorSummary(context);
|
|
364
|
-
let md = `# Trace Export: ${title}
|
|
365
|
-
|
|
366
|
-
`;
|
|
367
|
-
md += `**Test:** \`${title}\`
|
|
368
|
-
`;
|
|
369
|
-
md += `**Source:** \`${traceFile}\`
|
|
370
|
-
|
|
371
|
-
`;
|
|
372
|
-
md += `**Status:** ${hasErrors ? "FAILED" : "PASSED"} | **Duration:** ${duration}ms | **Actions:** ${actionCount} | **Errors:** ${errorCount}
|
|
373
|
-
|
|
374
|
-
`;
|
|
375
|
-
if (errorSummary.length > 0) {
|
|
376
|
-
md += `## Error Summary
|
|
377
|
-
`;
|
|
378
|
-
for (const error of errorSummary)
|
|
379
|
-
md += `- ${error}
|
|
380
|
-
`;
|
|
381
|
-
md += "\n";
|
|
382
|
-
}
|
|
383
|
-
md += `## Sections
|
|
384
|
-
`;
|
|
385
|
-
md += `- [Timeline](./timeline.md) - Step-by-step action timeline
|
|
386
|
-
`;
|
|
387
|
-
md += `- [Metadata](./metadata.md) - Browser/environment info
|
|
388
|
-
`;
|
|
389
|
-
md += `- [Errors](./errors.md) - Full error details with stack traces
|
|
390
|
-
`;
|
|
391
|
-
md += `- [Console](./console.md) - Browser console output
|
|
392
|
-
`;
|
|
393
|
-
md += `- [Network](./network.md) - HTTP request log
|
|
394
|
-
`;
|
|
395
|
-
return md;
|
|
396
|
-
}
|
|
397
|
-
function collectErrorSummary(context) {
|
|
398
|
-
const errors = [];
|
|
399
|
-
for (const error of context.errors)
|
|
400
|
-
errors.push(truncateString(stripAnsi(error.message), 100));
|
|
401
|
-
for (const action of context.actions) {
|
|
402
|
-
if (action.error)
|
|
403
|
-
errors.push(truncateString(stripAnsi(action.error.message), 100));
|
|
404
|
-
}
|
|
405
|
-
return errors.slice(0, 10);
|
|
406
|
-
}
|
|
407
|
-
function generateMetadataMarkdown(context) {
|
|
408
|
-
let md = `# Trace Metadata
|
|
409
|
-
|
|
410
|
-
`;
|
|
411
|
-
md += `## Environment
|
|
412
|
-
|
|
413
|
-
`;
|
|
414
|
-
md += `| Property | Value |
|
|
415
|
-
`;
|
|
416
|
-
md += `|----------|-------|
|
|
417
|
-
`;
|
|
418
|
-
md += `| Browser | ${context.browserName || "Unknown"} |
|
|
419
|
-
`;
|
|
420
|
-
if (context.channel)
|
|
421
|
-
md += `| Channel | ${context.channel} |
|
|
422
|
-
`;
|
|
423
|
-
if (context.platform)
|
|
424
|
-
md += `| Platform | ${context.platform} |
|
|
425
|
-
`;
|
|
426
|
-
if (context.playwrightVersion)
|
|
427
|
-
md += `| Playwright Version | ${context.playwrightVersion} |
|
|
428
|
-
`;
|
|
429
|
-
if (context.sdkLanguage)
|
|
430
|
-
md += `| SDK Language | ${context.sdkLanguage} |
|
|
431
|
-
`;
|
|
432
|
-
md += `
|
|
433
|
-
## Context Options
|
|
434
|
-
|
|
435
|
-
`;
|
|
436
|
-
md += `| Property | Value |
|
|
437
|
-
`;
|
|
438
|
-
md += `|----------|-------|
|
|
439
|
-
`;
|
|
440
|
-
if (context.options.viewport)
|
|
441
|
-
md += `| Viewport | ${context.options.viewport.width}x${context.options.viewport.height} |
|
|
442
|
-
`;
|
|
443
|
-
if (context.options.deviceScaleFactor)
|
|
444
|
-
md += `| Device Scale Factor | ${context.options.deviceScaleFactor} |
|
|
445
|
-
`;
|
|
446
|
-
if (context.options.isMobile !== void 0)
|
|
447
|
-
md += `| Mobile | ${context.options.isMobile} |
|
|
448
|
-
`;
|
|
449
|
-
if (context.options.userAgent)
|
|
450
|
-
md += `| User Agent | ${truncateString(context.options.userAgent, 80)} |
|
|
451
|
-
`;
|
|
452
|
-
if (context.options.baseURL)
|
|
453
|
-
md += `| Base URL | ${context.options.baseURL} |
|
|
454
|
-
`;
|
|
455
|
-
md += `
|
|
456
|
-
## Timing
|
|
457
|
-
|
|
458
|
-
`;
|
|
459
|
-
md += `| Property | Value |
|
|
460
|
-
`;
|
|
461
|
-
md += `|----------|-------|
|
|
462
|
-
`;
|
|
463
|
-
md += `| Wall Time | ${new Date(context.wallTime).toISOString()} |
|
|
464
|
-
`;
|
|
465
|
-
md += `| Duration | ${Math.round(context.endTime - context.startTime)}ms |
|
|
466
|
-
`;
|
|
467
|
-
return md;
|
|
468
|
-
}
|
|
469
|
-
function buildActionTree(actions) {
|
|
470
|
-
const itemMap = /* @__PURE__ */ new Map();
|
|
471
|
-
for (const action of actions) {
|
|
472
|
-
itemMap.set(action.callId, {
|
|
473
|
-
action,
|
|
474
|
-
children: [],
|
|
475
|
-
parent: void 0
|
|
476
|
-
});
|
|
477
|
-
}
|
|
478
|
-
const rootItem = {
|
|
479
|
-
action: {
|
|
480
|
-
callId: "root",
|
|
481
|
-
class: "Root",
|
|
482
|
-
method: "",
|
|
483
|
-
params: {},
|
|
484
|
-
startTime: actions[0]?.startTime || 0,
|
|
485
|
-
endTime: actions[actions.length - 1]?.endTime || 0,
|
|
486
|
-
log: []
|
|
487
|
-
},
|
|
488
|
-
children: [],
|
|
489
|
-
parent: void 0
|
|
490
|
-
};
|
|
491
|
-
for (const item of itemMap.values()) {
|
|
492
|
-
const parent = item.action.parentId ? itemMap.get(item.action.parentId) || rootItem : rootItem;
|
|
493
|
-
parent.children.push(item);
|
|
494
|
-
item.parent = parent;
|
|
495
|
-
}
|
|
496
|
-
const sortChildren = (item) => {
|
|
497
|
-
item.children.sort((a, b) => a.action.startTime - b.action.startTime);
|
|
498
|
-
for (const child of item.children)
|
|
499
|
-
sortChildren(child);
|
|
500
|
-
};
|
|
501
|
-
sortChildren(rootItem);
|
|
502
|
-
return rootItem;
|
|
503
|
-
}
|
|
504
|
-
function getActionTitle(action) {
|
|
505
|
-
if (action.title)
|
|
506
|
-
return action.title;
|
|
507
|
-
return `${action.class}.${action.method}`;
|
|
508
|
-
}
|
|
509
|
-
function buildStepSnapshotMap(actions) {
|
|
510
|
-
const map = /* @__PURE__ */ new Map();
|
|
511
|
-
for (const action of actions) {
|
|
512
|
-
if (action.stepId && (action.beforeSnapshot || action.afterSnapshot)) {
|
|
513
|
-
const existing = map.get(action.stepId) || {};
|
|
514
|
-
if (action.beforeSnapshot)
|
|
515
|
-
existing.before = action.beforeSnapshot;
|
|
516
|
-
if (action.afterSnapshot)
|
|
517
|
-
existing.after = action.afterSnapshot;
|
|
518
|
-
map.set(action.stepId, existing);
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
return map;
|
|
522
|
-
}
|
|
523
|
-
function generateTimelineMarkdown(actions, assetMap, stepSnapshotMap) {
|
|
524
|
-
if (actions.length === 0)
|
|
525
|
-
return `# Actions Timeline
|
|
526
|
-
|
|
527
|
-
No actions recorded.
|
|
528
|
-
`;
|
|
529
|
-
const filteredActions = actions.filter((action) => action.class === "Test");
|
|
530
|
-
const startTime = filteredActions[0]?.startTime || 0;
|
|
531
|
-
const totalDuration = filteredActions.length > 0 ? (filteredActions[filteredActions.length - 1].endTime || filteredActions[filteredActions.length - 1].startTime) - startTime : 0;
|
|
532
|
-
let md = `# Actions Timeline
|
|
533
|
-
|
|
534
|
-
`;
|
|
535
|
-
md += `Total actions: ${filteredActions.length} | Duration: ${Math.round(totalDuration)}ms
|
|
536
|
-
|
|
537
|
-
`;
|
|
538
|
-
const rootItem = buildActionTree(filteredActions);
|
|
539
|
-
const renderItem = (item, prefix, index, depth) => {
|
|
540
|
-
const action = item.action;
|
|
541
|
-
if (action.callId === "root")
|
|
542
|
-
return;
|
|
543
|
-
const number = prefix ? `${prefix}.${index}` : `${index}`;
|
|
544
|
-
const relativeTime = action.startTime - startTime;
|
|
545
|
-
const duration = (action.endTime || action.startTime) - action.startTime;
|
|
546
|
-
const hasError = !!action.error;
|
|
547
|
-
const title = getActionTitle(action);
|
|
548
|
-
const headingLevel = Math.min(depth + 1, 6);
|
|
549
|
-
const heading = "#".repeat(headingLevel);
|
|
550
|
-
md += `${heading} ${number}. ${title}${hasError ? " - ERROR" : ""}
|
|
551
|
-
|
|
552
|
-
`;
|
|
553
|
-
md += `- **Start:** ${Math.round(relativeTime)}ms
|
|
554
|
-
`;
|
|
555
|
-
md += `- **Duration:** ${Math.round(duration)}ms
|
|
556
|
-
`;
|
|
557
|
-
if (action.params && Object.keys(action.params).length > 0 && action.group !== "internal") {
|
|
558
|
-
const paramsStr = formatParams(action.params);
|
|
559
|
-
if (paramsStr !== "{}")
|
|
560
|
-
md += `- **Params:** \`${paramsStr}\`
|
|
561
|
-
`;
|
|
562
|
-
}
|
|
563
|
-
if (hasError)
|
|
564
|
-
md += `- **Error:** ${stripAnsi(action.error.message)}
|
|
565
|
-
`;
|
|
566
|
-
else if (action.result !== void 0 && action.group !== "internal")
|
|
567
|
-
md += `- **Result:** ${formatResult(action.result)}
|
|
568
|
-
`;
|
|
569
|
-
if (action.stack && action.stack.length > 0) {
|
|
570
|
-
const frame = action.stack[0];
|
|
571
|
-
md += `- **Source:** \`${frame.file}:${frame.line}\`
|
|
572
|
-
`;
|
|
573
|
-
}
|
|
574
|
-
const stepSnapshots = stepSnapshotMap.get(action.callId);
|
|
575
|
-
const beforeSnapshotName = action.beforeSnapshot || stepSnapshots?.before;
|
|
576
|
-
const afterSnapshotName = action.afterSnapshot || stepSnapshots?.after;
|
|
577
|
-
const beforeSnapshot = beforeSnapshotName ? resolveSnapshotLink(beforeSnapshotName, assetMap) : null;
|
|
578
|
-
const afterSnapshot = afterSnapshotName ? resolveSnapshotLink(afterSnapshotName, assetMap) : null;
|
|
579
|
-
if (beforeSnapshot || afterSnapshot) {
|
|
580
|
-
const links = [];
|
|
581
|
-
if (beforeSnapshot)
|
|
582
|
-
links.push(`[before](${beforeSnapshot})`);
|
|
583
|
-
if (afterSnapshot)
|
|
584
|
-
links.push(`[after](${afterSnapshot})`);
|
|
585
|
-
md += `- **Snapshots:** ${links.join(" | ")}
|
|
586
|
-
`;
|
|
587
|
-
}
|
|
588
|
-
if (action.log && action.log.length > 0) {
|
|
589
|
-
md += `
|
|
590
|
-
<details><summary>Action Log</summary>
|
|
591
|
-
|
|
592
|
-
`;
|
|
593
|
-
for (const entry of action.log)
|
|
594
|
-
md += `- ${entry.message}
|
|
595
|
-
`;
|
|
596
|
-
md += `
|
|
597
|
-
</details>
|
|
598
|
-
`;
|
|
599
|
-
}
|
|
600
|
-
if (hasError && action.stack && action.stack.length > 0) {
|
|
601
|
-
md += `
|
|
602
|
-
<details><summary>Stack Trace</summary>
|
|
603
|
-
|
|
604
|
-
`;
|
|
605
|
-
md += "```\n";
|
|
606
|
-
md += `Error: ${stripAnsi(action.error.message)}
|
|
607
|
-
`;
|
|
608
|
-
for (const frame of action.stack)
|
|
609
|
-
md += ` at ${frame.function || "(anonymous)"} (${frame.file}:${frame.line}:${frame.column})
|
|
610
|
-
`;
|
|
611
|
-
md += "```\n\n";
|
|
612
|
-
md += `</details>
|
|
613
|
-
`;
|
|
614
|
-
}
|
|
615
|
-
md += `
|
|
616
|
-
`;
|
|
617
|
-
for (let i = 0; i < item.children.length; i++)
|
|
618
|
-
renderItem(item.children[i], number, i + 1, depth + 1);
|
|
619
|
-
};
|
|
620
|
-
for (let i = 0; i < rootItem.children.length; i++)
|
|
621
|
-
renderItem(rootItem.children[i], "", i + 1, 1);
|
|
622
|
-
return md;
|
|
623
|
-
}
|
|
624
|
-
function resolveSnapshotLink(snapshotName, assetMap) {
|
|
625
|
-
if (assetMap.has(snapshotName))
|
|
626
|
-
return assetMap.get(snapshotName);
|
|
627
|
-
for (const [key, assetPath] of assetMap) {
|
|
628
|
-
if (snapshotName.includes(key) || key.includes(snapshotName))
|
|
629
|
-
return assetPath;
|
|
630
|
-
}
|
|
631
|
-
return null;
|
|
632
|
-
}
|
|
633
|
-
function generateErrorsMarkdown(errors, actions) {
|
|
634
|
-
const allErrors = [];
|
|
635
|
-
for (const error of errors) {
|
|
636
|
-
allErrors.push({
|
|
637
|
-
message: stripAnsi(error.message),
|
|
638
|
-
stack: error.stack
|
|
639
|
-
});
|
|
640
|
-
}
|
|
641
|
-
for (const action of actions) {
|
|
642
|
-
if (action.error) {
|
|
643
|
-
allErrors.push({
|
|
644
|
-
message: stripAnsi(action.error.message),
|
|
645
|
-
stack: action.stack,
|
|
646
|
-
source: action.stack?.[0] ? `${action.stack[0].file}:${action.stack[0].line}` : void 0
|
|
647
|
-
});
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
if (allErrors.length === 0)
|
|
651
|
-
return `# Errors
|
|
652
|
-
|
|
653
|
-
No errors recorded.
|
|
654
|
-
`;
|
|
655
|
-
let md = `# Errors
|
|
656
|
-
|
|
657
|
-
`;
|
|
658
|
-
md += `Total errors: ${allErrors.length}
|
|
659
|
-
|
|
660
|
-
`;
|
|
661
|
-
for (let i = 0; i < allErrors.length; i++) {
|
|
662
|
-
const error = allErrors[i];
|
|
663
|
-
md += `## Error ${i + 1}
|
|
664
|
-
|
|
665
|
-
`;
|
|
666
|
-
md += `**Message:** ${error.message}
|
|
667
|
-
|
|
668
|
-
`;
|
|
669
|
-
if (error.source)
|
|
670
|
-
md += `**Source:** \`${error.source}\`
|
|
671
|
-
|
|
672
|
-
`;
|
|
673
|
-
if (error.stack && error.stack.length > 0) {
|
|
674
|
-
md += `**Stack Trace:**
|
|
675
|
-
|
|
676
|
-
`;
|
|
677
|
-
md += "```\n";
|
|
678
|
-
for (const frame of error.stack)
|
|
679
|
-
md += ` at ${frame.function || "(anonymous)"} (${frame.file}:${frame.line}:${frame.column})
|
|
680
|
-
`;
|
|
681
|
-
md += "```\n\n";
|
|
682
|
-
}
|
|
683
|
-
md += `---
|
|
684
|
-
|
|
685
|
-
`;
|
|
686
|
-
}
|
|
687
|
-
return md;
|
|
688
|
-
}
|
|
689
|
-
function generateConsoleMarkdown(events) {
|
|
690
|
-
const consoleEvents = events.filter((e) => e.type === "console");
|
|
691
|
-
if (consoleEvents.length === 0)
|
|
692
|
-
return `# Console Log
|
|
693
|
-
|
|
694
|
-
No console messages recorded.
|
|
695
|
-
`;
|
|
696
|
-
let md = `# Console Log
|
|
697
|
-
|
|
698
|
-
`;
|
|
699
|
-
md += `Total messages: ${consoleEvents.length}
|
|
700
|
-
|
|
701
|
-
`;
|
|
702
|
-
md += `| Time | Type | Message | Location |
|
|
703
|
-
`;
|
|
704
|
-
md += `|------|------|---------|----------|
|
|
705
|
-
`;
|
|
706
|
-
for (const event of consoleEvents) {
|
|
707
|
-
const message = truncateString(event.text || "", 100).replace(/\|/g, "\\|").replace(/\n/g, " ");
|
|
708
|
-
const location = event.location ? `${event.location.url}:${event.location.lineNumber}` : "";
|
|
709
|
-
md += `| ${event.time}ms | ${event.messageType || "log"} | ${message} | ${truncateString(location, 50)} |
|
|
710
|
-
`;
|
|
711
|
-
}
|
|
712
|
-
return md;
|
|
713
|
-
}
|
|
714
|
-
function generateNetworkMarkdown(resources) {
|
|
715
|
-
if (resources.length === 0)
|
|
716
|
-
return `# Network Log
|
|
717
|
-
|
|
718
|
-
No network requests recorded.
|
|
719
|
-
`;
|
|
720
|
-
let md = `# Network Log
|
|
721
|
-
|
|
722
|
-
`;
|
|
723
|
-
md += `Total requests: ${resources.length}
|
|
724
|
-
|
|
725
|
-
`;
|
|
726
|
-
md += `| # | Method | URL | Status | Size |
|
|
727
|
-
`;
|
|
728
|
-
md += `|---|--------|-----|--------|------|
|
|
729
|
-
`;
|
|
730
|
-
const failedRequests = [];
|
|
731
|
-
for (let i = 0; i < resources.length; i++) {
|
|
732
|
-
const resource = resources[i];
|
|
733
|
-
const url = truncateString(resource.request.url, 60);
|
|
734
|
-
const status = resource.response.status;
|
|
735
|
-
const size = formatSize(resource.response.content?.size || 0);
|
|
736
|
-
md += `| ${i + 1} | ${resource.request.method} | ${url} | ${status} | ${size} |
|
|
737
|
-
`;
|
|
738
|
-
if (status >= 400)
|
|
739
|
-
failedRequests.push(resource);
|
|
740
|
-
}
|
|
741
|
-
if (failedRequests.length > 0) {
|
|
742
|
-
md += `
|
|
743
|
-
## Failed Requests
|
|
744
|
-
|
|
745
|
-
`;
|
|
746
|
-
for (const resource of failedRequests) {
|
|
747
|
-
md += `### ${resource.request.method} ${truncateString(resource.request.url, 80)} - ${resource.response.status}
|
|
748
|
-
|
|
749
|
-
`;
|
|
750
|
-
if (resource.response._failureText)
|
|
751
|
-
md += `**Failure:** ${resource.response._failureText}
|
|
752
|
-
|
|
753
|
-
`;
|
|
754
|
-
if (resource.response.content?.text) {
|
|
755
|
-
md += `<details><summary>Response</summary>
|
|
756
|
-
|
|
757
|
-
`;
|
|
758
|
-
md += "```\n";
|
|
759
|
-
md += truncateString(resource.response.content.text, 1e3);
|
|
760
|
-
md += "\n```\n\n";
|
|
761
|
-
md += `</details>
|
|
762
|
-
|
|
763
|
-
`;
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
return md;
|
|
768
|
-
}
|
|
769
|
-
const autoClosing = /* @__PURE__ */ new Set(["AREA", "BASE", "BR", "COL", "COMMAND", "EMBED", "HR", "IMG", "INPUT", "KEYGEN", "LINK", "MENUITEM", "META", "PARAM", "SOURCE", "TRACK", "WBR"]);
|
|
770
|
-
function isNodeNameAttributesChildNodesSnapshot(n) {
|
|
771
|
-
return Array.isArray(n) && typeof n[0] === "string";
|
|
772
|
-
}
|
|
773
|
-
function isSubtreeReferenceSnapshot(n) {
|
|
774
|
-
return Array.isArray(n) && Array.isArray(n[0]);
|
|
775
|
-
}
|
|
776
|
-
function buildNodeIndex(snapshot) {
|
|
777
|
-
const nodes = [];
|
|
778
|
-
const visit = (n) => {
|
|
779
|
-
if (typeof n === "string") {
|
|
780
|
-
nodes.push(n);
|
|
781
|
-
} else if (isNodeNameAttributesChildNodesSnapshot(n)) {
|
|
782
|
-
const [, , ...children] = n;
|
|
783
|
-
for (const child of children)
|
|
784
|
-
visit(child);
|
|
785
|
-
nodes.push(n);
|
|
786
|
-
}
|
|
787
|
-
};
|
|
788
|
-
visit(snapshot.html);
|
|
789
|
-
return nodes;
|
|
790
|
-
}
|
|
791
|
-
class ExportSnapshotRenderer {
|
|
792
|
-
constructor(snapshots, index, networkResourceMap) {
|
|
793
|
-
this._nodeIndexCache = /* @__PURE__ */ new Map();
|
|
794
|
-
// URL -> SHA1 from network log
|
|
795
|
-
this._usedSha1s = /* @__PURE__ */ new Set();
|
|
796
|
-
this._snapshots = snapshots;
|
|
797
|
-
this._index = index;
|
|
798
|
-
this._snapshot = snapshots[index];
|
|
799
|
-
this._baseUrl = snapshots[index].frameUrl;
|
|
800
|
-
this._networkResourceMap = networkResourceMap;
|
|
801
|
-
this._overrideMap = this._buildOverrideMap();
|
|
802
|
-
}
|
|
803
|
-
// Build a map of URL -> SHA1 from all resourceOverrides, resolving refs
|
|
804
|
-
_buildOverrideMap() {
|
|
805
|
-
const map = /* @__PURE__ */ new Map();
|
|
806
|
-
for (const override of this._snapshot.resourceOverrides) {
|
|
807
|
-
if (override.sha1) {
|
|
808
|
-
map.set(override.url, override.sha1);
|
|
809
|
-
} else if (override.ref !== void 0) {
|
|
810
|
-
const refIndex = this._index - override.ref;
|
|
811
|
-
if (refIndex >= 0 && refIndex < this._snapshots.length) {
|
|
812
|
-
const refSnapshot = this._snapshots[refIndex];
|
|
813
|
-
const refOverride = refSnapshot.resourceOverrides.find((o) => o.url === override.url);
|
|
814
|
-
if (refOverride?.sha1)
|
|
815
|
-
map.set(override.url, refOverride.sha1);
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
return map;
|
|
820
|
-
}
|
|
821
|
-
_getNodeIndex(snapshotIndex) {
|
|
822
|
-
let nodes = this._nodeIndexCache.get(snapshotIndex);
|
|
823
|
-
if (!nodes) {
|
|
824
|
-
nodes = buildNodeIndex(this._snapshots[snapshotIndex]);
|
|
825
|
-
this._nodeIndexCache.set(snapshotIndex, nodes);
|
|
826
|
-
}
|
|
827
|
-
return nodes;
|
|
828
|
-
}
|
|
829
|
-
// Resolve a potentially relative URL to absolute using base URL
|
|
830
|
-
_resolveUrl(url) {
|
|
831
|
-
if (!url || url.startsWith("data:") || url.startsWith("blob:") || url.startsWith("javascript:"))
|
|
832
|
-
return url;
|
|
833
|
-
try {
|
|
834
|
-
return new URL(url, this._baseUrl).href;
|
|
835
|
-
} catch {
|
|
836
|
-
return url;
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
// Rewrite URL to relative path for export
|
|
840
|
-
_rewriteUrl(url) {
|
|
841
|
-
let sha1 = this._overrideMap.get(url);
|
|
842
|
-
if (!sha1) {
|
|
843
|
-
const resolvedUrl = this._resolveUrl(url);
|
|
844
|
-
sha1 = this._overrideMap.get(resolvedUrl);
|
|
845
|
-
if (!sha1) {
|
|
846
|
-
sha1 = this._networkResourceMap.get(url) || this._networkResourceMap.get(resolvedUrl);
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
if (sha1) {
|
|
850
|
-
this._usedSha1s.add(sha1);
|
|
851
|
-
return `../resources/${sha1}`;
|
|
852
|
-
}
|
|
853
|
-
return url;
|
|
854
|
-
}
|
|
855
|
-
// Rewrite URLs in CSS text (url(...) references)
|
|
856
|
-
_rewriteCssUrls(cssText) {
|
|
857
|
-
return cssText.replace(/url\(\s*(['"]?)([^'")]+)\1\s*\)/gi, (match, quote, url) => {
|
|
858
|
-
const rewritten = this._rewriteUrl(url.trim());
|
|
859
|
-
return `url('${rewritten}')`;
|
|
860
|
-
});
|
|
861
|
-
}
|
|
862
|
-
// Get all SHA1s that were actually used during rendering
|
|
863
|
-
getUsedSha1s() {
|
|
864
|
-
return this._usedSha1s;
|
|
865
|
-
}
|
|
866
|
-
render() {
|
|
867
|
-
const result = [];
|
|
868
|
-
const visit = (n, snapshotIndex, parentTag) => {
|
|
869
|
-
if (typeof n === "string") {
|
|
870
|
-
if (parentTag === "STYLE" || parentTag === "style")
|
|
871
|
-
result.push(this._rewriteCssUrls((0, import_stringUtils.escapeHTML)(n)));
|
|
872
|
-
else
|
|
873
|
-
result.push((0, import_stringUtils.escapeHTML)(n));
|
|
874
|
-
return;
|
|
875
|
-
}
|
|
876
|
-
if (isSubtreeReferenceSnapshot(n)) {
|
|
877
|
-
const [snapshotsAgo, nodeIndex] = n[0];
|
|
878
|
-
const referenceIndex = snapshotIndex - snapshotsAgo;
|
|
879
|
-
if (referenceIndex >= 0 && referenceIndex <= snapshotIndex) {
|
|
880
|
-
const nodes = this._getNodeIndex(referenceIndex);
|
|
881
|
-
if (nodeIndex >= 0 && nodeIndex < nodes.length)
|
|
882
|
-
return visit(nodes[nodeIndex], referenceIndex, parentTag);
|
|
883
|
-
}
|
|
884
|
-
return;
|
|
885
|
-
}
|
|
886
|
-
if (isNodeNameAttributesChildNodesSnapshot(n)) {
|
|
887
|
-
const [name, nodeAttrs, ...children] = n;
|
|
888
|
-
const nodeName = name === "NOSCRIPT" ? "X-NOSCRIPT" : name;
|
|
889
|
-
const attrs = Object.entries(nodeAttrs || {});
|
|
890
|
-
if (nodeName === "BASE")
|
|
891
|
-
return;
|
|
892
|
-
result.push("<", nodeName.toLowerCase());
|
|
893
|
-
const isFrame = nodeName === "IFRAME" || nodeName === "FRAME";
|
|
894
|
-
const isAnchor = nodeName === "A";
|
|
895
|
-
const isLink = nodeName === "LINK";
|
|
896
|
-
const isScript = nodeName === "SCRIPT";
|
|
897
|
-
const isImg = nodeName === "IMG";
|
|
898
|
-
for (const [attr, value] of attrs) {
|
|
899
|
-
if (attr.startsWith("__playwright") && attr !== "__playwright_src__")
|
|
900
|
-
continue;
|
|
901
|
-
let attrName = attr;
|
|
902
|
-
let attrValue = value;
|
|
903
|
-
const attrLower = attr.toLowerCase();
|
|
904
|
-
if (isFrame && attr === "__playwright_src__") {
|
|
905
|
-
attrName = "src";
|
|
906
|
-
attrValue = this._rewriteUrl(value);
|
|
907
|
-
} else if (isLink && attrLower === "href") {
|
|
908
|
-
attrValue = this._rewriteUrl(value);
|
|
909
|
-
} else if ((isScript || isImg) && attrLower === "src") {
|
|
910
|
-
attrValue = this._rewriteUrl(value);
|
|
911
|
-
} else if (!isAnchor && !isLink && attrLower === "src") {
|
|
912
|
-
attrValue = this._rewriteUrl(value);
|
|
913
|
-
} else if (attrLower === "srcset") {
|
|
914
|
-
attrValue = this._rewriteSrcset(value);
|
|
915
|
-
} else if (attrLower === "style") {
|
|
916
|
-
attrValue = this._rewriteCssUrls(value);
|
|
917
|
-
}
|
|
918
|
-
result.push(" ", attrName, '="', (0, import_stringUtils.escapeHTMLAttribute)(attrValue), '"');
|
|
919
|
-
}
|
|
920
|
-
result.push(">");
|
|
921
|
-
for (const child of children)
|
|
922
|
-
visit(child, snapshotIndex, nodeName);
|
|
923
|
-
if (!autoClosing.has(nodeName))
|
|
924
|
-
result.push("</", nodeName.toLowerCase(), ">");
|
|
925
|
-
}
|
|
926
|
-
};
|
|
927
|
-
const snapshot = this._snapshot;
|
|
928
|
-
visit(snapshot.html, this._index, void 0);
|
|
929
|
-
const doctype = snapshot.doctype ? `<!DOCTYPE ${snapshot.doctype}>` : "<!DOCTYPE html>";
|
|
930
|
-
const comment = `<!-- Playwright Snapshot: ${snapshot.snapshotName} | URL: ${snapshot.frameUrl} | Timestamp: ${snapshot.timestamp} -->`;
|
|
931
|
-
return doctype + "\n" + comment + "\n" + result.join("");
|
|
932
|
-
}
|
|
933
|
-
// Rewrite srcset attribute (format: "url1 1x, url2 2x, ...")
|
|
934
|
-
_rewriteSrcset(srcset) {
|
|
935
|
-
return srcset.split(",").map((entry) => {
|
|
936
|
-
const parts = entry.trim().split(/\s+/);
|
|
937
|
-
if (parts.length >= 1) {
|
|
938
|
-
parts[0] = this._rewriteUrl(parts[0]);
|
|
939
|
-
}
|
|
940
|
-
return parts.join(" ");
|
|
941
|
-
}).join(", ");
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
|
-
function truncateString(str, maxLength) {
|
|
945
|
-
if (str.length <= maxLength)
|
|
946
|
-
return str;
|
|
947
|
-
return str.substring(0, maxLength - 3) + "...";
|
|
948
|
-
}
|
|
949
|
-
function formatParams(params) {
|
|
950
|
-
const str = JSON.stringify(params);
|
|
951
|
-
return truncateString(str, 200);
|
|
952
|
-
}
|
|
953
|
-
function formatResult(result) {
|
|
954
|
-
if (result === null || result === void 0)
|
|
955
|
-
return "null";
|
|
956
|
-
if (typeof result === "string")
|
|
957
|
-
return truncateString(result, 100);
|
|
958
|
-
const str = JSON.stringify(result);
|
|
959
|
-
return truncateString(str, 100);
|
|
960
|
-
}
|
|
961
|
-
function formatSize(bytes) {
|
|
962
|
-
if (bytes < 1024)
|
|
963
|
-
return `${bytes}B`;
|
|
964
|
-
if (bytes < 1024 * 1024)
|
|
965
|
-
return `${(bytes / 1024).toFixed(1)}KB`;
|
|
966
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
967
|
-
}
|
|
968
|
-
function stripAnsi(str) {
|
|
969
|
-
return str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
970
|
-
}
|
|
971
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
972
|
-
0 && (module.exports = {
|
|
973
|
-
exportTraceToMarkdown
|
|
974
|
-
});
|