ScuttleBot

Source Blame History 634 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 base_exports = {};
f7eb47b… lmata 30 __export(base_exports, {
f7eb47b… lmata 31 TerminalReporter: () => TerminalReporter,
f7eb47b… lmata 32 fitToWidth: () => fitToWidth,
f7eb47b… lmata 33 formatError: () => formatError,
f7eb47b… lmata 34 formatFailure: () => formatFailure,
f7eb47b… lmata 35 formatResultFailure: () => formatResultFailure,
f7eb47b… lmata 36 formatRetry: () => formatRetry,
f7eb47b… lmata 37 internalScreen: () => internalScreen,
f7eb47b… lmata 38 kOutputSymbol: () => kOutputSymbol,
f7eb47b… lmata 39 markErrorsAsReported: () => markErrorsAsReported,
f7eb47b… lmata 40 nonTerminalScreen: () => nonTerminalScreen,
f7eb47b… lmata 41 prepareErrorStack: () => prepareErrorStack,
f7eb47b… lmata 42 relativeFilePath: () => relativeFilePath,
f7eb47b… lmata 43 resolveOutputFile: () => resolveOutputFile,
f7eb47b… lmata 44 separator: () => separator,
f7eb47b… lmata 45 stepSuffix: () => stepSuffix,
f7eb47b… lmata 46 terminalScreen: () => terminalScreen
f7eb47b… lmata 47 });
f7eb47b… lmata 48 module.exports = __toCommonJS(base_exports);
f7eb47b… lmata 49 var import_path = __toESM(require("path"));
f7eb47b… lmata 50 var import_utils = require("playwright-core/lib/utils");
f7eb47b… lmata 51 var import_utilsBundle = require("playwright-core/lib/utilsBundle");
f7eb47b… lmata 52 var import_utils2 = require("playwright-core/lib/utils");
f7eb47b… lmata 53 var import_util = require("../util");
f7eb47b… lmata 54 var import_utilsBundle2 = require("../utilsBundle");
f7eb47b… lmata 55 const kOutputSymbol = Symbol("output");
f7eb47b… lmata 56 const DEFAULT_TTY_WIDTH = 100;
f7eb47b… lmata 57 const DEFAULT_TTY_HEIGHT = 40;
f7eb47b… lmata 58 const originalProcessStdout = process.stdout;
f7eb47b… lmata 59 const originalProcessStderr = process.stderr;
f7eb47b… lmata 60 const terminalScreen = (() => {
f7eb47b… lmata 61 let isTTY = !!originalProcessStdout.isTTY;
f7eb47b… lmata 62 let ttyWidth = originalProcessStdout.columns || 0;
f7eb47b… lmata 63 let ttyHeight = originalProcessStdout.rows || 0;
f7eb47b… lmata 64 if (process.env.PLAYWRIGHT_FORCE_TTY === "false" || process.env.PLAYWRIGHT_FORCE_TTY === "0") {
f7eb47b… lmata 65 isTTY = false;
f7eb47b… lmata 66 ttyWidth = 0;
f7eb47b… lmata 67 ttyHeight = 0;
f7eb47b… lmata 68 } else if (process.env.PLAYWRIGHT_FORCE_TTY === "true" || process.env.PLAYWRIGHT_FORCE_TTY === "1") {
f7eb47b… lmata 69 isTTY = true;
f7eb47b… lmata 70 ttyWidth = originalProcessStdout.columns || DEFAULT_TTY_WIDTH;
f7eb47b… lmata 71 ttyHeight = originalProcessStdout.rows || DEFAULT_TTY_HEIGHT;
f7eb47b… lmata 72 } else if (process.env.PLAYWRIGHT_FORCE_TTY) {
f7eb47b… lmata 73 isTTY = true;
f7eb47b… lmata 74 const sizeMatch = process.env.PLAYWRIGHT_FORCE_TTY.match(/^(\d+)x(\d+)$/);
f7eb47b… lmata 75 if (sizeMatch) {
f7eb47b… lmata 76 ttyWidth = +sizeMatch[1];
f7eb47b… lmata 77 ttyHeight = +sizeMatch[2];
f7eb47b… lmata 78 } else {
f7eb47b… lmata 79 ttyWidth = +process.env.PLAYWRIGHT_FORCE_TTY;
f7eb47b… lmata 80 ttyHeight = DEFAULT_TTY_HEIGHT;
f7eb47b… lmata 81 }
f7eb47b… lmata 82 if (isNaN(ttyWidth))
f7eb47b… lmata 83 ttyWidth = DEFAULT_TTY_WIDTH;
f7eb47b… lmata 84 if (isNaN(ttyHeight))
f7eb47b… lmata 85 ttyHeight = DEFAULT_TTY_HEIGHT;
f7eb47b… lmata 86 }
f7eb47b… lmata 87 let useColors = isTTY;
f7eb47b… lmata 88 if (process.env.DEBUG_COLORS === "0" || process.env.DEBUG_COLORS === "false" || process.env.FORCE_COLOR === "0" || process.env.FORCE_COLOR === "false")
f7eb47b… lmata 89 useColors = false;
f7eb47b… lmata 90 else if (process.env.DEBUG_COLORS || process.env.FORCE_COLOR)
f7eb47b… lmata 91 useColors = true;
f7eb47b… lmata 92 const colors = useColors ? import_utils2.colors : import_utils2.noColors;
f7eb47b… lmata 93 return {
f7eb47b… lmata 94 resolveFiles: "cwd",
f7eb47b… lmata 95 isTTY,
f7eb47b… lmata 96 ttyWidth,
f7eb47b… lmata 97 ttyHeight,
f7eb47b… lmata 98 colors,
f7eb47b… lmata 99 stdout: originalProcessStdout,
f7eb47b… lmata 100 stderr: originalProcessStderr
f7eb47b… lmata 101 };
f7eb47b… lmata 102 })();
f7eb47b… lmata 103 const nonTerminalScreen = {
f7eb47b… lmata 104 colors: terminalScreen.colors,
f7eb47b… lmata 105 isTTY: false,
f7eb47b… lmata 106 ttyWidth: 0,
f7eb47b… lmata 107 ttyHeight: 0,
f7eb47b… lmata 108 resolveFiles: "rootDir"
f7eb47b… lmata 109 };
f7eb47b… lmata 110 const internalScreen = {
f7eb47b… lmata 111 colors: import_utils2.colors,
f7eb47b… lmata 112 isTTY: false,
f7eb47b… lmata 113 ttyWidth: 0,
f7eb47b… lmata 114 ttyHeight: 0,
f7eb47b… lmata 115 resolveFiles: "rootDir"
f7eb47b… lmata 116 };
f7eb47b… lmata 117 class TerminalReporter {
f7eb47b… lmata 118 constructor(options = {}) {
f7eb47b… lmata 119 this.totalTestCount = 0;
f7eb47b… lmata 120 this.fileDurations = /* @__PURE__ */ new Map();
f7eb47b… lmata 121 this._fatalErrors = [];
f7eb47b… lmata 122 this._failureCount = 0;
f7eb47b… lmata 123 this.screen = options.screen ?? terminalScreen;
f7eb47b… lmata 124 this._options = options;
f7eb47b… lmata 125 }
f7eb47b… lmata 126 version() {
f7eb47b… lmata 127 return "v2";
f7eb47b… lmata 128 }
f7eb47b… lmata 129 onConfigure(config) {
f7eb47b… lmata 130 this.config = config;
f7eb47b… lmata 131 }
f7eb47b… lmata 132 onBegin(suite) {
f7eb47b… lmata 133 this.suite = suite;
f7eb47b… lmata 134 this.totalTestCount = suite.allTests().length;
f7eb47b… lmata 135 }
f7eb47b… lmata 136 onStdOut(chunk, test, result) {
f7eb47b… lmata 137 this._appendOutput({ chunk, type: "stdout" }, result);
f7eb47b… lmata 138 }
f7eb47b… lmata 139 onStdErr(chunk, test, result) {
f7eb47b… lmata 140 this._appendOutput({ chunk, type: "stderr" }, result);
f7eb47b… lmata 141 }
f7eb47b… lmata 142 _appendOutput(output, result) {
f7eb47b… lmata 143 if (!result)
f7eb47b… lmata 144 return;
f7eb47b… lmata 145 result[kOutputSymbol] = result[kOutputSymbol] || [];
f7eb47b… lmata 146 result[kOutputSymbol].push(output);
f7eb47b… lmata 147 }
f7eb47b… lmata 148 onTestEnd(test, result) {
f7eb47b… lmata 149 if (result.status !== "skipped" && result.status !== test.expectedStatus)
f7eb47b… lmata 150 ++this._failureCount;
f7eb47b… lmata 151 const projectName = test.titlePath()[1];
f7eb47b… lmata 152 const relativePath = relativeTestPath(this.screen, this.config, test);
f7eb47b… lmata 153 const fileAndProject = (projectName ? `[${projectName}] \u203A ` : "") + relativePath;
f7eb47b… lmata 154 const entry = this.fileDurations.get(fileAndProject) || { duration: 0, workers: /* @__PURE__ */ new Set() };
f7eb47b… lmata 155 entry.duration += result.duration;
f7eb47b… lmata 156 entry.workers.add(result.workerIndex);
f7eb47b… lmata 157 this.fileDurations.set(fileAndProject, entry);
f7eb47b… lmata 158 }
f7eb47b… lmata 159 onError(error) {
f7eb47b… lmata 160 this._fatalErrors.push(error);
f7eb47b… lmata 161 }
f7eb47b… lmata 162 async onEnd(result) {
f7eb47b… lmata 163 this.result = result;
f7eb47b… lmata 164 }
f7eb47b… lmata 165 fitToScreen(line, prefix) {
f7eb47b… lmata 166 if (!this.screen.ttyWidth) {
f7eb47b… lmata 167 return line;
f7eb47b… lmata 168 }
f7eb47b… lmata 169 return fitToWidth(line, this.screen.ttyWidth, prefix);
f7eb47b… lmata 170 }
f7eb47b… lmata 171 generateStartingMessage() {
f7eb47b… lmata 172 const jobs = this.config.metadata.actualWorkers ?? this.config.workers;
f7eb47b… lmata 173 const shardDetails = this.config.shard ? `, shard ${this.config.shard.current} of ${this.config.shard.total}` : "";
f7eb47b… lmata 174 if (!this.totalTestCount)
f7eb47b… lmata 175 return "";
f7eb47b… lmata 176 return "\n" + this.screen.colors.dim("Running ") + this.totalTestCount + this.screen.colors.dim(` test${this.totalTestCount !== 1 ? "s" : ""} using `) + jobs + this.screen.colors.dim(` worker${jobs !== 1 ? "s" : ""}${shardDetails}`);
f7eb47b… lmata 177 }
f7eb47b… lmata 178 getSlowTests() {
f7eb47b… lmata 179 if (!this.config.reportSlowTests)
f7eb47b… lmata 180 return [];
f7eb47b… lmata 181 const fileDurations = [...this.fileDurations.entries()].filter(([key, value]) => value.workers.size === 1).map(([key, value]) => [key, value.duration]);
f7eb47b… lmata 182 fileDurations.sort((a, b) => b[1] - a[1]);
f7eb47b… lmata 183 const count = Math.min(fileDurations.length, this.config.reportSlowTests.max || Number.POSITIVE_INFINITY);
f7eb47b… lmata 184 const threshold = this.config.reportSlowTests.threshold;
f7eb47b… lmata 185 return fileDurations.filter(([, duration]) => duration > threshold).slice(0, count);
f7eb47b… lmata 186 }
f7eb47b… lmata 187 generateSummaryMessage({ didNotRun, skipped, expected, interrupted, unexpected, flaky, fatalErrors }) {
f7eb47b… lmata 188 const tokens = [];
f7eb47b… lmata 189 if (unexpected.length) {
f7eb47b… lmata 190 tokens.push(this.screen.colors.red(` ${unexpected.length} failed`));
f7eb47b… lmata 191 for (const test of unexpected)
f7eb47b… lmata 192 tokens.push(this.screen.colors.red(this.formatTestHeader(test, { indent: " " })));
f7eb47b… lmata 193 }
f7eb47b… lmata 194 if (interrupted.length) {
f7eb47b… lmata 195 tokens.push(this.screen.colors.yellow(` ${interrupted.length} interrupted`));
f7eb47b… lmata 196 for (const test of interrupted)
f7eb47b… lmata 197 tokens.push(this.screen.colors.yellow(this.formatTestHeader(test, { indent: " " })));
f7eb47b… lmata 198 }
f7eb47b… lmata 199 if (flaky.length) {
f7eb47b… lmata 200 tokens.push(this.screen.colors.yellow(` ${flaky.length} flaky`));
f7eb47b… lmata 201 for (const test of flaky)
f7eb47b… lmata 202 tokens.push(this.screen.colors.yellow(this.formatTestHeader(test, { indent: " " })));
f7eb47b… lmata 203 }
f7eb47b… lmata 204 if (skipped)
f7eb47b… lmata 205 tokens.push(this.screen.colors.yellow(` ${skipped} skipped`));
f7eb47b… lmata 206 if (didNotRun)
f7eb47b… lmata 207 tokens.push(this.screen.colors.yellow(` ${didNotRun} did not run`));
f7eb47b… lmata 208 if (expected)
f7eb47b… lmata 209 tokens.push(this.screen.colors.green(` ${expected} passed`) + this.screen.colors.dim(` (${(0, import_utilsBundle.ms)(this.result.duration)})`));
f7eb47b… lmata 210 if (fatalErrors.length && expected + unexpected.length + interrupted.length + flaky.length > 0)
f7eb47b… lmata 211 tokens.push(this.screen.colors.red(` ${fatalErrors.length === 1 ? "1 error was not a part of any test" : fatalErrors.length + " errors were not a part of any test"}, see above for details`));
f7eb47b… lmata 212 return tokens.join("\n");
f7eb47b… lmata 213 }
f7eb47b… lmata 214 generateSummary() {
f7eb47b… lmata 215 let didNotRun = 0;
f7eb47b… lmata 216 let skipped = 0;
f7eb47b… lmata 217 let expected = 0;
f7eb47b… lmata 218 const interrupted = [];
f7eb47b… lmata 219 const interruptedToPrint = [];
f7eb47b… lmata 220 const unexpected = [];
f7eb47b… lmata 221 const flaky = [];
f7eb47b… lmata 222 this.suite.allTests().forEach((test) => {
f7eb47b… lmata 223 switch (test.outcome()) {
f7eb47b… lmata 224 case "skipped": {
f7eb47b… lmata 225 if (test.results.some((result) => result.status === "interrupted")) {
f7eb47b… lmata 226 if (test.results.some((result) => !!result.error))
f7eb47b… lmata 227 interruptedToPrint.push(test);
f7eb47b… lmata 228 interrupted.push(test);
f7eb47b… lmata 229 } else if (!test.results.length || test.expectedStatus !== "skipped") {
f7eb47b… lmata 230 ++didNotRun;
f7eb47b… lmata 231 } else {
f7eb47b… lmata 232 ++skipped;
f7eb47b… lmata 233 }
f7eb47b… lmata 234 break;
f7eb47b… lmata 235 }
f7eb47b… lmata 236 case "expected":
f7eb47b… lmata 237 ++expected;
f7eb47b… lmata 238 break;
f7eb47b… lmata 239 case "unexpected":
f7eb47b… lmata 240 unexpected.push(test);
f7eb47b… lmata 241 break;
f7eb47b… lmata 242 case "flaky":
f7eb47b… lmata 243 flaky.push(test);
f7eb47b… lmata 244 break;
f7eb47b… lmata 245 }
f7eb47b… lmata 246 });
f7eb47b… lmata 247 const failuresToPrint = [...unexpected, ...flaky, ...interruptedToPrint];
f7eb47b… lmata 248 return {
f7eb47b… lmata 249 didNotRun,
f7eb47b… lmata 250 skipped,
f7eb47b… lmata 251 expected,
f7eb47b… lmata 252 interrupted,
f7eb47b… lmata 253 unexpected,
f7eb47b… lmata 254 flaky,
f7eb47b… lmata 255 failuresToPrint,
f7eb47b… lmata 256 fatalErrors: this._fatalErrors
f7eb47b… lmata 257 };
f7eb47b… lmata 258 }
f7eb47b… lmata 259 epilogue(full) {
f7eb47b… lmata 260 const summary = this.generateSummary();
f7eb47b… lmata 261 const summaryMessage = this.generateSummaryMessage(summary);
f7eb47b… lmata 262 if (full && summary.failuresToPrint.length && !this._options.omitFailures)
f7eb47b… lmata 263 this._printFailures(summary.failuresToPrint);
f7eb47b… lmata 264 this._printSlowTests();
f7eb47b… lmata 265 this._printSummary(summaryMessage);
f7eb47b… lmata 266 }
f7eb47b… lmata 267 _printFailures(failures) {
f7eb47b… lmata 268 this.writeLine("");
f7eb47b… lmata 269 failures.forEach((test, index) => {
f7eb47b… lmata 270 this.writeLine(this.formatFailure(test, index + 1));
f7eb47b… lmata 271 });
f7eb47b… lmata 272 }
f7eb47b… lmata 273 _printSlowTests() {
f7eb47b… lmata 274 const slowTests = this.getSlowTests();
f7eb47b… lmata 275 slowTests.forEach(([file, duration]) => {
f7eb47b… lmata 276 this.writeLine(this.screen.colors.yellow(" Slow test file: ") + file + this.screen.colors.yellow(` (${(0, import_utilsBundle.ms)(duration)})`));
f7eb47b… lmata 277 });
f7eb47b… lmata 278 if (slowTests.length)
f7eb47b… lmata 279 this.writeLine(this.screen.colors.yellow(" Consider running tests from slow files in parallel. See: https://playwright.dev/docs/test-parallel"));
f7eb47b… lmata 280 }
f7eb47b… lmata 281 _printSummary(summary) {
f7eb47b… lmata 282 if (summary.trim())
f7eb47b… lmata 283 this.writeLine(summary);
f7eb47b… lmata 284 }
f7eb47b… lmata 285 willRetry(test) {
f7eb47b… lmata 286 return test.outcome() === "unexpected" && test.results.length <= test.retries;
f7eb47b… lmata 287 }
f7eb47b… lmata 288 formatTestTitle(test, step) {
f7eb47b… lmata 289 return formatTestTitle(this.screen, this.config, test, step, this._options);
f7eb47b… lmata 290 }
f7eb47b… lmata 291 formatTestHeader(test, options = {}) {
f7eb47b… lmata 292 return formatTestHeader(this.screen, this.config, test, { ...options, includeTestId: this._options.includeTestId });
f7eb47b… lmata 293 }
f7eb47b… lmata 294 formatFailure(test, index) {
f7eb47b… lmata 295 return formatFailure(this.screen, this.config, test, index, this._options);
f7eb47b… lmata 296 }
f7eb47b… lmata 297 formatError(error) {
f7eb47b… lmata 298 return formatError(this.screen, error);
f7eb47b… lmata 299 }
f7eb47b… lmata 300 formatResultErrors(test, result) {
f7eb47b… lmata 301 return formatResultErrors(this.screen, test, result);
f7eb47b… lmata 302 }
f7eb47b… lmata 303 writeLine(line) {
f7eb47b… lmata 304 this.screen.stdout?.write(line ? line + "\n" : "\n");
f7eb47b… lmata 305 }
f7eb47b… lmata 306 }
f7eb47b… lmata 307 function formatResultErrors(screen, test, result) {
f7eb47b… lmata 308 const lines = [];
f7eb47b… lmata 309 if (test.outcome() === "unexpected") {
f7eb47b… lmata 310 const errorDetails = formatResultFailure(screen, test, result, " ");
f7eb47b… lmata 311 if (errorDetails.length > 0)
f7eb47b… lmata 312 lines.push("");
f7eb47b… lmata 313 for (const error of errorDetails)
f7eb47b… lmata 314 lines.push(error.message, "");
f7eb47b… lmata 315 }
f7eb47b… lmata 316 return lines.join("\n");
f7eb47b… lmata 317 }
f7eb47b… lmata 318 function formatFailure(screen, config, test, index, options) {
f7eb47b… lmata 319 const lines = [];
f7eb47b… lmata 320 let printedHeader = false;
f7eb47b… lmata 321 for (const result of test.results) {
f7eb47b… lmata 322 const resultLines = [];
f7eb47b… lmata 323 const errors = formatResultFailure(screen, test, result, " ");
f7eb47b… lmata 324 if (!errors.length)
f7eb47b… lmata 325 continue;
f7eb47b… lmata 326 if (!printedHeader) {
f7eb47b… lmata 327 const header = formatTestHeader(screen, config, test, { indent: " ", index, mode: "error", includeTestId: options?.includeTestId });
f7eb47b… lmata 328 lines.push(screen.colors.red(header));
f7eb47b… lmata 329 printedHeader = true;
f7eb47b… lmata 330 }
f7eb47b… lmata 331 if (result.retry) {
f7eb47b… lmata 332 resultLines.push("");
f7eb47b… lmata 333 resultLines.push(screen.colors.gray(separator(screen, ` Retry #${result.retry}`)));
f7eb47b… lmata 334 }
f7eb47b… lmata 335 resultLines.push(...errors.map((error) => "\n" + error.message));
f7eb47b… lmata 336 const attachmentGroups = groupAttachments(result.attachments);
f7eb47b… lmata 337 for (let i = 0; i < attachmentGroups.length; ++i) {
f7eb47b… lmata 338 const attachment = attachmentGroups[i];
f7eb47b… lmata 339 if (attachment.name === "error-context" && attachment.path) {
f7eb47b… lmata 340 resultLines.push("");
f7eb47b… lmata 341 resultLines.push(screen.colors.dim(` Error Context: ${relativeFilePath(screen, config, attachment.path)}`));
f7eb47b… lmata 342 continue;
f7eb47b… lmata 343 }
f7eb47b… lmata 344 if (attachment.name.startsWith("_"))
f7eb47b… lmata 345 continue;
f7eb47b… lmata 346 const hasPrintableContent = attachment.contentType.startsWith("text/");
f7eb47b… lmata 347 if (!attachment.path && !hasPrintableContent)
f7eb47b… lmata 348 continue;
f7eb47b… lmata 349 resultLines.push("");
f7eb47b… lmata 350 resultLines.push(screen.colors.dim(separator(screen, ` attachment #${i + 1}: ${screen.colors.bold(attachment.name)} (${attachment.contentType})`)));
f7eb47b… lmata 351 if (attachment.actual?.path) {
f7eb47b… lmata 352 if (attachment.expected?.path) {
f7eb47b… lmata 353 const expectedPath = relativeFilePath(screen, config, attachment.expected.path);
f7eb47b… lmata 354 resultLines.push(screen.colors.dim(` Expected: ${expectedPath}`));
f7eb47b… lmata 355 }
f7eb47b… lmata 356 const actualPath = relativeFilePath(screen, config, attachment.actual.path);
f7eb47b… lmata 357 resultLines.push(screen.colors.dim(` Received: ${actualPath}`));
f7eb47b… lmata 358 if (attachment.previous?.path) {
f7eb47b… lmata 359 const previousPath = relativeFilePath(screen, config, attachment.previous.path);
f7eb47b… lmata 360 resultLines.push(screen.colors.dim(` Previous: ${previousPath}`));
f7eb47b… lmata 361 }
f7eb47b… lmata 362 if (attachment.diff?.path) {
f7eb47b… lmata 363 const diffPath = relativeFilePath(screen, config, attachment.diff.path);
f7eb47b… lmata 364 resultLines.push(screen.colors.dim(` Diff: ${diffPath}`));
f7eb47b… lmata 365 }
f7eb47b… lmata 366 } else if (attachment.path) {
f7eb47b… lmata 367 const relativePath = relativeFilePath(screen, config, attachment.path);
f7eb47b… lmata 368 resultLines.push(screen.colors.dim(` ${relativePath}`));
f7eb47b… lmata 369 if (attachment.name === "trace") {
f7eb47b… lmata 370 const packageManagerCommand = (0, import_utils.getPackageManagerExecCommand)();
f7eb47b… lmata 371 resultLines.push(screen.colors.dim(` Usage:`));
f7eb47b… lmata 372 resultLines.push("");
f7eb47b… lmata 373 resultLines.push(screen.colors.dim(` ${packageManagerCommand} playwright show-trace ${quotePathIfNeeded(relativePath)}`));
f7eb47b… lmata 374 resultLines.push("");
f7eb47b… lmata 375 }
f7eb47b… lmata 376 } else {
f7eb47b… lmata 377 if (attachment.contentType.startsWith("text/") && attachment.body) {
f7eb47b… lmata 378 let text = attachment.body.toString();
f7eb47b… lmata 379 if (text.length > 300)
f7eb47b… lmata 380 text = text.slice(0, 300) + "...";
f7eb47b… lmata 381 for (const line of text.split("\n"))
f7eb47b… lmata 382 resultLines.push(screen.colors.dim(` ${line}`));
f7eb47b… lmata 383 }
f7eb47b… lmata 384 }
f7eb47b… lmata 385 resultLines.push(screen.colors.dim(separator(screen, " ")));
f7eb47b… lmata 386 }
f7eb47b… lmata 387 lines.push(...resultLines);
f7eb47b… lmata 388 }
f7eb47b… lmata 389 lines.push("");
f7eb47b… lmata 390 return lines.join("\n");
f7eb47b… lmata 391 }
f7eb47b… lmata 392 function formatRetry(screen, result) {
f7eb47b… lmata 393 const retryLines = [];
f7eb47b… lmata 394 if (result.retry) {
f7eb47b… lmata 395 retryLines.push("");
f7eb47b… lmata 396 retryLines.push(screen.colors.gray(separator(screen, ` Retry #${result.retry}`)));
f7eb47b… lmata 397 }
f7eb47b… lmata 398 return retryLines;
f7eb47b… lmata 399 }
f7eb47b… lmata 400 function quotePathIfNeeded(path2) {
f7eb47b… lmata 401 if (/\s/.test(path2))
f7eb47b… lmata 402 return `"${path2}"`;
f7eb47b… lmata 403 return path2;
f7eb47b… lmata 404 }
f7eb47b… lmata 405 const kReportedSymbol = Symbol("reported");
f7eb47b… lmata 406 function markErrorsAsReported(result) {
f7eb47b… lmata 407 result[kReportedSymbol] = result.errors.length;
f7eb47b… lmata 408 }
f7eb47b… lmata 409 function formatResultFailure(screen, test, result, initialIndent) {
f7eb47b… lmata 410 const errorDetails = [];
f7eb47b… lmata 411 if (result.status === "passed" && test.expectedStatus === "failed") {
f7eb47b… lmata 412 errorDetails.push({
f7eb47b… lmata 413 message: indent(screen.colors.red(`Expected to fail, but passed.`), initialIndent)
f7eb47b… lmata 414 });
f7eb47b… lmata 415 }
f7eb47b… lmata 416 if (result.status === "interrupted") {
f7eb47b… lmata 417 errorDetails.push({
f7eb47b… lmata 418 message: indent(screen.colors.red(`Test was interrupted.`), initialIndent)
f7eb47b… lmata 419 });
f7eb47b… lmata 420 }
f7eb47b… lmata 421 const reportedIndex = result[kReportedSymbol] || 0;
f7eb47b… lmata 422 for (const error of result.errors.slice(reportedIndex)) {
f7eb47b… lmata 423 const formattedError = formatError(screen, error);
f7eb47b… lmata 424 errorDetails.push({
f7eb47b… lmata 425 message: indent(formattedError.message, initialIndent),
f7eb47b… lmata 426 location: formattedError.location
f7eb47b… lmata 427 });
f7eb47b… lmata 428 }
f7eb47b… lmata 429 return errorDetails;
f7eb47b… lmata 430 }
f7eb47b… lmata 431 function relativeFilePath(screen, config, file) {
f7eb47b… lmata 432 if (screen.resolveFiles === "cwd")
f7eb47b… lmata 433 return import_path.default.relative(process.cwd(), file);
f7eb47b… lmata 434 return import_path.default.relative(config.rootDir, file);
f7eb47b… lmata 435 }
f7eb47b… lmata 436 function relativeTestPath(screen, config, test) {
f7eb47b… lmata 437 return relativeFilePath(screen, config, test.location.file);
f7eb47b… lmata 438 }
f7eb47b… lmata 439 function stepSuffix(step) {
f7eb47b… lmata 440 const stepTitles = step ? step.titlePath() : [];
f7eb47b… lmata 441 return stepTitles.map((t) => t.split("\n")[0]).map((t) => " \u203A " + t).join("");
f7eb47b… lmata 442 }
f7eb47b… lmata 443 function formatTestTitle(screen, config, test, step, options = {}) {
f7eb47b… lmata 444 const [, projectName, , ...titles] = test.titlePath();
f7eb47b… lmata 445 const location = `${relativeTestPath(screen, config, test)}:${test.location.line}:${test.location.column}`;
f7eb47b… lmata 446 const testId = options.includeTestId ? `[id=${test.id}] ` : "";
f7eb47b… lmata 447 const projectLabel = options.includeTestId ? `project=` : "";
f7eb47b… lmata 448 const projectTitle = projectName ? `[${projectLabel}${projectName}] \u203A ` : "";
f7eb47b… lmata 449 const testTitle = `${testId}${projectTitle}${location} \u203A ${titles.join(" \u203A ")}`;
f7eb47b… lmata 450 const extraTags = test.tags.filter((t) => !testTitle.includes(t) && !config.tags.includes(t));
f7eb47b… lmata 451 return `${testTitle}${stepSuffix(step)}${extraTags.length ? " " + extraTags.join(" ") : ""}`;
f7eb47b… lmata 452 }
f7eb47b… lmata 453 function formatTestHeader(screen, config, test, options = {}) {
f7eb47b… lmata 454 const title = formatTestTitle(screen, config, test, void 0, options);
f7eb47b… lmata 455 const header = `${options.indent || ""}${options.index ? options.index + ") " : ""}${title}`;
f7eb47b… lmata 456 let fullHeader = header;
f7eb47b… lmata 457 if (options.mode === "error") {
f7eb47b… lmata 458 const stepPaths = /* @__PURE__ */ new Set();
f7eb47b… lmata 459 for (const result of test.results.filter((r) => !!r.errors.length)) {
f7eb47b… lmata 460 const stepPath = [];
f7eb47b… lmata 461 const visit = (steps) => {
f7eb47b… lmata 462 const errors = steps.filter((s) => s.error);
f7eb47b… lmata 463 if (errors.length > 1)
f7eb47b… lmata 464 return;
f7eb47b… lmata 465 if (errors.length === 1 && errors[0].category === "test.step") {
f7eb47b… lmata 466 stepPath.push(errors[0].title);
f7eb47b… lmata 467 visit(errors[0].steps);
f7eb47b… lmata 468 }
f7eb47b… lmata 469 };
f7eb47b… lmata 470 visit(result.steps);
f7eb47b… lmata 471 stepPaths.add(["", ...stepPath].join(" \u203A "));
f7eb47b… lmata 472 }
f7eb47b… lmata 473 fullHeader = header + (stepPaths.size === 1 ? stepPaths.values().next().value : "");
f7eb47b… lmata 474 }
f7eb47b… lmata 475 return separator(screen, fullHeader);
f7eb47b… lmata 476 }
f7eb47b… lmata 477 function formatError(screen, error) {
f7eb47b… lmata 478 const message = error.message || error.value || "";
f7eb47b… lmata 479 const stack = error.stack;
f7eb47b… lmata 480 if (!stack && !error.location)
f7eb47b… lmata 481 return { message };
f7eb47b… lmata 482 const tokens = [];
f7eb47b… lmata 483 const parsedStack = stack ? prepareErrorStack(stack) : void 0;
f7eb47b… lmata 484 tokens.push(parsedStack?.message || message);
f7eb47b… lmata 485 if (error.snippet) {
f7eb47b… lmata 486 let snippet = error.snippet;
f7eb47b… lmata 487 if (!screen.colors.enabled)
f7eb47b… lmata 488 snippet = (0, import_util.stripAnsiEscapes)(snippet);
f7eb47b… lmata 489 tokens.push("");
f7eb47b… lmata 490 tokens.push(snippet);
f7eb47b… lmata 491 }
f7eb47b… lmata 492 if (parsedStack && parsedStack.stackLines.length)
f7eb47b… lmata 493 tokens.push(screen.colors.dim(parsedStack.stackLines.join("\n")));
f7eb47b… lmata 494 let location = error.location;
f7eb47b… lmata 495 if (parsedStack && !location)
f7eb47b… lmata 496 location = parsedStack.location;
f7eb47b… lmata 497 if (error.cause)
f7eb47b… lmata 498 tokens.push(screen.colors.dim("[cause]: ") + formatError(screen, error.cause).message);
f7eb47b… lmata 499 return {
f7eb47b… lmata 500 location,
f7eb47b… lmata 501 message: tokens.join("\n")
f7eb47b… lmata 502 };
f7eb47b… lmata 503 }
f7eb47b… lmata 504 function separator(screen, text = "") {
f7eb47b… lmata 505 if (text)
f7eb47b… lmata 506 text += " ";
f7eb47b… lmata 507 const columns = Math.min(100, screen.ttyWidth || 100);
f7eb47b… lmata 508 return text + screen.colors.dim("\u2500".repeat(Math.max(0, columns - (0, import_util.stripAnsiEscapes)(text).length)));
f7eb47b… lmata 509 }
f7eb47b… lmata 510 function indent(lines, tab) {
f7eb47b… lmata 511 return lines.replace(/^(?=.+$)/gm, tab);
f7eb47b… lmata 512 }
f7eb47b… lmata 513 function prepareErrorStack(stack) {
f7eb47b… lmata 514 return (0, import_utils.parseErrorStack)(stack, import_path.default.sep, !!process.env.PWDEBUGIMPL);
f7eb47b… lmata 515 }
f7eb47b… lmata 516 function characterWidth(c) {
f7eb47b… lmata 517 return import_utilsBundle2.getEastAsianWidth.eastAsianWidth(c.codePointAt(0));
f7eb47b… lmata 518 }
f7eb47b… lmata 519 function stringWidth(v) {
f7eb47b… lmata 520 let width = 0;
f7eb47b… lmata 521 for (const { segment } of new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(v))
f7eb47b… lmata 522 width += characterWidth(segment);
f7eb47b… lmata 523 return width;
f7eb47b… lmata 524 }
f7eb47b… lmata 525 function suffixOfWidth(v, width) {
f7eb47b… lmata 526 const segments = [...new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(v)];
f7eb47b… lmata 527 let suffixBegin = v.length;
f7eb47b… lmata 528 for (const { segment, index } of segments.reverse()) {
f7eb47b… lmata 529 const segmentWidth = stringWidth(segment);
f7eb47b… lmata 530 if (segmentWidth > width)
f7eb47b… lmata 531 break;
f7eb47b… lmata 532 width -= segmentWidth;
f7eb47b… lmata 533 suffixBegin = index;
f7eb47b… lmata 534 }
f7eb47b… lmata 535 return v.substring(suffixBegin);
f7eb47b… lmata 536 }
f7eb47b… lmata 537 function fitToWidth(line, width, prefix) {
f7eb47b… lmata 538 const prefixLength = prefix ? (0, import_util.stripAnsiEscapes)(prefix).length : 0;
f7eb47b… lmata 539 width -= prefixLength;
f7eb47b… lmata 540 if (stringWidth(line) <= width)
f7eb47b… lmata 541 return line;
f7eb47b… lmata 542 const parts = line.split(import_util.ansiRegex);
f7eb47b… lmata 543 const taken = [];
f7eb47b… lmata 544 for (let i = parts.length - 1; i >= 0; i--) {
f7eb47b… lmata 545 if (i % 2) {
f7eb47b… lmata 546 taken.push(parts[i]);
f7eb47b… lmata 547 } else {
f7eb47b… lmata 548 let part = suffixOfWidth(parts[i], width);
f7eb47b… lmata 549 const wasTruncated = part.length < parts[i].length;
f7eb47b… lmata 550 if (wasTruncated && parts[i].length > 0) {
f7eb47b… lmata 551 part = "\u2026" + suffixOfWidth(parts[i], width - 1);
f7eb47b… lmata 552 }
f7eb47b… lmata 553 taken.push(part);
f7eb47b… lmata 554 width -= stringWidth(part);
f7eb47b… lmata 555 }
f7eb47b… lmata 556 }
f7eb47b… lmata 557 return taken.reverse().join("");
f7eb47b… lmata 558 }
f7eb47b… lmata 559 function resolveFromEnv(name) {
f7eb47b… lmata 560 const value = process.env[name];
f7eb47b… lmata 561 if (value)
f7eb47b… lmata 562 return import_path.default.resolve(process.cwd(), value);
f7eb47b… lmata 563 return void 0;
f7eb47b… lmata 564 }
f7eb47b… lmata 565 function resolveOutputFile(reporterName, options) {
f7eb47b… lmata 566 const name = reporterName.toUpperCase();
f7eb47b… lmata 567 let outputFile = resolveFromEnv(`PLAYWRIGHT_${name}_OUTPUT_FILE`);
f7eb47b… lmata 568 if (!outputFile && options.outputFile)
f7eb47b… lmata 569 outputFile = import_path.default.resolve(options.configDir, options.outputFile);
f7eb47b… lmata 570 if (outputFile)
f7eb47b… lmata 571 return { outputFile };
f7eb47b… lmata 572 let outputDir = resolveFromEnv(`PLAYWRIGHT_${name}_OUTPUT_DIR`);
f7eb47b… lmata 573 if (!outputDir && options.outputDir)
f7eb47b… lmata 574 outputDir = import_path.default.resolve(options.configDir, options.outputDir);
f7eb47b… lmata 575 if (!outputDir && options.default)
f7eb47b… lmata 576 outputDir = (0, import_util.resolveReporterOutputPath)(options.default.outputDir, options.configDir, void 0);
f7eb47b… lmata 577 if (!outputDir)
f7eb47b… lmata 578 outputDir = options.configDir;
f7eb47b… lmata 579 const reportName = process.env[`PLAYWRIGHT_${name}_OUTPUT_NAME`] ?? options.fileName ?? options.default?.fileName;
f7eb47b… lmata 580 if (!reportName)
f7eb47b… lmata 581 return void 0;
f7eb47b… lmata 582 outputFile = import_path.default.resolve(outputDir, reportName);
f7eb47b… lmata 583 return { outputFile, outputDir };
f7eb47b… lmata 584 }
f7eb47b… lmata 585 function groupAttachments(attachments) {
f7eb47b… lmata 586 const result = [];
f7eb47b… lmata 587 const attachmentsByPrefix = /* @__PURE__ */ new Map();
f7eb47b… lmata 588 for (const attachment of attachments) {
f7eb47b… lmata 589 if (!attachment.path) {
f7eb47b… lmata 590 result.push(attachment);
f7eb47b… lmata 591 continue;
f7eb47b… lmata 592 }
f7eb47b… lmata 593 const match = attachment.name.match(/^(.*)-(expected|actual|diff|previous)(\.[^.]+)?$/);
f7eb47b… lmata 594 if (!match) {
f7eb47b… lmata 595 result.push(attachment);
f7eb47b… lmata 596 continue;
f7eb47b… lmata 597 }
f7eb47b… lmata 598 const [, name, category] = match;
f7eb47b… lmata 599 let group = attachmentsByPrefix.get(name);
f7eb47b… lmata 600 if (!group) {
f7eb47b… lmata 601 group = { ...attachment, name };
f7eb47b… lmata 602 attachmentsByPrefix.set(name, group);
f7eb47b… lmata 603 result.push(group);
f7eb47b… lmata 604 }
f7eb47b… lmata 605 if (category === "expected")
f7eb47b… lmata 606 group.expected = attachment;
f7eb47b… lmata 607 else if (category === "actual")
f7eb47b… lmata 608 group.actual = attachment;
f7eb47b… lmata 609 else if (category === "diff")
f7eb47b… lmata 610 group.diff = attachment;
f7eb47b… lmata 611 else if (category === "previous")
f7eb47b… lmata 612 group.previous = attachment;
f7eb47b… lmata 613 }
f7eb47b… lmata 614 return result;
f7eb47b… lmata 615 }
f7eb47b… lmata 616 // Annotate the CommonJS export names for ESM import in node:
f7eb47b… lmata 617 0 && (module.exports = {
f7eb47b… lmata 618 TerminalReporter,
f7eb47b… lmata 619 fitToWidth,
f7eb47b… lmata 620 formatError,
f7eb47b… lmata 621 formatFailure,
f7eb47b… lmata 622 formatResultFailure,
f7eb47b… lmata 623 formatRetry,
f7eb47b… lmata 624 internalScreen,
f7eb47b… lmata 625 kOutputSymbol,
f7eb47b… lmata 626 markErrorsAsReported,
f7eb47b… lmata 627 nonTerminalScreen,
f7eb47b… lmata 628 prepareErrorStack,
f7eb47b… lmata 629 relativeFilePath,
f7eb47b… lmata 630 resolveOutputFile,
f7eb47b… lmata 631 separator,
f7eb47b… lmata 632 stepSuffix,
f7eb47b… lmata 633 terminalScreen
f7eb47b… lmata 634 });

Keyboard Shortcuts

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