"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var testTracing_exports = {}; __export(testTracing_exports, { TestTracing: () => TestTracing, testTraceEntryName: () => testTraceEntryName }); module.exports = __toCommonJS(testTracing_exports); var import_fs = __toESM(require("fs")); var import_path = __toESM(require("path")); var import_utils = require("playwright-core/lib/utils"); var import_zipBundle = require("playwright-core/lib/zipBundle"); var import_util = require("../util"); const testTraceEntryName = "test.trace"; const version = 8; let traceOrdinal = 0; class TestTracing { constructor(testInfo, artifactsDir) { this._traceEvents = []; this._temporaryTraceFiles = []; this._didFinishTestFunctionAndAfterEachHooks = false; this._testInfo = testInfo; this._artifactsDir = artifactsDir; this._tracesDir = import_path.default.join(this._artifactsDir, "traces"); this._contextCreatedEvent = { version, type: "context-options", origin: "testRunner", browserName: "", options: {}, platform: process.platform, wallTime: Date.now(), monotonicTime: (0, import_utils.monotonicTime)(), sdkLanguage: "javascript" }; this._appendTraceEvent(this._contextCreatedEvent); } _shouldCaptureTrace() { if (this._options?.mode === "on") return true; if (this._options?.mode === "retain-on-failure") return true; if (this._options?.mode === "on-first-retry" && this._testInfo.retry === 1) return true; if (this._options?.mode === "on-all-retries" && this._testInfo.retry > 0) return true; if (this._options?.mode === "retain-on-first-failure" && this._testInfo.retry === 0) return true; return false; } async startIfNeeded(value) { const defaultTraceOptions = { screenshots: true, snapshots: true, sources: true, attachments: true, _live: false, mode: "off" }; if (!value) { this._options = defaultTraceOptions; } else if (typeof value === "string") { this._options = { ...defaultTraceOptions, mode: value === "retry-with-trace" ? "on-first-retry" : value }; } else { const mode = value.mode || "off"; this._options = { ...defaultTraceOptions, ...value, mode: mode === "retry-with-trace" ? "on-first-retry" : mode }; } if (!this._shouldCaptureTrace()) { this._options = void 0; return; } if (!this._liveTraceFile && this._options._live) { this._liveTraceFile = { file: import_path.default.join(this._tracesDir, `${this._testInfo.testId}-test.trace`), fs: new import_utils.SerializedFS() }; this._liveTraceFile.fs.mkdir(import_path.default.dirname(this._liveTraceFile.file)); const data = this._traceEvents.map((e) => JSON.stringify(e)).join("\n") + "\n"; this._liveTraceFile.fs.writeFile(this._liveTraceFile.file, data); } } didFinishTestFunctionAndAfterEachHooks() { this._didFinishTestFunctionAndAfterEachHooks = true; } artifactsDir() { return this._artifactsDir; } tracesDir() { return this._tracesDir; } traceTitle() { return [import_path.default.relative(this._testInfo.project.testDir, this._testInfo.file) + ":" + this._testInfo.line, ...this._testInfo.titlePath.slice(1)].join(" \u203A "); } generateNextTraceRecordingName() { const ordinalSuffix = traceOrdinal ? `-recording${traceOrdinal}` : ""; ++traceOrdinal; const retrySuffix = this._testInfo.retry ? `-retry${this._testInfo.retry}` : ""; return `${this._testInfo.testId}${retrySuffix}${ordinalSuffix}`; } _generateNextTraceRecordingPath() { const file = import_path.default.join(this._artifactsDir, (0, import_utils.createGuid)() + ".zip"); this._temporaryTraceFiles.push(file); return file; } traceOptions() { return this._options; } maybeGenerateNextTraceRecordingPath() { if (this._didFinishTestFunctionAndAfterEachHooks && this._shouldAbandonTrace()) return; return this._generateNextTraceRecordingPath(); } _shouldAbandonTrace() { if (!this._options) return true; const testFailed = this._testInfo.status !== this._testInfo.expectedStatus; return !testFailed && (this._options.mode === "retain-on-failure" || this._options.mode === "retain-on-first-failure"); } async stopIfNeeded() { if (!this._options) return; const error = await this._liveTraceFile?.fs.syncAndGetError(); if (error) throw error; if (this._shouldAbandonTrace()) { for (const file of this._temporaryTraceFiles) await import_fs.default.promises.unlink(file).catch(() => { }); return; } const zipFile = new import_zipBundle.yazl.ZipFile(); if (!this._options?.attachments) { for (const event of this._traceEvents) { if (event.type === "after") delete event.attachments; } } if (this._options?.sources) { const sourceFiles = /* @__PURE__ */ new Set(); for (const event of this._traceEvents) { if (event.type === "before") { for (const frame of event.stack || []) sourceFiles.add(frame.file); } } for (const sourceFile of sourceFiles) { await import_fs.default.promises.readFile(sourceFile, "utf8").then((source) => { zipFile.addBuffer(Buffer.from(source), "resources/src@" + (0, import_utils.calculateSha1)(sourceFile) + ".txt"); }).catch(() => { }); } } const sha1s = /* @__PURE__ */ new Set(); for (const event of this._traceEvents.filter((e) => e.type === "after")) { for (const attachment of event.attachments || []) { let contentPromise; if (attachment.path) contentPromise = import_fs.default.promises.readFile(attachment.path).catch(() => void 0); else if (attachment.base64) contentPromise = Promise.resolve(Buffer.from(attachment.base64, "base64")); const content = await contentPromise; if (content === void 0) continue; const sha1 = (0, import_utils.calculateSha1)(content); attachment.sha1 = sha1; delete attachment.path; delete attachment.base64; if (sha1s.has(sha1)) continue; sha1s.add(sha1); zipFile.addBuffer(content, "resources/" + sha1); } } const traceContent = Buffer.from(this._traceEvents.map((e) => JSON.stringify(e)).join("\n")); zipFile.addBuffer(traceContent, testTraceEntryName); await new Promise((f) => { zipFile.end(void 0, () => { zipFile.outputStream.pipe(import_fs.default.createWriteStream(this._generateNextTraceRecordingPath())).on("close", f); }); }); const tracePath = this._testInfo.outputPath("trace.zip"); await mergeTraceFiles(tracePath, this._temporaryTraceFiles); this._testInfo.attachments.push({ name: "trace", path: tracePath, contentType: "application/zip" }); } appendForError(error) { const rawStack = error.stack?.split("\n") || []; const stack = rawStack ? (0, import_util.filteredStackTrace)(rawStack) : []; this._appendTraceEvent({ type: "error", message: this._formatError(error), stack }); } _formatError(error) { const parts = [error.message || String(error.value)]; if (error.cause) parts.push("[cause]: " + this._formatError(error.cause)); return parts.join("\n"); } appendStdioToTrace(type, chunk) { this._appendTraceEvent({ type, timestamp: (0, import_utils.monotonicTime)(), text: typeof chunk === "string" ? chunk : void 0, base64: typeof chunk === "string" ? void 0 : chunk.toString("base64") }); } appendBeforeActionForStep(options) { this._appendTraceEvent({ type: "before", callId: options.stepId, stepId: options.stepId, parentId: options.parentId, startTime: (0, import_utils.monotonicTime)(), class: "Test", method: options.category, title: options.title, params: Object.fromEntries(Object.entries(options.params || {}).map(([name, value]) => [name, generatePreview(value)])), stack: options.stack, group: options.group }); } appendAfterActionForStep(callId, error, attachments = [], annotations) { this._appendTraceEvent({ type: "after", callId, endTime: (0, import_utils.monotonicTime)(), attachments: serializeAttachments(attachments), annotations, error }); } _appendTraceEvent(event) { this._traceEvents.push(event); if (this._liveTraceFile) this._liveTraceFile.fs.appendFile(this._liveTraceFile.file, JSON.stringify(event) + "\n", true); } } function serializeAttachments(attachments) { if (attachments.length === 0) return void 0; return attachments.filter((a) => a.name !== "trace").map((a) => { return { name: a.name, contentType: a.contentType, path: a.path, base64: a.body?.toString("base64") }; }); } function generatePreview(value, visited = /* @__PURE__ */ new Set()) { if (visited.has(value)) return ""; visited.add(value); if (typeof value === "string") return value; if (typeof value === "number") return value.toString(); if (typeof value === "boolean") return value.toString(); if (value === null) return "null"; if (value === void 0) return "undefined"; if (Array.isArray(value)) return "[" + value.map((v) => generatePreview(v, visited)).join(", ") + "]"; if (typeof value === "object") return "Object"; return String(value); } async function mergeTraceFiles(fileName, temporaryTraceFiles) { temporaryTraceFiles = temporaryTraceFiles.filter((file) => import_fs.default.existsSync(file)); if (temporaryTraceFiles.length === 1) { await import_fs.default.promises.rename(temporaryTraceFiles[0], fileName); return; } const mergePromise = new import_utils.ManualPromise(); const zipFile = new import_zipBundle.yazl.ZipFile(); const entryNames = /* @__PURE__ */ new Set(); zipFile.on("error", (error) => mergePromise.reject(error)); for (let i = temporaryTraceFiles.length - 1; i >= 0; --i) { const tempFile = temporaryTraceFiles[i]; const promise = new import_utils.ManualPromise(); import_zipBundle.yauzl.open(tempFile, (err, inZipFile) => { if (err) { promise.reject(err); return; } let pendingEntries = inZipFile.entryCount; inZipFile.on("entry", (entry) => { let entryName = entry.fileName; if (entry.fileName === testTraceEntryName) { } else if (entry.fileName.match(/trace\.[a-z]*$/)) { entryName = i + "-" + entry.fileName; } if (entryNames.has(entryName)) { if (--pendingEntries === 0) promise.resolve(); return; } entryNames.add(entryName); inZipFile.openReadStream(entry, (err2, readStream) => { if (err2) { promise.reject(err2); return; } zipFile.addReadStream(readStream, entryName); if (--pendingEntries === 0) promise.resolve(); }); }); }); await promise; } zipFile.end(void 0, () => { zipFile.outputStream.pipe(import_fs.default.createWriteStream(fileName)).on("close", () => { void Promise.all(temporaryTraceFiles.map((tempFile) => import_fs.default.promises.unlink(tempFile))).then(() => { mergePromise.resolve(); }).catch((error) => mergePromise.reject(error)); }).on("error", (error) => mergePromise.reject(error)); }); await mergePromise; } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { TestTracing, testTraceEntryName });