@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.js CHANGED
@@ -1,19 +1,117 @@
1
- import require$$6 from 'fs';
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 crypto from 'crypto';
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
- __proto__: null,
13414
- hasBrowserEnv: hasBrowserEnv,
13415
- hasStandardBrowserWebWorkerEnv: hasStandardBrowserWebWorkerEnv,
13416
- hasStandardBrowserEnv: hasStandardBrowserEnv
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 = (ciBuildId, history_id, payload) => axios.post(`${API_BASE_URL}/reporter/runs/${ciBuildId}/test_entities/${history_id}/attempts`, payload);
18445
- const update$1 = (ciBuildId, history_id, id, payload) => axios.put(`${API_BASE_URL}/reporter/runs/${ciBuildId}/test_entities/${history_id}/attempts/${id}`, payload);
18446
- const attemptsApi = { create: create$2, update: update$1 };
18447
-
18448
- const getDirectUploadURL = (payload) => axios.post(`${API_BASE_URL}/reporter/direct_uploads`, {
18449
- blob: payload,
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 uploadToS3 = (data, { url, headers }) => axios.put(url, data, { headers });
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 ERRORS = {
18456
- onBegin: {
18457
- noTestsToReport: "No tests to report",
18458
- failedToGetCommitSha: "Failed to get current commit SHA.",
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) => `Starting ${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, id }) => `Reported ${title} with id: ${id} to NeetoPlaydash with status ${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, history_id: historyId };
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
- else
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 create = (ciBuildId, payload) => axios.post(`${API_BASE_URL}/reporter/runs/${ciBuildId}/test_entities`, {
18641
- test_entity: payload,
18642
- });
18643
- const testEntitiesApi = { create };
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 MyReporter {
18868
+ class NeetoPlaywrightReporter {
18646
18869
  constructor(options) {
18647
- this.onBegin = async (config, rootSuite) => {
18648
- var _a, _b, _c, _d;
18649
- const shard = config.shard;
18650
- this.config = config;
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
- process.on("unhandledRejection", async (error) => {
18828
- var _a, _b, _c;
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 { MyReporter as default };
18888
+ export { NeetoPlaywrightReporter as default };
18837
18889
  //# sourceMappingURL=index.js.map