ScuttleBot

scuttlebot / tests / e2e / node_modules / playwright / lib / worker / testTracing.js
Blame History Raw 346 lines
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 testTracing_exports = {};
30
__export(testTracing_exports, {
31
TestTracing: () => TestTracing,
32
testTraceEntryName: () => testTraceEntryName
33
});
34
module.exports = __toCommonJS(testTracing_exports);
35
var import_fs = __toESM(require("fs"));
36
var import_path = __toESM(require("path"));
37
var import_utils = require("playwright-core/lib/utils");
38
var import_zipBundle = require("playwright-core/lib/zipBundle");
39
var import_util = require("../util");
40
const testTraceEntryName = "test.trace";
41
const version = 8;
42
let traceOrdinal = 0;
43
class TestTracing {
44
constructor(testInfo, artifactsDir) {
45
this._traceEvents = [];
46
this._temporaryTraceFiles = [];
47
this._didFinishTestFunctionAndAfterEachHooks = false;
48
this._testInfo = testInfo;
49
this._artifactsDir = artifactsDir;
50
this._tracesDir = import_path.default.join(this._artifactsDir, "traces");
51
this._contextCreatedEvent = {
52
version,
53
type: "context-options",
54
origin: "testRunner",
55
browserName: "",
56
playwrightVersion: (0, import_utils.getPlaywrightVersion)(),
57
options: {},
58
platform: process.platform,
59
wallTime: Date.now(),
60
monotonicTime: (0, import_utils.monotonicTime)(),
61
sdkLanguage: "javascript"
62
};
63
this._appendTraceEvent(this._contextCreatedEvent);
64
}
65
_shouldCaptureTrace() {
66
if (this._options?.mode === "on")
67
return true;
68
if (this._options?.mode === "retain-on-failure")
69
return true;
70
if (this._options?.mode === "on-first-retry" && this._testInfo.retry === 1)
71
return true;
72
if (this._options?.mode === "on-all-retries" && this._testInfo.retry > 0)
73
return true;
74
if (this._options?.mode === "retain-on-first-failure" && this._testInfo.retry === 0)
75
return true;
76
return false;
77
}
78
async startIfNeeded(value) {
79
const defaultTraceOptions = { screenshots: true, snapshots: true, sources: true, attachments: true, _live: false, mode: "off" };
80
if (!value) {
81
this._options = defaultTraceOptions;
82
} else if (typeof value === "string") {
83
this._options = { ...defaultTraceOptions, mode: value === "retry-with-trace" ? "on-first-retry" : value };
84
} else {
85
const mode = value.mode || "off";
86
this._options = { ...defaultTraceOptions, ...value, mode: mode === "retry-with-trace" ? "on-first-retry" : mode };
87
}
88
if (!this._shouldCaptureTrace()) {
89
this._options = void 0;
90
return;
91
}
92
if (!this._liveTraceFile && this._options._live) {
93
this._liveTraceFile = { file: import_path.default.join(this._tracesDir, `${this._testInfo.testId}-test.trace`), fs: new import_utils.SerializedFS() };
94
this._liveTraceFile.fs.mkdir(import_path.default.dirname(this._liveTraceFile.file));
95
const data = this._traceEvents.map((e) => JSON.stringify(e)).join("\n") + "\n";
96
this._liveTraceFile.fs.writeFile(this._liveTraceFile.file, data);
97
}
98
}
99
didFinishTestFunctionAndAfterEachHooks() {
100
this._didFinishTestFunctionAndAfterEachHooks = true;
101
}
102
artifactsDir() {
103
return this._artifactsDir;
104
}
105
tracesDir() {
106
return this._tracesDir;
107
}
108
traceTitle() {
109
return [import_path.default.relative(this._testInfo.project.testDir, this._testInfo.file) + ":" + this._testInfo.line, ...this._testInfo.titlePath.slice(1)].join(" \u203A ");
110
}
111
generateNextTraceRecordingName() {
112
const ordinalSuffix = traceOrdinal ? `-recording${traceOrdinal}` : "";
113
++traceOrdinal;
114
const retrySuffix = this._testInfo.retry ? `-retry${this._testInfo.retry}` : "";
115
return `${this._testInfo.testId}${retrySuffix}${ordinalSuffix}`;
116
}
117
_generateNextTraceRecordingPath() {
118
const file = import_path.default.join(this._artifactsDir, (0, import_utils.createGuid)() + ".zip");
119
this._temporaryTraceFiles.push(file);
120
return file;
121
}
122
traceOptions() {
123
return this._options;
124
}
125
maybeGenerateNextTraceRecordingPath() {
126
if (this._didFinishTestFunctionAndAfterEachHooks && this._shouldAbandonTrace())
127
return;
128
return this._generateNextTraceRecordingPath();
129
}
130
_shouldAbandonTrace() {
131
if (!this._options)
132
return true;
133
const testFailed = this._testInfo.status !== this._testInfo.expectedStatus;
134
return !testFailed && (this._options.mode === "retain-on-failure" || this._options.mode === "retain-on-first-failure");
135
}
136
async stopIfNeeded() {
137
if (!this._options)
138
return;
139
const error = await this._liveTraceFile?.fs.syncAndGetError();
140
if (error)
141
throw error;
142
if (this._shouldAbandonTrace()) {
143
for (const file of this._temporaryTraceFiles)
144
await import_fs.default.promises.unlink(file).catch(() => {
145
});
146
return;
147
}
148
const zipFile = new import_zipBundle.yazl.ZipFile();
149
if (!this._options?.attachments) {
150
for (const event of this._traceEvents) {
151
if (event.type === "after")
152
delete event.attachments;
153
}
154
}
155
if (this._options?.sources) {
156
const sourceFiles = /* @__PURE__ */ new Set();
157
for (const event of this._traceEvents) {
158
if (event.type === "before") {
159
for (const frame of event.stack || [])
160
sourceFiles.add(frame.file);
161
}
162
}
163
for (const sourceFile of sourceFiles) {
164
await import_fs.default.promises.readFile(sourceFile, "utf8").then((source) => {
165
zipFile.addBuffer(Buffer.from(source), "resources/src@" + (0, import_utils.calculateSha1)(sourceFile) + ".txt");
166
}).catch(() => {
167
});
168
}
169
}
170
const sha1s = /* @__PURE__ */ new Set();
171
for (const event of this._traceEvents.filter((e) => e.type === "after")) {
172
for (const attachment of event.attachments || []) {
173
let contentPromise;
174
if (attachment.path)
175
contentPromise = import_fs.default.promises.readFile(attachment.path).catch(() => void 0);
176
else if (attachment.base64)
177
contentPromise = Promise.resolve(Buffer.from(attachment.base64, "base64"));
178
const content = await contentPromise;
179
if (content === void 0)
180
continue;
181
const sha1 = (0, import_utils.calculateSha1)(content);
182
attachment.sha1 = sha1;
183
delete attachment.path;
184
delete attachment.base64;
185
if (sha1s.has(sha1))
186
continue;
187
sha1s.add(sha1);
188
zipFile.addBuffer(content, "resources/" + sha1);
189
}
190
}
191
const traceContent = Buffer.from(this._traceEvents.map((e) => JSON.stringify(e)).join("\n"));
192
zipFile.addBuffer(traceContent, testTraceEntryName);
193
await new Promise((f) => {
194
zipFile.end(void 0, () => {
195
zipFile.outputStream.pipe(import_fs.default.createWriteStream(this._generateNextTraceRecordingPath())).on("close", f);
196
});
197
});
198
const tracePath = this._testInfo.outputPath("trace.zip");
199
await mergeTraceFiles(tracePath, this._temporaryTraceFiles);
200
this._testInfo.attachments.push({ name: "trace", path: tracePath, contentType: "application/zip" });
201
}
202
appendForError(error) {
203
const rawStack = error.stack?.split("\n") || [];
204
const stack = rawStack ? (0, import_util.filteredStackTrace)(rawStack) : [];
205
this._appendTraceEvent({
206
type: "error",
207
message: this._formatError(error),
208
stack
209
});
210
}
211
_formatError(error) {
212
const parts = [error.message || String(error.value)];
213
if (error.cause)
214
parts.push("[cause]: " + this._formatError(error.cause));
215
return parts.join("\n");
216
}
217
appendStdioToTrace(type, chunk) {
218
this._appendTraceEvent({
219
type,
220
timestamp: (0, import_utils.monotonicTime)(),
221
text: typeof chunk === "string" ? chunk : void 0,
222
base64: typeof chunk === "string" ? void 0 : chunk.toString("base64")
223
});
224
}
225
appendBeforeActionForStep(options) {
226
this._appendTraceEvent({
227
type: "before",
228
callId: options.stepId,
229
stepId: options.stepId,
230
parentId: options.parentId,
231
startTime: (0, import_utils.monotonicTime)(),
232
class: "Test",
233
method: options.category,
234
title: options.title,
235
params: Object.fromEntries(Object.entries(options.params || {}).map(([name, value]) => [name, generatePreview(value)])),
236
stack: options.stack,
237
group: options.group
238
});
239
}
240
appendAfterActionForStep(callId, error, attachments = [], annotations) {
241
this._appendTraceEvent({
242
type: "after",
243
callId,
244
endTime: (0, import_utils.monotonicTime)(),
245
attachments: serializeAttachments(attachments),
246
annotations,
247
error
248
});
249
}
250
_appendTraceEvent(event) {
251
this._traceEvents.push(event);
252
if (this._liveTraceFile)
253
this._liveTraceFile.fs.appendFile(this._liveTraceFile.file, JSON.stringify(event) + "\n", true);
254
}
255
}
256
function serializeAttachments(attachments) {
257
if (attachments.length === 0)
258
return void 0;
259
return attachments.filter((a) => a.name !== "trace").map((a) => {
260
return {
261
name: a.name,
262
contentType: a.contentType,
263
path: a.path,
264
base64: a.body?.toString("base64")
265
};
266
});
267
}
268
function generatePreview(value, visited = /* @__PURE__ */ new Set()) {
269
if (visited.has(value))
270
return "";
271
visited.add(value);
272
if (typeof value === "string")
273
return value;
274
if (typeof value === "number")
275
return value.toString();
276
if (typeof value === "boolean")
277
return value.toString();
278
if (value === null)
279
return "null";
280
if (value === void 0)
281
return "undefined";
282
if (Array.isArray(value))
283
return "[" + value.map((v) => generatePreview(v, visited)).join(", ") + "]";
284
if (typeof value === "object")
285
return "Object";
286
return String(value);
287
}
288
async function mergeTraceFiles(fileName, temporaryTraceFiles) {
289
temporaryTraceFiles = temporaryTraceFiles.filter((file) => import_fs.default.existsSync(file));
290
if (temporaryTraceFiles.length === 1) {
291
await import_fs.default.promises.rename(temporaryTraceFiles[0], fileName);
292
return;
293
}
294
const mergePromise = new import_utils.ManualPromise();
295
const zipFile = new import_zipBundle.yazl.ZipFile();
296
const entryNames = /* @__PURE__ */ new Set();
297
zipFile.on("error", (error) => mergePromise.reject(error));
298
for (let i = temporaryTraceFiles.length - 1; i >= 0; --i) {
299
const tempFile = temporaryTraceFiles[i];
300
const promise = new import_utils.ManualPromise();
301
import_zipBundle.yauzl.open(tempFile, (err, inZipFile) => {
302
if (err) {
303
promise.reject(err);
304
return;
305
}
306
let pendingEntries = inZipFile.entryCount;
307
inZipFile.on("entry", (entry) => {
308
let entryName = entry.fileName;
309
if (entry.fileName === testTraceEntryName) {
310
} else if (entry.fileName.match(/trace\.[a-z]*$/)) {
311
entryName = i + "-" + entry.fileName;
312
}
313
if (entryNames.has(entryName)) {
314
if (--pendingEntries === 0)
315
promise.resolve();
316
return;
317
}
318
entryNames.add(entryName);
319
inZipFile.openReadStream(entry, (err2, readStream) => {
320
if (err2) {
321
promise.reject(err2);
322
return;
323
}
324
zipFile.addReadStream(readStream, entryName);
325
if (--pendingEntries === 0)
326
promise.resolve();
327
});
328
});
329
});
330
await promise;
331
}
332
zipFile.end(void 0, () => {
333
zipFile.outputStream.pipe(import_fs.default.createWriteStream(fileName)).on("close", () => {
334
void Promise.all(temporaryTraceFiles.map((tempFile) => import_fs.default.promises.unlink(tempFile))).then(() => {
335
mergePromise.resolve();
336
}).catch((error) => mergePromise.reject(error));
337
}).on("error", (error) => mergePromise.reject(error));
338
});
339
await mergePromise;
340
}
341
// Annotate the CommonJS export names for ESM import in node:
342
0 && (module.exports = {
343
TestTracing,
344
testTraceEntryName
345
});
346

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button