612 lines
23 KiB
JavaScript
612 lines
23 KiB
JavaScript
"use strict";
|
|
var __create = Object.create;
|
|
var __defProp = Object.defineProperty;
|
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
var __getProtoOf = Object.getPrototypeOf;
|
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
var __export = (target, all) => {
|
|
for (var name in all)
|
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
};
|
|
var __copyProps = (to, from, except, desc) => {
|
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
for (let key of __getOwnPropNames(from))
|
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
}
|
|
return to;
|
|
};
|
|
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
// If the importer is in node compatibility mode or this is not an ESM
|
|
// file that has been converted to a CommonJS file using a Babel-
|
|
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
mod
|
|
));
|
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
var base_exports = {};
|
|
__export(base_exports, {
|
|
TerminalReporter: () => TerminalReporter,
|
|
fitToWidth: () => fitToWidth,
|
|
formatError: () => formatError,
|
|
formatFailure: () => formatFailure,
|
|
formatResultFailure: () => formatResultFailure,
|
|
formatRetry: () => formatRetry,
|
|
internalScreen: () => internalScreen,
|
|
kOutputSymbol: () => kOutputSymbol,
|
|
nonTerminalScreen: () => nonTerminalScreen,
|
|
prepareErrorStack: () => prepareErrorStack,
|
|
relativeFilePath: () => relativeFilePath,
|
|
resolveOutputFile: () => resolveOutputFile,
|
|
separator: () => separator,
|
|
stepSuffix: () => stepSuffix,
|
|
terminalScreen: () => terminalScreen
|
|
});
|
|
module.exports = __toCommonJS(base_exports);
|
|
var import_path = __toESM(require("path"));
|
|
var import_utils = require("playwright-core/lib/utils");
|
|
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
|
var import_utils2 = require("playwright-core/lib/utils");
|
|
var import_util = require("../util");
|
|
var import_utilsBundle2 = require("../utilsBundle");
|
|
const kOutputSymbol = Symbol("output");
|
|
const DEFAULT_TTY_WIDTH = 100;
|
|
const DEFAULT_TTY_HEIGHT = 40;
|
|
const originalProcessStdout = process.stdout;
|
|
const originalProcessStderr = process.stderr;
|
|
const terminalScreen = (() => {
|
|
let isTTY = !!originalProcessStdout.isTTY;
|
|
let ttyWidth = originalProcessStdout.columns || 0;
|
|
let ttyHeight = originalProcessStdout.rows || 0;
|
|
if (process.env.PLAYWRIGHT_FORCE_TTY === "false" || process.env.PLAYWRIGHT_FORCE_TTY === "0") {
|
|
isTTY = false;
|
|
ttyWidth = 0;
|
|
ttyHeight = 0;
|
|
} else if (process.env.PLAYWRIGHT_FORCE_TTY === "true" || process.env.PLAYWRIGHT_FORCE_TTY === "1") {
|
|
isTTY = true;
|
|
ttyWidth = originalProcessStdout.columns || DEFAULT_TTY_WIDTH;
|
|
ttyHeight = originalProcessStdout.rows || DEFAULT_TTY_HEIGHT;
|
|
} else if (process.env.PLAYWRIGHT_FORCE_TTY) {
|
|
isTTY = true;
|
|
const sizeMatch = process.env.PLAYWRIGHT_FORCE_TTY.match(/^(\d+)x(\d+)$/);
|
|
if (sizeMatch) {
|
|
ttyWidth = +sizeMatch[1];
|
|
ttyHeight = +sizeMatch[2];
|
|
} else {
|
|
ttyWidth = +process.env.PLAYWRIGHT_FORCE_TTY;
|
|
ttyHeight = DEFAULT_TTY_HEIGHT;
|
|
}
|
|
if (isNaN(ttyWidth))
|
|
ttyWidth = DEFAULT_TTY_WIDTH;
|
|
if (isNaN(ttyHeight))
|
|
ttyHeight = DEFAULT_TTY_HEIGHT;
|
|
}
|
|
let useColors = isTTY;
|
|
if (process.env.DEBUG_COLORS === "0" || process.env.DEBUG_COLORS === "false" || process.env.FORCE_COLOR === "0" || process.env.FORCE_COLOR === "false")
|
|
useColors = false;
|
|
else if (process.env.DEBUG_COLORS || process.env.FORCE_COLOR)
|
|
useColors = true;
|
|
const colors = useColors ? import_utils2.colors : import_utils2.noColors;
|
|
return {
|
|
resolveFiles: "cwd",
|
|
isTTY,
|
|
ttyWidth,
|
|
ttyHeight,
|
|
colors,
|
|
stdout: originalProcessStdout,
|
|
stderr: originalProcessStderr
|
|
};
|
|
})();
|
|
const nonTerminalScreen = {
|
|
colors: terminalScreen.colors,
|
|
isTTY: false,
|
|
ttyWidth: 0,
|
|
ttyHeight: 0,
|
|
resolveFiles: "rootDir"
|
|
};
|
|
const internalScreen = {
|
|
colors: import_utils2.colors,
|
|
isTTY: false,
|
|
ttyWidth: 0,
|
|
ttyHeight: 0,
|
|
resolveFiles: "rootDir"
|
|
};
|
|
class TerminalReporter {
|
|
constructor(options = {}) {
|
|
this.totalTestCount = 0;
|
|
this.fileDurations = /* @__PURE__ */ new Map();
|
|
this._fatalErrors = [];
|
|
this._failureCount = 0;
|
|
this.screen = options.screen ?? terminalScreen;
|
|
this._omitFailures = options.omitFailures || false;
|
|
}
|
|
version() {
|
|
return "v2";
|
|
}
|
|
onConfigure(config) {
|
|
this.config = config;
|
|
}
|
|
onBegin(suite) {
|
|
this.suite = suite;
|
|
this.totalTestCount = suite.allTests().length;
|
|
}
|
|
onStdOut(chunk, test, result) {
|
|
this._appendOutput({ chunk, type: "stdout" }, result);
|
|
}
|
|
onStdErr(chunk, test, result) {
|
|
this._appendOutput({ chunk, type: "stderr" }, result);
|
|
}
|
|
_appendOutput(output, result) {
|
|
if (!result)
|
|
return;
|
|
result[kOutputSymbol] = result[kOutputSymbol] || [];
|
|
result[kOutputSymbol].push(output);
|
|
}
|
|
onTestEnd(test, result) {
|
|
if (result.status !== "skipped" && result.status !== test.expectedStatus)
|
|
++this._failureCount;
|
|
const projectName = test.titlePath()[1];
|
|
const relativePath = relativeTestPath(this.screen, this.config, test);
|
|
const fileAndProject = (projectName ? `[${projectName}] \u203A ` : "") + relativePath;
|
|
const entry = this.fileDurations.get(fileAndProject) || { duration: 0, workers: /* @__PURE__ */ new Set() };
|
|
entry.duration += result.duration;
|
|
entry.workers.add(result.workerIndex);
|
|
this.fileDurations.set(fileAndProject, entry);
|
|
}
|
|
onError(error) {
|
|
this._fatalErrors.push(error);
|
|
}
|
|
async onEnd(result) {
|
|
this.result = result;
|
|
}
|
|
fitToScreen(line, prefix) {
|
|
if (!this.screen.ttyWidth) {
|
|
return line;
|
|
}
|
|
return fitToWidth(line, this.screen.ttyWidth, prefix);
|
|
}
|
|
generateStartingMessage() {
|
|
const jobs = this.config.metadata.actualWorkers ?? this.config.workers;
|
|
const shardDetails = this.config.shard ? `, shard ${this.config.shard.current} of ${this.config.shard.total}` : "";
|
|
if (!this.totalTestCount)
|
|
return "";
|
|
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}`);
|
|
}
|
|
getSlowTests() {
|
|
if (!this.config.reportSlowTests)
|
|
return [];
|
|
const fileDurations = [...this.fileDurations.entries()].filter(([key, value]) => value.workers.size === 1).map(([key, value]) => [key, value.duration]);
|
|
fileDurations.sort((a, b) => b[1] - a[1]);
|
|
const count = Math.min(fileDurations.length, this.config.reportSlowTests.max || Number.POSITIVE_INFINITY);
|
|
const threshold = this.config.reportSlowTests.threshold;
|
|
return fileDurations.filter(([, duration]) => duration > threshold).slice(0, count);
|
|
}
|
|
generateSummaryMessage({ didNotRun, skipped, expected, interrupted, unexpected, flaky, fatalErrors }) {
|
|
const tokens = [];
|
|
if (unexpected.length) {
|
|
tokens.push(this.screen.colors.red(` ${unexpected.length} failed`));
|
|
for (const test of unexpected)
|
|
tokens.push(this.screen.colors.red(this.formatTestHeader(test, { indent: " " })));
|
|
}
|
|
if (interrupted.length) {
|
|
tokens.push(this.screen.colors.yellow(` ${interrupted.length} interrupted`));
|
|
for (const test of interrupted)
|
|
tokens.push(this.screen.colors.yellow(this.formatTestHeader(test, { indent: " " })));
|
|
}
|
|
if (flaky.length) {
|
|
tokens.push(this.screen.colors.yellow(` ${flaky.length} flaky`));
|
|
for (const test of flaky)
|
|
tokens.push(this.screen.colors.yellow(this.formatTestHeader(test, { indent: " " })));
|
|
}
|
|
if (skipped)
|
|
tokens.push(this.screen.colors.yellow(` ${skipped} skipped`));
|
|
if (didNotRun)
|
|
tokens.push(this.screen.colors.yellow(` ${didNotRun} did not run`));
|
|
if (expected)
|
|
tokens.push(this.screen.colors.green(` ${expected} passed`) + this.screen.colors.dim(` (${(0, import_utilsBundle.ms)(this.result.duration)})`));
|
|
if (fatalErrors.length && expected + unexpected.length + interrupted.length + flaky.length > 0)
|
|
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`));
|
|
return tokens.join("\n");
|
|
}
|
|
generateSummary() {
|
|
let didNotRun = 0;
|
|
let skipped = 0;
|
|
let expected = 0;
|
|
const interrupted = [];
|
|
const interruptedToPrint = [];
|
|
const unexpected = [];
|
|
const flaky = [];
|
|
this.suite.allTests().forEach((test) => {
|
|
switch (test.outcome()) {
|
|
case "skipped": {
|
|
if (test.results.some((result) => result.status === "interrupted")) {
|
|
if (test.results.some((result) => !!result.error))
|
|
interruptedToPrint.push(test);
|
|
interrupted.push(test);
|
|
} else if (!test.results.length || test.expectedStatus !== "skipped") {
|
|
++didNotRun;
|
|
} else {
|
|
++skipped;
|
|
}
|
|
break;
|
|
}
|
|
case "expected":
|
|
++expected;
|
|
break;
|
|
case "unexpected":
|
|
unexpected.push(test);
|
|
break;
|
|
case "flaky":
|
|
flaky.push(test);
|
|
break;
|
|
}
|
|
});
|
|
const failuresToPrint = [...unexpected, ...flaky, ...interruptedToPrint];
|
|
return {
|
|
didNotRun,
|
|
skipped,
|
|
expected,
|
|
interrupted,
|
|
unexpected,
|
|
flaky,
|
|
failuresToPrint,
|
|
fatalErrors: this._fatalErrors
|
|
};
|
|
}
|
|
epilogue(full) {
|
|
const summary = this.generateSummary();
|
|
const summaryMessage = this.generateSummaryMessage(summary);
|
|
if (full && summary.failuresToPrint.length && !this._omitFailures)
|
|
this._printFailures(summary.failuresToPrint);
|
|
this._printSlowTests();
|
|
this._printSummary(summaryMessage);
|
|
}
|
|
_printFailures(failures) {
|
|
this.writeLine("");
|
|
failures.forEach((test, index) => {
|
|
this.writeLine(this.formatFailure(test, index + 1));
|
|
});
|
|
}
|
|
_printSlowTests() {
|
|
const slowTests = this.getSlowTests();
|
|
slowTests.forEach(([file, duration]) => {
|
|
this.writeLine(this.screen.colors.yellow(" Slow test file: ") + file + this.screen.colors.yellow(` (${(0, import_utilsBundle.ms)(duration)})`));
|
|
});
|
|
if (slowTests.length)
|
|
this.writeLine(this.screen.colors.yellow(" Consider running tests from slow files in parallel. See: https://playwright.dev/docs/test-parallel"));
|
|
}
|
|
_printSummary(summary) {
|
|
if (summary.trim())
|
|
this.writeLine(summary);
|
|
}
|
|
willRetry(test) {
|
|
return test.outcome() === "unexpected" && test.results.length <= test.retries;
|
|
}
|
|
formatTestTitle(test, step, omitLocation = false) {
|
|
return formatTestTitle(this.screen, this.config, test, step, omitLocation);
|
|
}
|
|
formatTestHeader(test, options = {}) {
|
|
return formatTestHeader(this.screen, this.config, test, options);
|
|
}
|
|
formatFailure(test, index) {
|
|
return formatFailure(this.screen, this.config, test, index);
|
|
}
|
|
formatError(error) {
|
|
return formatError(this.screen, error);
|
|
}
|
|
writeLine(line) {
|
|
this.screen.stdout?.write(line ? line + "\n" : "\n");
|
|
}
|
|
}
|
|
function formatFailure(screen, config, test, index) {
|
|
const lines = [];
|
|
const header = formatTestHeader(screen, config, test, { indent: " ", index, mode: "error" });
|
|
lines.push(screen.colors.red(header));
|
|
for (const result of test.results) {
|
|
const resultLines = [];
|
|
const errors = formatResultFailure(screen, test, result, " ");
|
|
if (!errors.length)
|
|
continue;
|
|
if (result.retry) {
|
|
resultLines.push("");
|
|
resultLines.push(screen.colors.gray(separator(screen, ` Retry #${result.retry}`)));
|
|
}
|
|
resultLines.push(...errors.map((error) => "\n" + error.message));
|
|
const attachmentGroups = groupAttachments(result.attachments);
|
|
for (let i = 0; i < attachmentGroups.length; ++i) {
|
|
const attachment = attachmentGroups[i];
|
|
if (attachment.name === "error-context" && attachment.path) {
|
|
resultLines.push("");
|
|
resultLines.push(screen.colors.dim(` Error Context: ${relativeFilePath(screen, config, attachment.path)}`));
|
|
continue;
|
|
}
|
|
if (attachment.name.startsWith("_"))
|
|
continue;
|
|
const hasPrintableContent = attachment.contentType.startsWith("text/");
|
|
if (!attachment.path && !hasPrintableContent)
|
|
continue;
|
|
resultLines.push("");
|
|
resultLines.push(screen.colors.dim(separator(screen, ` attachment #${i + 1}: ${screen.colors.bold(attachment.name)} (${attachment.contentType})`)));
|
|
if (attachment.actual?.path) {
|
|
if (attachment.expected?.path) {
|
|
const expectedPath = relativeFilePath(screen, config, attachment.expected.path);
|
|
resultLines.push(screen.colors.dim(` Expected: ${expectedPath}`));
|
|
}
|
|
const actualPath = relativeFilePath(screen, config, attachment.actual.path);
|
|
resultLines.push(screen.colors.dim(` Received: ${actualPath}`));
|
|
if (attachment.previous?.path) {
|
|
const previousPath = relativeFilePath(screen, config, attachment.previous.path);
|
|
resultLines.push(screen.colors.dim(` Previous: ${previousPath}`));
|
|
}
|
|
if (attachment.diff?.path) {
|
|
const diffPath = relativeFilePath(screen, config, attachment.diff.path);
|
|
resultLines.push(screen.colors.dim(` Diff: ${diffPath}`));
|
|
}
|
|
} else if (attachment.path) {
|
|
const relativePath = relativeFilePath(screen, config, attachment.path);
|
|
resultLines.push(screen.colors.dim(` ${relativePath}`));
|
|
if (attachment.name === "trace") {
|
|
const packageManagerCommand = (0, import_utils.getPackageManagerExecCommand)();
|
|
resultLines.push(screen.colors.dim(` Usage:`));
|
|
resultLines.push("");
|
|
resultLines.push(screen.colors.dim(` ${packageManagerCommand} playwright show-trace ${quotePathIfNeeded(relativePath)}`));
|
|
resultLines.push("");
|
|
}
|
|
} else {
|
|
if (attachment.contentType.startsWith("text/") && attachment.body) {
|
|
let text = attachment.body.toString();
|
|
if (text.length > 300)
|
|
text = text.slice(0, 300) + "...";
|
|
for (const line of text.split("\n"))
|
|
resultLines.push(screen.colors.dim(` ${line}`));
|
|
}
|
|
}
|
|
resultLines.push(screen.colors.dim(separator(screen, " ")));
|
|
}
|
|
lines.push(...resultLines);
|
|
}
|
|
lines.push("");
|
|
return lines.join("\n");
|
|
}
|
|
function formatRetry(screen, result) {
|
|
const retryLines = [];
|
|
if (result.retry) {
|
|
retryLines.push("");
|
|
retryLines.push(screen.colors.gray(separator(screen, ` Retry #${result.retry}`)));
|
|
}
|
|
return retryLines;
|
|
}
|
|
function quotePathIfNeeded(path2) {
|
|
if (/\s/.test(path2))
|
|
return `"${path2}"`;
|
|
return path2;
|
|
}
|
|
function formatResultFailure(screen, test, result, initialIndent) {
|
|
const errorDetails = [];
|
|
if (result.status === "passed" && test.expectedStatus === "failed") {
|
|
errorDetails.push({
|
|
message: indent(screen.colors.red(`Expected to fail, but passed.`), initialIndent)
|
|
});
|
|
}
|
|
if (result.status === "interrupted") {
|
|
errorDetails.push({
|
|
message: indent(screen.colors.red(`Test was interrupted.`), initialIndent)
|
|
});
|
|
}
|
|
for (const error of result.errors) {
|
|
const formattedError = formatError(screen, error);
|
|
errorDetails.push({
|
|
message: indent(formattedError.message, initialIndent),
|
|
location: formattedError.location
|
|
});
|
|
}
|
|
return errorDetails;
|
|
}
|
|
function relativeFilePath(screen, config, file) {
|
|
if (screen.resolveFiles === "cwd")
|
|
return import_path.default.relative(process.cwd(), file);
|
|
return import_path.default.relative(config.rootDir, file);
|
|
}
|
|
function relativeTestPath(screen, config, test) {
|
|
return relativeFilePath(screen, config, test.location.file);
|
|
}
|
|
function stepSuffix(step) {
|
|
const stepTitles = step ? step.titlePath() : [];
|
|
return stepTitles.map((t) => t.split("\n")[0]).map((t) => " \u203A " + t).join("");
|
|
}
|
|
function formatTestTitle(screen, config, test, step, omitLocation = false) {
|
|
const [, projectName, , ...titles] = test.titlePath();
|
|
let location;
|
|
if (omitLocation)
|
|
location = `${relativeTestPath(screen, config, test)}`;
|
|
else
|
|
location = `${relativeTestPath(screen, config, test)}:${test.location.line}:${test.location.column}`;
|
|
const projectTitle = projectName ? `[${projectName}] \u203A ` : "";
|
|
const testTitle = `${projectTitle}${location} \u203A ${titles.join(" \u203A ")}`;
|
|
const extraTags = test.tags.filter((t) => !testTitle.includes(t));
|
|
return `${testTitle}${stepSuffix(step)}${extraTags.length ? " " + extraTags.join(" ") : ""}`;
|
|
}
|
|
function formatTestHeader(screen, config, test, options = {}) {
|
|
const title = formatTestTitle(screen, config, test);
|
|
const header = `${options.indent || ""}${options.index ? options.index + ") " : ""}${title}`;
|
|
let fullHeader = header;
|
|
if (options.mode === "error") {
|
|
const stepPaths = /* @__PURE__ */ new Set();
|
|
for (const result of test.results.filter((r) => !!r.errors.length)) {
|
|
const stepPath = [];
|
|
const visit = (steps) => {
|
|
const errors = steps.filter((s) => s.error);
|
|
if (errors.length > 1)
|
|
return;
|
|
if (errors.length === 1 && errors[0].category === "test.step") {
|
|
stepPath.push(errors[0].title);
|
|
visit(errors[0].steps);
|
|
}
|
|
};
|
|
visit(result.steps);
|
|
stepPaths.add(["", ...stepPath].join(" \u203A "));
|
|
}
|
|
fullHeader = header + (stepPaths.size === 1 ? stepPaths.values().next().value : "");
|
|
}
|
|
return separator(screen, fullHeader);
|
|
}
|
|
function formatError(screen, error) {
|
|
const message = error.message || error.value || "";
|
|
const stack = error.stack;
|
|
if (!stack && !error.location)
|
|
return { message };
|
|
const tokens = [];
|
|
const parsedStack = stack ? prepareErrorStack(stack) : void 0;
|
|
tokens.push(parsedStack?.message || message);
|
|
if (error.snippet) {
|
|
let snippet = error.snippet;
|
|
if (!screen.colors.enabled)
|
|
snippet = (0, import_util.stripAnsiEscapes)(snippet);
|
|
tokens.push("");
|
|
tokens.push(snippet);
|
|
}
|
|
if (parsedStack && parsedStack.stackLines.length)
|
|
tokens.push(screen.colors.dim(parsedStack.stackLines.join("\n")));
|
|
let location = error.location;
|
|
if (parsedStack && !location)
|
|
location = parsedStack.location;
|
|
if (error.cause)
|
|
tokens.push(screen.colors.dim("[cause]: ") + formatError(screen, error.cause).message);
|
|
return {
|
|
location,
|
|
message: tokens.join("\n")
|
|
};
|
|
}
|
|
function separator(screen, text = "") {
|
|
if (text)
|
|
text += " ";
|
|
const columns = Math.min(100, screen.ttyWidth || 100);
|
|
return text + screen.colors.dim("\u2500".repeat(Math.max(0, columns - (0, import_util.stripAnsiEscapes)(text).length)));
|
|
}
|
|
function indent(lines, tab) {
|
|
return lines.replace(/^(?=.+$)/gm, tab);
|
|
}
|
|
function prepareErrorStack(stack) {
|
|
return (0, import_utils.parseErrorStack)(stack, import_path.default.sep, !!process.env.PWDEBUGIMPL);
|
|
}
|
|
function characterWidth(c) {
|
|
return import_utilsBundle2.getEastAsianWidth.eastAsianWidth(c.codePointAt(0));
|
|
}
|
|
function stringWidth(v) {
|
|
let width = 0;
|
|
for (const { segment } of new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(v))
|
|
width += characterWidth(segment);
|
|
return width;
|
|
}
|
|
function suffixOfWidth(v, width) {
|
|
const segments = [...new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(v)];
|
|
let suffixBegin = v.length;
|
|
for (const { segment, index } of segments.reverse()) {
|
|
const segmentWidth = stringWidth(segment);
|
|
if (segmentWidth > width)
|
|
break;
|
|
width -= segmentWidth;
|
|
suffixBegin = index;
|
|
}
|
|
return v.substring(suffixBegin);
|
|
}
|
|
function fitToWidth(line, width, prefix) {
|
|
const prefixLength = prefix ? (0, import_util.stripAnsiEscapes)(prefix).length : 0;
|
|
width -= prefixLength;
|
|
if (stringWidth(line) <= width)
|
|
return line;
|
|
const parts = line.split(import_util.ansiRegex);
|
|
const taken = [];
|
|
for (let i = parts.length - 1; i >= 0; i--) {
|
|
if (i % 2) {
|
|
taken.push(parts[i]);
|
|
} else {
|
|
let part = suffixOfWidth(parts[i], width);
|
|
const wasTruncated = part.length < parts[i].length;
|
|
if (wasTruncated && parts[i].length > 0) {
|
|
part = "\u2026" + suffixOfWidth(parts[i], width - 1);
|
|
}
|
|
taken.push(part);
|
|
width -= stringWidth(part);
|
|
}
|
|
}
|
|
return taken.reverse().join("");
|
|
}
|
|
function resolveFromEnv(name) {
|
|
const value = process.env[name];
|
|
if (value)
|
|
return import_path.default.resolve(process.cwd(), value);
|
|
return void 0;
|
|
}
|
|
function resolveOutputFile(reporterName, options) {
|
|
const name = reporterName.toUpperCase();
|
|
let outputFile = resolveFromEnv(`PLAYWRIGHT_${name}_OUTPUT_FILE`);
|
|
if (!outputFile && options.outputFile)
|
|
outputFile = import_path.default.resolve(options.configDir, options.outputFile);
|
|
if (outputFile)
|
|
return { outputFile };
|
|
let outputDir = resolveFromEnv(`PLAYWRIGHT_${name}_OUTPUT_DIR`);
|
|
if (!outputDir && options.outputDir)
|
|
outputDir = import_path.default.resolve(options.configDir, options.outputDir);
|
|
if (!outputDir && options.default)
|
|
outputDir = (0, import_util.resolveReporterOutputPath)(options.default.outputDir, options.configDir, void 0);
|
|
if (!outputDir)
|
|
outputDir = options.configDir;
|
|
const reportName = process.env[`PLAYWRIGHT_${name}_OUTPUT_NAME`] ?? options.fileName ?? options.default?.fileName;
|
|
if (!reportName)
|
|
return void 0;
|
|
outputFile = import_path.default.resolve(outputDir, reportName);
|
|
return { outputFile, outputDir };
|
|
}
|
|
function groupAttachments(attachments) {
|
|
const result = [];
|
|
const attachmentsByPrefix = /* @__PURE__ */ new Map();
|
|
for (const attachment of attachments) {
|
|
if (!attachment.path) {
|
|
result.push(attachment);
|
|
continue;
|
|
}
|
|
const match = attachment.name.match(/^(.*)-(expected|actual|diff|previous)(\.[^.]+)?$/);
|
|
if (!match) {
|
|
result.push(attachment);
|
|
continue;
|
|
}
|
|
const [, name, category] = match;
|
|
let group = attachmentsByPrefix.get(name);
|
|
if (!group) {
|
|
group = { ...attachment, name };
|
|
attachmentsByPrefix.set(name, group);
|
|
result.push(group);
|
|
}
|
|
if (category === "expected")
|
|
group.expected = attachment;
|
|
else if (category === "actual")
|
|
group.actual = attachment;
|
|
else if (category === "diff")
|
|
group.diff = attachment;
|
|
else if (category === "previous")
|
|
group.previous = attachment;
|
|
}
|
|
return result;
|
|
}
|
|
// Annotate the CommonJS export names for ESM import in node:
|
|
0 && (module.exports = {
|
|
TerminalReporter,
|
|
fitToWidth,
|
|
formatError,
|
|
formatFailure,
|
|
formatResultFailure,
|
|
formatRetry,
|
|
internalScreen,
|
|
kOutputSymbol,
|
|
nonTerminalScreen,
|
|
prepareErrorStack,
|
|
relativeFilePath,
|
|
resolveOutputFile,
|
|
separator,
|
|
stepSuffix,
|
|
terminalScreen
|
|
});
|