@bigbinary/neeto-playwright-reporter 1.3.24 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.cjs.js +356 -304
- package/index.cjs.js.map +1 -1
- package/index.d.ts +7 -15
- package/index.js +355 -303
- package/index.js.map +1 -1
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1,19 +1,117 @@
|
|
|
1
|
-
import
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
import { camelToSnakeCase, keysToSnakeCase, keysToCamelCase } from '@bigbinary/neeto-cist';
|
|
2
3
|
import require$$1, { TextEncoder } from 'util';
|
|
3
4
|
import stream, { Readable } from 'stream';
|
|
4
5
|
import require$$1$1 from 'path';
|
|
5
6
|
import require$$3 from 'http';
|
|
6
7
|
import require$$4 from 'https';
|
|
7
8
|
import require$$0$1 from 'url';
|
|
9
|
+
import require$$6, { readFileSync } from 'fs';
|
|
8
10
|
import require$$4$1 from 'assert';
|
|
9
11
|
import require$$1$2 from 'tty';
|
|
10
12
|
import require$$0$2 from 'os';
|
|
11
13
|
import zlib from 'zlib';
|
|
12
14
|
import EventEmitter from 'events';
|
|
13
|
-
import
|
|
14
|
-
import { uniq } from 'ramda';
|
|
15
|
+
import { uniq, mergeDeepRight } from 'ramda';
|
|
15
16
|
import childProcess from 'child_process';
|
|
16
17
|
|
|
18
|
+
const ERRORS = {
|
|
19
|
+
onBegin: {
|
|
20
|
+
noTestsToReport: "No tests to report",
|
|
21
|
+
failedToGetCommitSha: "Failed to get current commit SHA.",
|
|
22
|
+
failedToGetAuthor: "Failed to get current author.",
|
|
23
|
+
failedToGetBranch: "Failed to get current branch.",
|
|
24
|
+
failedToGetCommitMessage: "Failed to get current commit message.",
|
|
25
|
+
failedToInitializeRun: "Failed to initialize run in reporter",
|
|
26
|
+
},
|
|
27
|
+
onTestBegin: {
|
|
28
|
+
failedToReportTest: (testTitle, historyId) => `Failed to report test "${testTitle}" with history ID ${historyId}`,
|
|
29
|
+
},
|
|
30
|
+
onEnd: {
|
|
31
|
+
failedToReportRunStatus: "Failed to report run status",
|
|
32
|
+
},
|
|
33
|
+
onTestEnd: {
|
|
34
|
+
attemptAlreadyReported: "Attempt was already reported",
|
|
35
|
+
},
|
|
36
|
+
heartbeat: {
|
|
37
|
+
stopped: "Run was stopped at the reporter",
|
|
38
|
+
},
|
|
39
|
+
common: {
|
|
40
|
+
timeoutExceeded: "Timed out waiting for condition.",
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const LOG_LEVEL_FORMATTERS = {
|
|
45
|
+
bold: "\x1b[1m",
|
|
46
|
+
dim: "\x1b[2m",
|
|
47
|
+
underline: "\x1b[4m",
|
|
48
|
+
invertBackground: "\x1b[7m",
|
|
49
|
+
hidden: "\x1b[8m",
|
|
50
|
+
error: "\u001b[31m\x1b[1m",
|
|
51
|
+
redText: "\u001b[31m\x1b[1m", // Same ANSI style as error but in log level printing important errors
|
|
52
|
+
warning: "\u001b[33m\x1b[1m",
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
class ConsoleLogFormatted {
|
|
56
|
+
constructor() {
|
|
57
|
+
this.LOG_LEVELS = {
|
|
58
|
+
info: ["bold", "dim", "underline", "invertBackground", "hidden", "redText"],
|
|
59
|
+
get warning() {
|
|
60
|
+
return [...this.info, "warning"];
|
|
61
|
+
},
|
|
62
|
+
get error() {
|
|
63
|
+
return [...this.warning, "error"];
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
this.shouldPrintMessage = (type) => {
|
|
67
|
+
var _a;
|
|
68
|
+
const logLevel = ((_a = process.env.PLAYDASH_LOG_LEVEL) !== null && _a !== void 0 ? _a : "info");
|
|
69
|
+
const validMethods = this.LOG_LEVELS[logLevel] || [];
|
|
70
|
+
return validMethods.includes(type);
|
|
71
|
+
};
|
|
72
|
+
Object.entries(LOG_LEVEL_FORMATTERS).forEach(([logLevel, ansiFormatter]) => {
|
|
73
|
+
this.createMethod(logLevel, ansiFormatter);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
createMethod(logLevel, ansiFormatter) {
|
|
77
|
+
this[logLevel] = (message) => {
|
|
78
|
+
if (this.shouldPrintMessage(logLevel)) {
|
|
79
|
+
console.log(`${ansiFormatter}${message}\x1b[0m\u001b[0m`);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
var consoleLogFormatted = new ConsoleLogFormatted();
|
|
85
|
+
|
|
86
|
+
const createShardObject = ({ currentShard = 0, status = "running", duration = null, }) => ({
|
|
87
|
+
[currentShard]: { status, duration },
|
|
88
|
+
});
|
|
89
|
+
const waitUntilTimeout = (timeout) => new Promise(resolve => setTimeout(resolve, timeout));
|
|
90
|
+
const waitUntilCondition = async (condition, timeout = 100 * 1000 // Default timeout after 100 seconds
|
|
91
|
+
) => {
|
|
92
|
+
const startTime = new Date();
|
|
93
|
+
const timeoutTime = new Date(startTime.getTime() + timeout);
|
|
94
|
+
while (!condition()) {
|
|
95
|
+
if (new Date().getTime() >= timeoutTime.getTime())
|
|
96
|
+
return consoleLogFormatted.redText(ERRORS.common.timeoutExceeded);
|
|
97
|
+
await waitUntilTimeout(100); // Poll every 100 milliseconds until timeout
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
const convertToSha1 = (text) => crypto.createHash("sha1").update(text).digest("hex").slice(0, 20);
|
|
101
|
+
const generateHistoryId = (projectId, specName, describeTitles, testTitle) => {
|
|
102
|
+
const titles = [...describeTitles, testTitle];
|
|
103
|
+
const targetString = `[project=${projectId}]${specName}\x1e${titles.join("\x1e")}`;
|
|
104
|
+
const targetStringSha = convertToSha1(targetString);
|
|
105
|
+
const pathNameSha = convertToSha1(specName);
|
|
106
|
+
return `${pathNameSha}-${targetStringSha}`;
|
|
107
|
+
};
|
|
108
|
+
const joinHyphenCase = (...strings) => strings.join("-");
|
|
109
|
+
const convertTopLevelKeysToSnakeCase = (object) => Object.entries(object).reduce((acc, curr) => {
|
|
110
|
+
const snakeCaseKey = camelToSnakeCase(curr[0]);
|
|
111
|
+
acc[snakeCaseKey] = curr[1];
|
|
112
|
+
return acc;
|
|
113
|
+
}, {});
|
|
114
|
+
|
|
17
115
|
function bind(fn, thisArg) {
|
|
18
116
|
return function wrap() {
|
|
19
117
|
return fn.apply(thisArg, arguments);
|
|
@@ -13410,10 +13508,10 @@ const hasStandardBrowserWebWorkerEnv = (() => {
|
|
|
13410
13508
|
})();
|
|
13411
13509
|
|
|
13412
13510
|
var utils = /*#__PURE__*/Object.freeze({
|
|
13413
|
-
|
|
13414
|
-
|
|
13415
|
-
|
|
13416
|
-
|
|
13511
|
+
__proto__: null,
|
|
13512
|
+
hasBrowserEnv: hasBrowserEnv,
|
|
13513
|
+
hasStandardBrowserWebWorkerEnv: hasStandardBrowserWebWorkerEnv,
|
|
13514
|
+
hasStandardBrowserEnv: hasStandardBrowserEnv
|
|
13417
13515
|
});
|
|
13418
13516
|
|
|
13419
13517
|
var platform = {
|
|
@@ -18432,49 +18530,26 @@ axios.HttpStatusCode = HttpStatusCode;
|
|
|
18432
18530
|
|
|
18433
18531
|
axios.default = axios;
|
|
18434
18532
|
|
|
18435
|
-
const HEADERS_KEYS = {
|
|
18436
|
-
applicationKey: "Application-Key",
|
|
18437
|
-
contentType: "Content-Type",
|
|
18438
|
-
accept: "Accept",
|
|
18439
|
-
apiKey: "X-Api-Key",
|
|
18440
|
-
projectKey: "Project-Key",
|
|
18441
|
-
};
|
|
18442
18533
|
const API_BASE_URL = "/api/v1";
|
|
18443
18534
|
|
|
18444
|
-
const create$2 = (
|
|
18445
|
-
const
|
|
18446
|
-
|
|
18447
|
-
|
|
18448
|
-
|
|
18449
|
-
|
|
18535
|
+
const create$2 = (payload) => {
|
|
18536
|
+
const snakeCasedPayload = convertTopLevelKeysToSnakeCase(payload);
|
|
18537
|
+
return axios.post(`${API_BASE_URL}/reporter/runs`, {
|
|
18538
|
+
run: snakeCasedPayload,
|
|
18539
|
+
}, { skipSnakeCaseConversion: true });
|
|
18540
|
+
};
|
|
18541
|
+
const update$1 = (ciBuildId, payload) => axios.put(`${API_BASE_URL}/reporter/runs/${ciBuildId}`, {
|
|
18542
|
+
run: payload,
|
|
18450
18543
|
});
|
|
18451
|
-
const
|
|
18452
|
-
|
|
18453
|
-
const VALID_ASSET_TYPES = ["screenshot", "video", "trace"];
|
|
18544
|
+
const heartbeat = (ciBuildId) => axios.get(`${API_BASE_URL}/reporter/runs/${ciBuildId}/heartbeat`);
|
|
18545
|
+
const runsApi = { create: create$2, update: update$1, heartbeat };
|
|
18454
18546
|
|
|
18455
|
-
const
|
|
18456
|
-
|
|
18457
|
-
|
|
18458
|
-
|
|
18459
|
-
failedToGetAuthor: "Failed to get current author.",
|
|
18460
|
-
failedToGetBranch: "Failed to get current branch.",
|
|
18461
|
-
failedToGetCommitMessage: "Failed to get current commit message.",
|
|
18462
|
-
failedToInitializeRun: "Failed to initialize run in reporter",
|
|
18463
|
-
},
|
|
18464
|
-
onTestBegin: {
|
|
18465
|
-
failedToReportTest: (testTitle, historyId) => `Failed to report test "${testTitle}" with history ID ${historyId}`,
|
|
18466
|
-
},
|
|
18467
|
-
onEnd: {
|
|
18468
|
-
failedToReportRunStatus: "Failed to report run status",
|
|
18469
|
-
},
|
|
18470
|
-
onTestEnd: {
|
|
18471
|
-
attemptAlreadyReported: "Attempt was already reported",
|
|
18472
|
-
},
|
|
18473
|
-
heartbeat: {
|
|
18474
|
-
stopped: "Run was stopped at the reporter",
|
|
18475
|
-
},
|
|
18476
|
-
};
|
|
18547
|
+
const create$1 = (ciBuildId, payload) => axios.post(`${API_BASE_URL}/reporter/runs/${ciBuildId}/test_entities`, {
|
|
18548
|
+
testEntity: payload,
|
|
18549
|
+
});
|
|
18550
|
+
const testEntitiesApi = { create: create$1 };
|
|
18477
18551
|
|
|
18552
|
+
const EQUALS = "=".repeat(10);
|
|
18478
18553
|
const MESSAGES = {
|
|
18479
18554
|
onBegin: {
|
|
18480
18555
|
testStarted: "Started reporting to NeetoPlaydash 🎭",
|
|
@@ -18483,86 +18558,18 @@ const MESSAGES = {
|
|
|
18483
18558
|
currentShard: (currentShard) => `Current shard: ${currentShard}`,
|
|
18484
18559
|
},
|
|
18485
18560
|
onTestBegin: {
|
|
18486
|
-
startingTest: (titlePath) =>
|
|
18561
|
+
startingTest: (titlePath, historyId) => `${EQUALS}Starting ${titlePath}${EQUALS}
|
|
18562
|
+
Time: ${new Date().toUTCString()}
|
|
18563
|
+
History ID: ${historyId}`,
|
|
18487
18564
|
},
|
|
18488
18565
|
onTestEnd: {
|
|
18489
|
-
reportedTest: ({ title, status,
|
|
18566
|
+
reportedTest: ({ title, status, historyId, completedAt, }) => `Reported ${title} with id: ${historyId} to NeetoPlaydash with status ${status} at ${completedAt}`,
|
|
18490
18567
|
},
|
|
18491
18568
|
onEnd: {
|
|
18492
18569
|
runReported: "Run completed and reported to NeetoPlaydash 🎉",
|
|
18493
18570
|
},
|
|
18494
18571
|
};
|
|
18495
18572
|
|
|
18496
|
-
const createShardObject = ({ currentShard = 0, status = "running", duration = null, }) => ({
|
|
18497
|
-
[currentShard]: { status, duration },
|
|
18498
|
-
});
|
|
18499
|
-
const waitUntilTimeout = (timeout) => new Promise(resolve => setTimeout(resolve, timeout));
|
|
18500
|
-
const convertToSha1 = (text) => crypto.createHash("sha1").update(text).digest("hex").slice(0, 20);
|
|
18501
|
-
const generateHistoryId = (projectId, specName, describeTitles, testTitle) => {
|
|
18502
|
-
const titles = [...describeTitles, testTitle];
|
|
18503
|
-
const targetString = `[project=${projectId}]${specName}\x1e${titles.join("\x1e")}`;
|
|
18504
|
-
const targetStringSha = convertToSha1(targetString);
|
|
18505
|
-
const pathNameSha = convertToSha1(specName);
|
|
18506
|
-
return `${pathNameSha}-${targetStringSha}`;
|
|
18507
|
-
};
|
|
18508
|
-
|
|
18509
|
-
const LOG_LEVEL_FORMATTERS = {
|
|
18510
|
-
bold: "\x1b[1m",
|
|
18511
|
-
dim: "\x1b[2m",
|
|
18512
|
-
underline: "\x1b[4m",
|
|
18513
|
-
invertBackground: "\x1b[7m",
|
|
18514
|
-
hidden: "\x1b[8m",
|
|
18515
|
-
error: "\u001b[31m\x1b[1m",
|
|
18516
|
-
redText: "\u001b[31m\x1b[1m", // Same ANSI style as error but in log level printing important errors
|
|
18517
|
-
warning: "\u001b[33m\x1b[1m",
|
|
18518
|
-
};
|
|
18519
|
-
|
|
18520
|
-
class ConsoleLogFormatted {
|
|
18521
|
-
constructor() {
|
|
18522
|
-
this.LOG_LEVELS = {
|
|
18523
|
-
info: ["bold", "dim", "underline", "invertBackground", "hidden", "redText"],
|
|
18524
|
-
get warning() {
|
|
18525
|
-
return [...this.info, "warning"];
|
|
18526
|
-
},
|
|
18527
|
-
get error() {
|
|
18528
|
-
return [...this.warning, "error"];
|
|
18529
|
-
},
|
|
18530
|
-
};
|
|
18531
|
-
this.shouldPrintMessage = (type) => {
|
|
18532
|
-
var _a;
|
|
18533
|
-
const logLevel = ((_a = process.env.PLAYDASH_LOG_LEVEL) !== null && _a !== void 0 ? _a : "info");
|
|
18534
|
-
const validMethods = this.LOG_LEVELS[logLevel] || [];
|
|
18535
|
-
return validMethods.includes(type);
|
|
18536
|
-
};
|
|
18537
|
-
Object.entries(LOG_LEVEL_FORMATTERS).forEach(([logLevel, ansiFormatter]) => {
|
|
18538
|
-
this.createMethod(logLevel, ansiFormatter);
|
|
18539
|
-
});
|
|
18540
|
-
}
|
|
18541
|
-
createMethod(logLevel, ansiFormatter) {
|
|
18542
|
-
this[logLevel] = (message) => {
|
|
18543
|
-
if (this.shouldPrintMessage(logLevel)) {
|
|
18544
|
-
console.log(`${ansiFormatter}${message}\x1b[0m\u001b[0m`);
|
|
18545
|
-
}
|
|
18546
|
-
};
|
|
18547
|
-
}
|
|
18548
|
-
}
|
|
18549
|
-
var consoleLogFormatted = new ConsoleLogFormatted();
|
|
18550
|
-
|
|
18551
|
-
const getFileData = (file, contentType, filename) => {
|
|
18552
|
-
const byte_size = file.byteLength;
|
|
18553
|
-
const checksum = crypto.createHash("md5").update(file).digest("base64");
|
|
18554
|
-
return { file, filename, checksum, byte_size, content_type: contentType };
|
|
18555
|
-
};
|
|
18556
|
-
|
|
18557
|
-
const create$1 = (payload) => axios.post(`${API_BASE_URL}/reporter/runs`, {
|
|
18558
|
-
run: payload,
|
|
18559
|
-
});
|
|
18560
|
-
const update = (ciBuildId, payload) => axios.put(`${API_BASE_URL}/reporter/runs/${ciBuildId}`, {
|
|
18561
|
-
run: payload,
|
|
18562
|
-
});
|
|
18563
|
-
const heartbeat = (ciBuildId) => axios.get(`${API_BASE_URL}/reporter/runs/${ciBuildId}/heartbeat`);
|
|
18564
|
-
const runsApi = { create: create$1, update, heartbeat };
|
|
18565
|
-
|
|
18566
18573
|
const executeCommandLine = ({ command, messageOnError, shouldThrowError = false, logLevel = "warning", }) => {
|
|
18567
18574
|
try {
|
|
18568
18575
|
return childProcess.execSync(command).toString().trim();
|
|
@@ -18602,7 +18609,7 @@ const getTestData = (test, rootDir) => {
|
|
|
18602
18609
|
const spec = file.replace(`${rootDir}/`, "");
|
|
18603
18610
|
const describe = getDescribePath({ titlePath, title, spec, project });
|
|
18604
18611
|
const historyId = generateHistoryId(project !== null && project !== void 0 ? project : "", spec, describe, title);
|
|
18605
|
-
return { title, describe, project, spec,
|
|
18612
|
+
return { title, describe, project, spec, historyId };
|
|
18606
18613
|
};
|
|
18607
18614
|
const getInitializerData = ({ rootDir }, rootSuite) => uniq(rootSuite.allTests().map(test => getTestData(test, rootDir)));
|
|
18608
18615
|
const sendHeartBeatSignal = async (ciBuildId) => {
|
|
@@ -18616,13 +18623,209 @@ const sendHeartBeatSignal = async (ciBuildId) => {
|
|
|
18616
18623
|
}
|
|
18617
18624
|
};
|
|
18618
18625
|
|
|
18626
|
+
const onBegin = async (self, config, rootSuite) => {
|
|
18627
|
+
var _a, _b, _c, _d;
|
|
18628
|
+
const shard = config.shard;
|
|
18629
|
+
self.config = config;
|
|
18630
|
+
const currentShard = (self.currentShard = (_a = shard === null || shard === void 0 ? void 0 : shard.current) !== null && _a !== void 0 ? _a : 0);
|
|
18631
|
+
let attempts = {};
|
|
18632
|
+
self.totalTestCount = rootSuite.allTests().length;
|
|
18633
|
+
self.rootDir = config.rootDir;
|
|
18634
|
+
try {
|
|
18635
|
+
const runDetails = {
|
|
18636
|
+
commitId: getCurrentCommitSha(),
|
|
18637
|
+
commitName: getCurrentCommitMessage(),
|
|
18638
|
+
author: getCurrentAuthor(),
|
|
18639
|
+
branch: getCurrentBranch(),
|
|
18640
|
+
tags: typeof self.tags === "string" ? self.tags.split(",") : self.tags,
|
|
18641
|
+
ciBuildId: self.ciBuildId,
|
|
18642
|
+
configuration: config,
|
|
18643
|
+
shards: createShardObject({ currentShard }),
|
|
18644
|
+
};
|
|
18645
|
+
await runsApi.create(runDetails);
|
|
18646
|
+
self.hasRunStarted = true;
|
|
18647
|
+
if (self.totalTestCount === 0) {
|
|
18648
|
+
consoleLogFormatted.error(ERRORS.onBegin.noTestsToReport);
|
|
18649
|
+
return;
|
|
18650
|
+
}
|
|
18651
|
+
({ data: attempts } = await testEntitiesApi.create(self.ciBuildId, {
|
|
18652
|
+
testEntities: getInitializerData(config, rootSuite),
|
|
18653
|
+
shard: currentShard,
|
|
18654
|
+
}));
|
|
18655
|
+
}
|
|
18656
|
+
catch (error) {
|
|
18657
|
+
const axiosError = error;
|
|
18658
|
+
const data = (_b = axiosError.response) === null || _b === void 0 ? void 0 : _b.data;
|
|
18659
|
+
consoleLogFormatted.error((_c = data === null || data === void 0 ? void 0 : data.error) !== null && _c !== void 0 ? _c : axiosError.message);
|
|
18660
|
+
consoleLogFormatted.error(ERRORS.onBegin.failedToInitializeRun);
|
|
18661
|
+
if (((_d = axiosError.response) === null || _d === void 0 ? void 0 : _d.status) === 422) {
|
|
18662
|
+
process.kill(process.pid, "SIGKILL");
|
|
18663
|
+
process.exit(1);
|
|
18664
|
+
}
|
|
18665
|
+
}
|
|
18666
|
+
consoleLogFormatted.underline(MESSAGES.onBegin.testStarted);
|
|
18667
|
+
consoleLogFormatted.bold(MESSAGES.onBegin.ciBuildId(self.ciBuildId));
|
|
18668
|
+
if (shard) {
|
|
18669
|
+
consoleLogFormatted.bold(MESSAGES.onBegin.totalShards(shard.total));
|
|
18670
|
+
consoleLogFormatted.bold(MESSAGES.onBegin.currentShard(currentShard));
|
|
18671
|
+
}
|
|
18672
|
+
await sendHeartBeatSignal(self.ciBuildId);
|
|
18673
|
+
self.heartbeatInterval = setInterval(async () => await sendHeartBeatSignal(self.ciBuildId), 45000);
|
|
18674
|
+
self.attempts = attempts;
|
|
18675
|
+
};
|
|
18676
|
+
|
|
18677
|
+
const onEnd = async (self, { status, duration }) => {
|
|
18678
|
+
var _a, _b;
|
|
18679
|
+
try {
|
|
18680
|
+
const { currentShard } = self;
|
|
18681
|
+
await waitUntilCondition(() => self.hasRunStarted && self.unreportedAttemptCount === 0);
|
|
18682
|
+
await Promise.allSettled(self.testResultCalls);
|
|
18683
|
+
await runsApi.update(self.ciBuildId, {
|
|
18684
|
+
shards: createShardObject({
|
|
18685
|
+
currentShard,
|
|
18686
|
+
status,
|
|
18687
|
+
duration,
|
|
18688
|
+
}),
|
|
18689
|
+
});
|
|
18690
|
+
}
|
|
18691
|
+
catch (error) {
|
|
18692
|
+
const data = (_a = error.response) === null || _a === void 0 ? void 0 : _a.data;
|
|
18693
|
+
consoleLogFormatted.error((_b = data.error) !== null && _b !== void 0 ? _b : error.message);
|
|
18694
|
+
consoleLogFormatted.error(ERRORS.onEnd.failedToReportRunStatus);
|
|
18695
|
+
}
|
|
18696
|
+
finally {
|
|
18697
|
+
consoleLogFormatted.invertBackground(MESSAGES.onEnd.runReported);
|
|
18698
|
+
clearInterval(self.heartbeatInterval);
|
|
18699
|
+
}
|
|
18700
|
+
};
|
|
18701
|
+
|
|
18702
|
+
const create = (ciBuildId, historyId, payload) => axios.post(`${API_BASE_URL}/reporter/runs/${ciBuildId}/test_entities/${historyId}/attempts`, payload);
|
|
18703
|
+
const update = (ciBuildId, historyId, id, payload) => axios.put(`${API_BASE_URL}/reporter/runs/${ciBuildId}/test_entities/${historyId}/attempts/${id}`, payload);
|
|
18704
|
+
const attemptsApi = { create, update };
|
|
18705
|
+
|
|
18706
|
+
const FIRST_ATTEMPT_INDEX = "0-0";
|
|
18707
|
+
|
|
18708
|
+
const onTestBegin = async (self, test, { retry }) => {
|
|
18709
|
+
const { title, repeatEachIndex } = test;
|
|
18710
|
+
const { rootDir, currentShard, ciBuildId } = self;
|
|
18711
|
+
const { historyId } = getTestData(test, rootDir);
|
|
18712
|
+
const startedAt = new Date();
|
|
18713
|
+
const attemptIndex = joinHyphenCase(retry, repeatEachIndex);
|
|
18714
|
+
self.unreportedAttemptCount++;
|
|
18715
|
+
consoleLogFormatted.invertBackground(MESSAGES.onTestBegin.startingTest(title, historyId));
|
|
18716
|
+
try {
|
|
18717
|
+
await waitUntilCondition(() => { var _a, _b; return (_b = (_a = self.attempts) === null || _a === void 0 ? void 0 : _a[historyId]) === null || _b === void 0 ? void 0 : _b[FIRST_ATTEMPT_INDEX]; });
|
|
18718
|
+
const attemptsPayload = {
|
|
18719
|
+
status: "running",
|
|
18720
|
+
startedAt: startedAt.toString(),
|
|
18721
|
+
shard: currentShard,
|
|
18722
|
+
repeatEachIndex,
|
|
18723
|
+
};
|
|
18724
|
+
if (retry === 0 && repeatEachIndex === 0) {
|
|
18725
|
+
await attemptsApi.update(ciBuildId, historyId, self.attempts[historyId][FIRST_ATTEMPT_INDEX], attemptsPayload);
|
|
18726
|
+
}
|
|
18727
|
+
else {
|
|
18728
|
+
const { data: { attemptId: attemptId }, } = await attemptsApi.create(ciBuildId, historyId, attemptsPayload);
|
|
18729
|
+
self.attempts = mergeDeepRight(self.attempts, {
|
|
18730
|
+
[historyId]: { [attemptIndex]: attemptId },
|
|
18731
|
+
});
|
|
18732
|
+
}
|
|
18733
|
+
}
|
|
18734
|
+
catch (error) {
|
|
18735
|
+
consoleLogFormatted.error(error.message);
|
|
18736
|
+
consoleLogFormatted.error(ERRORS.onTestBegin.failedToReportTest(title, historyId));
|
|
18737
|
+
await sendHeartBeatSignal(ciBuildId);
|
|
18738
|
+
}
|
|
18739
|
+
};
|
|
18740
|
+
|
|
18741
|
+
const getDirectUploadURL = (payload) => axios.post(`${API_BASE_URL}/reporter/direct_uploads`, {
|
|
18742
|
+
blob: payload,
|
|
18743
|
+
});
|
|
18744
|
+
const uploadToS3 = (data, { url, headers }) => axios.put(url, data, { headers });
|
|
18745
|
+
|
|
18746
|
+
const VALID_ASSET_TYPES = ["screenshot", "video", "trace"];
|
|
18747
|
+
const generateErrorReport = ({ location, snippet }) => {
|
|
18748
|
+
if (!location)
|
|
18749
|
+
return null;
|
|
18750
|
+
const { file, line, column } = location;
|
|
18751
|
+
return `# ${file} (${line}:${column}) \n\n\n ${snippet}`;
|
|
18752
|
+
};
|
|
18753
|
+
|
|
18754
|
+
const getFileData = (file, contentType, filename) => {
|
|
18755
|
+
const byteSize = file.byteLength;
|
|
18756
|
+
const checksum = crypto.createHash("md5").update(file).digest("base64");
|
|
18757
|
+
return { file, filename, checksum, byteSize, contentType };
|
|
18758
|
+
};
|
|
18759
|
+
const generateFileName = (path, name, contentType) => path !== null && path !== void 0 ? path : `${name}.${contentType.split("/")[1]}`;
|
|
18760
|
+
|
|
18619
18761
|
const evaluateStatus = (outcome, status) => {
|
|
18620
18762
|
if (outcome === "flaky")
|
|
18621
18763
|
return outcome;
|
|
18622
|
-
|
|
18623
|
-
return status;
|
|
18764
|
+
return status;
|
|
18624
18765
|
};
|
|
18625
18766
|
|
|
18767
|
+
const onTestEnd = async (self, testCase, { status, duration, errors, error, retry, attachments }) => {
|
|
18768
|
+
const completedAt = new Date();
|
|
18769
|
+
const { currentShard, rootDir, ciBuildId } = self;
|
|
18770
|
+
const { title, repeatEachIndex } = testCase;
|
|
18771
|
+
const { historyId } = getTestData(testCase, rootDir);
|
|
18772
|
+
const testOutcome = testCase.outcome();
|
|
18773
|
+
const attemptIndex = joinHyphenCase(retry, repeatEachIndex);
|
|
18774
|
+
const outcome = {
|
|
18775
|
+
shard: currentShard,
|
|
18776
|
+
isExpected: testOutcome === "expected",
|
|
18777
|
+
repeatEachIndex: repeatEachIndex,
|
|
18778
|
+
status: evaluateStatus(testOutcome, status),
|
|
18779
|
+
};
|
|
18780
|
+
try {
|
|
18781
|
+
const testResult = {
|
|
18782
|
+
outcome,
|
|
18783
|
+
status,
|
|
18784
|
+
duration,
|
|
18785
|
+
log: errors.map(error => { var _a; return (_a = error.message) !== null && _a !== void 0 ? _a : ""; }).join("\n"),
|
|
18786
|
+
screenshots: [],
|
|
18787
|
+
videos: [],
|
|
18788
|
+
traces: [],
|
|
18789
|
+
completedAt: completedAt.toString(),
|
|
18790
|
+
errorSnippet: error && generateErrorReport(error),
|
|
18791
|
+
};
|
|
18792
|
+
consoleLogFormatted.underline(title);
|
|
18793
|
+
await Promise.all(attachments.map(async ({ name, path, body, contentType, }) => {
|
|
18794
|
+
consoleLogFormatted.bold(`${name}: ${path}`);
|
|
18795
|
+
if (VALID_ASSET_TYPES.includes(name)) {
|
|
18796
|
+
const buffer = path ? readFileSync(path) : body;
|
|
18797
|
+
const fileName = generateFileName(path, name, contentType);
|
|
18798
|
+
const { file, ...metadata } = getFileData(buffer, contentType, fileName);
|
|
18799
|
+
const { data: { signedId, directUpload }, } = await getDirectUploadURL(metadata);
|
|
18800
|
+
testResult[`${name}s`].push(signedId);
|
|
18801
|
+
return uploadToS3(file, directUpload);
|
|
18802
|
+
}
|
|
18803
|
+
}));
|
|
18804
|
+
await waitUntilCondition(() => self.attempts[historyId][attemptIndex]);
|
|
18805
|
+
const reportToNeetoPlaydash = attemptsApi.update(ciBuildId, historyId, self.attempts[historyId][attemptIndex], testResult);
|
|
18806
|
+
self.testResultCalls.push(reportToNeetoPlaydash);
|
|
18807
|
+
await reportToNeetoPlaydash;
|
|
18808
|
+
consoleLogFormatted.bold(MESSAGES.onTestEnd.reportedTest({ title, status, historyId, completedAt }));
|
|
18809
|
+
}
|
|
18810
|
+
catch (error) {
|
|
18811
|
+
consoleLogFormatted.error(error.message);
|
|
18812
|
+
consoleLogFormatted.error(ERRORS.onTestBegin.failedToReportTest(title, historyId));
|
|
18813
|
+
await sendHeartBeatSignal(ciBuildId);
|
|
18814
|
+
}
|
|
18815
|
+
finally {
|
|
18816
|
+
self.unreportedAttemptCount--;
|
|
18817
|
+
}
|
|
18818
|
+
};
|
|
18819
|
+
|
|
18820
|
+
const HEADERS_KEYS = {
|
|
18821
|
+
applicationKey: "Application-Key",
|
|
18822
|
+
contentType: "Content-Type",
|
|
18823
|
+
accept: "Accept",
|
|
18824
|
+
apiKey: "X-Api-Key",
|
|
18825
|
+
projectKey: "Project-Key",
|
|
18826
|
+
};
|
|
18827
|
+
|
|
18828
|
+
const isValidObject = (data) => data === Object(data) && !(data instanceof Buffer);
|
|
18626
18829
|
const setAuthHeaders = ({ projectKey, apiKey, }) => {
|
|
18627
18830
|
axios.defaults.headers = {
|
|
18628
18831
|
...axios.defaults.headers,
|
|
@@ -18634,204 +18837,53 @@ const setAuthHeaders = ({ projectKey, apiKey, }) => {
|
|
|
18634
18837
|
};
|
|
18635
18838
|
function initializeAxios({ projectKey, baseURL, apiKey, }) {
|
|
18636
18839
|
axios.defaults.baseURL = baseURL !== null && baseURL !== void 0 ? baseURL : "https://connect.neetoplaydash.com";
|
|
18840
|
+
axios.defaults.skipSnakeCaseConversion = false;
|
|
18637
18841
|
setAuthHeaders({ projectKey, apiKey });
|
|
18842
|
+
axios.interceptors.request.use(config => {
|
|
18843
|
+
const data = config.data;
|
|
18844
|
+
if (isValidObject(data) && !config.skipSnakeCaseConversion) {
|
|
18845
|
+
config.data = keysToSnakeCase(data);
|
|
18846
|
+
}
|
|
18847
|
+
return config;
|
|
18848
|
+
});
|
|
18849
|
+
axios.interceptors.response.use(config => {
|
|
18850
|
+
const data = config.data;
|
|
18851
|
+
if (isValidObject(data)) {
|
|
18852
|
+
config.data = keysToCamelCase(data);
|
|
18853
|
+
}
|
|
18854
|
+
return config;
|
|
18855
|
+
});
|
|
18638
18856
|
}
|
|
18639
18857
|
|
|
18640
|
-
const
|
|
18641
|
-
|
|
18642
|
-
|
|
18643
|
-
|
|
18858
|
+
const initialize = (self, options) => {
|
|
18859
|
+
initializeAxios(options);
|
|
18860
|
+
process.on("unhandledRejection", async (error) => {
|
|
18861
|
+
var _a, _b, _c;
|
|
18862
|
+
const data = (_a = error.response) === null || _a === void 0 ? void 0 : _a.data;
|
|
18863
|
+
await sendHeartBeatSignal(self.ciBuildId);
|
|
18864
|
+
consoleLogFormatted.error((_c = (_b = data === null || data === void 0 ? void 0 : data.error) !== null && _b !== void 0 ? _b : error.message) !== null && _c !== void 0 ? _c : JSON.stringify(error));
|
|
18865
|
+
});
|
|
18866
|
+
};
|
|
18644
18867
|
|
|
18645
|
-
class
|
|
18868
|
+
class NeetoPlaywrightReporter {
|
|
18646
18869
|
constructor(options) {
|
|
18647
|
-
this.onBegin =
|
|
18648
|
-
|
|
18649
|
-
|
|
18650
|
-
|
|
18651
|
-
this.currentShard = shard === null || shard === void 0 ? void 0 : shard.current;
|
|
18652
|
-
let attempts = {};
|
|
18653
|
-
this.totalTestCount = rootSuite.allTests().length;
|
|
18654
|
-
this.rootDir = config.rootDir;
|
|
18655
|
-
try {
|
|
18656
|
-
const runDetails = {
|
|
18657
|
-
commit_id: getCurrentCommitSha(),
|
|
18658
|
-
commit_name: getCurrentCommitMessage(),
|
|
18659
|
-
author: getCurrentAuthor(),
|
|
18660
|
-
branch: getCurrentBranch(),
|
|
18661
|
-
tags: typeof this.tags === "string" ? this.tags.split(",") : this.tags,
|
|
18662
|
-
ci_build_id: this.ciBuildId,
|
|
18663
|
-
configuration: config,
|
|
18664
|
-
shards: createShardObject({ currentShard: shard === null || shard === void 0 ? void 0 : shard.current }),
|
|
18665
|
-
};
|
|
18666
|
-
await runsApi.create(runDetails);
|
|
18667
|
-
this.hasRunStarted = true;
|
|
18668
|
-
if (this.totalTestCount === 0) {
|
|
18669
|
-
consoleLogFormatted.error(ERRORS.onBegin.noTestsToReport);
|
|
18670
|
-
return;
|
|
18671
|
-
}
|
|
18672
|
-
({ data: attempts } = await testEntitiesApi.create(this.ciBuildId, {
|
|
18673
|
-
test_entities: getInitializerData(config, rootSuite),
|
|
18674
|
-
shard: (_a = this.currentShard) !== null && _a !== void 0 ? _a : 0,
|
|
18675
|
-
}));
|
|
18676
|
-
}
|
|
18677
|
-
catch (error) {
|
|
18678
|
-
const axiosError = error;
|
|
18679
|
-
const data = (_b = axiosError.response) === null || _b === void 0 ? void 0 : _b.data;
|
|
18680
|
-
consoleLogFormatted.error((_c = data === null || data === void 0 ? void 0 : data.error) !== null && _c !== void 0 ? _c : axiosError.message);
|
|
18681
|
-
consoleLogFormatted.error(ERRORS.onBegin.failedToInitializeRun);
|
|
18682
|
-
process.kill(process.pid, "SIGKILL");
|
|
18683
|
-
((_d = axiosError.response) === null || _d === void 0 ? void 0 : _d.status) === 422 && process.exit(1);
|
|
18684
|
-
}
|
|
18685
|
-
consoleLogFormatted.underline(MESSAGES.onBegin.testStarted);
|
|
18686
|
-
consoleLogFormatted.dim(MESSAGES.onBegin.ciBuildId(this.ciBuildId));
|
|
18687
|
-
if (shard) {
|
|
18688
|
-
consoleLogFormatted.dim(MESSAGES.onBegin.totalShards(shard.total));
|
|
18689
|
-
consoleLogFormatted.dim(MESSAGES.onBegin.currentShard(shard.current));
|
|
18690
|
-
}
|
|
18691
|
-
await sendHeartBeatSignal(this.ciBuildId);
|
|
18692
|
-
this.heartbeatInterval = setInterval(async () => await sendHeartBeatSignal(this.ciBuildId), 45000);
|
|
18693
|
-
this.attempts = attempts;
|
|
18694
|
-
};
|
|
18695
|
-
this.onTestBegin = async (test, { retry }) => {
|
|
18696
|
-
var _a, _b;
|
|
18697
|
-
const { title, repeatEachIndex } = test;
|
|
18698
|
-
const { history_id: id } = getTestData(test, this.rootDir);
|
|
18699
|
-
this.retryAttemptStartedAt = new Date();
|
|
18700
|
-
this.unreportedAttemptCount++;
|
|
18701
|
-
consoleLogFormatted.invertBackground(MESSAGES.onTestBegin.startingTest(title));
|
|
18702
|
-
try {
|
|
18703
|
-
while (!((_b = (_a = this.attempts) === null || _a === void 0 ? void 0 : _a[id]) === null || _b === void 0 ? void 0 : _b["0-0"]))
|
|
18704
|
-
await waitUntilTimeout(100); // Poll every 100 milliseconds
|
|
18705
|
-
const attemptsPayload = {
|
|
18706
|
-
status: "running",
|
|
18707
|
-
started_at: this.retryAttemptStartedAt.toString(),
|
|
18708
|
-
shard: this.currentShard,
|
|
18709
|
-
repeat_each_index: repeatEachIndex,
|
|
18710
|
-
};
|
|
18711
|
-
if (retry === 0 && repeatEachIndex === 0) {
|
|
18712
|
-
await attemptsApi.update(this.ciBuildId, id, this.attempts[id]["0-0"], attemptsPayload);
|
|
18713
|
-
}
|
|
18714
|
-
else {
|
|
18715
|
-
const { data: { history_id, attempt_id }, } = await attemptsApi.create(this.ciBuildId, id, attemptsPayload);
|
|
18716
|
-
this.attempts = {
|
|
18717
|
-
...this.attempts,
|
|
18718
|
-
[history_id]: {
|
|
18719
|
-
...this.attempts[history_id],
|
|
18720
|
-
[`${retry}-${repeatEachIndex}`]: attempt_id,
|
|
18721
|
-
},
|
|
18722
|
-
};
|
|
18723
|
-
this.testAttemptIds.push(`${history_id}-${attempt_id}`);
|
|
18724
|
-
}
|
|
18725
|
-
}
|
|
18726
|
-
catch (error) {
|
|
18727
|
-
consoleLogFormatted.error(error.message);
|
|
18728
|
-
consoleLogFormatted.error(ERRORS.onTestBegin.failedToReportTest(title, id));
|
|
18729
|
-
await sendHeartBeatSignal(this.ciBuildId);
|
|
18730
|
-
}
|
|
18731
|
-
};
|
|
18732
|
-
this.onTestEnd = async (testCase, { status, duration, errors, error, retry, attachments }) => {
|
|
18733
|
-
var _a;
|
|
18734
|
-
const completedAt = new Date();
|
|
18735
|
-
const errorLocation = error === null || error === void 0 ? void 0 : error.location;
|
|
18736
|
-
const { title, repeatEachIndex } = testCase;
|
|
18737
|
-
const { history_id: id } = getTestData(testCase, this.rootDir);
|
|
18738
|
-
const testOutcome = testCase.outcome();
|
|
18739
|
-
const outcome = {
|
|
18740
|
-
shard: (_a = this.currentShard) !== null && _a !== void 0 ? _a : 0,
|
|
18741
|
-
is_expected: testOutcome === "expected",
|
|
18742
|
-
repeat_each_index: repeatEachIndex,
|
|
18743
|
-
status: evaluateStatus(testOutcome, status),
|
|
18744
|
-
};
|
|
18745
|
-
try {
|
|
18746
|
-
const testResult = {
|
|
18747
|
-
outcome,
|
|
18748
|
-
status,
|
|
18749
|
-
duration,
|
|
18750
|
-
log: errors.map(error => { var _a; return (_a = error.message) !== null && _a !== void 0 ? _a : ""; }).join("\n"),
|
|
18751
|
-
screenshots: [],
|
|
18752
|
-
videos: [],
|
|
18753
|
-
traces: [],
|
|
18754
|
-
completed_at: completedAt.toString(),
|
|
18755
|
-
error_snippet: errorLocation
|
|
18756
|
-
? `# ${errorLocation === null || errorLocation === void 0 ? void 0 : errorLocation.file} (${errorLocation === null || errorLocation === void 0 ? void 0 : errorLocation.line}:${errorLocation === null || errorLocation === void 0 ? void 0 : errorLocation.column}) \n\n\n ${error === null || error === void 0 ? void 0 : error.snippet}`
|
|
18757
|
-
: null,
|
|
18758
|
-
};
|
|
18759
|
-
consoleLogFormatted.underline(title);
|
|
18760
|
-
await Promise.all(attachments.map(async ({ name, path, body, contentType, }) => {
|
|
18761
|
-
consoleLogFormatted.dim(`${name}: ${path}`);
|
|
18762
|
-
if (VALID_ASSET_TYPES.includes(name)) {
|
|
18763
|
-
const buffer = path ? require$$6.readFileSync(path) : body;
|
|
18764
|
-
const fileName = path
|
|
18765
|
-
? path.split("/").slice(-1)[0]
|
|
18766
|
-
: `${name}.${contentType.split("/").slice(-1)[0]}`;
|
|
18767
|
-
const { file, ...metadata } = getFileData(buffer, contentType, fileName);
|
|
18768
|
-
const { data: { signed_id, direct_upload }, } = await getDirectUploadURL(metadata);
|
|
18769
|
-
const pluralizedAsset = `${name}s`;
|
|
18770
|
-
testResult[pluralizedAsset].push(signed_id);
|
|
18771
|
-
return uploadToS3(file, direct_upload);
|
|
18772
|
-
}
|
|
18773
|
-
}));
|
|
18774
|
-
while (!(this.attempts[id][`${retry}-${repeatEachIndex}`] ||
|
|
18775
|
-
this.testAttemptIds.includes(`${id}-${this.attempts[id][`${retry}-${repeatEachIndex}`]}`)))
|
|
18776
|
-
await waitUntilTimeout(100);
|
|
18777
|
-
const reportToNeetoPlaydash = attemptsApi.update(this.ciBuildId, id, this.attempts[id][`${retry}-${repeatEachIndex}`], testResult);
|
|
18778
|
-
this.testResultCalls.push(reportToNeetoPlaydash);
|
|
18779
|
-
await reportToNeetoPlaydash;
|
|
18780
|
-
consoleLogFormatted.invertBackground(MESSAGES.onTestEnd.reportedTest({ title, status, id }));
|
|
18781
|
-
}
|
|
18782
|
-
catch (error) {
|
|
18783
|
-
consoleLogFormatted.error(error.message);
|
|
18784
|
-
consoleLogFormatted.error(ERRORS.onTestBegin.failedToReportTest(title, id));
|
|
18785
|
-
await sendHeartBeatSignal(this.ciBuildId);
|
|
18786
|
-
}
|
|
18787
|
-
finally {
|
|
18788
|
-
this.unreportedAttemptCount--;
|
|
18789
|
-
}
|
|
18790
|
-
};
|
|
18791
|
-
this.onEnd = async ({ status, duration }) => {
|
|
18792
|
-
var _a, _b;
|
|
18793
|
-
try {
|
|
18794
|
-
while (!(this.hasRunStarted && this.unreportedAttemptCount === 0))
|
|
18795
|
-
await waitUntilTimeout(100);
|
|
18796
|
-
await Promise.allSettled(this.testResultCalls);
|
|
18797
|
-
await runsApi.update(this.ciBuildId, {
|
|
18798
|
-
shards: createShardObject({
|
|
18799
|
-
currentShard: this.currentShard,
|
|
18800
|
-
status,
|
|
18801
|
-
duration,
|
|
18802
|
-
}),
|
|
18803
|
-
});
|
|
18804
|
-
}
|
|
18805
|
-
catch (error) {
|
|
18806
|
-
const data = (_a = error.response) === null || _a === void 0 ? void 0 : _a.data;
|
|
18807
|
-
consoleLogFormatted.error((_b = data.error) !== null && _b !== void 0 ? _b : error.message);
|
|
18808
|
-
consoleLogFormatted.error(ERRORS.onEnd.failedToReportRunStatus);
|
|
18809
|
-
}
|
|
18810
|
-
finally {
|
|
18811
|
-
consoleLogFormatted.invertBackground(MESSAGES.onEnd.runReported);
|
|
18812
|
-
clearInterval(this.heartbeatInterval);
|
|
18813
|
-
}
|
|
18814
|
-
};
|
|
18815
|
-
initializeAxios(options);
|
|
18870
|
+
this.onBegin = (config, rootSuite) => onBegin(this, config, rootSuite);
|
|
18871
|
+
this.onTestBegin = (test, result) => onTestBegin(this, test, result);
|
|
18872
|
+
this.onTestEnd = async (testCase, testResult) => onTestEnd(this, testCase, testResult);
|
|
18873
|
+
this.onEnd = async (fullResult) => onEnd(this, fullResult);
|
|
18816
18874
|
this.attempts = {};
|
|
18817
18875
|
this.tags = options.tags;
|
|
18818
18876
|
this.ciBuildId = options.ciBuildId;
|
|
18819
|
-
this.retryAttemptStartedAt = new Date();
|
|
18820
18877
|
this.testResultCalls = [];
|
|
18821
18878
|
this.totalTestCount = 0;
|
|
18822
18879
|
this.unreportedAttemptCount = 0;
|
|
18823
18880
|
this.hasRunStarted = false;
|
|
18824
|
-
this.testAttemptIds = [];
|
|
18825
18881
|
this.heartbeatInterval = null;
|
|
18826
18882
|
this.rootDir = "";
|
|
18827
|
-
|
|
18828
|
-
|
|
18829
|
-
const data = (_a = error.response) === null || _a === void 0 ? void 0 : _a.data;
|
|
18830
|
-
await sendHeartBeatSignal(this.ciBuildId);
|
|
18831
|
-
consoleLogFormatted.error((_c = (_b = data === null || data === void 0 ? void 0 : data.error) !== null && _b !== void 0 ? _b : error.message) !== null && _c !== void 0 ? _c : JSON.stringify(error));
|
|
18832
|
-
});
|
|
18883
|
+
this.currentShard = 0;
|
|
18884
|
+
initialize(this, options);
|
|
18833
18885
|
}
|
|
18834
18886
|
}
|
|
18835
18887
|
|
|
18836
|
-
export {
|
|
18888
|
+
export { NeetoPlaywrightReporter as default };
|
|
18837
18889
|
//# sourceMappingURL=index.js.map
|