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