@supatest/playwright-reporter 0.0.2 → 0.0.4
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/dist/index.cjs +215 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -1
- package/dist/index.d.ts +35 -1
- package/dist/index.js +211 -38
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
ErrorCollector: () => ErrorCollector,
|
|
33
34
|
default: () => SupatestPlaywrightReporter
|
|
34
35
|
});
|
|
35
36
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -173,6 +174,117 @@ var SupatestApiClient = class {
|
|
|
173
174
|
}
|
|
174
175
|
};
|
|
175
176
|
|
|
177
|
+
// src/error-collector.ts
|
|
178
|
+
var ErrorCollector = class {
|
|
179
|
+
errors = [];
|
|
180
|
+
recordError(category, message, context) {
|
|
181
|
+
const error = {
|
|
182
|
+
category,
|
|
183
|
+
message,
|
|
184
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
185
|
+
testId: context?.testId,
|
|
186
|
+
testTitle: context?.testTitle,
|
|
187
|
+
attachmentName: context?.attachmentName,
|
|
188
|
+
filePath: context?.filePath,
|
|
189
|
+
originalError: context?.error instanceof Error ? context.error.stack : void 0
|
|
190
|
+
};
|
|
191
|
+
this.errors.push(error);
|
|
192
|
+
}
|
|
193
|
+
getSummary() {
|
|
194
|
+
const byCategory = {
|
|
195
|
+
RUN_CREATE: 0,
|
|
196
|
+
TEST_SUBMISSION: 0,
|
|
197
|
+
ATTACHMENT_SIGN: 0,
|
|
198
|
+
ATTACHMENT_UPLOAD: 0,
|
|
199
|
+
FILE_READ: 0,
|
|
200
|
+
RUN_COMPLETE: 0
|
|
201
|
+
};
|
|
202
|
+
for (const error of this.errors) {
|
|
203
|
+
byCategory[error.category]++;
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
totalErrors: this.errors.length,
|
|
207
|
+
byCategory,
|
|
208
|
+
errors: this.errors
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
hasErrors() {
|
|
212
|
+
return this.errors.length > 0;
|
|
213
|
+
}
|
|
214
|
+
formatSummary() {
|
|
215
|
+
if (this.errors.length === 0) {
|
|
216
|
+
return "";
|
|
217
|
+
}
|
|
218
|
+
const summary = this.getSummary();
|
|
219
|
+
const lines = [
|
|
220
|
+
"",
|
|
221
|
+
"\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",
|
|
222
|
+
"\u26A0\uFE0F Supatest Reporter - Error Summary",
|
|
223
|
+
"\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",
|
|
224
|
+
`Total Errors: ${summary.totalErrors}`,
|
|
225
|
+
""
|
|
226
|
+
];
|
|
227
|
+
for (const [category, count] of Object.entries(summary.byCategory)) {
|
|
228
|
+
if (count === 0) continue;
|
|
229
|
+
const icon = this.getCategoryIcon(category);
|
|
230
|
+
const label = this.getCategoryLabel(category);
|
|
231
|
+
lines.push(`${icon} ${label}: ${count}`);
|
|
232
|
+
const categoryErrors = this.errors.filter((e) => e.category === category).slice(0, 3);
|
|
233
|
+
for (const error of categoryErrors) {
|
|
234
|
+
lines.push(` \u2022 ${this.formatError(error)}`);
|
|
235
|
+
}
|
|
236
|
+
const remaining = count - categoryErrors.length;
|
|
237
|
+
if (remaining > 0) {
|
|
238
|
+
lines.push(` ... and ${remaining} more`);
|
|
239
|
+
}
|
|
240
|
+
lines.push("");
|
|
241
|
+
}
|
|
242
|
+
lines.push(
|
|
243
|
+
"\u2139\uFE0F Note: Test execution was not interrupted. Some results may not be visible in the dashboard."
|
|
244
|
+
);
|
|
245
|
+
lines.push(
|
|
246
|
+
"\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"
|
|
247
|
+
);
|
|
248
|
+
return lines.join("\n");
|
|
249
|
+
}
|
|
250
|
+
getCategoryIcon(category) {
|
|
251
|
+
const icons = {
|
|
252
|
+
RUN_CREATE: "\u{1F680}",
|
|
253
|
+
TEST_SUBMISSION: "\u{1F4DD}",
|
|
254
|
+
ATTACHMENT_SIGN: "\u{1F510}",
|
|
255
|
+
ATTACHMENT_UPLOAD: "\u{1F4CE}",
|
|
256
|
+
FILE_READ: "\u{1F4C1}",
|
|
257
|
+
RUN_COMPLETE: "\u{1F3C1}"
|
|
258
|
+
};
|
|
259
|
+
return icons[category];
|
|
260
|
+
}
|
|
261
|
+
getCategoryLabel(category) {
|
|
262
|
+
const labels = {
|
|
263
|
+
RUN_CREATE: "Run Initialization",
|
|
264
|
+
TEST_SUBMISSION: "Test Result Submission",
|
|
265
|
+
ATTACHMENT_SIGN: "Attachment Signing",
|
|
266
|
+
ATTACHMENT_UPLOAD: "Attachment Upload",
|
|
267
|
+
FILE_READ: "File Access",
|
|
268
|
+
RUN_COMPLETE: "Run Completion"
|
|
269
|
+
};
|
|
270
|
+
return labels[category];
|
|
271
|
+
}
|
|
272
|
+
formatError(error) {
|
|
273
|
+
const parts = [];
|
|
274
|
+
if (error.testTitle) {
|
|
275
|
+
parts.push(`Test: "${error.testTitle}"`);
|
|
276
|
+
}
|
|
277
|
+
if (error.attachmentName) {
|
|
278
|
+
parts.push(`Attachment: "${error.attachmentName}"`);
|
|
279
|
+
}
|
|
280
|
+
if (error.filePath) {
|
|
281
|
+
parts.push(`File: ${error.filePath}`);
|
|
282
|
+
}
|
|
283
|
+
parts.push(error.message);
|
|
284
|
+
return parts.join(" - ");
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
|
|
176
288
|
// src/git-utils.ts
|
|
177
289
|
var import_simple_git = require("simple-git");
|
|
178
290
|
async function getLocalGitInfo(rootDir) {
|
|
@@ -241,13 +353,26 @@ var AttachmentUploader = class {
|
|
|
241
353
|
}
|
|
242
354
|
async upload(signedUrl, filePath, contentType) {
|
|
243
355
|
if (this.options.dryRun) {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
356
|
+
try {
|
|
357
|
+
const stats = import_node_fs.default.statSync(filePath);
|
|
358
|
+
console.log(
|
|
359
|
+
`[supatest][dry-run] Would upload ${filePath} (${stats.size} bytes) to S3`
|
|
360
|
+
);
|
|
361
|
+
} catch (error) {
|
|
362
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
363
|
+
console.warn(
|
|
364
|
+
`[supatest][dry-run] Cannot access file ${filePath}: ${message}`
|
|
365
|
+
);
|
|
366
|
+
}
|
|
248
367
|
return;
|
|
249
368
|
}
|
|
250
|
-
|
|
369
|
+
let fileBuffer;
|
|
370
|
+
try {
|
|
371
|
+
fileBuffer = await import_node_fs.default.promises.readFile(filePath);
|
|
372
|
+
} catch (error) {
|
|
373
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
374
|
+
throw new Error(`Failed to read file ${filePath}: ${message}`);
|
|
375
|
+
}
|
|
251
376
|
await withRetry(async () => {
|
|
252
377
|
const controller = new AbortController();
|
|
253
378
|
const timeoutId = setTimeout(
|
|
@@ -261,7 +386,7 @@ var AttachmentUploader = class {
|
|
|
261
386
|
"Content-Type": contentType,
|
|
262
387
|
"Content-Length": String(fileBuffer.length)
|
|
263
388
|
},
|
|
264
|
-
body: fileBuffer,
|
|
389
|
+
body: new Uint8Array(fileBuffer),
|
|
265
390
|
signal: controller.signal
|
|
266
391
|
});
|
|
267
392
|
if (!response.ok) {
|
|
@@ -282,11 +407,20 @@ var AttachmentUploader = class {
|
|
|
282
407
|
const results = await Promise.allSettled(
|
|
283
408
|
items.map(
|
|
284
409
|
(item, index) => limit(async () => {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
410
|
+
try {
|
|
411
|
+
await this.upload(item.signedUrl, item.filePath, item.contentType);
|
|
412
|
+
return {
|
|
413
|
+
success: true,
|
|
414
|
+
attachmentId: signedUploads[index]?.attachmentId
|
|
415
|
+
};
|
|
416
|
+
} catch (error) {
|
|
417
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
418
|
+
return {
|
|
419
|
+
success: false,
|
|
420
|
+
attachmentId: signedUploads[index]?.attachmentId,
|
|
421
|
+
error: `${item.filePath}: ${message}`
|
|
422
|
+
};
|
|
423
|
+
}
|
|
290
424
|
})
|
|
291
425
|
)
|
|
292
426
|
);
|
|
@@ -294,17 +428,18 @@ var AttachmentUploader = class {
|
|
|
294
428
|
if (result.status === "fulfilled") {
|
|
295
429
|
return result.value;
|
|
296
430
|
}
|
|
431
|
+
const message = result.reason instanceof Error ? result.reason.message : String(result.reason);
|
|
297
432
|
return {
|
|
298
433
|
success: false,
|
|
299
434
|
attachmentId: signedUploads[index]?.attachmentId,
|
|
300
|
-
error:
|
|
435
|
+
error: message
|
|
301
436
|
};
|
|
302
437
|
});
|
|
303
438
|
}
|
|
304
439
|
};
|
|
305
440
|
|
|
306
441
|
// src/index.ts
|
|
307
|
-
var DEFAULT_API_URL = "https://api.supatest.
|
|
442
|
+
var DEFAULT_API_URL = "https://code-api.supatest.ai";
|
|
308
443
|
var DEFAULT_MAX_CONCURRENT_UPLOADS = 5;
|
|
309
444
|
var DEFAULT_RETRY_ATTEMPTS = 3;
|
|
310
445
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
@@ -314,6 +449,7 @@ var SupatestPlaywrightReporter = class {
|
|
|
314
449
|
options;
|
|
315
450
|
client;
|
|
316
451
|
uploader;
|
|
452
|
+
errorCollector = new ErrorCollector();
|
|
317
453
|
runId;
|
|
318
454
|
uploadQueue = [];
|
|
319
455
|
uploadLimit;
|
|
@@ -414,7 +550,8 @@ var SupatestPlaywrightReporter = class {
|
|
|
414
550
|
`Run ${this.runId} started (${allTests.length} tests across ${testFiles.size} files, ${projects.length} projects)`
|
|
415
551
|
);
|
|
416
552
|
} catch (error) {
|
|
417
|
-
|
|
553
|
+
const errorMsg = this.getErrorMessage(error);
|
|
554
|
+
this.errorCollector.recordError("RUN_CREATE", errorMsg, { error });
|
|
418
555
|
this.disabled = true;
|
|
419
556
|
}
|
|
420
557
|
}
|
|
@@ -433,15 +570,14 @@ var SupatestPlaywrightReporter = class {
|
|
|
433
570
|
}
|
|
434
571
|
async onEnd(result) {
|
|
435
572
|
if (this.disabled || !this.runId) {
|
|
573
|
+
if (this.disabled && this.errorCollector.hasErrors()) {
|
|
574
|
+
console.log(this.errorCollector.formatSummary());
|
|
575
|
+
}
|
|
436
576
|
this.logInfo("Reporter disabled, skipping onEnd");
|
|
437
577
|
return;
|
|
438
578
|
}
|
|
439
579
|
this.logInfo(`Waiting for ${this.uploadQueue.length} pending uploads...`);
|
|
440
|
-
|
|
441
|
-
const failures = results.filter((r) => r.status === "rejected");
|
|
442
|
-
if (failures.length > 0) {
|
|
443
|
-
this.logWarn(`${failures.length} uploads failed`);
|
|
444
|
-
}
|
|
580
|
+
await Promise.allSettled(this.uploadQueue);
|
|
445
581
|
const summary = this.computeSummary(result);
|
|
446
582
|
const endedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
447
583
|
const startTime = this.startedAt ? new Date(this.startedAt).getTime() : 0;
|
|
@@ -458,12 +594,16 @@ var SupatestPlaywrightReporter = class {
|
|
|
458
594
|
});
|
|
459
595
|
this.logInfo(`Run ${this.runId} completed: ${result.status}`);
|
|
460
596
|
} catch (error) {
|
|
461
|
-
|
|
597
|
+
const errorMsg = this.getErrorMessage(error);
|
|
598
|
+
this.errorCollector.recordError("RUN_COMPLETE", errorMsg, { error });
|
|
599
|
+
}
|
|
600
|
+
if (this.errorCollector.hasErrors()) {
|
|
601
|
+
console.log(this.errorCollector.formatSummary());
|
|
462
602
|
}
|
|
463
603
|
}
|
|
464
604
|
async processTestResult(test, result) {
|
|
465
605
|
const testId = this.getTestId(test);
|
|
466
|
-
const resultId = this.getResultId(testId, result.retry);
|
|
606
|
+
const resultId = this.getResultId(testId, result.retry, result.workerIndex, result.parallelIndex);
|
|
467
607
|
try {
|
|
468
608
|
const payload = this.buildTestPayload(test, result);
|
|
469
609
|
await this.client.submitTest(this.runId, payload);
|
|
@@ -478,10 +618,12 @@ var SupatestPlaywrightReporter = class {
|
|
|
478
618
|
retries: (existing?.retries ?? 0) + (result.retry > 0 ? 1 : 0)
|
|
479
619
|
});
|
|
480
620
|
} catch (error) {
|
|
481
|
-
this.
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
621
|
+
const errorMsg = this.getErrorMessage(error);
|
|
622
|
+
this.errorCollector.recordError("TEST_SUBMISSION", errorMsg, {
|
|
623
|
+
testId,
|
|
624
|
+
testTitle: test.title,
|
|
625
|
+
error
|
|
626
|
+
});
|
|
485
627
|
}
|
|
486
628
|
}
|
|
487
629
|
buildTestPayload(test, result) {
|
|
@@ -489,7 +631,7 @@ var SupatestPlaywrightReporter = class {
|
|
|
489
631
|
const tags = test.tags ?? [];
|
|
490
632
|
const annotations = this.serializeAnnotations(test.annotations ?? []);
|
|
491
633
|
const resultEntry = {
|
|
492
|
-
resultId: this.getResultId(testId, result.retry),
|
|
634
|
+
resultId: this.getResultId(testId, result.retry, result.workerIndex, result.parallelIndex),
|
|
493
635
|
retry: result.retry,
|
|
494
636
|
status: result.status,
|
|
495
637
|
startTime: result.startTime?.toISOString?.() ?? void 0,
|
|
@@ -606,7 +748,24 @@ var SupatestPlaywrightReporter = class {
|
|
|
606
748
|
async uploadAttachments(result, testResultId) {
|
|
607
749
|
const attachments = (result.attachments ?? []).filter((a) => a.path);
|
|
608
750
|
if (attachments.length === 0) return;
|
|
609
|
-
const
|
|
751
|
+
const validAttachments = [];
|
|
752
|
+
for (const attachment of attachments) {
|
|
753
|
+
try {
|
|
754
|
+
await import_node_fs2.default.promises.access(attachment.path, import_node_fs2.default.constants.R_OK);
|
|
755
|
+
validAttachments.push(attachment);
|
|
756
|
+
} catch (error) {
|
|
757
|
+
const errorMsg = `File not accessible: ${attachment.path}`;
|
|
758
|
+
this.errorCollector.recordError("FILE_READ", errorMsg, {
|
|
759
|
+
attachmentName: attachment.name,
|
|
760
|
+
filePath: attachment.path,
|
|
761
|
+
error
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
if (validAttachments.length === 0) {
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
const meta = validAttachments.map((a) => ({
|
|
610
769
|
name: a.name,
|
|
611
770
|
filename: import_node_path.default.basename(a.path),
|
|
612
771
|
contentType: a.contentType,
|
|
@@ -620,18 +779,30 @@ var SupatestPlaywrightReporter = class {
|
|
|
620
779
|
});
|
|
621
780
|
const uploadItems = uploads.map((u, i) => ({
|
|
622
781
|
signedUrl: u.signedUrl,
|
|
623
|
-
filePath:
|
|
624
|
-
contentType:
|
|
782
|
+
filePath: validAttachments[i].path,
|
|
783
|
+
contentType: validAttachments[i].contentType
|
|
625
784
|
}));
|
|
626
785
|
const results = await this.uploader.uploadBatch(uploadItems, uploads);
|
|
627
|
-
const
|
|
628
|
-
if (
|
|
629
|
-
|
|
786
|
+
const failures = results.filter((r) => !r.success);
|
|
787
|
+
if (failures.length > 0) {
|
|
788
|
+
failures.forEach((failure) => {
|
|
789
|
+
const attachment = validAttachments.find(
|
|
790
|
+
(_, i) => uploads[i]?.attachmentId === failure.attachmentId
|
|
791
|
+
);
|
|
792
|
+
this.errorCollector.recordError(
|
|
793
|
+
"ATTACHMENT_UPLOAD",
|
|
794
|
+
failure.error || "Upload failed",
|
|
795
|
+
{
|
|
796
|
+
attachmentName: attachment?.name,
|
|
797
|
+
filePath: attachment?.path,
|
|
798
|
+
error: failure.error
|
|
799
|
+
}
|
|
800
|
+
);
|
|
801
|
+
});
|
|
630
802
|
}
|
|
631
803
|
} catch (error) {
|
|
632
|
-
this.
|
|
633
|
-
|
|
634
|
-
);
|
|
804
|
+
const errorMsg = this.getErrorMessage(error);
|
|
805
|
+
this.errorCollector.recordError("ATTACHMENT_SIGN", errorMsg, { error });
|
|
635
806
|
}
|
|
636
807
|
}
|
|
637
808
|
buildProjectConfigs(config) {
|
|
@@ -733,11 +904,13 @@ var SupatestPlaywrightReporter = class {
|
|
|
733
904
|
}
|
|
734
905
|
}
|
|
735
906
|
getTestId(test) {
|
|
736
|
-
const
|
|
907
|
+
const projectName = test.parent?.project()?.name ?? "unknown";
|
|
908
|
+
const key = `${test.location.file}:${test.location.line}:${test.title}:${projectName}`;
|
|
737
909
|
return this.hashKey(key);
|
|
738
910
|
}
|
|
739
|
-
getResultId(testId, retry) {
|
|
740
|
-
|
|
911
|
+
getResultId(testId, retry, workerIndex, parallelIndex) {
|
|
912
|
+
const key = parallelIndex !== void 0 ? `${testId}:${retry}:${parallelIndex}` : workerIndex !== void 0 ? `${testId}:${retry}:${workerIndex}` : `${testId}:${retry}`;
|
|
913
|
+
return this.hashKey(key);
|
|
741
914
|
}
|
|
742
915
|
hashKey(value) {
|
|
743
916
|
return (0, import_node_crypto.createHash)("sha256").update(value).digest("hex").slice(0, 12);
|
|
@@ -897,4 +1070,8 @@ var SupatestPlaywrightReporter = class {
|
|
|
897
1070
|
console.warn(`[supatest] ${message}`);
|
|
898
1071
|
}
|
|
899
1072
|
};
|
|
1073
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1074
|
+
0 && (module.exports = {
|
|
1075
|
+
ErrorCollector
|
|
1076
|
+
});
|
|
900
1077
|
//# sourceMappingURL=index.cjs.map
|