ScuttleBot

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

Keyboard Shortcuts

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