@supatest/playwright-reporter 0.0.3 → 0.0.5
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 +160 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +160 -15
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -35,9 +35,9 @@ __export(index_exports, {
|
|
|
35
35
|
});
|
|
36
36
|
module.exports = __toCommonJS(index_exports);
|
|
37
37
|
var import_node_crypto = require("crypto");
|
|
38
|
-
var
|
|
38
|
+
var import_node_fs3 = __toESM(require("fs"), 1);
|
|
39
39
|
var import_node_os = __toESM(require("os"), 1);
|
|
40
|
-
var
|
|
40
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
41
41
|
|
|
42
42
|
// src/retry.ts
|
|
43
43
|
var defaultRetryConfig = {
|
|
@@ -135,8 +135,8 @@ var SupatestApiClient = class {
|
|
|
135
135
|
}
|
|
136
136
|
await this.request("POST", `/v1/runs/${runId}/complete`, data);
|
|
137
137
|
}
|
|
138
|
-
async request(method,
|
|
139
|
-
const url = `${this.options.apiUrl}${
|
|
138
|
+
async request(method, path3, body) {
|
|
139
|
+
const url = `${this.options.apiUrl}${path3}`;
|
|
140
140
|
return withRetry(async () => {
|
|
141
141
|
const controller = new AbortController();
|
|
142
142
|
const timeoutId = setTimeout(
|
|
@@ -344,8 +344,119 @@ async function getLocalGitInfo(rootDir) {
|
|
|
344
344
|
}
|
|
345
345
|
}
|
|
346
346
|
|
|
347
|
-
// src/
|
|
347
|
+
// src/source-reader.ts
|
|
348
348
|
var import_node_fs = __toESM(require("fs"), 1);
|
|
349
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
350
|
+
var CONTEXT_LINES_BEFORE = 3;
|
|
351
|
+
var CONTEXT_LINES_AFTER = 3;
|
|
352
|
+
var MAX_SNIPPET_SIZE = 2e3;
|
|
353
|
+
var SourceReader = class {
|
|
354
|
+
cache = /* @__PURE__ */ new Map();
|
|
355
|
+
fileIndex = /* @__PURE__ */ new Map();
|
|
356
|
+
rootDir;
|
|
357
|
+
constructor(rootDir) {
|
|
358
|
+
this.rootDir = rootDir;
|
|
359
|
+
this.buildFileIndex(rootDir);
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Recursively scan directory and build index of .ts/.tsx/.js/.jsx files.
|
|
363
|
+
*/
|
|
364
|
+
buildFileIndex(dir) {
|
|
365
|
+
try {
|
|
366
|
+
const entries = import_node_fs.default.readdirSync(dir, { withFileTypes: true });
|
|
367
|
+
for (const entry of entries) {
|
|
368
|
+
const fullPath = import_node_path.default.join(dir, entry.name);
|
|
369
|
+
if (entry.isDirectory()) {
|
|
370
|
+
if (entry.name === "node_modules" || entry.name.startsWith(".")) {
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
this.buildFileIndex(fullPath);
|
|
374
|
+
} else if (entry.isFile()) {
|
|
375
|
+
const ext = import_node_path.default.extname(entry.name).toLowerCase();
|
|
376
|
+
if ([".ts", ".tsx", ".js", ".jsx"].includes(ext)) {
|
|
377
|
+
const filename = entry.name;
|
|
378
|
+
if (!this.fileIndex.has(filename)) {
|
|
379
|
+
this.fileIndex.set(filename, fullPath);
|
|
380
|
+
}
|
|
381
|
+
const relativePath = import_node_path.default.relative(this.rootDir, fullPath);
|
|
382
|
+
this.fileIndex.set(relativePath, fullPath);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
} catch {
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Resolve a file path to an absolute path.
|
|
391
|
+
* Handles absolute paths, relative paths, and filename-only paths.
|
|
392
|
+
*/
|
|
393
|
+
resolvePath(file) {
|
|
394
|
+
if (import_node_path.default.isAbsolute(file)) {
|
|
395
|
+
if (import_node_fs.default.existsSync(file)) {
|
|
396
|
+
return file;
|
|
397
|
+
}
|
|
398
|
+
const filename2 = import_node_path.default.basename(file);
|
|
399
|
+
return this.fileIndex.get(filename2);
|
|
400
|
+
}
|
|
401
|
+
const asRelative = import_node_path.default.join(this.rootDir, file);
|
|
402
|
+
if (import_node_fs.default.existsSync(asRelative)) {
|
|
403
|
+
return asRelative;
|
|
404
|
+
}
|
|
405
|
+
if (this.fileIndex.has(file)) {
|
|
406
|
+
return this.fileIndex.get(file);
|
|
407
|
+
}
|
|
408
|
+
const filename = import_node_path.default.basename(file);
|
|
409
|
+
return this.fileIndex.get(filename);
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Get a code snippet around a specific line in a file.
|
|
413
|
+
* Returns undefined if the file cannot be read or snippet is too large.
|
|
414
|
+
*/
|
|
415
|
+
getSnippet(file, line) {
|
|
416
|
+
try {
|
|
417
|
+
const absolutePath = this.resolvePath(file);
|
|
418
|
+
if (!absolutePath) {
|
|
419
|
+
return void 0;
|
|
420
|
+
}
|
|
421
|
+
let lines = this.cache.get(absolutePath);
|
|
422
|
+
if (!lines) {
|
|
423
|
+
const content = import_node_fs.default.readFileSync(absolutePath, "utf-8");
|
|
424
|
+
lines = content.split("\n");
|
|
425
|
+
this.cache.set(absolutePath, lines);
|
|
426
|
+
}
|
|
427
|
+
if (line < 1 || line > lines.length) {
|
|
428
|
+
return void 0;
|
|
429
|
+
}
|
|
430
|
+
const startLine = Math.max(1, line - CONTEXT_LINES_BEFORE);
|
|
431
|
+
const endLine = Math.min(lines.length, line + CONTEXT_LINES_AFTER);
|
|
432
|
+
const snippetLines = lines.slice(startLine - 1, endLine);
|
|
433
|
+
const code = snippetLines.join("\n");
|
|
434
|
+
if (code.length > MAX_SNIPPET_SIZE) {
|
|
435
|
+
return void 0;
|
|
436
|
+
}
|
|
437
|
+
const ext = import_node_path.default.extname(file).toLowerCase();
|
|
438
|
+
const language = ext === ".ts" || ext === ".tsx" ? "typescript" : "javascript";
|
|
439
|
+
return {
|
|
440
|
+
code,
|
|
441
|
+
startLine,
|
|
442
|
+
highlightLine: line,
|
|
443
|
+
language
|
|
444
|
+
};
|
|
445
|
+
} catch {
|
|
446
|
+
return void 0;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Clear the file cache and index to free memory.
|
|
451
|
+
*/
|
|
452
|
+
clear() {
|
|
453
|
+
this.cache.clear();
|
|
454
|
+
this.fileIndex.clear();
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
// src/uploader.ts
|
|
459
|
+
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
349
460
|
var AttachmentUploader = class {
|
|
350
461
|
options;
|
|
351
462
|
constructor(options) {
|
|
@@ -354,7 +465,7 @@ var AttachmentUploader = class {
|
|
|
354
465
|
async upload(signedUrl, filePath, contentType) {
|
|
355
466
|
if (this.options.dryRun) {
|
|
356
467
|
try {
|
|
357
|
-
const stats =
|
|
468
|
+
const stats = import_node_fs2.default.statSync(filePath);
|
|
358
469
|
console.log(
|
|
359
470
|
`[supatest][dry-run] Would upload ${filePath} (${stats.size} bytes) to S3`
|
|
360
471
|
);
|
|
@@ -368,7 +479,7 @@ var AttachmentUploader = class {
|
|
|
368
479
|
}
|
|
369
480
|
let fileBuffer;
|
|
370
481
|
try {
|
|
371
|
-
fileBuffer = await
|
|
482
|
+
fileBuffer = await import_node_fs2.default.promises.readFile(filePath);
|
|
372
483
|
} catch (error) {
|
|
373
484
|
const message = error instanceof Error ? error.message : String(error);
|
|
374
485
|
throw new Error(`Failed to read file ${filePath}: ${message}`);
|
|
@@ -461,6 +572,7 @@ var SupatestPlaywrightReporter = class {
|
|
|
461
572
|
config;
|
|
462
573
|
rootDir;
|
|
463
574
|
stepIdCounter = 0;
|
|
575
|
+
sourceReader;
|
|
464
576
|
constructor(options = {}) {
|
|
465
577
|
this.options = {
|
|
466
578
|
apiUrl: DEFAULT_API_URL,
|
|
@@ -475,6 +587,7 @@ var SupatestPlaywrightReporter = class {
|
|
|
475
587
|
async onBegin(config, suite) {
|
|
476
588
|
this.config = config;
|
|
477
589
|
this.rootDir = config.rootDir;
|
|
590
|
+
this.sourceReader = new SourceReader(config.rootDir);
|
|
478
591
|
if (!this.options.projectId) {
|
|
479
592
|
this.options.projectId = process.env.SUPATEST_PROJECT_ID ?? "";
|
|
480
593
|
}
|
|
@@ -600,6 +713,7 @@ var SupatestPlaywrightReporter = class {
|
|
|
600
713
|
if (this.errorCollector.hasErrors()) {
|
|
601
714
|
console.log(this.errorCollector.formatSummary());
|
|
602
715
|
}
|
|
716
|
+
this.sourceReader?.clear();
|
|
603
717
|
}
|
|
604
718
|
async processTestResult(test, result) {
|
|
605
719
|
const testId = this.getTestId(test);
|
|
@@ -676,6 +790,13 @@ var SupatestPlaywrightReporter = class {
|
|
|
676
790
|
}
|
|
677
791
|
serializeStep(step) {
|
|
678
792
|
const stepId = `step_${++this.stepIdCounter}`;
|
|
793
|
+
let snippet;
|
|
794
|
+
if (step.location && this.sourceReader) {
|
|
795
|
+
snippet = this.sourceReader.getSnippet(
|
|
796
|
+
step.location.file,
|
|
797
|
+
step.location.line
|
|
798
|
+
);
|
|
799
|
+
}
|
|
679
800
|
return {
|
|
680
801
|
stepId,
|
|
681
802
|
title: step.title,
|
|
@@ -691,8 +812,9 @@ var SupatestPlaywrightReporter = class {
|
|
|
691
812
|
// Recursively serialize nested steps
|
|
692
813
|
steps: step.steps && step.steps.length > 0 ? this.serializeSteps(step.steps) : void 0,
|
|
693
814
|
// Step attachments (from step.attachments if available)
|
|
694
|
-
attachments: void 0
|
|
815
|
+
attachments: void 0,
|
|
695
816
|
// Playwright doesn't expose step.attachments directly
|
|
817
|
+
snippet
|
|
696
818
|
};
|
|
697
819
|
}
|
|
698
820
|
serializeErrors(errors) {
|
|
@@ -721,7 +843,7 @@ var SupatestPlaywrightReporter = class {
|
|
|
721
843
|
serializeAttachmentMeta(attachments) {
|
|
722
844
|
return attachments.filter((a) => a.path || a.body).map((a) => ({
|
|
723
845
|
name: a.name,
|
|
724
|
-
filename: a.path ?
|
|
846
|
+
filename: a.path ? import_node_path2.default.basename(a.path) : a.name,
|
|
725
847
|
contentType: a.contentType,
|
|
726
848
|
sizeBytes: this.getAttachmentSize(a),
|
|
727
849
|
kind: this.getAttachmentKind(a.name, a.contentType)
|
|
@@ -751,7 +873,7 @@ var SupatestPlaywrightReporter = class {
|
|
|
751
873
|
const validAttachments = [];
|
|
752
874
|
for (const attachment of attachments) {
|
|
753
875
|
try {
|
|
754
|
-
await
|
|
876
|
+
await import_node_fs3.default.promises.access(attachment.path, import_node_fs3.default.constants.R_OK);
|
|
755
877
|
validAttachments.push(attachment);
|
|
756
878
|
} catch (error) {
|
|
757
879
|
const errorMsg = `File not accessible: ${attachment.path}`;
|
|
@@ -767,7 +889,7 @@ var SupatestPlaywrightReporter = class {
|
|
|
767
889
|
}
|
|
768
890
|
const meta = validAttachments.map((a) => ({
|
|
769
891
|
name: a.name,
|
|
770
|
-
filename:
|
|
892
|
+
filename: import_node_path2.default.basename(a.path),
|
|
771
893
|
contentType: a.contentType,
|
|
772
894
|
sizeBytes: this.getFileSize(a.path),
|
|
773
895
|
kind: this.getAttachmentKind(a.name, a.contentType)
|
|
@@ -903,10 +1025,33 @@ var SupatestPlaywrightReporter = class {
|
|
|
903
1025
|
return "complete";
|
|
904
1026
|
}
|
|
905
1027
|
}
|
|
1028
|
+
/**
|
|
1029
|
+
* Compute a stable test ID for tracking across runs.
|
|
1030
|
+
* Priority:
|
|
1031
|
+
* 1. Explicit @id:XXX tag (e.g., @id:TC-001)
|
|
1032
|
+
* 2. Annotation with type 'id' (e.g., test.info().annotations.push({ type: 'id', description: 'TC-001' }))
|
|
1033
|
+
* 3. Fallback: hash of file + titlePath + projectName (like Allure's historyId)
|
|
1034
|
+
*
|
|
1035
|
+
* Note: Does NOT include line number, so tests can move within a file without losing history.
|
|
1036
|
+
*/
|
|
906
1037
|
getTestId(test) {
|
|
907
|
-
const
|
|
908
|
-
const
|
|
909
|
-
|
|
1038
|
+
const tags = test.tags ?? [];
|
|
1039
|
+
const idTag = tags.find((tag) => tag.startsWith("@id:"));
|
|
1040
|
+
if (idTag) {
|
|
1041
|
+
return idTag.slice(4);
|
|
1042
|
+
}
|
|
1043
|
+
const annotations = test.annotations ?? [];
|
|
1044
|
+
const idAnnotation = annotations.find((a) => a.type === "id");
|
|
1045
|
+
if (idAnnotation?.description) {
|
|
1046
|
+
return idAnnotation.description;
|
|
1047
|
+
}
|
|
1048
|
+
const projectName = test.parent?.project()?.name ?? "default";
|
|
1049
|
+
const pathParts = [
|
|
1050
|
+
this.relativePath(test.location.file),
|
|
1051
|
+
...test.titlePath(),
|
|
1052
|
+
projectName
|
|
1053
|
+
];
|
|
1054
|
+
return this.hashKey(pathParts.join("::"));
|
|
910
1055
|
}
|
|
911
1056
|
getResultId(testId, retry, workerIndex, parallelIndex) {
|
|
912
1057
|
const key = parallelIndex !== void 0 ? `${testId}:${retry}:${parallelIndex}` : workerIndex !== void 0 ? `${testId}:${retry}:${workerIndex}` : `${testId}:${retry}`;
|
|
@@ -923,7 +1068,7 @@ var SupatestPlaywrightReporter = class {
|
|
|
923
1068
|
}
|
|
924
1069
|
getFileSize(filePath) {
|
|
925
1070
|
try {
|
|
926
|
-
return
|
|
1071
|
+
return import_node_fs3.default.statSync(filePath).size;
|
|
927
1072
|
} catch {
|
|
928
1073
|
return 0;
|
|
929
1074
|
}
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/retry.ts","../src/api-client.ts","../src/error-collector.ts","../src/git-utils.ts","../src/uploader.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport type {\n\tFullConfig,\n\tFullResult,\n\tReporter,\n\tSuite,\n\tTestCase,\n\tTestResult,\n\tTestStep,\n} from \"@playwright/test/reporter\";\nimport { SupatestApiClient } from \"./api-client.js\";\nimport { ErrorCollector } from \"./error-collector.js\";\nimport { getLocalGitInfo } from \"./git-utils.js\";\nimport type {\n\tAnnotationInfo,\n\tAttachmentKind,\n\tAttachmentMeta,\n\tCreateRunRequest,\n\tEnvironmentInfo,\n\tErrorInfo,\n\tProjectConfig,\n\tReporterOptions,\n\tRunSummary,\n\tStepInfo,\n\tTestOutcome,\n\tTestResultEntry,\n\tTestResultPayload,\n\tTestStatus,\n} from \"./types.js\";\nimport { AttachmentUploader } from \"./uploader.js\";\n\nconst DEFAULT_API_URL = \"https://code-api.supatest.ai\";\nconst DEFAULT_MAX_CONCURRENT_UPLOADS = 5;\nconst DEFAULT_RETRY_ATTEMPTS = 3;\nconst DEFAULT_TIMEOUT_MS = 30000;\nconst MAX_STDOUT_LINES = 100;\nconst MAX_STDERR_LINES = 100;\n\ntype TestInfo = {\n\tstatus: TestStatus;\n\toutcome: TestOutcome;\n\tretries: number;\n};\n\nexport default class SupatestPlaywrightReporter implements Reporter {\n\tprivate readonly options: ReporterOptions;\n\tprivate client!: SupatestApiClient;\n\tprivate uploader!: AttachmentUploader;\n\tprivate errorCollector = new ErrorCollector();\n\tprivate runId?: string;\n\tprivate uploadQueue: Promise<void>[] = [];\n\tprivate uploadLimit!: <T>(fn: () => Promise<T>) => Promise<T>;\n\tprivate startedAt?: string;\n\tprivate firstTestStartTime?: number;\n\tprivate firstFailureTime?: number;\n\tprivate testsProcessed = new Map<string, TestInfo>();\n\tprivate disabled = false;\n\tprivate config?: FullConfig;\n\tprivate rootDir?: string;\n\tprivate stepIdCounter = 0;\n\n\tconstructor(options: ReporterOptions = {} as ReporterOptions) {\n\t\tthis.options = {\n\t\t\tapiUrl: DEFAULT_API_URL,\n\t\t\tuploadAssets: true,\n\t\t\tmaxConcurrentUploads: DEFAULT_MAX_CONCURRENT_UPLOADS,\n\t\t\tretryAttempts: DEFAULT_RETRY_ATTEMPTS,\n\t\t\ttimeoutMs: DEFAULT_TIMEOUT_MS,\n\t\t\tdryRun: false,\n\t\t\t...options,\n\t\t};\n\t}\n\n\tasync onBegin(config: FullConfig, suite: Suite): Promise<void> {\n\t\tthis.config = config;\n\t\tthis.rootDir = config.rootDir;\n\n\t\t// Validate required options\n\t\tif (!this.options.projectId) {\n\t\t\tthis.options.projectId = process.env.SUPATEST_PROJECT_ID ?? \"\";\n\t\t}\n\t\tif (!this.options.apiKey) {\n\t\t\tthis.options.apiKey = process.env.SUPATEST_API_KEY ?? \"\";\n\t\t}\n\n\t\t// Check if we should run in dry-run mode\n\t\tif (process.env.SUPATEST_DRY_RUN === \"true\") {\n\t\t\tthis.options.dryRun = true;\n\t\t}\n\n\t\tif (!this.options.projectId || !this.options.apiKey) {\n\t\t\tif (!this.options.dryRun) {\n\t\t\t\tthis.logWarn(\n\t\t\t\t\t\"Missing projectId or apiKey. Set SUPATEST_PROJECT_ID and SUPATEST_API_KEY env vars, or pass them as options.\",\n\t\t\t\t);\n\t\t\t\tthis.disabled = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Initialize clients\n\t\tthis.client = new SupatestApiClient({\n\t\t\tapiKey: this.options.apiKey,\n\t\t\tapiUrl: this.options.apiUrl!,\n\t\t\ttimeoutMs: this.options.timeoutMs!,\n\t\t\tretryAttempts: this.options.retryAttempts!,\n\t\t\tdryRun: this.options.dryRun!,\n\t\t});\n\n\t\tthis.uploader = new AttachmentUploader({\n\t\t\tmaxConcurrent: this.options.maxConcurrentUploads!,\n\t\t\ttimeoutMs: this.options.timeoutMs!,\n\t\t\tdryRun: this.options.dryRun!,\n\t\t});\n\n\t\t// Initialize p-limit for upload queue\n\t\tconst pLimit = (await import(\"p-limit\")).default;\n\t\tthis.uploadLimit = pLimit(this.options.maxConcurrentUploads!);\n\n\t\tthis.startedAt = new Date().toISOString();\n\t\tconst allTests = suite.allTests();\n\n\t\t// Build project configs\n\t\tconst projects = this.buildProjectConfigs(config);\n\n\t\t// Count unique test files\n\t\tconst testFiles = new Set(allTests.map((t) => t.location.file));\n\n\t\ttry {\n\t\t\tconst runRequest: CreateRunRequest = {\n\t\t\t\tprojectId: this.options.projectId,\n\t\t\t\tstartedAt: this.startedAt,\n\t\t\t\tplaywright: {\n\t\t\t\t\tversion: config.version,\n\t\t\t\t\tworkers: config.workers,\n\t\t\t\t\tretries: config.projects?.[0]?.retries ?? 0,\n\t\t\t\t\tfullyParallel: config.fullyParallel,\n\t\t\t\t\tforbidOnly: config.forbidOnly,\n\t\t\t\t\tglobalTimeout: config.globalTimeout,\n\t\t\t\t\treportSlowTests: config.reportSlowTests\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\tmax: config.reportSlowTests.max,\n\t\t\t\t\t\t\t\tthreshold: config.reportSlowTests.threshold,\n\t\t\t\t\t\t }\n\t\t\t\t\t\t: undefined,\n\t\t\t\t},\n\t\t\t\tprojects,\n\t\t\t\ttestStats: {\n\t\t\t\t\ttotalFiles: testFiles.size,\n\t\t\t\t\ttotalTests: allTests.length,\n\t\t\t\t\ttotalProjects: config.projects?.length ?? 0,\n\t\t\t\t},\n\t\t\t\tshard: config.shard\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tcurrent: config.shard.current,\n\t\t\t\t\t\t\ttotal: config.shard.total,\n\t\t\t\t\t }\n\t\t\t\t\t: undefined,\n\t\t\t\tenvironment: this.getEnvironmentInfo(config),\n\t\t\t\tgit: await this.getGitInfo(),\n\t\t\t\tconfigPath: config.configFile,\n\t\t\t\trootDir: config.rootDir,\n\t\t\t\ttestDir: config.projects?.[0]?.testDir,\n\t\t\t\toutputDir: config.projects?.[0]?.outputDir,\n\t\t\t};\n\n\t\t\tconst response = await this.client.createRun(runRequest);\n\n\t\t\tthis.runId = response.runId;\n\t\t\tthis.logInfo(\n\t\t\t\t`Run ${this.runId} started (${allTests.length} tests across ${testFiles.size} files, ${projects.length} projects)`,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tconst errorMsg = this.getErrorMessage(error);\n\t\t\tthis.errorCollector.recordError(\"RUN_CREATE\", errorMsg, { error });\n\t\t\tthis.disabled = true;\n\t\t}\n\t}\n\n\tonTestEnd(test: TestCase, result: TestResult): void {\n\t\tif (this.disabled || !this.runId) return;\n\n\t\t// Track first test start time\n\t\tif (!this.firstTestStartTime && result.startTime) {\n\t\t\tthis.firstTestStartTime = result.startTime.getTime();\n\t\t}\n\n\t\t// Track first failure time\n\t\tif (\n\t\t\t!this.firstFailureTime &&\n\t\t\t(result.status === \"failed\" || result.status === \"timedOut\")\n\t\t) {\n\t\t\tthis.firstFailureTime = Date.now();\n\t\t}\n\n\t\t// Fire and forget - returns immediately (non-blocking)\n\t\tconst promise = this.uploadLimit(() =>\n\t\t\tthis.processTestResult(test, result),\n\t\t);\n\t\tthis.uploadQueue.push(promise);\n\t}\n\n\tasync onEnd(result: FullResult): Promise<void> {\n\t\tif (this.disabled || !this.runId) {\n\t\t\t// Show error summary even if disabled\n\t\t\tif (this.disabled && this.errorCollector.hasErrors()) {\n\t\t\t\tconsole.log(this.errorCollector.formatSummary());\n\t\t\t}\n\t\t\tthis.logInfo(\"Reporter disabled, skipping onEnd\");\n\t\t\treturn;\n\t\t}\n\n\t\t// Wait for all uploads to complete\n\t\tthis.logInfo(`Waiting for ${this.uploadQueue.length} pending uploads...`);\n\t\tawait Promise.allSettled(this.uploadQueue);\n\n\t\t// Complete the run\n\t\tconst summary = this.computeSummary(result);\n\t\tconst endedAt = new Date().toISOString();\n\t\tconst startTime = this.startedAt ? new Date(this.startedAt).getTime() : 0;\n\n\t\ttry {\n\t\t\tawait this.client.completeRun(this.runId, {\n\t\t\t\tstatus: this.mapRunStatus(result.status),\n\t\t\t\tendedAt,\n\t\t\t\tsummary,\n\t\t\t\ttiming: {\n\t\t\t\t\ttotalDurationMs: Math.round(result.duration),\n\t\t\t\t\ttimeToFirstTest: this.firstTestStartTime\n\t\t\t\t\t\t? Math.round(this.firstTestStartTime - startTime)\n\t\t\t\t\t\t: undefined,\n\t\t\t\t\ttimeToFirstFailure: this.firstFailureTime\n\t\t\t\t\t\t? Math.round(this.firstFailureTime - startTime)\n\t\t\t\t\t\t: undefined,\n\t\t\t\t},\n\t\t\t});\n\t\t\tthis.logInfo(`Run ${this.runId} completed: ${result.status}`);\n\t\t} catch (error) {\n\t\t\tconst errorMsg = this.getErrorMessage(error);\n\n\t\t\tthis.errorCollector.recordError(\"RUN_COMPLETE\", errorMsg, { error });\n\t\t}\n\n\t\t// Print error summary at the end\n\t\tif (this.errorCollector.hasErrors()) {\n\t\t\tconsole.log(this.errorCollector.formatSummary());\n\t\t}\n\t}\n\n\tprivate async processTestResult(\n\t\ttest: TestCase,\n\t\tresult: TestResult,\n\t): Promise<void> {\n\t\tconst testId = this.getTestId(test);\n\t\tconst resultId = this.getResultId(testId, result.retry, result.workerIndex, result.parallelIndex);\n\n\t\ttry {\n\t\t\tconst payload = this.buildTestPayload(test, result);\n\t\t\tawait this.client.submitTest(this.runId!, payload);\n\n\t\t\t// Upload attachments if enabled\n\t\t\tif (this.options.uploadAssets) {\n\t\t\t\tawait this.uploadAttachments(result, resultId);\n\t\t\t}\n\n\t\t\t// Track test outcome\n\t\t\tconst outcome = this.getTestOutcome(test, result);\n\t\t\tconst existing = this.testsProcessed.get(testId);\n\t\t\tthis.testsProcessed.set(testId, {\n\t\t\t\tstatus: result.status as TestStatus,\n\t\t\t\toutcome,\n\t\t\t\tretries: (existing?.retries ?? 0) + (result.retry > 0 ? 1 : 0),\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tconst errorMsg = this.getErrorMessage(error);\n\n\t\t\tthis.errorCollector.recordError(\"TEST_SUBMISSION\", errorMsg, {\n\t\t\t\ttestId,\n\t\t\t\ttestTitle: test.title,\n\t\t\t\terror,\n\t\t\t});\n\n\t\t\t// Don't throw - let Promise.allSettled handle it\n\t\t}\n\t}\n\n\tprivate buildTestPayload(\n\t\ttest: TestCase,\n\t\tresult: TestResult,\n\t): TestResultPayload {\n\t\tconst testId = this.getTestId(test);\n\n\t\t// Extract tags\n\t\tconst tags = test.tags ?? [];\n\n\t\t// Build all annotations\n\t\tconst annotations = this.serializeAnnotations(test.annotations ?? []);\n\n\t\t// Build result entry with full data\n\t\tconst resultEntry: TestResultEntry = {\n\t\t\tresultId: this.getResultId(testId, result.retry, result.workerIndex, result.parallelIndex),\n\t\t\tretry: result.retry,\n\t\t\tstatus: result.status as TestStatus,\n\t\t\tstartTime: result.startTime?.toISOString?.() ?? undefined,\n\t\t\tdurationMs: result.duration,\n\t\t\tworkerIndex: result.workerIndex,\n\t\t\tparallelIndex: result.parallelIndex,\n\t\t\terrors: this.serializeErrors(result.errors ?? []),\n\t\t\tsteps: this.serializeSteps(result.steps ?? []),\n\t\t\tannotations: this.serializeAnnotations(result.annotations ?? []),\n\t\t\tattachments: this.serializeAttachmentMeta(result.attachments ?? []),\n\t\t\tstdout: this.serializeOutput(result.stdout ?? [], MAX_STDOUT_LINES),\n\t\t\tstderr: this.serializeOutput(result.stderr ?? [], MAX_STDERR_LINES),\n\t\t};\n\n\t\t// Get project name from test's parent suite chain\n\t\tconst projectName = test.parent?.project()?.name ?? \"unknown\";\n\n\t\treturn {\n\t\t\ttestId,\n\t\t\tplaywrightId: test.id,\n\t\t\tfile: this.relativePath(test.location.file),\n\t\t\tlocation: {\n\t\t\t\tfile: this.relativePath(test.location.file),\n\t\t\t\tline: test.location.line,\n\t\t\t\tcolumn: test.location.column,\n\t\t\t},\n\t\t\ttitle: test.title,\n\t\t\ttitlePath: test.titlePath(),\n\t\t\ttags,\n\t\t\tannotations,\n\t\t\texpectedStatus: test.expectedStatus as TestStatus,\n\t\t\toutcome: this.getTestOutcome(test, result),\n\t\t\ttimeout: test.timeout,\n\t\t\tretries: test.retries,\n\t\t\trepeatEachIndex:\n\t\t\t\ttest.repeatEachIndex > 0 ? test.repeatEachIndex : undefined,\n\t\t\tstatus: result.status as TestStatus,\n\t\t\tdurationMs: result.duration,\n\t\t\tretryCount: result.retry,\n\t\t\tresults: [resultEntry],\n\t\t\tprojectName,\n\t\t};\n\t}\n\n\tprivate serializeSteps(steps: readonly TestStep[]): StepInfo[] {\n\t\treturn steps.map((step) => this.serializeStep(step));\n\t}\n\n\tprivate serializeStep(step: TestStep): StepInfo {\n\t\tconst stepId = `step_${++this.stepIdCounter}`;\n\n\t\treturn {\n\t\t\tstepId,\n\t\t\ttitle: step.title,\n\t\t\tcategory: step.category,\n\t\t\tstartTime: step.startTime?.toISOString?.() ?? undefined,\n\t\t\tdurationMs: step.duration,\n\t\t\tlocation: step.location\n\t\t\t\t? {\n\t\t\t\t\t\tfile: this.relativePath(step.location.file),\n\t\t\t\t\t\tline: step.location.line,\n\t\t\t\t\t\tcolumn: step.location.column,\n\t\t\t\t }\n\t\t\t\t: undefined,\n\t\t\terror: step.error ? this.serializeError(step.error) : undefined,\n\t\t\t// Recursively serialize nested steps\n\t\t\tsteps:\n\t\t\t\tstep.steps && step.steps.length > 0\n\t\t\t\t\t? this.serializeSteps(step.steps)\n\t\t\t\t\t: undefined,\n\t\t\t// Step attachments (from step.attachments if available)\n\t\t\tattachments: undefined, // Playwright doesn't expose step.attachments directly\n\t\t};\n\t}\n\n\tprivate serializeErrors(\n\t\terrors: TestResult[\"errors\"],\n\t): ErrorInfo[] {\n\t\treturn errors.map((e) => this.serializeError(e));\n\t}\n\n\tprivate serializeError(error: {\n\t\tmessage?: string;\n\t\tstack?: string;\n\t\tvalue?: unknown;\n\t\tlocation?: { file: string; line: number; column: number };\n\t}): ErrorInfo {\n\t\treturn {\n\t\t\tmessage: error.message,\n\t\t\tstack: error.stack,\n\t\t\tvalue: error.value !== undefined ? String(error.value) : undefined,\n\t\t\tlocation: error.location\n\t\t\t\t? {\n\t\t\t\t\t\tfile: this.relativePath(error.location.file),\n\t\t\t\t\t\tline: error.location.line,\n\t\t\t\t\t\tcolumn: error.location.column,\n\t\t\t\t }\n\t\t\t\t: undefined,\n\t\t\t// Could extract code snippet from file here if needed\n\t\t\tsnippet: undefined,\n\t\t};\n\t}\n\n\tprivate serializeAnnotations(\n\t\tannotations: readonly { type: string; description?: string }[],\n\t): AnnotationInfo[] {\n\t\treturn annotations.map((a) => ({\n\t\t\ttype: a.type,\n\t\t\tdescription: a.description,\n\t\t}));\n\t}\n\n\tprivate serializeAttachmentMeta(\n\t\tattachments: TestResult[\"attachments\"],\n\t): AttachmentMeta[] {\n\t\treturn attachments\n\t\t\t.filter((a) => a.path || a.body)\n\t\t\t.map((a) => ({\n\t\t\t\tname: a.name,\n\t\t\t\tfilename: a.path ? path.basename(a.path) : a.name,\n\t\t\t\tcontentType: a.contentType,\n\t\t\t\tsizeBytes: this.getAttachmentSize(a),\n\t\t\t\tkind: this.getAttachmentKind(a.name, a.contentType),\n\t\t\t}));\n\t}\n\n\tprivate serializeOutput(\n\t\toutput: (string | Buffer)[],\n\t\tmaxLines: number,\n\t): string[] {\n\t\tconst lines: string[] = [];\n\t\tfor (const chunk of output) {\n\t\t\tconst str = chunk.toString();\n\t\t\tlines.push(...str.split(\"\\n\"));\n\t\t\tif (lines.length >= maxLines) break;\n\t\t}\n\t\treturn lines.slice(0, maxLines);\n\t}\n\n\tprivate getTestOutcome(test: TestCase, result: TestResult): TestOutcome {\n\t\t// Use Playwright's built-in outcome() if available\n\t\tif (typeof test.outcome === \"function\") {\n\t\t\treturn test.outcome() as TestOutcome;\n\t\t}\n\n\t\t// Fallback logic\n\t\tif (result.status === \"skipped\") return \"skipped\";\n\t\tif (result.status === test.expectedStatus) return \"expected\";\n\t\tif (result.status === \"passed\" && result.retry > 0) return \"flaky\";\n\t\treturn \"unexpected\";\n\t}\n\n\tprivate async uploadAttachments(\n\t\tresult: TestResult,\n\t\ttestResultId: string,\n\t): Promise<void> {\n\t\tconst attachments = (result.attachments ?? []).filter((a) => a.path);\n\t\tif (attachments.length === 0) return;\n\n\t\t// Validate files exist before attempting upload\n\t\tconst validAttachments: typeof attachments = [];\n\t\tfor (const attachment of attachments) {\n\t\t\ttry {\n\t\t\t\tawait fs.promises.access(attachment.path!, fs.constants.R_OK);\n\t\t\t\tvalidAttachments.push(attachment);\n\t\t\t} catch (error) {\n\t\t\t\tconst errorMsg = `File not accessible: ${attachment.path}`;\n\n\t\t\t\tthis.errorCollector.recordError(\"FILE_READ\", errorMsg, {\n\t\t\t\t\tattachmentName: attachment.name,\n\t\t\t\t\tfilePath: attachment.path,\n\t\t\t\t\terror,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tif (validAttachments.length === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst meta: AttachmentMeta[] = validAttachments.map((a) => ({\n\t\t\tname: a.name,\n\t\t\tfilename: path.basename(a.path!),\n\t\t\tcontentType: a.contentType,\n\t\t\tsizeBytes: this.getFileSize(a.path!),\n\t\t\tkind: this.getAttachmentKind(a.name, a.contentType),\n\t\t}));\n\n\t\ttry {\n\t\t\tconst { uploads } = await this.client.signAttachments(this.runId!, {\n\t\t\t\ttestResultId,\n\t\t\t\tattachments: meta,\n\t\t\t});\n\n\t\t\tconst uploadItems = uploads.map((u, i) => ({\n\t\t\t\tsignedUrl: u.signedUrl,\n\t\t\t\tfilePath: validAttachments[i].path!,\n\t\t\t\tcontentType: validAttachments[i].contentType,\n\t\t\t}));\n\n\t\t\tconst results = await this.uploader.uploadBatch(uploadItems, uploads);\n\t\t\tconst failures = results.filter((r) => !r.success);\n\n\t\t\tif (failures.length > 0) {\n\t\t\t\t// Record each failure\n\t\t\t\tfailures.forEach((failure) => {\n\t\t\t\t\tconst attachment = validAttachments.find(\n\t\t\t\t\t\t(_, i) => uploads[i]?.attachmentId === failure.attachmentId,\n\t\t\t\t\t);\n\n\t\t\t\t\tthis.errorCollector.recordError(\n\t\t\t\t\t\t\"ATTACHMENT_UPLOAD\",\n\t\t\t\t\t\tfailure.error || \"Upload failed\",\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tattachmentName: attachment?.name,\n\t\t\t\t\t\t\tfilePath: attachment?.path,\n\t\t\t\t\t\t\terror: failure.error,\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t});\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst errorMsg = this.getErrorMessage(error);\n\n\t\t\tthis.errorCollector.recordError(\"ATTACHMENT_SIGN\", errorMsg, { error });\n\t\t}\n\t}\n\n\tprivate buildProjectConfigs(config: FullConfig): ProjectConfig[] {\n\t\treturn (config.projects ?? []).map((project) => {\n\t\t\tconst use = project.use ?? {} as Record<string, unknown>;\n\t\t\treturn {\n\t\t\t\tname: project.name,\n\t\t\t\tbrowserName: use.browserName as string | undefined,\n\t\t\t\tchannel: use.channel as string | undefined,\n\t\t\t\tdeviceName: (use as Record<string, unknown>).deviceName as string | undefined,\n\t\t\t\tviewport: use.viewport\n\t\t\t\t\t? { width: (use.viewport as { width: number; height: number }).width, height: (use.viewport as { width: number; height: number }).height }\n\t\t\t\t\t: undefined,\n\t\t\t\tlocale: use.locale as string | undefined,\n\t\t\t\ttimezone: use.timezoneId as string | undefined,\n\t\t\t\tcolorScheme: use.colorScheme as ProjectConfig[\"colorScheme\"],\n\t\t\t\tisMobile: use.isMobile as boolean | undefined,\n\t\t\t\thasTouch: use.hasTouch as boolean | undefined,\n\t\t\t\ttestDir: project.testDir,\n\t\t\t\ttimeout: project.timeout,\n\t\t\t};\n\t\t});\n\t}\n\n\tprivate getEnvironmentInfo(config: FullConfig): EnvironmentInfo {\n\t\tconst ciInfo = this.getCIInfo();\n\n\t\treturn {\n\t\t\tos: {\n\t\t\t\tplatform: os.platform(),\n\t\t\t\trelease: os.release(),\n\t\t\t\tarch: os.arch(),\n\t\t\t},\n\t\t\tnode: {\n\t\t\t\tversion: process.version,\n\t\t\t},\n\t\t\tmachine: {\n\t\t\t\tcpus: os.cpus().length,\n\t\t\t\tmemory: os.totalmem(),\n\t\t\t\thostname: os.hostname(),\n\t\t\t},\n\t\t\tplaywright: {\n\t\t\t\tversion: config.version,\n\t\t\t},\n\t\t\tci: ciInfo,\n\t\t};\n\t}\n\n\tprivate computeSummary(result: FullResult): RunSummary {\n\t\tlet passed = 0;\n\t\tlet failed = 0;\n\t\tlet flaky = 0;\n\t\tlet skipped = 0;\n\t\tlet timedOut = 0;\n\t\tlet interrupted = 0;\n\t\tlet expected = 0;\n\t\tlet unexpected = 0;\n\n\t\tfor (const [, testInfo] of this.testsProcessed) {\n\t\t\tswitch (testInfo.status) {\n\t\t\t\tcase \"passed\":\n\t\t\t\t\tpassed += 1;\n\t\t\t\t\tif (testInfo.retries > 0) flaky += 1;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"failed\":\n\t\t\t\t\tfailed += 1;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"skipped\":\n\t\t\t\t\tskipped += 1;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"timedOut\":\n\t\t\t\t\ttimedOut += 1;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"interrupted\":\n\t\t\t\t\tinterrupted += 1;\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Count outcomes\n\t\t\tif (testInfo.outcome === \"expected\") expected += 1;\n\t\t\telse if (testInfo.outcome === \"unexpected\") unexpected += 1;\n\t\t}\n\n\t\treturn {\n\t\t\ttotal: this.testsProcessed.size,\n\t\t\tpassed,\n\t\t\tfailed,\n\t\t\tflaky,\n\t\t\tskipped,\n\t\t\ttimedOut,\n\t\t\tinterrupted,\n\t\t\tdurationMs: Math.round(result.duration),\n\t\t\texpected,\n\t\t\tunexpected,\n\t\t};\n\t}\n\n\tprivate mapRunStatus(\n\t\tstatus: FullResult[\"status\"],\n\t): \"complete\" | \"errored\" | \"interrupted\" {\n\t\tswitch (status) {\n\t\t\tcase \"passed\":\n\t\t\t\treturn \"complete\";\n\t\t\tcase \"failed\":\n\t\t\tcase \"timedout\":\n\t\t\t\treturn \"errored\";\n\t\t\tcase \"interrupted\":\n\t\t\t\treturn \"interrupted\";\n\t\t\tdefault:\n\t\t\t\treturn \"complete\";\n\t\t}\n\t}\n\n\tprivate getTestId(test: TestCase): string {\n\t\tconst projectName = test.parent?.project()?.name ?? \"unknown\";\n\t\tconst key = `${test.location.file}:${test.location.line}:${test.title}:${projectName}`;\n\t\treturn this.hashKey(key);\n\t}\n\n\tprivate getResultId(testId: string, retry: number, workerIndex?: number, parallelIndex?: number): string {\n\t\t// Include worker/parallel index to ensure uniqueness across parallel runs\n\t\tconst key = parallelIndex !== undefined\n\t\t\t? `${testId}:${retry}:${parallelIndex}`\n\t\t\t: workerIndex !== undefined\n\t\t\t? `${testId}:${retry}:${workerIndex}`\n\t\t\t: `${testId}:${retry}`;\n\t\treturn this.hashKey(key);\n\t}\n\n\tprivate hashKey(value: string): string {\n\t\treturn createHash(\"sha256\").update(value).digest(\"hex\").slice(0, 12);\n\t}\n\n\tprivate relativePath(filePath: string): string {\n\t\tif (this.rootDir && filePath.startsWith(this.rootDir)) {\n\t\t\treturn filePath.slice(this.rootDir.length + 1);\n\t\t}\n\t\treturn filePath;\n\t}\n\n\tprivate getFileSize(filePath: string): number {\n\t\ttry {\n\t\t\treturn fs.statSync(filePath).size;\n\t\t} catch {\n\t\t\treturn 0;\n\t\t}\n\t}\n\n\tprivate getAttachmentSize(\n\t\tattachment: TestResult[\"attachments\"][number],\n\t): number {\n\t\tif (attachment.path) {\n\t\t\treturn this.getFileSize(attachment.path);\n\t\t}\n\t\tif (attachment.body) {\n\t\t\treturn attachment.body.length;\n\t\t}\n\t\treturn 0;\n\t}\n\n\tprivate getAttachmentKind(name: string, contentType: string): AttachmentKind {\n\t\tif (name === \"video\" || contentType.startsWith(\"video/\")) return \"video\";\n\t\tif (name === \"trace\" || name.endsWith(\".zip\")) return \"trace\";\n\t\tif (name.includes(\"screenshot\") || contentType === \"image/png\")\n\t\t\treturn \"screenshot\";\n\t\tif (name === \"stdout\") return \"stdout\";\n\t\tif (name === \"stderr\") return \"stderr\";\n\t\treturn \"other\";\n\t}\n\n\tprivate async getGitInfo() {\n\t\t// Try CI environment variables first\n\t\tconst ciGitInfo = {\n\t\t\tbranch:\n\t\t\t\tprocess.env.GITHUB_REF_NAME ??\n\t\t\t\tprocess.env.GITHUB_HEAD_REF ??\n\t\t\t\tprocess.env.CI_COMMIT_BRANCH ??\n\t\t\t\tprocess.env.GITLAB_CI_COMMIT_BRANCH ??\n\t\t\t\tprocess.env.GIT_BRANCH,\n\t\t\tcommit:\n\t\t\t\tprocess.env.GITHUB_SHA ??\n\t\t\t\tprocess.env.CI_COMMIT_SHA ??\n\t\t\t\tprocess.env.GITLAB_CI_COMMIT_SHA ??\n\t\t\t\tprocess.env.GIT_COMMIT,\n\t\t\tcommitMessage:\n\t\t\t\tprocess.env.CI_COMMIT_MESSAGE ??\n\t\t\t\tprocess.env.GITLAB_CI_COMMIT_MESSAGE,\n\t\t\trepo:\n\t\t\t\tprocess.env.GITHUB_REPOSITORY ??\n\t\t\t\tprocess.env.CI_PROJECT_PATH ??\n\t\t\t\tprocess.env.GITLAB_CI_PROJECT_PATH ??\n\t\t\t\tprocess.env.GIT_REPO,\n\t\t\tauthor: process.env.GITHUB_ACTOR ?? process.env.GITLAB_USER_NAME,\n\t\t\tauthorEmail: process.env.GITLAB_USER_EMAIL,\n\t\t\ttag: process.env.GITHUB_REF_TYPE === \"tag\" ? process.env.GITHUB_REF_NAME : undefined,\n\t\t};\n\n\t\t// If we have CI git info, use it (prioritize CI env vars)\n\t\tif (ciGitInfo.branch || ciGitInfo.commit) {\n\t\t\treturn ciGitInfo;\n\t\t}\n\n\t\t// Fall back to local git for local development\n\t\tconst localGitInfo = await getLocalGitInfo(this.rootDir);\n\n\t\t// Merge CI info with local git info (CI takes precedence)\n\t\treturn {\n\t\t\tbranch: ciGitInfo.branch ?? localGitInfo.branch,\n\t\t\tcommit: ciGitInfo.commit ?? localGitInfo.commit,\n\t\t\tcommitMessage: ciGitInfo.commitMessage ?? localGitInfo.commitMessage,\n\t\t\trepo: ciGitInfo.repo ?? localGitInfo.repo,\n\t\t\tauthor: ciGitInfo.author ?? localGitInfo.author,\n\t\t\tauthorEmail: ciGitInfo.authorEmail ?? localGitInfo.authorEmail,\n\t\t\ttag: ciGitInfo.tag ?? localGitInfo.tag,\n\t\t\tdirty: localGitInfo.dirty,\n\t\t};\n\t}\n\n\tprivate getCIInfo(): EnvironmentInfo[\"ci\"] | undefined {\n\t\tif (process.env.GITHUB_ACTIONS) {\n\t\t\treturn {\n\t\t\t\tprovider: \"github-actions\",\n\t\t\t\trunId: process.env.GITHUB_RUN_ID,\n\t\t\t\tjobId: process.env.GITHUB_JOB,\n\t\t\t\tjobUrl: `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`,\n\t\t\t\tbuildNumber: process.env.GITHUB_RUN_NUMBER,\n\t\t\t\tbranch: process.env.GITHUB_REF_NAME,\n\t\t\t\tpullRequest: process.env.GITHUB_EVENT_NAME === \"pull_request\"\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tnumber: process.env.GITHUB_PR_NUMBER ?? \"\",\n\t\t\t\t\t\t\turl: `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/pull/${process.env.GITHUB_PR_NUMBER}`,\n\t\t\t\t\t }\n\t\t\t\t\t: undefined,\n\t\t\t};\n\t\t}\n\t\tif (process.env.GITLAB_CI) {\n\t\t\treturn {\n\t\t\t\tprovider: \"gitlab-ci\",\n\t\t\t\trunId: process.env.CI_PIPELINE_ID,\n\t\t\t\tjobId: process.env.CI_JOB_ID,\n\t\t\t\tjobUrl: process.env.CI_JOB_URL,\n\t\t\t\tbuildNumber: process.env.CI_PIPELINE_IID,\n\t\t\t\tbranch: process.env.CI_COMMIT_BRANCH,\n\t\t\t\tpullRequest: process.env.CI_MERGE_REQUEST_IID\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tnumber: process.env.CI_MERGE_REQUEST_IID,\n\t\t\t\t\t\t\turl: process.env.CI_MERGE_REQUEST_PROJECT_URL + \"/-/merge_requests/\" + process.env.CI_MERGE_REQUEST_IID,\n\t\t\t\t\t\t\ttitle: process.env.CI_MERGE_REQUEST_TITLE,\n\t\t\t\t\t }\n\t\t\t\t\t: undefined,\n\t\t\t};\n\t\t}\n\t\tif (process.env.JENKINS_URL) {\n\t\t\treturn {\n\t\t\t\tprovider: \"jenkins\",\n\t\t\t\trunId: process.env.BUILD_ID,\n\t\t\t\tjobUrl: process.env.BUILD_URL,\n\t\t\t\tbuildNumber: process.env.BUILD_NUMBER,\n\t\t\t\tbranch: process.env.GIT_BRANCH,\n\t\t\t};\n\t\t}\n\t\tif (process.env.CIRCLECI) {\n\t\t\treturn {\n\t\t\t\tprovider: \"circleci\",\n\t\t\t\trunId: process.env.CIRCLE_WORKFLOW_ID,\n\t\t\t\tjobId: process.env.CIRCLE_JOB,\n\t\t\t\tjobUrl: process.env.CIRCLE_BUILD_URL,\n\t\t\t\tbuildNumber: process.env.CIRCLE_BUILD_NUM,\n\t\t\t\tbranch: process.env.CIRCLE_BRANCH,\n\t\t\t\tpullRequest: process.env.CIRCLE_PULL_REQUEST\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tnumber: process.env.CIRCLE_PR_NUMBER ?? \"\",\n\t\t\t\t\t\t\turl: process.env.CIRCLE_PULL_REQUEST,\n\t\t\t\t\t }\n\t\t\t\t\t: undefined,\n\t\t\t};\n\t\t}\n\t\tif (process.env.TRAVIS) {\n\t\t\treturn {\n\t\t\t\tprovider: \"travis\",\n\t\t\t\trunId: process.env.TRAVIS_BUILD_ID,\n\t\t\t\tjobId: process.env.TRAVIS_JOB_ID,\n\t\t\t\tjobUrl: process.env.TRAVIS_JOB_WEB_URL,\n\t\t\t\tbuildNumber: process.env.TRAVIS_BUILD_NUMBER,\n\t\t\t\tbranch: process.env.TRAVIS_BRANCH,\n\t\t\t\tpullRequest: process.env.TRAVIS_PULL_REQUEST !== \"false\"\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tnumber: process.env.TRAVIS_PULL_REQUEST ?? \"\",\n\t\t\t\t\t\t\turl: `https://github.com/${process.env.TRAVIS_REPO_SLUG}/pull/${process.env.TRAVIS_PULL_REQUEST}`,\n\t\t\t\t\t }\n\t\t\t\t\t: undefined,\n\t\t\t};\n\t\t}\n\t\tif (process.env.BUILDKITE) {\n\t\t\treturn {\n\t\t\t\tprovider: \"buildkite\",\n\t\t\t\trunId: process.env.BUILDKITE_BUILD_ID,\n\t\t\t\tjobId: process.env.BUILDKITE_JOB_ID,\n\t\t\t\tjobUrl: process.env.BUILDKITE_BUILD_URL,\n\t\t\t\tbuildNumber: process.env.BUILDKITE_BUILD_NUMBER,\n\t\t\t\tbranch: process.env.BUILDKITE_BRANCH,\n\t\t\t};\n\t\t}\n\t\tif (process.env.AZURE_PIPELINES || process.env.TF_BUILD) {\n\t\t\treturn {\n\t\t\t\tprovider: \"azure-pipelines\",\n\t\t\t\trunId: process.env.BUILD_BUILDID,\n\t\t\t\tjobUrl: `${process.env.SYSTEM_COLLECTIONURI}${process.env.SYSTEM_TEAMPROJECT}/_build/results?buildId=${process.env.BUILD_BUILDID}`,\n\t\t\t\tbuildNumber: process.env.BUILD_BUILDNUMBER,\n\t\t\t\tbranch: process.env.BUILD_SOURCEBRANCH,\n\t\t\t};\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tprivate getErrorMessage(error: unknown): string {\n\t\tif (error instanceof Error) return error.message;\n\t\treturn String(error);\n\t}\n\n\tprivate logInfo(message: string): void {\n\t\tconsole.log(`[supatest] ${message}`);\n\t}\n\n\tprivate logWarn(message: string): void {\n\t\tconsole.warn(`[supatest] ${message}`);\n\t}\n}\n\nexport { ErrorCollector } from \"./error-collector.js\";\n// Re-export types for consumers\nexport type { ErrorCategory, ErrorSummary, ReporterOptions } from \"./types.js\";\n","export type RetryConfig = {\n\tmaxAttempts: number;\n\tbaseDelayMs: number;\n\tmaxDelayMs: number;\n\tretryOnStatusCodes: number[];\n};\n\nexport const defaultRetryConfig: RetryConfig = {\n\tmaxAttempts: 3,\n\tbaseDelayMs: 1000,\n\tmaxDelayMs: 10000,\n\tretryOnStatusCodes: [408, 429, 500, 502, 503, 504],\n};\n\nfunction sleep(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction shouldRetry(error: unknown, config: RetryConfig): boolean {\n\tif (error instanceof Error && \"statusCode\" in error) {\n\t\tconst statusCode = (error as Error & { statusCode: number }).statusCode;\n\t\treturn config.retryOnStatusCodes.includes(statusCode);\n\t}\n\t// Network errors should be retried\n\tif (error instanceof Error) {\n\t\tconst networkErrors = [\"ECONNRESET\", \"ETIMEDOUT\", \"ECONNREFUSED\", \"ENOTFOUND\"];\n\t\treturn networkErrors.some((e) => error.message.includes(e));\n\t}\n\treturn false;\n}\n\nexport async function withRetry<T>(\n\tfn: () => Promise<T>,\n\tconfig: RetryConfig = defaultRetryConfig,\n): Promise<T> {\n\tlet lastError: unknown;\n\n\tfor (let attempt = 1; attempt <= config.maxAttempts; attempt++) {\n\t\ttry {\n\t\t\treturn await fn();\n\t\t} catch (error) {\n\t\t\tlastError = error;\n\n\t\t\tif (attempt === config.maxAttempts) {\n\t\t\t\tthrow error;\n\t\t\t}\n\n\t\t\tif (!shouldRetry(error, config)) {\n\t\t\t\tthrow error;\n\t\t\t}\n\n\t\t\tconst delay = Math.min(\n\t\t\t\tconfig.baseDelayMs * Math.pow(2, attempt - 1),\n\t\t\t\tconfig.maxDelayMs,\n\t\t\t);\n\n\t\t\tawait sleep(delay);\n\t\t}\n\t}\n\n\tthrow lastError;\n}\n","import { defaultRetryConfig, type RetryConfig, withRetry } from \"./retry.js\";\nimport type {\n\tCompleteRunRequest,\n\tCreateRunRequest,\n\tCreateRunResponse,\n\tSignAttachmentsRequest,\n\tSignAttachmentsResponse,\n\tTestResultPayload,\n} from \"./types.js\";\n\nexport type ApiClientOptions = {\n\tapiKey: string;\n\tapiUrl: string;\n\ttimeoutMs: number;\n\tretryAttempts: number;\n\tdryRun: boolean;\n};\n\nexport class SupatestApiClient {\n\tprivate readonly options: ApiClientOptions;\n\tprivate readonly retryConfig: RetryConfig;\n\n\tconstructor(options: ApiClientOptions) {\n\t\tthis.options = options;\n\t\tthis.retryConfig = {\n\t\t\t...defaultRetryConfig,\n\t\t\tmaxAttempts: options.retryAttempts,\n\t\t};\n\t}\n\n\tasync createRun(data: CreateRunRequest): Promise<CreateRunResponse> {\n\t\tif (this.options.dryRun) {\n\t\t\tthis.logPayload(\"POST /v1/runs\", data);\n\t\t\t// Return mock response for dry run\n\t\t\treturn {\n\t\t\t\trunId: `mock_run_${Date.now()}`,\n\t\t\t\tstatus: \"running\",\n\t\t\t};\n\t\t}\n\n\t\treturn this.request<CreateRunResponse>(\"POST\", \"/v1/runs\", data);\n\t}\n\n\tasync submitTest(runId: string, data: TestResultPayload): Promise<void> {\n\t\tif (this.options.dryRun) {\n\t\t\tthis.logPayload(`POST /v1/runs/${runId}/tests`, data);\n\t\t\treturn;\n\t\t}\n\n\t\tawait this.request(\"POST\", `/v1/runs/${runId}/tests`, data);\n\t}\n\n\tasync signAttachments(\n\t\trunId: string,\n\t\tdata: SignAttachmentsRequest,\n\t): Promise<SignAttachmentsResponse> {\n\t\tif (this.options.dryRun) {\n\t\t\tthis.logPayload(`POST /v1/runs/${runId}/attachments/sign`, data);\n\t\t\t// Return mock signed URLs for dry run\n\t\t\treturn {\n\t\t\t\tuploads: data.attachments.map((att, i) => ({\n\t\t\t\t\tattachmentId: `mock_att_${i}_${Date.now()}`,\n\t\t\t\t\tsignedUrl: `https://mock-s3.example.com/uploads/${att.filename}`,\n\t\t\t\t\texpiresAt: new Date(Date.now() + 15 * 60 * 1000).toISOString(),\n\t\t\t\t})),\n\t\t\t};\n\t\t}\n\n\t\treturn this.request<SignAttachmentsResponse>(\n\t\t\t\"POST\",\n\t\t\t`/v1/runs/${runId}/attachments/sign`,\n\t\t\tdata,\n\t\t);\n\t}\n\n\tasync completeRun(runId: string, data: CompleteRunRequest): Promise<void> {\n\t\tif (this.options.dryRun) {\n\t\t\tthis.logPayload(`POST /v1/runs/${runId}/complete`, data);\n\t\t\treturn;\n\t\t}\n\n\t\tawait this.request(\"POST\", `/v1/runs/${runId}/complete`, data);\n\t}\n\n\tprivate async request<T>(\n\t\tmethod: string,\n\t\tpath: string,\n\t\tbody?: unknown,\n\t): Promise<T> {\n\t\tconst url = `${this.options.apiUrl}${path}`;\n\n\t\treturn withRetry(async () => {\n\t\t\tconst controller = new AbortController();\n\t\t\tconst timeoutId = setTimeout(\n\t\t\t\t() => controller.abort(),\n\t\t\t\tthis.options.timeoutMs,\n\t\t\t);\n\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(url, {\n\t\t\t\t\tmethod,\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t\tAuthorization: `Bearer ${this.options.apiKey}`,\n\t\t\t\t\t},\n\t\t\t\t\tbody: body ? JSON.stringify(body) : undefined,\n\t\t\t\t\tsignal: controller.signal,\n\t\t\t\t});\n\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tconst error = new Error(\n\t\t\t\t\t\t`API request failed: ${response.status} ${response.statusText}`,\n\t\t\t\t\t) as Error & { statusCode: number };\n\t\t\t\t\terror.statusCode = response.status;\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\n\t\t\t\tconst text = await response.text();\n\t\t\t\treturn text ? (JSON.parse(text) as T) : ({} as T);\n\t\t\t} finally {\n\t\t\t\tclearTimeout(timeoutId);\n\t\t\t}\n\t\t}, this.retryConfig);\n\t}\n\n\tprivate logPayload(endpoint: string, data: unknown): void {\n\t\tconsole.log(`\\n[supatest][dry-run] ${endpoint}`);\n\t\tconsole.log(JSON.stringify(data, null, 2));\n\t}\n}\n","import type { ErrorCategory, ErrorSummary, ReporterError } from \"./types.js\";\n\nexport class ErrorCollector {\n\tprivate errors: ReporterError[] = [];\n\n\trecordError(\n\t\tcategory: ErrorCategory,\n\t\tmessage: string,\n\t\tcontext?: {\n\t\t\ttestId?: string;\n\t\t\ttestTitle?: string;\n\t\t\tattachmentName?: string;\n\t\t\tfilePath?: string;\n\t\t\terror?: unknown;\n\t\t},\n\t): void {\n\t\tconst error: ReporterError = {\n\t\t\tcategory,\n\t\t\tmessage,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\ttestId: context?.testId,\n\t\t\ttestTitle: context?.testTitle,\n\t\t\tattachmentName: context?.attachmentName,\n\t\t\tfilePath: context?.filePath,\n\t\t\toriginalError:\n\t\t\t\tcontext?.error instanceof Error ? context.error.stack : undefined,\n\t\t};\n\n\t\tthis.errors.push(error);\n\t}\n\n\tgetSummary(): ErrorSummary {\n\t\tconst byCategory: Record<ErrorCategory, number> = {\n\t\t\tRUN_CREATE: 0,\n\t\t\tTEST_SUBMISSION: 0,\n\t\t\tATTACHMENT_SIGN: 0,\n\t\t\tATTACHMENT_UPLOAD: 0,\n\t\t\tFILE_READ: 0,\n\t\t\tRUN_COMPLETE: 0,\n\t\t};\n\n\t\tfor (const error of this.errors) {\n\t\t\tbyCategory[error.category]++;\n\t\t}\n\n\t\treturn {\n\t\t\ttotalErrors: this.errors.length,\n\t\t\tbyCategory,\n\t\t\terrors: this.errors,\n\t\t};\n\t}\n\n\thasErrors(): boolean {\n\t\treturn this.errors.length > 0;\n\t}\n\n\tformatSummary(): string {\n\t\tif (this.errors.length === 0) {\n\t\t\treturn \"\";\n\t\t}\n\n\t\tconst summary = this.getSummary();\n\t\tconst lines: string[] = [\n\t\t\t\"\",\n\t\t\t\"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\",\n\t\t\t\"⚠️ Supatest Reporter - Error Summary\",\n\t\t\t\"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\",\n\t\t\t`Total Errors: ${summary.totalErrors}`,\n\t\t\t\"\",\n\t\t];\n\n\t\t// Show errors by category\n\t\tfor (const [category, count] of Object.entries(summary.byCategory)) {\n\t\t\tif (count === 0) continue;\n\n\t\t\tconst icon = this.getCategoryIcon(category as ErrorCategory);\n\t\t\tconst label = this.getCategoryLabel(category as ErrorCategory);\n\t\t\tlines.push(`${icon} ${label}: ${count}`);\n\n\t\t\t// Show first 3 errors in this category\n\t\t\tconst categoryErrors = this.errors\n\t\t\t\t.filter((e) => e.category === category)\n\t\t\t\t.slice(0, 3);\n\n\t\t\tfor (const error of categoryErrors) {\n\t\t\t\tlines.push(` • ${this.formatError(error)}`);\n\t\t\t}\n\n\t\t\tconst remaining = count - categoryErrors.length;\n\t\t\tif (remaining > 0) {\n\t\t\t\tlines.push(` ... and ${remaining} more`);\n\t\t\t}\n\t\t\tlines.push(\"\");\n\t\t}\n\n\t\tlines.push(\n\t\t\t\"ℹ️ Note: Test execution was not interrupted. Some results may not be visible in the dashboard.\",\n\t\t);\n\t\tlines.push(\n\t\t\t\"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\",\n\t\t);\n\n\t\treturn lines.join(\"\\n\");\n\t}\n\n\tprivate getCategoryIcon(category: ErrorCategory): string {\n\t\tconst icons: Record<ErrorCategory, string> = {\n\t\t\tRUN_CREATE: \"🚀\",\n\t\t\tTEST_SUBMISSION: \"📝\",\n\t\t\tATTACHMENT_SIGN: \"🔐\",\n\t\t\tATTACHMENT_UPLOAD: \"📎\",\n\t\t\tFILE_READ: \"📁\",\n\t\t\tRUN_COMPLETE: \"🏁\",\n\t\t};\n\t\treturn icons[category];\n\t}\n\n\tprivate getCategoryLabel(category: ErrorCategory): string {\n\t\tconst labels: Record<ErrorCategory, string> = {\n\t\t\tRUN_CREATE: \"Run Initialization\",\n\t\t\tTEST_SUBMISSION: \"Test Result Submission\",\n\t\t\tATTACHMENT_SIGN: \"Attachment Signing\",\n\t\t\tATTACHMENT_UPLOAD: \"Attachment Upload\",\n\t\t\tFILE_READ: \"File Access\",\n\t\t\tRUN_COMPLETE: \"Run Completion\",\n\t\t};\n\t\treturn labels[category];\n\t}\n\n\tprivate formatError(error: ReporterError): string {\n\t\tconst parts: string[] = [];\n\n\t\tif (error.testTitle) {\n\t\t\tparts.push(`Test: \"${error.testTitle}\"`);\n\t\t}\n\n\t\tif (error.attachmentName) {\n\t\t\tparts.push(`Attachment: \"${error.attachmentName}\"`);\n\t\t}\n\n\t\tif (error.filePath) {\n\t\t\tparts.push(`File: ${error.filePath}`);\n\t\t}\n\n\t\tparts.push(error.message);\n\n\t\treturn parts.join(\" - \");\n\t}\n}\n","import { SimpleGit, simpleGit } from \"simple-git\";\n\nexport type GitInfo = {\n\tbranch?: string;\n\tcommit?: string;\n\tcommitMessage?: string;\n\trepo?: string;\n\tauthor?: string;\n\tauthorEmail?: string;\n\ttag?: string;\n\tdirty?: boolean;\n};\n\n/**\n * Get git information from the local repository\n * Falls back gracefully if git is not available or not a git repo\n */\nexport async function getLocalGitInfo(rootDir?: string): Promise<GitInfo> {\n\ttry {\n\t\tconst git: SimpleGit = simpleGit(rootDir || process.cwd());\n\n\t\t// Check if it's a git repository\n\t\tconst isRepo = await git.checkIsRepo();\n\t\tif (!isRepo) {\n\t\t\treturn {};\n\t\t}\n\n\t\tconst [branchResult, commitResult, status, remotes] = await Promise.all([\n\t\t\tgit.revparse([\"--abbrev-ref\", \"HEAD\"]).catch(() => undefined),\n\t\t\tgit.revparse([\"HEAD\"]).catch(() => undefined),\n\t\t\tgit.status().catch(() => undefined),\n\t\t\tgit.getRemotes(true).catch(() => []),\n\t\t]);\n\n\t\tconst branch = typeof branchResult === \"string\" ? branchResult.trim() : undefined;\n\t\tconst commit = typeof commitResult === \"string\" ? commitResult.trim() : undefined;\n\n\t\t// Get commit message (subject line)\n\t\tlet commitMessage: string | undefined;\n\t\ttry {\n\t\t\tconst logOutput = await git.raw([\"log\", \"-1\", \"--format=%s\"]);\n\t\t\tcommitMessage = typeof logOutput === \"string\" ? logOutput.trim() : undefined;\n\t\t} catch {\n\t\t\t// Ignore error\n\t\t}\n\n\t\t// Get author info from config\n\t\tlet author: string | undefined;\n\t\tlet authorEmail: string | undefined;\n\t\ttry {\n\t\t\tconst authorConfig = await git\n\t\t\t\t.getConfig(\"user.name\")\n\t\t\t\t.catch(() => undefined);\n\t\t\tconst emailConfig = await git\n\t\t\t\t.getConfig(\"user.email\")\n\t\t\t\t.catch(() => undefined);\n\t\t\tauthor =\n\t\t\t\tauthorConfig && typeof (authorConfig as any).value === \"string\"\n\t\t\t\t\t? (authorConfig as any).value.trim()\n\t\t\t\t\t: undefined;\n\t\t\tauthorEmail =\n\t\t\t\temailConfig && typeof (emailConfig as any).value === \"string\"\n\t\t\t\t\t? (emailConfig as any).value.trim()\n\t\t\t\t\t: undefined;\n\t\t} catch {\n\t\t\t// Ignore error\n\t\t}\n\n\t\t// Get current tag if on a tag\n\t\tlet tag: string | undefined;\n\t\ttry {\n\t\t\tconst tagResult = await git.raw([\"describe\", \"--tags\", \"--exact-match\"]).catch(() => undefined);\n\t\t\ttag = typeof tagResult === \"string\" ? tagResult.trim() : undefined;\n\t\t} catch {\n\t\t\t// Ignore error - not on a tag\n\t\t}\n\n\t\t// Get repository URL\n\t\tlet repo: string | undefined;\n\t\tconst remote = remotes && remotes.length > 0 ? remotes[0] : null;\n\t\tif (remote && remote.refs && remote.refs.fetch) {\n\t\t\trepo = remote.refs.fetch;\n\t\t}\n\n\t\t// Check for uncommitted changes\n\t\tconst dirty = status ? status.modified.length > 0 || status.created.length > 0 || status.deleted.length > 0 : false;\n\n\t\treturn {\n\t\t\tbranch,\n\t\t\tcommit,\n\t\t\tcommitMessage,\n\t\t\trepo,\n\t\t\tauthor,\n\t\t\tauthorEmail,\n\t\t\ttag,\n\t\t\tdirty,\n\t\t};\n\t} catch (error) {\n\t\t// If anything goes wrong, return empty object\n\t\t// This is a best-effort operation\n\t\treturn {};\n\t}\n}\n","import fs from \"node:fs\";\nimport { defaultRetryConfig, withRetry } from \"./retry.js\";\nimport type { SignedUpload, UploadItem, UploadResult } from \"./types.js\";\n\nexport type UploaderOptions = {\n\tmaxConcurrent: number;\n\ttimeoutMs: number;\n\tdryRun: boolean;\n};\n\nexport class AttachmentUploader {\n\tprivate readonly options: UploaderOptions;\n\n\tconstructor(options: UploaderOptions) {\n\t\tthis.options = options;\n\t}\n\n\tasync upload(\n\t\tsignedUrl: string,\n\t\tfilePath: string,\n\t\tcontentType: string,\n\t): Promise<void> {\n\t\tif (this.options.dryRun) {\n\t\t\ttry {\n\t\t\t\tconst stats = fs.statSync(filePath);\n\t\t\t\tconsole.log(\n\t\t\t\t\t`[supatest][dry-run] Would upload ${filePath} (${stats.size} bytes) to S3`,\n\t\t\t\t);\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`[supatest][dry-run] Cannot access file ${filePath}: ${message}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tlet fileBuffer: Buffer;\n\t\ttry {\n\t\t\tfileBuffer = await fs.promises.readFile(filePath);\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tthrow new Error(`Failed to read file ${filePath}: ${message}`);\n\t\t}\n\n\t\tawait withRetry(async () => {\n\t\t\tconst controller = new AbortController();\n\t\t\tconst timeoutId = setTimeout(\n\t\t\t\t() => controller.abort(),\n\t\t\t\tthis.options.timeoutMs,\n\t\t\t);\n\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(signedUrl, {\n\t\t\t\t\tmethod: \"PUT\",\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t\"Content-Type\": contentType,\n\t\t\t\t\t\t\"Content-Length\": String(fileBuffer.length),\n\t\t\t\t\t},\n\t\t\t\t\tbody: new Uint8Array(fileBuffer),\n\t\t\t\t\tsignal: controller.signal,\n\t\t\t\t});\n\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tconst error = new Error(\n\t\t\t\t\t\t`S3 upload failed: ${response.status} ${response.statusText}`,\n\t\t\t\t\t) as Error & { statusCode: number };\n\t\t\t\t\terror.statusCode = response.status;\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tclearTimeout(timeoutId);\n\t\t\t}\n\t\t}, defaultRetryConfig);\n\t}\n\n\tasync uploadBatch(\n\t\titems: UploadItem[],\n\t\tsignedUploads: SignedUpload[],\n\t): Promise<UploadResult[]> {\n\t\t// Use dynamic import for p-limit (ESM module)\n\t\tconst pLimit = (await import(\"p-limit\")).default;\n\t\tconst limit = pLimit(this.options.maxConcurrent);\n\n\t\tconst results = await Promise.allSettled(\n\t\t\titems.map((item, index) =>\n\t\t\t\tlimit(async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait this.upload(item.signedUrl, item.filePath, item.contentType);\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t\t\tattachmentId: signedUploads[index]?.attachmentId,\n\t\t\t\t\t\t};\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t// Enhanced error with file context\n\t\t\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tsuccess: false,\n\t\t\t\t\t\t\tattachmentId: signedUploads[index]?.attachmentId,\n\t\t\t\t\t\t\terror: `${item.filePath}: ${message}`,\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t\t),\n\t\t);\n\n\t\treturn results.map((result, index) => {\n\t\t\tif (result.status === \"fulfilled\") {\n\t\t\t\treturn result.value;\n\t\t\t}\n\t\t\t// Shouldn't happen since we catch inside, but handle it\n\t\t\tconst message = result.reason instanceof Error ? result.reason.message : String(result.reason);\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\tattachmentId: signedUploads[index]?.attachmentId,\n\t\t\t\terror: message,\n\t\t\t};\n\t\t});\n\t}\n}\n"],"mappings":"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","names":["import_node_fs","path","fs","path","fs","os"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/retry.ts","../src/api-client.ts","../src/error-collector.ts","../src/git-utils.ts","../src/source-reader.ts","../src/uploader.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport type {\n\tFullConfig,\n\tFullResult,\n\tReporter,\n\tSuite,\n\tTestCase,\n\tTestResult,\n\tTestStep,\n} from \"@playwright/test/reporter\";\nimport { SupatestApiClient } from \"./api-client.js\";\nimport { ErrorCollector } from \"./error-collector.js\";\nimport { getLocalGitInfo } from \"./git-utils.js\";\nimport { SourceReader } from \"./source-reader.js\";\nimport type {\n\tAnnotationInfo,\n\tAttachmentKind,\n\tAttachmentMeta,\n\tCreateRunRequest,\n\tEnvironmentInfo,\n\tErrorInfo,\n\tProjectConfig,\n\tReporterOptions,\n\tRunSummary,\n\tSourceSnippet,\n\tStepInfo,\n\tTestOutcome,\n\tTestResultEntry,\n\tTestResultPayload,\n\tTestStatus,\n} from \"./types.js\";\nimport { AttachmentUploader } from \"./uploader.js\";\n\nconst DEFAULT_API_URL = \"https://code-api.supatest.ai\";\nconst DEFAULT_MAX_CONCURRENT_UPLOADS = 5;\nconst DEFAULT_RETRY_ATTEMPTS = 3;\nconst DEFAULT_TIMEOUT_MS = 30000;\nconst MAX_STDOUT_LINES = 100;\nconst MAX_STDERR_LINES = 100;\n\ntype TestInfo = {\n\tstatus: TestStatus;\n\toutcome: TestOutcome;\n\tretries: number;\n};\n\nexport default class SupatestPlaywrightReporter implements Reporter {\n\tprivate readonly options: ReporterOptions;\n\tprivate client!: SupatestApiClient;\n\tprivate uploader!: AttachmentUploader;\n\tprivate errorCollector = new ErrorCollector();\n\tprivate runId?: string;\n\tprivate uploadQueue: Promise<void>[] = [];\n\tprivate uploadLimit!: <T>(fn: () => Promise<T>) => Promise<T>;\n\tprivate startedAt?: string;\n\tprivate firstTestStartTime?: number;\n\tprivate firstFailureTime?: number;\n\tprivate testsProcessed = new Map<string, TestInfo>();\n\tprivate disabled = false;\n\tprivate config?: FullConfig;\n\tprivate rootDir?: string;\n\tprivate stepIdCounter = 0;\n\tprivate sourceReader?: SourceReader;\n\n\tconstructor(options: ReporterOptions = {} as ReporterOptions) {\n\t\tthis.options = {\n\t\t\tapiUrl: DEFAULT_API_URL,\n\t\t\tuploadAssets: true,\n\t\t\tmaxConcurrentUploads: DEFAULT_MAX_CONCURRENT_UPLOADS,\n\t\t\tretryAttempts: DEFAULT_RETRY_ATTEMPTS,\n\t\t\ttimeoutMs: DEFAULT_TIMEOUT_MS,\n\t\t\tdryRun: false,\n\t\t\t...options,\n\t\t};\n\t}\n\n\tasync onBegin(config: FullConfig, suite: Suite): Promise<void> {\n\t\tthis.config = config;\n\t\tthis.rootDir = config.rootDir;\n\t\tthis.sourceReader = new SourceReader(config.rootDir);\n\n\t\t// Validate required options\n\t\tif (!this.options.projectId) {\n\t\t\tthis.options.projectId = process.env.SUPATEST_PROJECT_ID ?? \"\";\n\t\t}\n\t\tif (!this.options.apiKey) {\n\t\t\tthis.options.apiKey = process.env.SUPATEST_API_KEY ?? \"\";\n\t\t}\n\n\t\t// Check if we should run in dry-run mode\n\t\tif (process.env.SUPATEST_DRY_RUN === \"true\") {\n\t\t\tthis.options.dryRun = true;\n\t\t}\n\n\t\tif (!this.options.projectId || !this.options.apiKey) {\n\t\t\tif (!this.options.dryRun) {\n\t\t\t\tthis.logWarn(\n\t\t\t\t\t\"Missing projectId or apiKey. Set SUPATEST_PROJECT_ID and SUPATEST_API_KEY env vars, or pass them as options.\",\n\t\t\t\t);\n\t\t\t\tthis.disabled = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Initialize clients\n\t\tthis.client = new SupatestApiClient({\n\t\t\tapiKey: this.options.apiKey,\n\t\t\tapiUrl: this.options.apiUrl!,\n\t\t\ttimeoutMs: this.options.timeoutMs!,\n\t\t\tretryAttempts: this.options.retryAttempts!,\n\t\t\tdryRun: this.options.dryRun!,\n\t\t});\n\n\t\tthis.uploader = new AttachmentUploader({\n\t\t\tmaxConcurrent: this.options.maxConcurrentUploads!,\n\t\t\ttimeoutMs: this.options.timeoutMs!,\n\t\t\tdryRun: this.options.dryRun!,\n\t\t});\n\n\t\t// Initialize p-limit for upload queue\n\t\tconst pLimit = (await import(\"p-limit\")).default;\n\t\tthis.uploadLimit = pLimit(this.options.maxConcurrentUploads!);\n\n\t\tthis.startedAt = new Date().toISOString();\n\t\tconst allTests = suite.allTests();\n\n\t\t// Build project configs\n\t\tconst projects = this.buildProjectConfigs(config);\n\n\t\t// Count unique test files\n\t\tconst testFiles = new Set(allTests.map((t) => t.location.file));\n\n\t\ttry {\n\t\t\tconst runRequest: CreateRunRequest = {\n\t\t\t\tprojectId: this.options.projectId,\n\t\t\t\tstartedAt: this.startedAt,\n\t\t\t\tplaywright: {\n\t\t\t\t\tversion: config.version,\n\t\t\t\t\tworkers: config.workers,\n\t\t\t\t\tretries: config.projects?.[0]?.retries ?? 0,\n\t\t\t\t\tfullyParallel: config.fullyParallel,\n\t\t\t\t\tforbidOnly: config.forbidOnly,\n\t\t\t\t\tglobalTimeout: config.globalTimeout,\n\t\t\t\t\treportSlowTests: config.reportSlowTests\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\tmax: config.reportSlowTests.max,\n\t\t\t\t\t\t\t\tthreshold: config.reportSlowTests.threshold,\n\t\t\t\t\t\t }\n\t\t\t\t\t\t: undefined,\n\t\t\t\t},\n\t\t\t\tprojects,\n\t\t\t\ttestStats: {\n\t\t\t\t\ttotalFiles: testFiles.size,\n\t\t\t\t\ttotalTests: allTests.length,\n\t\t\t\t\ttotalProjects: config.projects?.length ?? 0,\n\t\t\t\t},\n\t\t\t\tshard: config.shard\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tcurrent: config.shard.current,\n\t\t\t\t\t\t\ttotal: config.shard.total,\n\t\t\t\t\t }\n\t\t\t\t\t: undefined,\n\t\t\t\tenvironment: this.getEnvironmentInfo(config),\n\t\t\t\tgit: await this.getGitInfo(),\n\t\t\t\tconfigPath: config.configFile,\n\t\t\t\trootDir: config.rootDir,\n\t\t\t\ttestDir: config.projects?.[0]?.testDir,\n\t\t\t\toutputDir: config.projects?.[0]?.outputDir,\n\t\t\t};\n\n\t\t\tconst response = await this.client.createRun(runRequest);\n\n\t\t\tthis.runId = response.runId;\n\t\t\tthis.logInfo(\n\t\t\t\t`Run ${this.runId} started (${allTests.length} tests across ${testFiles.size} files, ${projects.length} projects)`,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tconst errorMsg = this.getErrorMessage(error);\n\t\t\tthis.errorCollector.recordError(\"RUN_CREATE\", errorMsg, { error });\n\t\t\tthis.disabled = true;\n\t\t}\n\t}\n\n\tonTestEnd(test: TestCase, result: TestResult): void {\n\t\tif (this.disabled || !this.runId) return;\n\n\t\t// Track first test start time\n\t\tif (!this.firstTestStartTime && result.startTime) {\n\t\t\tthis.firstTestStartTime = result.startTime.getTime();\n\t\t}\n\n\t\t// Track first failure time\n\t\tif (\n\t\t\t!this.firstFailureTime &&\n\t\t\t(result.status === \"failed\" || result.status === \"timedOut\")\n\t\t) {\n\t\t\tthis.firstFailureTime = Date.now();\n\t\t}\n\n\t\t// Fire and forget - returns immediately (non-blocking)\n\t\tconst promise = this.uploadLimit(() =>\n\t\t\tthis.processTestResult(test, result),\n\t\t);\n\t\tthis.uploadQueue.push(promise);\n\t}\n\n\tasync onEnd(result: FullResult): Promise<void> {\n\t\tif (this.disabled || !this.runId) {\n\t\t\t// Show error summary even if disabled\n\t\t\tif (this.disabled && this.errorCollector.hasErrors()) {\n\t\t\t\tconsole.log(this.errorCollector.formatSummary());\n\t\t\t}\n\t\t\tthis.logInfo(\"Reporter disabled, skipping onEnd\");\n\t\t\treturn;\n\t\t}\n\n\t\t// Wait for all uploads to complete\n\t\tthis.logInfo(`Waiting for ${this.uploadQueue.length} pending uploads...`);\n\t\tawait Promise.allSettled(this.uploadQueue);\n\n\t\t// Complete the run\n\t\tconst summary = this.computeSummary(result);\n\t\tconst endedAt = new Date().toISOString();\n\t\tconst startTime = this.startedAt ? new Date(this.startedAt).getTime() : 0;\n\n\t\ttry {\n\t\t\tawait this.client.completeRun(this.runId, {\n\t\t\t\tstatus: this.mapRunStatus(result.status),\n\t\t\t\tendedAt,\n\t\t\t\tsummary,\n\t\t\t\ttiming: {\n\t\t\t\t\ttotalDurationMs: Math.round(result.duration),\n\t\t\t\t\ttimeToFirstTest: this.firstTestStartTime\n\t\t\t\t\t\t? Math.round(this.firstTestStartTime - startTime)\n\t\t\t\t\t\t: undefined,\n\t\t\t\t\ttimeToFirstFailure: this.firstFailureTime\n\t\t\t\t\t\t? Math.round(this.firstFailureTime - startTime)\n\t\t\t\t\t\t: undefined,\n\t\t\t\t},\n\t\t\t});\n\t\t\tthis.logInfo(`Run ${this.runId} completed: ${result.status}`);\n\t\t} catch (error) {\n\t\t\tconst errorMsg = this.getErrorMessage(error);\n\n\t\t\tthis.errorCollector.recordError(\"RUN_COMPLETE\", errorMsg, { error });\n\t\t}\n\n\t\t// Print error summary at the end\n\t\tif (this.errorCollector.hasErrors()) {\n\t\t\tconsole.log(this.errorCollector.formatSummary());\n\t\t}\n\n\t\t// Clear source reader cache to free memory\n\t\tthis.sourceReader?.clear();\n\t}\n\n\tprivate async processTestResult(\n\t\ttest: TestCase,\n\t\tresult: TestResult,\n\t): Promise<void> {\n\t\tconst testId = this.getTestId(test);\n\t\tconst resultId = this.getResultId(testId, result.retry, result.workerIndex, result.parallelIndex);\n\n\t\ttry {\n\t\t\tconst payload = this.buildTestPayload(test, result);\n\t\t\tawait this.client.submitTest(this.runId!, payload);\n\n\t\t\t// Upload attachments if enabled\n\t\t\tif (this.options.uploadAssets) {\n\t\t\t\tawait this.uploadAttachments(result, resultId);\n\t\t\t}\n\n\t\t\t// Track test outcome\n\t\t\tconst outcome = this.getTestOutcome(test, result);\n\t\t\tconst existing = this.testsProcessed.get(testId);\n\t\t\tthis.testsProcessed.set(testId, {\n\t\t\t\tstatus: result.status as TestStatus,\n\t\t\t\toutcome,\n\t\t\t\tretries: (existing?.retries ?? 0) + (result.retry > 0 ? 1 : 0),\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tconst errorMsg = this.getErrorMessage(error);\n\n\t\t\tthis.errorCollector.recordError(\"TEST_SUBMISSION\", errorMsg, {\n\t\t\t\ttestId,\n\t\t\t\ttestTitle: test.title,\n\t\t\t\terror,\n\t\t\t});\n\n\t\t\t// Don't throw - let Promise.allSettled handle it\n\t\t}\n\t}\n\n\tprivate buildTestPayload(\n\t\ttest: TestCase,\n\t\tresult: TestResult,\n\t): TestResultPayload {\n\t\tconst testId = this.getTestId(test);\n\n\t\t// Extract tags\n\t\tconst tags = test.tags ?? [];\n\n\t\t// Build all annotations\n\t\tconst annotations = this.serializeAnnotations(test.annotations ?? []);\n\n\t\t// Build result entry with full data\n\t\tconst resultEntry: TestResultEntry = {\n\t\t\tresultId: this.getResultId(testId, result.retry, result.workerIndex, result.parallelIndex),\n\t\t\tretry: result.retry,\n\t\t\tstatus: result.status as TestStatus,\n\t\t\tstartTime: result.startTime?.toISOString?.() ?? undefined,\n\t\t\tdurationMs: result.duration,\n\t\t\tworkerIndex: result.workerIndex,\n\t\t\tparallelIndex: result.parallelIndex,\n\t\t\terrors: this.serializeErrors(result.errors ?? []),\n\t\t\tsteps: this.serializeSteps(result.steps ?? []),\n\t\t\tannotations: this.serializeAnnotations(result.annotations ?? []),\n\t\t\tattachments: this.serializeAttachmentMeta(result.attachments ?? []),\n\t\t\tstdout: this.serializeOutput(result.stdout ?? [], MAX_STDOUT_LINES),\n\t\t\tstderr: this.serializeOutput(result.stderr ?? [], MAX_STDERR_LINES),\n\t\t};\n\n\t\t// Get project name from test's parent suite chain\n\t\tconst projectName = test.parent?.project()?.name ?? \"unknown\";\n\n\t\treturn {\n\t\t\ttestId,\n\t\t\tplaywrightId: test.id,\n\t\t\tfile: this.relativePath(test.location.file),\n\t\t\tlocation: {\n\t\t\t\tfile: this.relativePath(test.location.file),\n\t\t\t\tline: test.location.line,\n\t\t\t\tcolumn: test.location.column,\n\t\t\t},\n\t\t\ttitle: test.title,\n\t\t\ttitlePath: test.titlePath(),\n\t\t\ttags,\n\t\t\tannotations,\n\t\t\texpectedStatus: test.expectedStatus as TestStatus,\n\t\t\toutcome: this.getTestOutcome(test, result),\n\t\t\ttimeout: test.timeout,\n\t\t\tretries: test.retries,\n\t\t\trepeatEachIndex:\n\t\t\t\ttest.repeatEachIndex > 0 ? test.repeatEachIndex : undefined,\n\t\t\tstatus: result.status as TestStatus,\n\t\t\tdurationMs: result.duration,\n\t\t\tretryCount: result.retry,\n\t\t\tresults: [resultEntry],\n\t\t\tprojectName,\n\t\t};\n\t}\n\n\tprivate serializeSteps(steps: readonly TestStep[]): StepInfo[] {\n\t\treturn steps.map((step) => this.serializeStep(step));\n\t}\n\n\tprivate serializeStep(step: TestStep): StepInfo {\n\t\tconst stepId = `step_${++this.stepIdCounter}`;\n\n\t\t// Capture source code snippet if location is available\n\t\tlet snippet: SourceSnippet | undefined;\n\t\tif (step.location && this.sourceReader) {\n\t\t\tsnippet = this.sourceReader.getSnippet(\n\t\t\t\tstep.location.file,\n\t\t\t\tstep.location.line,\n\t\t\t);\n\t\t}\n\n\t\treturn {\n\t\t\tstepId,\n\t\t\ttitle: step.title,\n\t\t\tcategory: step.category,\n\t\t\tstartTime: step.startTime?.toISOString?.() ?? undefined,\n\t\t\tdurationMs: step.duration,\n\t\t\tlocation: step.location\n\t\t\t\t? {\n\t\t\t\t\t\tfile: this.relativePath(step.location.file),\n\t\t\t\t\t\tline: step.location.line,\n\t\t\t\t\t\tcolumn: step.location.column,\n\t\t\t\t }\n\t\t\t\t: undefined,\n\t\t\terror: step.error ? this.serializeError(step.error) : undefined,\n\t\t\t// Recursively serialize nested steps\n\t\t\tsteps:\n\t\t\t\tstep.steps && step.steps.length > 0\n\t\t\t\t\t? this.serializeSteps(step.steps)\n\t\t\t\t\t: undefined,\n\t\t\t// Step attachments (from step.attachments if available)\n\t\t\tattachments: undefined, // Playwright doesn't expose step.attachments directly\n\t\t\tsnippet,\n\t\t};\n\t}\n\n\tprivate serializeErrors(\n\t\terrors: TestResult[\"errors\"],\n\t): ErrorInfo[] {\n\t\treturn errors.map((e) => this.serializeError(e));\n\t}\n\n\tprivate serializeError(error: {\n\t\tmessage?: string;\n\t\tstack?: string;\n\t\tvalue?: unknown;\n\t\tlocation?: { file: string; line: number; column: number };\n\t}): ErrorInfo {\n\t\treturn {\n\t\t\tmessage: error.message,\n\t\t\tstack: error.stack,\n\t\t\tvalue: error.value !== undefined ? String(error.value) : undefined,\n\t\t\tlocation: error.location\n\t\t\t\t? {\n\t\t\t\t\t\tfile: this.relativePath(error.location.file),\n\t\t\t\t\t\tline: error.location.line,\n\t\t\t\t\t\tcolumn: error.location.column,\n\t\t\t\t }\n\t\t\t\t: undefined,\n\t\t\t// Could extract code snippet from file here if needed\n\t\t\tsnippet: undefined,\n\t\t};\n\t}\n\n\tprivate serializeAnnotations(\n\t\tannotations: readonly { type: string; description?: string }[],\n\t): AnnotationInfo[] {\n\t\treturn annotations.map((a) => ({\n\t\t\ttype: a.type,\n\t\t\tdescription: a.description,\n\t\t}));\n\t}\n\n\tprivate serializeAttachmentMeta(\n\t\tattachments: TestResult[\"attachments\"],\n\t): AttachmentMeta[] {\n\t\treturn attachments\n\t\t\t.filter((a) => a.path || a.body)\n\t\t\t.map((a) => ({\n\t\t\t\tname: a.name,\n\t\t\t\tfilename: a.path ? path.basename(a.path) : a.name,\n\t\t\t\tcontentType: a.contentType,\n\t\t\t\tsizeBytes: this.getAttachmentSize(a),\n\t\t\t\tkind: this.getAttachmentKind(a.name, a.contentType),\n\t\t\t}));\n\t}\n\n\tprivate serializeOutput(\n\t\toutput: (string | Buffer)[],\n\t\tmaxLines: number,\n\t): string[] {\n\t\tconst lines: string[] = [];\n\t\tfor (const chunk of output) {\n\t\t\tconst str = chunk.toString();\n\t\t\tlines.push(...str.split(\"\\n\"));\n\t\t\tif (lines.length >= maxLines) break;\n\t\t}\n\t\treturn lines.slice(0, maxLines);\n\t}\n\n\tprivate getTestOutcome(test: TestCase, result: TestResult): TestOutcome {\n\t\t// Use Playwright's built-in outcome() if available\n\t\tif (typeof test.outcome === \"function\") {\n\t\t\treturn test.outcome() as TestOutcome;\n\t\t}\n\n\t\t// Fallback logic\n\t\tif (result.status === \"skipped\") return \"skipped\";\n\t\tif (result.status === test.expectedStatus) return \"expected\";\n\t\tif (result.status === \"passed\" && result.retry > 0) return \"flaky\";\n\t\treturn \"unexpected\";\n\t}\n\n\tprivate async uploadAttachments(\n\t\tresult: TestResult,\n\t\ttestResultId: string,\n\t): Promise<void> {\n\t\tconst attachments = (result.attachments ?? []).filter((a) => a.path);\n\t\tif (attachments.length === 0) return;\n\n\t\t// Validate files exist before attempting upload\n\t\tconst validAttachments: typeof attachments = [];\n\t\tfor (const attachment of attachments) {\n\t\t\ttry {\n\t\t\t\tawait fs.promises.access(attachment.path!, fs.constants.R_OK);\n\t\t\t\tvalidAttachments.push(attachment);\n\t\t\t} catch (error) {\n\t\t\t\tconst errorMsg = `File not accessible: ${attachment.path}`;\n\n\t\t\t\tthis.errorCollector.recordError(\"FILE_READ\", errorMsg, {\n\t\t\t\t\tattachmentName: attachment.name,\n\t\t\t\t\tfilePath: attachment.path,\n\t\t\t\t\terror,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tif (validAttachments.length === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst meta: AttachmentMeta[] = validAttachments.map((a) => ({\n\t\t\tname: a.name,\n\t\t\tfilename: path.basename(a.path!),\n\t\t\tcontentType: a.contentType,\n\t\t\tsizeBytes: this.getFileSize(a.path!),\n\t\t\tkind: this.getAttachmentKind(a.name, a.contentType),\n\t\t}));\n\n\t\ttry {\n\t\t\tconst { uploads } = await this.client.signAttachments(this.runId!, {\n\t\t\t\ttestResultId,\n\t\t\t\tattachments: meta,\n\t\t\t});\n\n\t\t\tconst uploadItems = uploads.map((u, i) => ({\n\t\t\t\tsignedUrl: u.signedUrl,\n\t\t\t\tfilePath: validAttachments[i].path!,\n\t\t\t\tcontentType: validAttachments[i].contentType,\n\t\t\t}));\n\n\t\t\tconst results = await this.uploader.uploadBatch(uploadItems, uploads);\n\t\t\tconst failures = results.filter((r) => !r.success);\n\n\t\t\tif (failures.length > 0) {\n\t\t\t\t// Record each failure\n\t\t\t\tfailures.forEach((failure) => {\n\t\t\t\t\tconst attachment = validAttachments.find(\n\t\t\t\t\t\t(_, i) => uploads[i]?.attachmentId === failure.attachmentId,\n\t\t\t\t\t);\n\n\t\t\t\t\tthis.errorCollector.recordError(\n\t\t\t\t\t\t\"ATTACHMENT_UPLOAD\",\n\t\t\t\t\t\tfailure.error || \"Upload failed\",\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tattachmentName: attachment?.name,\n\t\t\t\t\t\t\tfilePath: attachment?.path,\n\t\t\t\t\t\t\terror: failure.error,\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t});\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst errorMsg = this.getErrorMessage(error);\n\n\t\t\tthis.errorCollector.recordError(\"ATTACHMENT_SIGN\", errorMsg, { error });\n\t\t}\n\t}\n\n\tprivate buildProjectConfigs(config: FullConfig): ProjectConfig[] {\n\t\treturn (config.projects ?? []).map((project) => {\n\t\t\tconst use = project.use ?? {} as Record<string, unknown>;\n\t\t\treturn {\n\t\t\t\tname: project.name,\n\t\t\t\tbrowserName: use.browserName as string | undefined,\n\t\t\t\tchannel: use.channel as string | undefined,\n\t\t\t\tdeviceName: (use as Record<string, unknown>).deviceName as string | undefined,\n\t\t\t\tviewport: use.viewport\n\t\t\t\t\t? { width: (use.viewport as { width: number; height: number }).width, height: (use.viewport as { width: number; height: number }).height }\n\t\t\t\t\t: undefined,\n\t\t\t\tlocale: use.locale as string | undefined,\n\t\t\t\ttimezone: use.timezoneId as string | undefined,\n\t\t\t\tcolorScheme: use.colorScheme as ProjectConfig[\"colorScheme\"],\n\t\t\t\tisMobile: use.isMobile as boolean | undefined,\n\t\t\t\thasTouch: use.hasTouch as boolean | undefined,\n\t\t\t\ttestDir: project.testDir,\n\t\t\t\ttimeout: project.timeout,\n\t\t\t};\n\t\t});\n\t}\n\n\tprivate getEnvironmentInfo(config: FullConfig): EnvironmentInfo {\n\t\tconst ciInfo = this.getCIInfo();\n\n\t\treturn {\n\t\t\tos: {\n\t\t\t\tplatform: os.platform(),\n\t\t\t\trelease: os.release(),\n\t\t\t\tarch: os.arch(),\n\t\t\t},\n\t\t\tnode: {\n\t\t\t\tversion: process.version,\n\t\t\t},\n\t\t\tmachine: {\n\t\t\t\tcpus: os.cpus().length,\n\t\t\t\tmemory: os.totalmem(),\n\t\t\t\thostname: os.hostname(),\n\t\t\t},\n\t\t\tplaywright: {\n\t\t\t\tversion: config.version,\n\t\t\t},\n\t\t\tci: ciInfo,\n\t\t};\n\t}\n\n\tprivate computeSummary(result: FullResult): RunSummary {\n\t\tlet passed = 0;\n\t\tlet failed = 0;\n\t\tlet flaky = 0;\n\t\tlet skipped = 0;\n\t\tlet timedOut = 0;\n\t\tlet interrupted = 0;\n\t\tlet expected = 0;\n\t\tlet unexpected = 0;\n\n\t\tfor (const [, testInfo] of this.testsProcessed) {\n\t\t\tswitch (testInfo.status) {\n\t\t\t\tcase \"passed\":\n\t\t\t\t\tpassed += 1;\n\t\t\t\t\tif (testInfo.retries > 0) flaky += 1;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"failed\":\n\t\t\t\t\tfailed += 1;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"skipped\":\n\t\t\t\t\tskipped += 1;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"timedOut\":\n\t\t\t\t\ttimedOut += 1;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"interrupted\":\n\t\t\t\t\tinterrupted += 1;\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Count outcomes\n\t\t\tif (testInfo.outcome === \"expected\") expected += 1;\n\t\t\telse if (testInfo.outcome === \"unexpected\") unexpected += 1;\n\t\t}\n\n\t\treturn {\n\t\t\ttotal: this.testsProcessed.size,\n\t\t\tpassed,\n\t\t\tfailed,\n\t\t\tflaky,\n\t\t\tskipped,\n\t\t\ttimedOut,\n\t\t\tinterrupted,\n\t\t\tdurationMs: Math.round(result.duration),\n\t\t\texpected,\n\t\t\tunexpected,\n\t\t};\n\t}\n\n\tprivate mapRunStatus(\n\t\tstatus: FullResult[\"status\"],\n\t): \"complete\" | \"errored\" | \"interrupted\" {\n\t\tswitch (status) {\n\t\t\tcase \"passed\":\n\t\t\t\treturn \"complete\";\n\t\t\tcase \"failed\":\n\t\t\tcase \"timedout\":\n\t\t\t\treturn \"errored\";\n\t\t\tcase \"interrupted\":\n\t\t\t\treturn \"interrupted\";\n\t\t\tdefault:\n\t\t\t\treturn \"complete\";\n\t\t}\n\t}\n\n\t/**\n\t * Compute a stable test ID for tracking across runs.\n\t * Priority:\n\t * 1. Explicit @id:XXX tag (e.g., @id:TC-001)\n\t * 2. Annotation with type 'id' (e.g., test.info().annotations.push({ type: 'id', description: 'TC-001' }))\n\t * 3. Fallback: hash of file + titlePath + projectName (like Allure's historyId)\n\t *\n\t * Note: Does NOT include line number, so tests can move within a file without losing history.\n\t */\n\tprivate getTestId(test: TestCase): string {\n\t\t// 1. Check for explicit @id:XXX tag\n\t\tconst tags = test.tags ?? [];\n\t\tconst idTag = tags.find((tag) => tag.startsWith(\"@id:\"));\n\t\tif (idTag) {\n\t\t\treturn idTag.slice(4); // Remove \"@id:\" prefix\n\t\t}\n\n\t\t// 2. Check for annotation with type 'id'\n\t\tconst annotations = test.annotations ?? [];\n\t\tconst idAnnotation = annotations.find((a) => a.type === \"id\");\n\t\tif (idAnnotation?.description) {\n\t\t\treturn idAnnotation.description;\n\t\t}\n\n\t\t// 3. Fallback: compute from path (like Allure's historyId)\n\t\tconst projectName = test.parent?.project()?.name ?? \"default\";\n\t\tconst pathParts = [\n\t\t\tthis.relativePath(test.location.file),\n\t\t\t...test.titlePath(),\n\t\t\tprojectName,\n\t\t];\n\t\treturn this.hashKey(pathParts.join(\"::\"));\n\t}\n\n\tprivate getResultId(testId: string, retry: number, workerIndex?: number, parallelIndex?: number): string {\n\t\t// Include worker/parallel index to ensure uniqueness across parallel runs\n\t\tconst key = parallelIndex !== undefined\n\t\t\t? `${testId}:${retry}:${parallelIndex}`\n\t\t\t: workerIndex !== undefined\n\t\t\t? `${testId}:${retry}:${workerIndex}`\n\t\t\t: `${testId}:${retry}`;\n\t\treturn this.hashKey(key);\n\t}\n\n\tprivate hashKey(value: string): string {\n\t\treturn createHash(\"sha256\").update(value).digest(\"hex\").slice(0, 12);\n\t}\n\n\tprivate relativePath(filePath: string): string {\n\t\tif (this.rootDir && filePath.startsWith(this.rootDir)) {\n\t\t\treturn filePath.slice(this.rootDir.length + 1);\n\t\t}\n\t\treturn filePath;\n\t}\n\n\tprivate getFileSize(filePath: string): number {\n\t\ttry {\n\t\t\treturn fs.statSync(filePath).size;\n\t\t} catch {\n\t\t\treturn 0;\n\t\t}\n\t}\n\n\tprivate getAttachmentSize(\n\t\tattachment: TestResult[\"attachments\"][number],\n\t): number {\n\t\tif (attachment.path) {\n\t\t\treturn this.getFileSize(attachment.path);\n\t\t}\n\t\tif (attachment.body) {\n\t\t\treturn attachment.body.length;\n\t\t}\n\t\treturn 0;\n\t}\n\n\tprivate getAttachmentKind(name: string, contentType: string): AttachmentKind {\n\t\tif (name === \"video\" || contentType.startsWith(\"video/\")) return \"video\";\n\t\tif (name === \"trace\" || name.endsWith(\".zip\")) return \"trace\";\n\t\tif (name.includes(\"screenshot\") || contentType === \"image/png\")\n\t\t\treturn \"screenshot\";\n\t\tif (name === \"stdout\") return \"stdout\";\n\t\tif (name === \"stderr\") return \"stderr\";\n\t\treturn \"other\";\n\t}\n\n\tprivate async getGitInfo() {\n\t\t// Try CI environment variables first\n\t\tconst ciGitInfo = {\n\t\t\tbranch:\n\t\t\t\tprocess.env.GITHUB_REF_NAME ??\n\t\t\t\tprocess.env.GITHUB_HEAD_REF ??\n\t\t\t\tprocess.env.CI_COMMIT_BRANCH ??\n\t\t\t\tprocess.env.GITLAB_CI_COMMIT_BRANCH ??\n\t\t\t\tprocess.env.GIT_BRANCH,\n\t\t\tcommit:\n\t\t\t\tprocess.env.GITHUB_SHA ??\n\t\t\t\tprocess.env.CI_COMMIT_SHA ??\n\t\t\t\tprocess.env.GITLAB_CI_COMMIT_SHA ??\n\t\t\t\tprocess.env.GIT_COMMIT,\n\t\t\tcommitMessage:\n\t\t\t\tprocess.env.CI_COMMIT_MESSAGE ??\n\t\t\t\tprocess.env.GITLAB_CI_COMMIT_MESSAGE,\n\t\t\trepo:\n\t\t\t\tprocess.env.GITHUB_REPOSITORY ??\n\t\t\t\tprocess.env.CI_PROJECT_PATH ??\n\t\t\t\tprocess.env.GITLAB_CI_PROJECT_PATH ??\n\t\t\t\tprocess.env.GIT_REPO,\n\t\t\tauthor: process.env.GITHUB_ACTOR ?? process.env.GITLAB_USER_NAME,\n\t\t\tauthorEmail: process.env.GITLAB_USER_EMAIL,\n\t\t\ttag: process.env.GITHUB_REF_TYPE === \"tag\" ? process.env.GITHUB_REF_NAME : undefined,\n\t\t};\n\n\t\t// If we have CI git info, use it (prioritize CI env vars)\n\t\tif (ciGitInfo.branch || ciGitInfo.commit) {\n\t\t\treturn ciGitInfo;\n\t\t}\n\n\t\t// Fall back to local git for local development\n\t\tconst localGitInfo = await getLocalGitInfo(this.rootDir);\n\n\t\t// Merge CI info with local git info (CI takes precedence)\n\t\treturn {\n\t\t\tbranch: ciGitInfo.branch ?? localGitInfo.branch,\n\t\t\tcommit: ciGitInfo.commit ?? localGitInfo.commit,\n\t\t\tcommitMessage: ciGitInfo.commitMessage ?? localGitInfo.commitMessage,\n\t\t\trepo: ciGitInfo.repo ?? localGitInfo.repo,\n\t\t\tauthor: ciGitInfo.author ?? localGitInfo.author,\n\t\t\tauthorEmail: ciGitInfo.authorEmail ?? localGitInfo.authorEmail,\n\t\t\ttag: ciGitInfo.tag ?? localGitInfo.tag,\n\t\t\tdirty: localGitInfo.dirty,\n\t\t};\n\t}\n\n\tprivate getCIInfo(): EnvironmentInfo[\"ci\"] | undefined {\n\t\tif (process.env.GITHUB_ACTIONS) {\n\t\t\treturn {\n\t\t\t\tprovider: \"github-actions\",\n\t\t\t\trunId: process.env.GITHUB_RUN_ID,\n\t\t\t\tjobId: process.env.GITHUB_JOB,\n\t\t\t\tjobUrl: `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`,\n\t\t\t\tbuildNumber: process.env.GITHUB_RUN_NUMBER,\n\t\t\t\tbranch: process.env.GITHUB_REF_NAME,\n\t\t\t\tpullRequest: process.env.GITHUB_EVENT_NAME === \"pull_request\"\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tnumber: process.env.GITHUB_PR_NUMBER ?? \"\",\n\t\t\t\t\t\t\turl: `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/pull/${process.env.GITHUB_PR_NUMBER}`,\n\t\t\t\t\t }\n\t\t\t\t\t: undefined,\n\t\t\t};\n\t\t}\n\t\tif (process.env.GITLAB_CI) {\n\t\t\treturn {\n\t\t\t\tprovider: \"gitlab-ci\",\n\t\t\t\trunId: process.env.CI_PIPELINE_ID,\n\t\t\t\tjobId: process.env.CI_JOB_ID,\n\t\t\t\tjobUrl: process.env.CI_JOB_URL,\n\t\t\t\tbuildNumber: process.env.CI_PIPELINE_IID,\n\t\t\t\tbranch: process.env.CI_COMMIT_BRANCH,\n\t\t\t\tpullRequest: process.env.CI_MERGE_REQUEST_IID\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tnumber: process.env.CI_MERGE_REQUEST_IID,\n\t\t\t\t\t\t\turl: process.env.CI_MERGE_REQUEST_PROJECT_URL + \"/-/merge_requests/\" + process.env.CI_MERGE_REQUEST_IID,\n\t\t\t\t\t\t\ttitle: process.env.CI_MERGE_REQUEST_TITLE,\n\t\t\t\t\t }\n\t\t\t\t\t: undefined,\n\t\t\t};\n\t\t}\n\t\tif (process.env.JENKINS_URL) {\n\t\t\treturn {\n\t\t\t\tprovider: \"jenkins\",\n\t\t\t\trunId: process.env.BUILD_ID,\n\t\t\t\tjobUrl: process.env.BUILD_URL,\n\t\t\t\tbuildNumber: process.env.BUILD_NUMBER,\n\t\t\t\tbranch: process.env.GIT_BRANCH,\n\t\t\t};\n\t\t}\n\t\tif (process.env.CIRCLECI) {\n\t\t\treturn {\n\t\t\t\tprovider: \"circleci\",\n\t\t\t\trunId: process.env.CIRCLE_WORKFLOW_ID,\n\t\t\t\tjobId: process.env.CIRCLE_JOB,\n\t\t\t\tjobUrl: process.env.CIRCLE_BUILD_URL,\n\t\t\t\tbuildNumber: process.env.CIRCLE_BUILD_NUM,\n\t\t\t\tbranch: process.env.CIRCLE_BRANCH,\n\t\t\t\tpullRequest: process.env.CIRCLE_PULL_REQUEST\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tnumber: process.env.CIRCLE_PR_NUMBER ?? \"\",\n\t\t\t\t\t\t\turl: process.env.CIRCLE_PULL_REQUEST,\n\t\t\t\t\t }\n\t\t\t\t\t: undefined,\n\t\t\t};\n\t\t}\n\t\tif (process.env.TRAVIS) {\n\t\t\treturn {\n\t\t\t\tprovider: \"travis\",\n\t\t\t\trunId: process.env.TRAVIS_BUILD_ID,\n\t\t\t\tjobId: process.env.TRAVIS_JOB_ID,\n\t\t\t\tjobUrl: process.env.TRAVIS_JOB_WEB_URL,\n\t\t\t\tbuildNumber: process.env.TRAVIS_BUILD_NUMBER,\n\t\t\t\tbranch: process.env.TRAVIS_BRANCH,\n\t\t\t\tpullRequest: process.env.TRAVIS_PULL_REQUEST !== \"false\"\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tnumber: process.env.TRAVIS_PULL_REQUEST ?? \"\",\n\t\t\t\t\t\t\turl: `https://github.com/${process.env.TRAVIS_REPO_SLUG}/pull/${process.env.TRAVIS_PULL_REQUEST}`,\n\t\t\t\t\t }\n\t\t\t\t\t: undefined,\n\t\t\t};\n\t\t}\n\t\tif (process.env.BUILDKITE) {\n\t\t\treturn {\n\t\t\t\tprovider: \"buildkite\",\n\t\t\t\trunId: process.env.BUILDKITE_BUILD_ID,\n\t\t\t\tjobId: process.env.BUILDKITE_JOB_ID,\n\t\t\t\tjobUrl: process.env.BUILDKITE_BUILD_URL,\n\t\t\t\tbuildNumber: process.env.BUILDKITE_BUILD_NUMBER,\n\t\t\t\tbranch: process.env.BUILDKITE_BRANCH,\n\t\t\t};\n\t\t}\n\t\tif (process.env.AZURE_PIPELINES || process.env.TF_BUILD) {\n\t\t\treturn {\n\t\t\t\tprovider: \"azure-pipelines\",\n\t\t\t\trunId: process.env.BUILD_BUILDID,\n\t\t\t\tjobUrl: `${process.env.SYSTEM_COLLECTIONURI}${process.env.SYSTEM_TEAMPROJECT}/_build/results?buildId=${process.env.BUILD_BUILDID}`,\n\t\t\t\tbuildNumber: process.env.BUILD_BUILDNUMBER,\n\t\t\t\tbranch: process.env.BUILD_SOURCEBRANCH,\n\t\t\t};\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tprivate getErrorMessage(error: unknown): string {\n\t\tif (error instanceof Error) return error.message;\n\t\treturn String(error);\n\t}\n\n\tprivate logInfo(message: string): void {\n\t\tconsole.log(`[supatest] ${message}`);\n\t}\n\n\tprivate logWarn(message: string): void {\n\t\tconsole.warn(`[supatest] ${message}`);\n\t}\n}\n\nexport { ErrorCollector } from \"./error-collector.js\";\n// Re-export types for consumers\nexport type { ErrorCategory, ErrorSummary, ReporterOptions } from \"./types.js\";\n","export type RetryConfig = {\n\tmaxAttempts: number;\n\tbaseDelayMs: number;\n\tmaxDelayMs: number;\n\tretryOnStatusCodes: number[];\n};\n\nexport const defaultRetryConfig: RetryConfig = {\n\tmaxAttempts: 3,\n\tbaseDelayMs: 1000,\n\tmaxDelayMs: 10000,\n\tretryOnStatusCodes: [408, 429, 500, 502, 503, 504],\n};\n\nfunction sleep(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction shouldRetry(error: unknown, config: RetryConfig): boolean {\n\tif (error instanceof Error && \"statusCode\" in error) {\n\t\tconst statusCode = (error as Error & { statusCode: number }).statusCode;\n\t\treturn config.retryOnStatusCodes.includes(statusCode);\n\t}\n\t// Network errors should be retried\n\tif (error instanceof Error) {\n\t\tconst networkErrors = [\"ECONNRESET\", \"ETIMEDOUT\", \"ECONNREFUSED\", \"ENOTFOUND\"];\n\t\treturn networkErrors.some((e) => error.message.includes(e));\n\t}\n\treturn false;\n}\n\nexport async function withRetry<T>(\n\tfn: () => Promise<T>,\n\tconfig: RetryConfig = defaultRetryConfig,\n): Promise<T> {\n\tlet lastError: unknown;\n\n\tfor (let attempt = 1; attempt <= config.maxAttempts; attempt++) {\n\t\ttry {\n\t\t\treturn await fn();\n\t\t} catch (error) {\n\t\t\tlastError = error;\n\n\t\t\tif (attempt === config.maxAttempts) {\n\t\t\t\tthrow error;\n\t\t\t}\n\n\t\t\tif (!shouldRetry(error, config)) {\n\t\t\t\tthrow error;\n\t\t\t}\n\n\t\t\tconst delay = Math.min(\n\t\t\t\tconfig.baseDelayMs * Math.pow(2, attempt - 1),\n\t\t\t\tconfig.maxDelayMs,\n\t\t\t);\n\n\t\t\tawait sleep(delay);\n\t\t}\n\t}\n\n\tthrow lastError;\n}\n","import { defaultRetryConfig, type RetryConfig, withRetry } from \"./retry.js\";\nimport type {\n\tCompleteRunRequest,\n\tCreateRunRequest,\n\tCreateRunResponse,\n\tSignAttachmentsRequest,\n\tSignAttachmentsResponse,\n\tTestResultPayload,\n} from \"./types.js\";\n\nexport type ApiClientOptions = {\n\tapiKey: string;\n\tapiUrl: string;\n\ttimeoutMs: number;\n\tretryAttempts: number;\n\tdryRun: boolean;\n};\n\nexport class SupatestApiClient {\n\tprivate readonly options: ApiClientOptions;\n\tprivate readonly retryConfig: RetryConfig;\n\n\tconstructor(options: ApiClientOptions) {\n\t\tthis.options = options;\n\t\tthis.retryConfig = {\n\t\t\t...defaultRetryConfig,\n\t\t\tmaxAttempts: options.retryAttempts,\n\t\t};\n\t}\n\n\tasync createRun(data: CreateRunRequest): Promise<CreateRunResponse> {\n\t\tif (this.options.dryRun) {\n\t\t\tthis.logPayload(\"POST /v1/runs\", data);\n\t\t\t// Return mock response for dry run\n\t\t\treturn {\n\t\t\t\trunId: `mock_run_${Date.now()}`,\n\t\t\t\tstatus: \"running\",\n\t\t\t};\n\t\t}\n\n\t\treturn this.request<CreateRunResponse>(\"POST\", \"/v1/runs\", data);\n\t}\n\n\tasync submitTest(runId: string, data: TestResultPayload): Promise<void> {\n\t\tif (this.options.dryRun) {\n\t\t\tthis.logPayload(`POST /v1/runs/${runId}/tests`, data);\n\t\t\treturn;\n\t\t}\n\n\t\tawait this.request(\"POST\", `/v1/runs/${runId}/tests`, data);\n\t}\n\n\tasync signAttachments(\n\t\trunId: string,\n\t\tdata: SignAttachmentsRequest,\n\t): Promise<SignAttachmentsResponse> {\n\t\tif (this.options.dryRun) {\n\t\t\tthis.logPayload(`POST /v1/runs/${runId}/attachments/sign`, data);\n\t\t\t// Return mock signed URLs for dry run\n\t\t\treturn {\n\t\t\t\tuploads: data.attachments.map((att, i) => ({\n\t\t\t\t\tattachmentId: `mock_att_${i}_${Date.now()}`,\n\t\t\t\t\tsignedUrl: `https://mock-s3.example.com/uploads/${att.filename}`,\n\t\t\t\t\texpiresAt: new Date(Date.now() + 15 * 60 * 1000).toISOString(),\n\t\t\t\t})),\n\t\t\t};\n\t\t}\n\n\t\treturn this.request<SignAttachmentsResponse>(\n\t\t\t\"POST\",\n\t\t\t`/v1/runs/${runId}/attachments/sign`,\n\t\t\tdata,\n\t\t);\n\t}\n\n\tasync completeRun(runId: string, data: CompleteRunRequest): Promise<void> {\n\t\tif (this.options.dryRun) {\n\t\t\tthis.logPayload(`POST /v1/runs/${runId}/complete`, data);\n\t\t\treturn;\n\t\t}\n\n\t\tawait this.request(\"POST\", `/v1/runs/${runId}/complete`, data);\n\t}\n\n\tprivate async request<T>(\n\t\tmethod: string,\n\t\tpath: string,\n\t\tbody?: unknown,\n\t): Promise<T> {\n\t\tconst url = `${this.options.apiUrl}${path}`;\n\n\t\treturn withRetry(async () => {\n\t\t\tconst controller = new AbortController();\n\t\t\tconst timeoutId = setTimeout(\n\t\t\t\t() => controller.abort(),\n\t\t\t\tthis.options.timeoutMs,\n\t\t\t);\n\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(url, {\n\t\t\t\t\tmethod,\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t\tAuthorization: `Bearer ${this.options.apiKey}`,\n\t\t\t\t\t},\n\t\t\t\t\tbody: body ? JSON.stringify(body) : undefined,\n\t\t\t\t\tsignal: controller.signal,\n\t\t\t\t});\n\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tconst error = new Error(\n\t\t\t\t\t\t`API request failed: ${response.status} ${response.statusText}`,\n\t\t\t\t\t) as Error & { statusCode: number };\n\t\t\t\t\terror.statusCode = response.status;\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\n\t\t\t\tconst text = await response.text();\n\t\t\t\treturn text ? (JSON.parse(text) as T) : ({} as T);\n\t\t\t} finally {\n\t\t\t\tclearTimeout(timeoutId);\n\t\t\t}\n\t\t}, this.retryConfig);\n\t}\n\n\tprivate logPayload(endpoint: string, data: unknown): void {\n\t\tconsole.log(`\\n[supatest][dry-run] ${endpoint}`);\n\t\tconsole.log(JSON.stringify(data, null, 2));\n\t}\n}\n","import type { ErrorCategory, ErrorSummary, ReporterError } from \"./types.js\";\n\nexport class ErrorCollector {\n\tprivate errors: ReporterError[] = [];\n\n\trecordError(\n\t\tcategory: ErrorCategory,\n\t\tmessage: string,\n\t\tcontext?: {\n\t\t\ttestId?: string;\n\t\t\ttestTitle?: string;\n\t\t\tattachmentName?: string;\n\t\t\tfilePath?: string;\n\t\t\terror?: unknown;\n\t\t},\n\t): void {\n\t\tconst error: ReporterError = {\n\t\t\tcategory,\n\t\t\tmessage,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\ttestId: context?.testId,\n\t\t\ttestTitle: context?.testTitle,\n\t\t\tattachmentName: context?.attachmentName,\n\t\t\tfilePath: context?.filePath,\n\t\t\toriginalError:\n\t\t\t\tcontext?.error instanceof Error ? context.error.stack : undefined,\n\t\t};\n\n\t\tthis.errors.push(error);\n\t}\n\n\tgetSummary(): ErrorSummary {\n\t\tconst byCategory: Record<ErrorCategory, number> = {\n\t\t\tRUN_CREATE: 0,\n\t\t\tTEST_SUBMISSION: 0,\n\t\t\tATTACHMENT_SIGN: 0,\n\t\t\tATTACHMENT_UPLOAD: 0,\n\t\t\tFILE_READ: 0,\n\t\t\tRUN_COMPLETE: 0,\n\t\t};\n\n\t\tfor (const error of this.errors) {\n\t\t\tbyCategory[error.category]++;\n\t\t}\n\n\t\treturn {\n\t\t\ttotalErrors: this.errors.length,\n\t\t\tbyCategory,\n\t\t\terrors: this.errors,\n\t\t};\n\t}\n\n\thasErrors(): boolean {\n\t\treturn this.errors.length > 0;\n\t}\n\n\tformatSummary(): string {\n\t\tif (this.errors.length === 0) {\n\t\t\treturn \"\";\n\t\t}\n\n\t\tconst summary = this.getSummary();\n\t\tconst lines: string[] = [\n\t\t\t\"\",\n\t\t\t\"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\",\n\t\t\t\"⚠️ Supatest Reporter - Error Summary\",\n\t\t\t\"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\",\n\t\t\t`Total Errors: ${summary.totalErrors}`,\n\t\t\t\"\",\n\t\t];\n\n\t\t// Show errors by category\n\t\tfor (const [category, count] of Object.entries(summary.byCategory)) {\n\t\t\tif (count === 0) continue;\n\n\t\t\tconst icon = this.getCategoryIcon(category as ErrorCategory);\n\t\t\tconst label = this.getCategoryLabel(category as ErrorCategory);\n\t\t\tlines.push(`${icon} ${label}: ${count}`);\n\n\t\t\t// Show first 3 errors in this category\n\t\t\tconst categoryErrors = this.errors\n\t\t\t\t.filter((e) => e.category === category)\n\t\t\t\t.slice(0, 3);\n\n\t\t\tfor (const error of categoryErrors) {\n\t\t\t\tlines.push(` • ${this.formatError(error)}`);\n\t\t\t}\n\n\t\t\tconst remaining = count - categoryErrors.length;\n\t\t\tif (remaining > 0) {\n\t\t\t\tlines.push(` ... and ${remaining} more`);\n\t\t\t}\n\t\t\tlines.push(\"\");\n\t\t}\n\n\t\tlines.push(\n\t\t\t\"ℹ️ Note: Test execution was not interrupted. Some results may not be visible in the dashboard.\",\n\t\t);\n\t\tlines.push(\n\t\t\t\"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\",\n\t\t);\n\n\t\treturn lines.join(\"\\n\");\n\t}\n\n\tprivate getCategoryIcon(category: ErrorCategory): string {\n\t\tconst icons: Record<ErrorCategory, string> = {\n\t\t\tRUN_CREATE: \"🚀\",\n\t\t\tTEST_SUBMISSION: \"📝\",\n\t\t\tATTACHMENT_SIGN: \"🔐\",\n\t\t\tATTACHMENT_UPLOAD: \"📎\",\n\t\t\tFILE_READ: \"📁\",\n\t\t\tRUN_COMPLETE: \"🏁\",\n\t\t};\n\t\treturn icons[category];\n\t}\n\n\tprivate getCategoryLabel(category: ErrorCategory): string {\n\t\tconst labels: Record<ErrorCategory, string> = {\n\t\t\tRUN_CREATE: \"Run Initialization\",\n\t\t\tTEST_SUBMISSION: \"Test Result Submission\",\n\t\t\tATTACHMENT_SIGN: \"Attachment Signing\",\n\t\t\tATTACHMENT_UPLOAD: \"Attachment Upload\",\n\t\t\tFILE_READ: \"File Access\",\n\t\t\tRUN_COMPLETE: \"Run Completion\",\n\t\t};\n\t\treturn labels[category];\n\t}\n\n\tprivate formatError(error: ReporterError): string {\n\t\tconst parts: string[] = [];\n\n\t\tif (error.testTitle) {\n\t\t\tparts.push(`Test: \"${error.testTitle}\"`);\n\t\t}\n\n\t\tif (error.attachmentName) {\n\t\t\tparts.push(`Attachment: \"${error.attachmentName}\"`);\n\t\t}\n\n\t\tif (error.filePath) {\n\t\t\tparts.push(`File: ${error.filePath}`);\n\t\t}\n\n\t\tparts.push(error.message);\n\n\t\treturn parts.join(\" - \");\n\t}\n}\n","import { SimpleGit, simpleGit } from \"simple-git\";\n\nexport type GitInfo = {\n\tbranch?: string;\n\tcommit?: string;\n\tcommitMessage?: string;\n\trepo?: string;\n\tauthor?: string;\n\tauthorEmail?: string;\n\ttag?: string;\n\tdirty?: boolean;\n};\n\n/**\n * Get git information from the local repository\n * Falls back gracefully if git is not available or not a git repo\n */\nexport async function getLocalGitInfo(rootDir?: string): Promise<GitInfo> {\n\ttry {\n\t\tconst git: SimpleGit = simpleGit(rootDir || process.cwd());\n\n\t\t// Check if it's a git repository\n\t\tconst isRepo = await git.checkIsRepo();\n\t\tif (!isRepo) {\n\t\t\treturn {};\n\t\t}\n\n\t\tconst [branchResult, commitResult, status, remotes] = await Promise.all([\n\t\t\tgit.revparse([\"--abbrev-ref\", \"HEAD\"]).catch(() => undefined),\n\t\t\tgit.revparse([\"HEAD\"]).catch(() => undefined),\n\t\t\tgit.status().catch(() => undefined),\n\t\t\tgit.getRemotes(true).catch(() => []),\n\t\t]);\n\n\t\tconst branch = typeof branchResult === \"string\" ? branchResult.trim() : undefined;\n\t\tconst commit = typeof commitResult === \"string\" ? commitResult.trim() : undefined;\n\n\t\t// Get commit message (subject line)\n\t\tlet commitMessage: string | undefined;\n\t\ttry {\n\t\t\tconst logOutput = await git.raw([\"log\", \"-1\", \"--format=%s\"]);\n\t\t\tcommitMessage = typeof logOutput === \"string\" ? logOutput.trim() : undefined;\n\t\t} catch {\n\t\t\t// Ignore error\n\t\t}\n\n\t\t// Get author info from config\n\t\tlet author: string | undefined;\n\t\tlet authorEmail: string | undefined;\n\t\ttry {\n\t\t\tconst authorConfig = await git\n\t\t\t\t.getConfig(\"user.name\")\n\t\t\t\t.catch(() => undefined);\n\t\t\tconst emailConfig = await git\n\t\t\t\t.getConfig(\"user.email\")\n\t\t\t\t.catch(() => undefined);\n\t\t\tauthor =\n\t\t\t\tauthorConfig && typeof (authorConfig as any).value === \"string\"\n\t\t\t\t\t? (authorConfig as any).value.trim()\n\t\t\t\t\t: undefined;\n\t\t\tauthorEmail =\n\t\t\t\temailConfig && typeof (emailConfig as any).value === \"string\"\n\t\t\t\t\t? (emailConfig as any).value.trim()\n\t\t\t\t\t: undefined;\n\t\t} catch {\n\t\t\t// Ignore error\n\t\t}\n\n\t\t// Get current tag if on a tag\n\t\tlet tag: string | undefined;\n\t\ttry {\n\t\t\tconst tagResult = await git.raw([\"describe\", \"--tags\", \"--exact-match\"]).catch(() => undefined);\n\t\t\ttag = typeof tagResult === \"string\" ? tagResult.trim() : undefined;\n\t\t} catch {\n\t\t\t// Ignore error - not on a tag\n\t\t}\n\n\t\t// Get repository URL\n\t\tlet repo: string | undefined;\n\t\tconst remote = remotes && remotes.length > 0 ? remotes[0] : null;\n\t\tif (remote && remote.refs && remote.refs.fetch) {\n\t\t\trepo = remote.refs.fetch;\n\t\t}\n\n\t\t// Check for uncommitted changes\n\t\tconst dirty = status ? status.modified.length > 0 || status.created.length > 0 || status.deleted.length > 0 : false;\n\n\t\treturn {\n\t\t\tbranch,\n\t\t\tcommit,\n\t\t\tcommitMessage,\n\t\t\trepo,\n\t\t\tauthor,\n\t\t\tauthorEmail,\n\t\t\ttag,\n\t\t\tdirty,\n\t\t};\n\t} catch (error) {\n\t\t// If anything goes wrong, return empty object\n\t\t// This is a best-effort operation\n\t\treturn {};\n\t}\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { SourceSnippet } from \"./types.js\";\n\nconst CONTEXT_LINES_BEFORE = 3;\nconst CONTEXT_LINES_AFTER = 3;\nconst MAX_SNIPPET_SIZE = 2000; // Characters\n\ntype SourceCache = Map<string, string[]>;\ntype FileIndex = Map<string, string>; // filename -> absolute path\n\n/**\n * Utility class for reading source files and extracting code snippets.\n * Caches file contents to avoid repeated disk reads.\n * Builds an index of source files to resolve partial paths.\n */\nexport class SourceReader {\n\tprivate cache: SourceCache = new Map();\n\tprivate fileIndex: FileIndex = new Map();\n\tprivate rootDir: string;\n\n\tconstructor(rootDir: string) {\n\t\tthis.rootDir = rootDir;\n\t\tthis.buildFileIndex(rootDir);\n\t}\n\n\t/**\n\t * Recursively scan directory and build index of .ts/.tsx/.js/.jsx files.\n\t */\n\tprivate buildFileIndex(dir: string): void {\n\t\ttry {\n\t\t\tconst entries = fs.readdirSync(dir, { withFileTypes: true });\n\t\t\tfor (const entry of entries) {\n\t\t\t\tconst fullPath = path.join(dir, entry.name);\n\t\t\t\tif (entry.isDirectory()) {\n\t\t\t\t\t// Skip node_modules and hidden directories\n\t\t\t\t\tif (entry.name === \"node_modules\" || entry.name.startsWith(\".\")) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tthis.buildFileIndex(fullPath);\n\t\t\t\t} else if (entry.isFile()) {\n\t\t\t\t\tconst ext = path.extname(entry.name).toLowerCase();\n\t\t\t\t\tif ([\".ts\", \".tsx\", \".js\", \".jsx\"].includes(ext)) {\n\t\t\t\t\t\t// Store by filename (for partial path matching)\n\t\t\t\t\t\tconst filename = entry.name;\n\t\t\t\t\t\t// If multiple files have same name, keep first found\n\t\t\t\t\t\tif (!this.fileIndex.has(filename)) {\n\t\t\t\t\t\t\tthis.fileIndex.set(filename, fullPath);\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Also store by relative path from rootDir\n\t\t\t\t\t\tconst relativePath = path.relative(this.rootDir, fullPath);\n\t\t\t\t\t\tthis.fileIndex.set(relativePath, fullPath);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Directory not readable, skip\n\t\t}\n\t}\n\n\t/**\n\t * Resolve a file path to an absolute path.\n\t * Handles absolute paths, relative paths, and filename-only paths.\n\t */\n\tprivate resolvePath(file: string): string | undefined {\n\t\t// Already absolute and exists\n\t\tif (path.isAbsolute(file)) {\n\t\t\tif (fs.existsSync(file)) {\n\t\t\t\treturn file;\n\t\t\t}\n\t\t\t// Try just the filename\n\t\t\tconst filename = path.basename(file);\n\t\t\treturn this.fileIndex.get(filename);\n\t\t}\n\n\t\t// Try as relative path from rootDir\n\t\tconst asRelative = path.join(this.rootDir, file);\n\t\tif (fs.existsSync(asRelative)) {\n\t\t\treturn asRelative;\n\t\t}\n\n\t\t// Try from index (exact match on relative path or filename)\n\t\tif (this.fileIndex.has(file)) {\n\t\t\treturn this.fileIndex.get(file);\n\t\t}\n\n\t\t// Try just the filename\n\t\tconst filename = path.basename(file);\n\t\treturn this.fileIndex.get(filename);\n\t}\n\n\t/**\n\t * Get a code snippet around a specific line in a file.\n\t * Returns undefined if the file cannot be read or snippet is too large.\n\t */\n\tgetSnippet(file: string, line: number): SourceSnippet | undefined {\n\t\ttry {\n\t\t\tconst absolutePath = this.resolvePath(file);\n\t\t\tif (!absolutePath) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\t// Get or cache file lines\n\t\t\tlet lines = this.cache.get(absolutePath);\n\t\t\tif (!lines) {\n\t\t\t\tconst content = fs.readFileSync(absolutePath, \"utf-8\");\n\t\t\t\tlines = content.split(\"\\n\");\n\t\t\t\tthis.cache.set(absolutePath, lines);\n\t\t\t}\n\n\t\t\t// Validate line number\n\t\t\tif (line < 1 || line > lines.length) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\t// Calculate snippet range\n\t\t\tconst startLine = Math.max(1, line - CONTEXT_LINES_BEFORE);\n\t\t\tconst endLine = Math.min(lines.length, line + CONTEXT_LINES_AFTER);\n\n\t\t\t// Extract snippet\n\t\t\tconst snippetLines = lines.slice(startLine - 1, endLine);\n\t\t\tconst code = snippetLines.join(\"\\n\");\n\n\t\t\t// Check size limit\n\t\t\tif (code.length > MAX_SNIPPET_SIZE) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\t// Detect language from file extension\n\t\t\tconst ext = path.extname(file).toLowerCase();\n\t\t\tconst language =\n\t\t\t\text === \".ts\" || ext === \".tsx\" ? \"typescript\" : \"javascript\";\n\n\t\t\treturn {\n\t\t\t\tcode,\n\t\t\t\tstartLine,\n\t\t\t\thighlightLine: line,\n\t\t\t\tlanguage,\n\t\t\t};\n\t\t} catch {\n\t\t\t// Silently fail - snippet is optional\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\t/**\n\t * Clear the file cache and index to free memory.\n\t */\n\tclear(): void {\n\t\tthis.cache.clear();\n\t\tthis.fileIndex.clear();\n\t}\n}\n","import fs from \"node:fs\";\nimport { defaultRetryConfig, withRetry } from \"./retry.js\";\nimport type { SignedUpload, UploadItem, UploadResult } from \"./types.js\";\n\nexport type UploaderOptions = {\n\tmaxConcurrent: number;\n\ttimeoutMs: number;\n\tdryRun: boolean;\n};\n\nexport class AttachmentUploader {\n\tprivate readonly options: UploaderOptions;\n\n\tconstructor(options: UploaderOptions) {\n\t\tthis.options = options;\n\t}\n\n\tasync upload(\n\t\tsignedUrl: string,\n\t\tfilePath: string,\n\t\tcontentType: string,\n\t): Promise<void> {\n\t\tif (this.options.dryRun) {\n\t\t\ttry {\n\t\t\t\tconst stats = fs.statSync(filePath);\n\t\t\t\tconsole.log(\n\t\t\t\t\t`[supatest][dry-run] Would upload ${filePath} (${stats.size} bytes) to S3`,\n\t\t\t\t);\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`[supatest][dry-run] Cannot access file ${filePath}: ${message}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tlet fileBuffer: Buffer;\n\t\ttry {\n\t\t\tfileBuffer = await fs.promises.readFile(filePath);\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tthrow new Error(`Failed to read file ${filePath}: ${message}`);\n\t\t}\n\n\t\tawait withRetry(async () => {\n\t\t\tconst controller = new AbortController();\n\t\t\tconst timeoutId = setTimeout(\n\t\t\t\t() => controller.abort(),\n\t\t\t\tthis.options.timeoutMs,\n\t\t\t);\n\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(signedUrl, {\n\t\t\t\t\tmethod: \"PUT\",\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t\"Content-Type\": contentType,\n\t\t\t\t\t\t\"Content-Length\": String(fileBuffer.length),\n\t\t\t\t\t},\n\t\t\t\t\tbody: new Uint8Array(fileBuffer),\n\t\t\t\t\tsignal: controller.signal,\n\t\t\t\t});\n\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tconst error = new Error(\n\t\t\t\t\t\t`S3 upload failed: ${response.status} ${response.statusText}`,\n\t\t\t\t\t) as Error & { statusCode: number };\n\t\t\t\t\terror.statusCode = response.status;\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tclearTimeout(timeoutId);\n\t\t\t}\n\t\t}, defaultRetryConfig);\n\t}\n\n\tasync uploadBatch(\n\t\titems: UploadItem[],\n\t\tsignedUploads: SignedUpload[],\n\t): Promise<UploadResult[]> {\n\t\t// Use dynamic import for p-limit (ESM module)\n\t\tconst pLimit = (await import(\"p-limit\")).default;\n\t\tconst limit = pLimit(this.options.maxConcurrent);\n\n\t\tconst results = await Promise.allSettled(\n\t\t\titems.map((item, index) =>\n\t\t\t\tlimit(async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait this.upload(item.signedUrl, item.filePath, item.contentType);\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t\t\tattachmentId: signedUploads[index]?.attachmentId,\n\t\t\t\t\t\t};\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t// Enhanced error with file context\n\t\t\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tsuccess: false,\n\t\t\t\t\t\t\tattachmentId: signedUploads[index]?.attachmentId,\n\t\t\t\t\t\t\terror: `${item.filePath}: ${message}`,\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t\t),\n\t\t);\n\n\t\treturn results.map((result, index) => {\n\t\t\tif (result.status === \"fulfilled\") {\n\t\t\t\treturn result.value;\n\t\t\t}\n\t\t\t// Shouldn't happen since we catch inside, but handle it\n\t\t\tconst message = result.reason instanceof Error ? result.reason.message : String(result.reason);\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\tattachmentId: signedUploads[index]?.attachmentId,\n\t\t\t\terror: message,\n\t\t\t};\n\t\t});\n\t}\n}\n"],"mappings":"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","names":["import_node_fs","import_node_path","path","fs","path","filename","import_node_fs","fs","path","fs","os"]}
|