ScuttleBot

Source Blame History 633 lines
f7eb47b… lmata 1 "use strict";
f7eb47b… lmata 2 var __create = Object.create;
f7eb47b… lmata 3 var __defProp = Object.defineProperty;
f7eb47b… lmata 4 var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
f7eb47b… lmata 5 var __getOwnPropNames = Object.getOwnPropertyNames;
f7eb47b… lmata 6 var __getProtoOf = Object.getPrototypeOf;
f7eb47b… lmata 7 var __hasOwnProp = Object.prototype.hasOwnProperty;
f7eb47b… lmata 8 var __export = (target, all) => {
f7eb47b… lmata 9 for (var name in all)
f7eb47b… lmata 10 __defProp(target, name, { get: all[name], enumerable: true });
f7eb47b… lmata 11 };
f7eb47b… lmata 12 var __copyProps = (to, from, except, desc) => {
f7eb47b… lmata 13 if (from && typeof from === "object" || typeof from === "function") {
f7eb47b… lmata 14 for (let key of __getOwnPropNames(from))
f7eb47b… lmata 15 if (!__hasOwnProp.call(to, key) && key !== except)
f7eb47b… lmata 16 __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
f7eb47b… lmata 17 }
f7eb47b… lmata 18 return to;
f7eb47b… lmata 19 };
f7eb47b… lmata 20 var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
f7eb47b… lmata 21 // If the importer is in node compatibility mode or this is not an ESM
f7eb47b… lmata 22 // file that has been converted to a CommonJS file using a Babel-
f7eb47b… lmata 23 // compatible transform (i.e. "__esModule" has not been set), then set
f7eb47b… lmata 24 // "default" to the CommonJS "module.exports" for node compatibility.
f7eb47b… lmata 25 isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
f7eb47b… lmata 26 mod
f7eb47b… lmata 27 ));
f7eb47b… lmata 28 var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
f7eb47b… lmata 29 var html_exports = {};
f7eb47b… lmata 30 __export(html_exports, {
f7eb47b… lmata 31 default: () => html_default,
f7eb47b… lmata 32 showHTMLReport: () => showHTMLReport,
f7eb47b… lmata 33 startHtmlReportServer: () => startHtmlReportServer
f7eb47b… lmata 34 });
f7eb47b… lmata 35 module.exports = __toCommonJS(html_exports);
f7eb47b… lmata 36 var import_fs = __toESM(require("fs"));
f7eb47b… lmata 37 var import_path = __toESM(require("path"));
f7eb47b… lmata 38 var import_stream = require("stream");
f7eb47b… lmata 39 var import_utils = require("playwright-core/lib/utils");
f7eb47b… lmata 40 var import_utils2 = require("playwright-core/lib/utils");
f7eb47b… lmata 41 var import_utilsBundle = require("playwright-core/lib/utilsBundle");
f7eb47b… lmata 42 var import_utilsBundle2 = require("playwright-core/lib/utilsBundle");
f7eb47b… lmata 43 var import_zipBundle = require("playwright-core/lib/zipBundle");
f7eb47b… lmata 44 var import_base = require("./base");
f7eb47b… lmata 45 var import_babelBundle = require("../transform/babelBundle");
f7eb47b… lmata 46 var import_util = require("../util");
f7eb47b… lmata 47 const htmlReportOptions = ["always", "never", "on-failure"];
f7eb47b… lmata 48 const isHtmlReportOption = (type) => {
f7eb47b… lmata 49 return htmlReportOptions.includes(type);
f7eb47b… lmata 50 };
f7eb47b… lmata 51 class HtmlReporter {
f7eb47b… lmata 52 constructor(options) {
f7eb47b… lmata 53 this._topLevelErrors = [];
f7eb47b… lmata 54 this._machines = [];
f7eb47b… lmata 55 this._options = options;
f7eb47b… lmata 56 }
f7eb47b… lmata 57 version() {
f7eb47b… lmata 58 return "v2";
f7eb47b… lmata 59 }
f7eb47b… lmata 60 printsToStdio() {
f7eb47b… lmata 61 return false;
f7eb47b… lmata 62 }
f7eb47b… lmata 63 onConfigure(config) {
f7eb47b… lmata 64 this.config = config;
f7eb47b… lmata 65 }
f7eb47b… lmata 66 onBegin(suite) {
f7eb47b… lmata 67 const { outputFolder, open: open2, attachmentsBaseURL, host, port } = this._resolveOptions();
f7eb47b… lmata 68 this._outputFolder = outputFolder;
f7eb47b… lmata 69 this._open = open2;
f7eb47b… lmata 70 this._host = host;
f7eb47b… lmata 71 this._port = port;
f7eb47b… lmata 72 this._attachmentsBaseURL = attachmentsBaseURL;
f7eb47b… lmata 73 const reportedWarnings = /* @__PURE__ */ new Set();
f7eb47b… lmata 74 for (const project of this.config.projects) {
f7eb47b… lmata 75 if (this._isSubdirectory(outputFolder, project.outputDir) || this._isSubdirectory(project.outputDir, outputFolder)) {
f7eb47b… lmata 76 const key = outputFolder + "|" + project.outputDir;
f7eb47b… lmata 77 if (reportedWarnings.has(key))
f7eb47b… lmata 78 continue;
f7eb47b… lmata 79 reportedWarnings.add(key);
f7eb47b… lmata 80 writeLine(import_utils2.colors.red(`Configuration Error: HTML reporter output folder clashes with the tests output folder:`));
f7eb47b… lmata 81 writeLine(`
f7eb47b… lmata 82 html reporter folder: ${import_utils2.colors.bold(outputFolder)}
f7eb47b… lmata 83 test results folder: ${import_utils2.colors.bold(project.outputDir)}`);
f7eb47b… lmata 84 writeLine("");
f7eb47b… lmata 85 writeLine(`HTML reporter will clear its output directory prior to being generated, which will lead to the artifact loss.
f7eb47b… lmata 86 `);
f7eb47b… lmata 87 }
f7eb47b… lmata 88 }
f7eb47b… lmata 89 this.suite = suite;
f7eb47b… lmata 90 }
f7eb47b… lmata 91 _resolveOptions() {
f7eb47b… lmata 92 const outputFolder = reportFolderFromEnv() ?? (0, import_util.resolveReporterOutputPath)("playwright-report", this._options.configDir, this._options.outputFolder);
f7eb47b… lmata 93 return {
f7eb47b… lmata 94 outputFolder,
f7eb47b… lmata 95 open: getHtmlReportOptionProcessEnv() || this._options.open || "on-failure",
f7eb47b… lmata 96 attachmentsBaseURL: process.env.PLAYWRIGHT_HTML_ATTACHMENTS_BASE_URL || this._options.attachmentsBaseURL || "data/",
f7eb47b… lmata 97 host: process.env.PLAYWRIGHT_HTML_HOST || this._options.host,
f7eb47b… lmata 98 port: process.env.PLAYWRIGHT_HTML_PORT ? +process.env.PLAYWRIGHT_HTML_PORT : this._options.port
f7eb47b… lmata 99 };
f7eb47b… lmata 100 }
f7eb47b… lmata 101 _isSubdirectory(parentDir, dir) {
f7eb47b… lmata 102 const relativePath = import_path.default.relative(parentDir, dir);
f7eb47b… lmata 103 return !!relativePath && !relativePath.startsWith("..") && !import_path.default.isAbsolute(relativePath);
f7eb47b… lmata 104 }
f7eb47b… lmata 105 onError(error) {
f7eb47b… lmata 106 this._topLevelErrors.push(error);
f7eb47b… lmata 107 }
f7eb47b… lmata 108 onMachineEnd(result) {
f7eb47b… lmata 109 this._machines.push(result);
f7eb47b… lmata 110 }
f7eb47b… lmata 111 async onEnd(result) {
f7eb47b… lmata 112 const projectSuites = this.suite.suites;
f7eb47b… lmata 113 await (0, import_utils.removeFolders)([this._outputFolder]);
f7eb47b… lmata 114 let noSnippets;
f7eb47b… lmata 115 if (process.env.PLAYWRIGHT_HTML_NO_SNIPPETS === "false" || process.env.PLAYWRIGHT_HTML_NO_SNIPPETS === "0")
f7eb47b… lmata 116 noSnippets = false;
f7eb47b… lmata 117 else if (process.env.PLAYWRIGHT_HTML_NO_SNIPPETS)
f7eb47b… lmata 118 noSnippets = true;
f7eb47b… lmata 119 noSnippets = noSnippets || this._options.noSnippets;
f7eb47b… lmata 120 let noCopyPrompt;
f7eb47b… lmata 121 if (process.env.PLAYWRIGHT_HTML_NO_COPY_PROMPT === "false" || process.env.PLAYWRIGHT_HTML_NO_COPY_PROMPT === "0")
f7eb47b… lmata 122 noCopyPrompt = false;
f7eb47b… lmata 123 else if (process.env.PLAYWRIGHT_HTML_NO_COPY_PROMPT)
f7eb47b… lmata 124 noCopyPrompt = true;
f7eb47b… lmata 125 noCopyPrompt = noCopyPrompt || this._options.noCopyPrompt;
f7eb47b… lmata 126 const builder = new HtmlBuilder(this.config, this._outputFolder, this._attachmentsBaseURL, {
f7eb47b… lmata 127 title: process.env.PLAYWRIGHT_HTML_TITLE || this._options.title,
f7eb47b… lmata 128 noSnippets,
f7eb47b… lmata 129 noCopyPrompt
f7eb47b… lmata 130 });
f7eb47b… lmata 131 this._buildResult = await builder.build(this.config.metadata, projectSuites, result, this._topLevelErrors, this._machines);
f7eb47b… lmata 132 }
f7eb47b… lmata 133 async onExit() {
f7eb47b… lmata 134 if (process.env.CI || !this._buildResult)
f7eb47b… lmata 135 return;
f7eb47b… lmata 136 const { ok, singleTestId } = this._buildResult;
f7eb47b… lmata 137 const shouldOpen = !!process.stdin.isTTY && (this._open === "always" || !ok && this._open === "on-failure");
f7eb47b… lmata 138 if (shouldOpen) {
f7eb47b… lmata 139 await showHTMLReport(this._outputFolder, this._host, this._port, singleTestId);
f7eb47b… lmata 140 } else if (this._options._mode === "test" && !!process.stdin.isTTY) {
f7eb47b… lmata 141 const packageManagerCommand = (0, import_utils.getPackageManagerExecCommand)();
f7eb47b… lmata 142 const relativeReportPath = this._outputFolder === standaloneDefaultFolder() ? "" : " " + import_path.default.relative(process.cwd(), this._outputFolder);
f7eb47b… lmata 143 const hostArg = this._host ? ` --host ${this._host}` : "";
f7eb47b… lmata 144 const portArg = this._port ? ` --port ${this._port}` : "";
f7eb47b… lmata 145 writeLine("");
f7eb47b… lmata 146 writeLine("To open last HTML report run:");
f7eb47b… lmata 147 writeLine(import_utils2.colors.cyan(`
f7eb47b… lmata 148 ${packageManagerCommand} playwright show-report${relativeReportPath}${hostArg}${portArg}
f7eb47b… lmata 149 `));
f7eb47b… lmata 150 }
f7eb47b… lmata 151 }
f7eb47b… lmata 152 }
f7eb47b… lmata 153 function reportFolderFromEnv() {
f7eb47b… lmata 154 const envValue = process.env.PLAYWRIGHT_HTML_OUTPUT_DIR || process.env.PLAYWRIGHT_HTML_REPORT;
f7eb47b… lmata 155 return envValue ? import_path.default.resolve(envValue) : void 0;
f7eb47b… lmata 156 }
f7eb47b… lmata 157 function getHtmlReportOptionProcessEnv() {
f7eb47b… lmata 158 const htmlOpenEnv = process.env.PLAYWRIGHT_HTML_OPEN || process.env.PW_TEST_HTML_REPORT_OPEN;
f7eb47b… lmata 159 if (!htmlOpenEnv)
f7eb47b… lmata 160 return void 0;
f7eb47b… lmata 161 if (!isHtmlReportOption(htmlOpenEnv)) {
f7eb47b… lmata 162 writeLine(import_utils2.colors.red(`Configuration Error: HTML reporter Invalid value for PLAYWRIGHT_HTML_OPEN: ${htmlOpenEnv}. Valid values are: ${htmlReportOptions.join(", ")}`));
f7eb47b… lmata 163 return void 0;
f7eb47b… lmata 164 }
f7eb47b… lmata 165 return htmlOpenEnv;
f7eb47b… lmata 166 }
f7eb47b… lmata 167 function standaloneDefaultFolder() {
f7eb47b… lmata 168 return reportFolderFromEnv() ?? (0, import_util.resolveReporterOutputPath)("playwright-report", process.cwd(), void 0);
f7eb47b… lmata 169 }
f7eb47b… lmata 170 async function showHTMLReport(reportFolder, host = "localhost", port, testId) {
f7eb47b… lmata 171 const folder = reportFolder ?? standaloneDefaultFolder();
f7eb47b… lmata 172 try {
f7eb47b… lmata 173 (0, import_utils.assert)(import_fs.default.statSync(folder).isDirectory());
f7eb47b… lmata 174 } catch (e) {
f7eb47b… lmata 175 writeLine(import_utils2.colors.red(`No report found at "${folder}"`));
f7eb47b… lmata 176 (0, import_utils.gracefullyProcessExitDoNotHang)(1);
f7eb47b… lmata 177 return;
f7eb47b… lmata 178 }
f7eb47b… lmata 179 const server = startHtmlReportServer(folder);
f7eb47b… lmata 180 await server.start({ port, host, preferredPort: port ? void 0 : 9323 });
f7eb47b… lmata 181 let url = server.urlPrefix("human-readable");
f7eb47b… lmata 182 writeLine("");
f7eb47b… lmata 183 writeLine(import_utils2.colors.cyan(` Serving HTML report at ${url}. Press Ctrl+C to quit.`));
f7eb47b… lmata 184 if (testId)
f7eb47b… lmata 185 url += `#?testId=${testId}`;
f7eb47b… lmata 186 url = url.replace("0.0.0.0", "localhost");
f7eb47b… lmata 187 await (0, import_utilsBundle.open)(url, { wait: true }).catch(() => {
f7eb47b… lmata 188 });
f7eb47b… lmata 189 await new Promise(() => {
f7eb47b… lmata 190 });
f7eb47b… lmata 191 }
f7eb47b… lmata 192 function startHtmlReportServer(folder) {
f7eb47b… lmata 193 const server = new import_utils.HttpServer();
f7eb47b… lmata 194 server.routePrefix("/", (request, response) => {
f7eb47b… lmata 195 let relativePath = new URL("http://localhost" + request.url).pathname;
f7eb47b… lmata 196 if (relativePath.startsWith("/trace/file")) {
f7eb47b… lmata 197 const url = new URL("http://localhost" + request.url);
f7eb47b… lmata 198 try {
f7eb47b… lmata 199 return server.serveFile(request, response, url.searchParams.get("path"));
f7eb47b… lmata 200 } catch (e) {
f7eb47b… lmata 201 return false;
f7eb47b… lmata 202 }
f7eb47b… lmata 203 }
f7eb47b… lmata 204 if (relativePath === "/")
f7eb47b… lmata 205 relativePath = "/index.html";
f7eb47b… lmata 206 const absolutePath = import_path.default.join(folder, ...relativePath.split("/"));
f7eb47b… lmata 207 return server.serveFile(request, response, absolutePath);
f7eb47b… lmata 208 });
f7eb47b… lmata 209 return server;
f7eb47b… lmata 210 }
f7eb47b… lmata 211 class HtmlBuilder {
f7eb47b… lmata 212 constructor(config, outputDir, attachmentsBaseURL, options) {
f7eb47b… lmata 213 this._stepsInFile = new import_utils.MultiMap();
f7eb47b… lmata 214 this._hasTraces = false;
f7eb47b… lmata 215 this._config = config;
f7eb47b… lmata 216 this._reportFolder = outputDir;
f7eb47b… lmata 217 this._options = options;
f7eb47b… lmata 218 import_fs.default.mkdirSync(this._reportFolder, { recursive: true });
f7eb47b… lmata 219 this._dataZipFile = new import_zipBundle.yazl.ZipFile();
f7eb47b… lmata 220 this._attachmentsBaseURL = attachmentsBaseURL;
f7eb47b… lmata 221 }
f7eb47b… lmata 222 async build(metadata, projectSuites, result, topLevelErrors, machines) {
f7eb47b… lmata 223 const data = /* @__PURE__ */ new Map();
f7eb47b… lmata 224 for (const projectSuite of projectSuites) {
f7eb47b… lmata 225 const projectName = projectSuite.project().name;
f7eb47b… lmata 226 for (const fileSuite of projectSuite.suites) {
f7eb47b… lmata 227 const fileName = this._relativeLocation(fileSuite.location).file;
f7eb47b… lmata 228 this._createEntryForSuite(data, projectName, fileSuite, fileName, true);
f7eb47b… lmata 229 }
f7eb47b… lmata 230 }
f7eb47b… lmata 231 if (!this._options.noSnippets)
f7eb47b… lmata 232 createSnippets(this._stepsInFile);
f7eb47b… lmata 233 let ok = true;
f7eb47b… lmata 234 for (const [fileId, { testFile, testFileSummary }] of data) {
f7eb47b… lmata 235 const stats = testFileSummary.stats;
f7eb47b… lmata 236 for (const test of testFileSummary.tests) {
f7eb47b… lmata 237 if (test.outcome === "expected")
f7eb47b… lmata 238 ++stats.expected;
f7eb47b… lmata 239 if (test.outcome === "skipped")
f7eb47b… lmata 240 ++stats.skipped;
f7eb47b… lmata 241 if (test.outcome === "unexpected")
f7eb47b… lmata 242 ++stats.unexpected;
f7eb47b… lmata 243 if (test.outcome === "flaky")
f7eb47b… lmata 244 ++stats.flaky;
f7eb47b… lmata 245 ++stats.total;
f7eb47b… lmata 246 }
f7eb47b… lmata 247 stats.ok = stats.unexpected + stats.flaky === 0;
f7eb47b… lmata 248 if (!stats.ok)
f7eb47b… lmata 249 ok = false;
f7eb47b… lmata 250 const testCaseSummaryComparator = (t1, t2) => {
f7eb47b… lmata 251 const w1 = (t1.outcome === "unexpected" ? 1e3 : 0) + (t1.outcome === "flaky" ? 1 : 0);
f7eb47b… lmata 252 const w2 = (t2.outcome === "unexpected" ? 1e3 : 0) + (t2.outcome === "flaky" ? 1 : 0);
f7eb47b… lmata 253 return w2 - w1;
f7eb47b… lmata 254 };
f7eb47b… lmata 255 testFileSummary.tests.sort(testCaseSummaryComparator);
f7eb47b… lmata 256 this._addDataFile(fileId + ".json", testFile);
f7eb47b… lmata 257 }
f7eb47b… lmata 258 const htmlReport = {
f7eb47b… lmata 259 metadata,
f7eb47b… lmata 260 startTime: result.startTime.getTime(),
f7eb47b… lmata 261 duration: result.duration,
f7eb47b… lmata 262 files: [...data.values()].map((e) => e.testFileSummary),
f7eb47b… lmata 263 projectNames: projectSuites.map((r) => r.project().name),
f7eb47b… lmata 264 stats: { ...[...data.values()].reduce((a, e) => addStats(a, e.testFileSummary.stats), emptyStats()) },
f7eb47b… lmata 265 errors: topLevelErrors.map((error) => (0, import_base.formatError)(import_base.internalScreen, error).message),
f7eb47b… lmata 266 options: this._options,
f7eb47b… lmata 267 machines: machines.map((s) => ({
f7eb47b… lmata 268 duration: s.duration,
f7eb47b… lmata 269 startTime: s.startTime.getTime(),
f7eb47b… lmata 270 tag: s.tag,
f7eb47b… lmata 271 shardIndex: s.shardIndex
f7eb47b… lmata 272 }))
f7eb47b… lmata 273 };
f7eb47b… lmata 274 htmlReport.files.sort((f1, f2) => {
f7eb47b… lmata 275 const w1 = f1.stats.unexpected * 1e3 + f1.stats.flaky;
f7eb47b… lmata 276 const w2 = f2.stats.unexpected * 1e3 + f2.stats.flaky;
f7eb47b… lmata 277 return w2 - w1;
f7eb47b… lmata 278 });
f7eb47b… lmata 279 this._addDataFile("report.json", htmlReport);
f7eb47b… lmata 280 let singleTestId;
f7eb47b… lmata 281 if (htmlReport.stats.total === 1) {
f7eb47b… lmata 282 const testFile = data.values().next().value.testFile;
f7eb47b… lmata 283 singleTestId = testFile.tests[0].testId;
f7eb47b… lmata 284 }
f7eb47b… lmata 285 const appFolder = import_path.default.join(require.resolve("playwright-core"), "..", "lib", "vite", "htmlReport");
f7eb47b… lmata 286 await (0, import_utils.copyFileAndMakeWritable)(import_path.default.join(appFolder, "index.html"), import_path.default.join(this._reportFolder, "index.html"));
f7eb47b… lmata 287 if (this._hasTraces) {
f7eb47b… lmata 288 const traceViewerFolder = import_path.default.join(require.resolve("playwright-core"), "..", "lib", "vite", "traceViewer");
f7eb47b… lmata 289 const traceViewerTargetFolder = import_path.default.join(this._reportFolder, "trace");
f7eb47b… lmata 290 const traceViewerAssetsTargetFolder = import_path.default.join(traceViewerTargetFolder, "assets");
f7eb47b… lmata 291 import_fs.default.mkdirSync(traceViewerAssetsTargetFolder, { recursive: true });
f7eb47b… lmata 292 for (const file of import_fs.default.readdirSync(traceViewerFolder)) {
f7eb47b… lmata 293 if (file.endsWith(".map") || file.includes("watch") || file.includes("assets"))
f7eb47b… lmata 294 continue;
f7eb47b… lmata 295 await (0, import_utils.copyFileAndMakeWritable)(import_path.default.join(traceViewerFolder, file), import_path.default.join(traceViewerTargetFolder, file));
f7eb47b… lmata 296 }
f7eb47b… lmata 297 for (const file of import_fs.default.readdirSync(import_path.default.join(traceViewerFolder, "assets"))) {
f7eb47b… lmata 298 if (file.endsWith(".map") || file.includes("xtermModule"))
f7eb47b… lmata 299 continue;
f7eb47b… lmata 300 await (0, import_utils.copyFileAndMakeWritable)(import_path.default.join(traceViewerFolder, "assets", file), import_path.default.join(traceViewerAssetsTargetFolder, file));
f7eb47b… lmata 301 }
f7eb47b… lmata 302 }
f7eb47b… lmata 303 await this._writeReportData(import_path.default.join(this._reportFolder, "index.html"));
f7eb47b… lmata 304 return { ok, singleTestId };
f7eb47b… lmata 305 }
f7eb47b… lmata 306 async _writeReportData(filePath) {
f7eb47b… lmata 307 import_fs.default.appendFileSync(filePath, '<script id="playwrightReportBase64" type="application/zip">data:application/zip;base64,');
f7eb47b… lmata 308 await new Promise((f) => {
f7eb47b… lmata 309 this._dataZipFile.end(void 0, () => {
f7eb47b… lmata 310 this._dataZipFile.outputStream.pipe(new Base64Encoder()).pipe(import_fs.default.createWriteStream(filePath, { flags: "a" })).on("close", f);
f7eb47b… lmata 311 });
f7eb47b… lmata 312 });
f7eb47b… lmata 313 import_fs.default.appendFileSync(filePath, "</script>");
f7eb47b… lmata 314 }
f7eb47b… lmata 315 _addDataFile(fileName, data) {
f7eb47b… lmata 316 this._dataZipFile.addBuffer(Buffer.from(JSON.stringify(data)), fileName);
f7eb47b… lmata 317 }
f7eb47b… lmata 318 _createEntryForSuite(data, projectName, suite, fileName, deep) {
f7eb47b… lmata 319 const fileId = (0, import_utils.calculateSha1)(fileName).slice(0, 20);
f7eb47b… lmata 320 let fileEntry = data.get(fileId);
f7eb47b… lmata 321 if (!fileEntry) {
f7eb47b… lmata 322 fileEntry = {
f7eb47b… lmata 323 testFile: { fileId, fileName, tests: [] },
f7eb47b… lmata 324 testFileSummary: { fileId, fileName, tests: [], stats: emptyStats() }
f7eb47b… lmata 325 };
f7eb47b… lmata 326 data.set(fileId, fileEntry);
f7eb47b… lmata 327 }
f7eb47b… lmata 328 const { testFile, testFileSummary } = fileEntry;
f7eb47b… lmata 329 const testEntries = [];
f7eb47b… lmata 330 this._processSuite(suite, projectName, [], deep, testEntries);
f7eb47b… lmata 331 for (const test of testEntries) {
f7eb47b… lmata 332 testFile.tests.push(test.testCase);
f7eb47b… lmata 333 testFileSummary.tests.push(test.testCaseSummary);
f7eb47b… lmata 334 }
f7eb47b… lmata 335 }
f7eb47b… lmata 336 _processSuite(suite, projectName, path2, deep, outTests) {
f7eb47b… lmata 337 const newPath = [...path2, suite.title];
f7eb47b… lmata 338 suite.entries().forEach((e) => {
f7eb47b… lmata 339 if (e.type === "test")
f7eb47b… lmata 340 outTests.push(this._createTestEntry(e, projectName, newPath));
f7eb47b… lmata 341 else if (deep)
f7eb47b… lmata 342 this._processSuite(e, projectName, newPath, deep, outTests);
f7eb47b… lmata 343 });
f7eb47b… lmata 344 }
f7eb47b… lmata 345 _createTestEntry(test, projectName, path2) {
f7eb47b… lmata 346 const duration = test.results.reduce((a, r) => a + r.duration, 0);
f7eb47b… lmata 347 const location = this._relativeLocation(test.location);
f7eb47b… lmata 348 path2 = path2.slice(1).filter((path3) => path3.length > 0);
f7eb47b… lmata 349 const results = test.results.map((r) => this._createTestResult(test, r));
f7eb47b… lmata 350 return {
f7eb47b… lmata 351 testCase: {
f7eb47b… lmata 352 testId: test.id,
f7eb47b… lmata 353 title: test.title,
f7eb47b… lmata 354 projectName,
f7eb47b… lmata 355 location,
f7eb47b… lmata 356 duration,
f7eb47b… lmata 357 annotations: this._serializeAnnotations(test.annotations),
f7eb47b… lmata 358 tags: test.tags,
f7eb47b… lmata 359 outcome: test.outcome(),
f7eb47b… lmata 360 path: path2,
f7eb47b… lmata 361 results,
f7eb47b… lmata 362 ok: test.outcome() === "expected" || test.outcome() === "flaky"
f7eb47b… lmata 363 },
f7eb47b… lmata 364 testCaseSummary: {
f7eb47b… lmata 365 testId: test.id,
f7eb47b… lmata 366 title: test.title,
f7eb47b… lmata 367 projectName,
f7eb47b… lmata 368 location,
f7eb47b… lmata 369 duration,
f7eb47b… lmata 370 annotations: this._serializeAnnotations(test.annotations),
f7eb47b… lmata 371 tags: test.tags,
f7eb47b… lmata 372 outcome: test.outcome(),
f7eb47b… lmata 373 path: path2,
f7eb47b… lmata 374 ok: test.outcome() === "expected" || test.outcome() === "flaky",
f7eb47b… lmata 375 results: results.map((result) => {
f7eb47b… lmata 376 return { attachments: result.attachments.map((a) => ({ name: a.name, contentType: a.contentType, path: a.path })) };
f7eb47b… lmata 377 })
f7eb47b… lmata 378 }
f7eb47b… lmata 379 };
f7eb47b… lmata 380 }
f7eb47b… lmata 381 _serializeAttachments(attachments) {
f7eb47b… lmata 382 let lastAttachment;
f7eb47b… lmata 383 return attachments.map((a) => {
f7eb47b… lmata 384 if (a.name === "trace")
f7eb47b… lmata 385 this._hasTraces = true;
f7eb47b… lmata 386 if ((a.name === "stdout" || a.name === "stderr") && a.contentType === "text/plain") {
f7eb47b… lmata 387 if (lastAttachment && lastAttachment.name === a.name && lastAttachment.contentType === a.contentType) {
f7eb47b… lmata 388 lastAttachment.body += (0, import_util.stripAnsiEscapes)(a.body);
f7eb47b… lmata 389 return null;
f7eb47b… lmata 390 }
f7eb47b… lmata 391 a.body = (0, import_util.stripAnsiEscapes)(a.body);
f7eb47b… lmata 392 lastAttachment = a;
f7eb47b… lmata 393 return a;
f7eb47b… lmata 394 }
f7eb47b… lmata 395 if (a.path) {
f7eb47b… lmata 396 let fileName = a.path;
f7eb47b… lmata 397 try {
f7eb47b… lmata 398 const buffer = import_fs.default.readFileSync(a.path);
f7eb47b… lmata 399 const sha1 = (0, import_utils.calculateSha1)(buffer) + import_path.default.extname(a.path);
f7eb47b… lmata 400 fileName = this._attachmentsBaseURL + sha1;
f7eb47b… lmata 401 import_fs.default.mkdirSync(import_path.default.join(this._reportFolder, "data"), { recursive: true });
f7eb47b… lmata 402 import_fs.default.writeFileSync(import_path.default.join(this._reportFolder, "data", sha1), buffer);
f7eb47b… lmata 403 } catch (e) {
f7eb47b… lmata 404 }
f7eb47b… lmata 405 return {
f7eb47b… lmata 406 name: a.name,
f7eb47b… lmata 407 contentType: a.contentType,
f7eb47b… lmata 408 path: fileName,
f7eb47b… lmata 409 body: a.body
f7eb47b… lmata 410 };
f7eb47b… lmata 411 }
f7eb47b… lmata 412 if (a.body instanceof Buffer) {
f7eb47b… lmata 413 if (isTextContentType(a.contentType)) {
f7eb47b… lmata 414 const charset = a.contentType.match(/charset=(.*)/)?.[1];
f7eb47b… lmata 415 try {
f7eb47b… lmata 416 const body = a.body.toString(charset || "utf-8");
f7eb47b… lmata 417 return {
f7eb47b… lmata 418 name: a.name,
f7eb47b… lmata 419 contentType: a.contentType,
f7eb47b… lmata 420 body
f7eb47b… lmata 421 };
f7eb47b… lmata 422 } catch (e) {
f7eb47b… lmata 423 }
f7eb47b… lmata 424 }
f7eb47b… lmata 425 import_fs.default.mkdirSync(import_path.default.join(this._reportFolder, "data"), { recursive: true });
f7eb47b… lmata 426 const extension = (0, import_utils.sanitizeForFilePath)(import_path.default.extname(a.name).replace(/^\./, "")) || import_utilsBundle2.mime.getExtension(a.contentType) || "dat";
f7eb47b… lmata 427 const sha1 = (0, import_utils.calculateSha1)(a.body) + "." + extension;
f7eb47b… lmata 428 import_fs.default.writeFileSync(import_path.default.join(this._reportFolder, "data", sha1), a.body);
f7eb47b… lmata 429 return {
f7eb47b… lmata 430 name: a.name,
f7eb47b… lmata 431 contentType: a.contentType,
f7eb47b… lmata 432 path: this._attachmentsBaseURL + sha1
f7eb47b… lmata 433 };
f7eb47b… lmata 434 }
f7eb47b… lmata 435 return {
f7eb47b… lmata 436 name: a.name,
f7eb47b… lmata 437 contentType: a.contentType,
f7eb47b… lmata 438 body: a.body
f7eb47b… lmata 439 };
f7eb47b… lmata 440 }).filter(Boolean);
f7eb47b… lmata 441 }
f7eb47b… lmata 442 _serializeAnnotations(annotations) {
f7eb47b… lmata 443 return annotations.map((a) => ({
f7eb47b… lmata 444 type: a.type,
f7eb47b… lmata 445 description: a.description === void 0 ? void 0 : String(a.description),
f7eb47b… lmata 446 location: a.location ? {
f7eb47b… lmata 447 file: a.location.file,
f7eb47b… lmata 448 line: a.location.line,
f7eb47b… lmata 449 column: a.location.column
f7eb47b… lmata 450 } : void 0
f7eb47b… lmata 451 }));
f7eb47b… lmata 452 }
f7eb47b… lmata 453 _createTestResult(test, result) {
f7eb47b… lmata 454 return {
f7eb47b… lmata 455 duration: result.duration,
f7eb47b… lmata 456 startTime: result.startTime.toISOString(),
f7eb47b… lmata 457 retry: result.retry,
f7eb47b… lmata 458 steps: dedupeSteps(result.steps).map((s) => this._createTestStep(s, result)),
f7eb47b… lmata 459 errors: (0, import_base.formatResultFailure)(import_base.internalScreen, test, result, "").map((error) => {
f7eb47b… lmata 460 return {
f7eb47b… lmata 461 message: error.message,
f7eb47b… lmata 462 codeframe: error.location ? createErrorCodeframe(error.message, error.location) : void 0
f7eb47b… lmata 463 };
f7eb47b… lmata 464 }),
f7eb47b… lmata 465 status: result.status,
f7eb47b… lmata 466 annotations: this._serializeAnnotations(result.annotations),
f7eb47b… lmata 467 attachments: this._serializeAttachments([
f7eb47b… lmata 468 ...result.attachments,
f7eb47b… lmata 469 ...result.stdout.map((m) => stdioAttachment(m, "stdout")),
f7eb47b… lmata 470 ...result.stderr.map((m) => stdioAttachment(m, "stderr"))
f7eb47b… lmata 471 ])
f7eb47b… lmata 472 };
f7eb47b… lmata 473 }
f7eb47b… lmata 474 _createTestStep(dedupedStep, result) {
f7eb47b… lmata 475 const { step, duration, count } = dedupedStep;
f7eb47b… lmata 476 const skipped = dedupedStep.step.annotations?.find((a) => a.type === "skip");
f7eb47b… lmata 477 let title = step.title;
f7eb47b… lmata 478 if (skipped)
f7eb47b… lmata 479 title = `${title} (skipped${skipped.description ? ": " + skipped.description : ""})`;
f7eb47b… lmata 480 const testStep = {
f7eb47b… lmata 481 title,
f7eb47b… lmata 482 startTime: step.startTime.toISOString(),
f7eb47b… lmata 483 duration,
f7eb47b… lmata 484 steps: dedupeSteps(step.steps).map((s) => this._createTestStep(s, result)),
f7eb47b… lmata 485 attachments: step.attachments.map((s) => {
f7eb47b… lmata 486 const index = result.attachments.indexOf(s);
f7eb47b… lmata 487 if (index === -1)
f7eb47b… lmata 488 throw new Error("Unexpected, attachment not found");
f7eb47b… lmata 489 return index;
f7eb47b… lmata 490 }),
f7eb47b… lmata 491 location: this._relativeLocation(step.location),
f7eb47b… lmata 492 error: step.error?.message,
f7eb47b… lmata 493 count,
f7eb47b… lmata 494 skipped: !!skipped
f7eb47b… lmata 495 };
f7eb47b… lmata 496 if (step.location)
f7eb47b… lmata 497 this._stepsInFile.set(step.location.file, testStep);
f7eb47b… lmata 498 return testStep;
f7eb47b… lmata 499 }
f7eb47b… lmata 500 _relativeLocation(location) {
f7eb47b… lmata 501 if (!location)
f7eb47b… lmata 502 return void 0;
f7eb47b… lmata 503 const file = (0, import_utils.toPosixPath)(import_path.default.relative(this._config.rootDir, location.file));
f7eb47b… lmata 504 return {
f7eb47b… lmata 505 file,
f7eb47b… lmata 506 line: location.line,
f7eb47b… lmata 507 column: location.column
f7eb47b… lmata 508 };
f7eb47b… lmata 509 }
f7eb47b… lmata 510 }
f7eb47b… lmata 511 const emptyStats = () => {
f7eb47b… lmata 512 return {
f7eb47b… lmata 513 total: 0,
f7eb47b… lmata 514 expected: 0,
f7eb47b… lmata 515 unexpected: 0,
f7eb47b… lmata 516 flaky: 0,
f7eb47b… lmata 517 skipped: 0,
f7eb47b… lmata 518 ok: true
f7eb47b… lmata 519 };
f7eb47b… lmata 520 };
f7eb47b… lmata 521 const addStats = (stats, delta) => {
f7eb47b… lmata 522 stats.total += delta.total;
f7eb47b… lmata 523 stats.skipped += delta.skipped;
f7eb47b… lmata 524 stats.expected += delta.expected;
f7eb47b… lmata 525 stats.unexpected += delta.unexpected;
f7eb47b… lmata 526 stats.flaky += delta.flaky;
f7eb47b… lmata 527 stats.ok = stats.ok && delta.ok;
f7eb47b… lmata 528 return stats;
f7eb47b… lmata 529 };
f7eb47b… lmata 530 class Base64Encoder extends import_stream.Transform {
f7eb47b… lmata 531 _transform(chunk, encoding, callback) {
f7eb47b… lmata 532 if (this._remainder) {
f7eb47b… lmata 533 chunk = Buffer.concat([this._remainder, chunk]);
f7eb47b… lmata 534 this._remainder = void 0;
f7eb47b… lmata 535 }
f7eb47b… lmata 536 const remaining = chunk.length % 3;
f7eb47b… lmata 537 if (remaining) {
f7eb47b… lmata 538 this._remainder = chunk.slice(chunk.length - remaining);
f7eb47b… lmata 539 chunk = chunk.slice(0, chunk.length - remaining);
f7eb47b… lmata 540 }
f7eb47b… lmata 541 chunk = chunk.toString("base64");
f7eb47b… lmata 542 this.push(Buffer.from(chunk));
f7eb47b… lmata 543 callback();
f7eb47b… lmata 544 }
f7eb47b… lmata 545 _flush(callback) {
f7eb47b… lmata 546 if (this._remainder)
f7eb47b… lmata 547 this.push(Buffer.from(this._remainder.toString("base64")));
f7eb47b… lmata 548 callback();
f7eb47b… lmata 549 }
f7eb47b… lmata 550 }
f7eb47b… lmata 551 function isTextContentType(contentType) {
f7eb47b… lmata 552 return contentType.startsWith("text/") || contentType.startsWith("application/json");
f7eb47b… lmata 553 }
f7eb47b… lmata 554 function stdioAttachment(chunk, type) {
f7eb47b… lmata 555 return {
f7eb47b… lmata 556 name: type,
f7eb47b… lmata 557 contentType: "text/plain",
f7eb47b… lmata 558 body: typeof chunk === "string" ? chunk : chunk.toString("utf-8")
f7eb47b… lmata 559 };
f7eb47b… lmata 560 }
f7eb47b… lmata 561 function dedupeSteps(steps) {
f7eb47b… lmata 562 const result = [];
f7eb47b… lmata 563 let lastResult = void 0;
f7eb47b… lmata 564 for (const step of steps) {
f7eb47b… lmata 565 const canDedupe = !step.error && step.duration >= 0 && step.location?.file && !step.steps.length;
f7eb47b… lmata 566 const lastStep = lastResult?.step;
f7eb47b… lmata 567 if (canDedupe && lastResult && lastStep && step.category === lastStep.category && step.title === lastStep.title && step.location?.file === lastStep.location?.file && step.location?.line === lastStep.location?.line && step.location?.column === lastStep.location?.column) {
f7eb47b… lmata 568 ++lastResult.count;
f7eb47b… lmata 569 lastResult.duration += step.duration;
f7eb47b… lmata 570 continue;
f7eb47b… lmata 571 }
f7eb47b… lmata 572 lastResult = { step, count: 1, duration: step.duration };
f7eb47b… lmata 573 result.push(lastResult);
f7eb47b… lmata 574 if (!canDedupe)
f7eb47b… lmata 575 lastResult = void 0;
f7eb47b… lmata 576 }
f7eb47b… lmata 577 return result;
f7eb47b… lmata 578 }
f7eb47b… lmata 579 function createSnippets(stepsInFile) {
f7eb47b… lmata 580 for (const file of stepsInFile.keys()) {
f7eb47b… lmata 581 let source;
f7eb47b… lmata 582 try {
f7eb47b… lmata 583 source = import_fs.default.readFileSync(file, "utf-8") + "\n//";
f7eb47b… lmata 584 } catch (e) {
f7eb47b… lmata 585 continue;
f7eb47b… lmata 586 }
f7eb47b… lmata 587 const lines = source.split("\n").length;
f7eb47b… lmata 588 const highlighted = (0, import_babelBundle.codeFrameColumns)(source, { start: { line: lines, column: 1 } }, { highlightCode: true, linesAbove: lines, linesBelow: 0 });
f7eb47b… lmata 589 const highlightedLines = highlighted.split("\n");
f7eb47b… lmata 590 const lineWithArrow = highlightedLines[highlightedLines.length - 1];
f7eb47b… lmata 591 for (const step of stepsInFile.get(file)) {
f7eb47b… lmata 592 if (step.location.line < 2 || step.location.line >= lines)
f7eb47b… lmata 593 continue;
f7eb47b… lmata 594 const snippetLines = highlightedLines.slice(step.location.line - 2, step.location.line + 1);
f7eb47b… lmata 595 const index = lineWithArrow.indexOf("^");
f7eb47b… lmata 596 const shiftedArrow = lineWithArrow.slice(0, index) + " ".repeat(step.location.column - 1) + lineWithArrow.slice(index);
f7eb47b… lmata 597 snippetLines.splice(2, 0, shiftedArrow);
f7eb47b… lmata 598 step.snippet = snippetLines.join("\n");
f7eb47b… lmata 599 }
f7eb47b… lmata 600 }
f7eb47b… lmata 601 }
f7eb47b… lmata 602 function createErrorCodeframe(message, location) {
f7eb47b… lmata 603 let source;
f7eb47b… lmata 604 try {
f7eb47b… lmata 605 source = import_fs.default.readFileSync(location.file, "utf-8") + "\n//";
f7eb47b… lmata 606 } catch (e) {
f7eb47b… lmata 607 return;
f7eb47b… lmata 608 }
f7eb47b… lmata 609 return (0, import_babelBundle.codeFrameColumns)(
f7eb47b… lmata 610 source,
f7eb47b… lmata 611 {
f7eb47b… lmata 612 start: {
f7eb47b… lmata 613 line: location.line,
f7eb47b… lmata 614 column: location.column
f7eb47b… lmata 615 }
f7eb47b… lmata 616 },
f7eb47b… lmata 617 {
f7eb47b… lmata 618 highlightCode: false,
f7eb47b… lmata 619 linesAbove: 100,
f7eb47b… lmata 620 linesBelow: 100,
f7eb47b… lmata 621 message: (0, import_util.stripAnsiEscapes)(message).split("\n")[0] || void 0
f7eb47b… lmata 622 }
f7eb47b… lmata 623 );
f7eb47b… lmata 624 }
f7eb47b… lmata 625 function writeLine(line) {
f7eb47b… lmata 626 process.stdout.write(line + "\n");
f7eb47b… lmata 627 }
f7eb47b… lmata 628 var html_default = HtmlReporter;
f7eb47b… lmata 629 // Annotate the CommonJS export names for ESM import in node:
f7eb47b… lmata 630 0 && (module.exports = {
f7eb47b… lmata 631 showHTMLReport,
f7eb47b… lmata 632 startHtmlReportServer
f7eb47b… lmata 633 });

Keyboard Shortcuts

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