@checkly/playwright-reporter 0.1.9 → 0.1.10

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.d.ts CHANGED
@@ -81,6 +81,13 @@ interface ChecklyReporterOptions {
81
81
  * sessionName: ({ directoryName, config }) => `E2E: ${directoryName} (${config.projects.length} projects)`
82
82
  */
83
83
  sessionName?: SessionNameOption;
84
+ /**
85
+ * Enable verbose logging for debugging
86
+ * Logs detailed information about each phase of report generation
87
+ * Can also be enabled via CHECKLY_REPORTER_VERBOSE=true environment variable
88
+ * @default false
89
+ */
90
+ verbose?: boolean;
84
91
  }
85
92
  /**
86
93
  * Warning types that can be attached to test results
@@ -99,6 +106,67 @@ interface ChecklyWarning {
99
106
  */
100
107
  message: string;
101
108
  }
109
+ /**
110
+ * Console message type matching the webapp's CheckRunNavigationTracePage.console type
111
+ */
112
+ type ConsoleMessageType = 'debug' | 'error' | 'info' | 'log' | 'warning';
113
+ /**
114
+ * Location of a console message in source code
115
+ */
116
+ interface ConsoleMessageLocation {
117
+ url: string;
118
+ columnNumber: number;
119
+ lineNumber: number;
120
+ }
121
+ /**
122
+ * Console message extracted from Playwright trace
123
+ * Matches the webapp's CheckRunNavigationTracePage.console array item type
124
+ */
125
+ interface ConsoleMessage {
126
+ /**
127
+ * Unique identifier for the console message
128
+ */
129
+ id: string;
130
+ /**
131
+ * Source location where the console message was triggered
132
+ */
133
+ location: ConsoleMessageLocation;
134
+ /**
135
+ * The text content of the console message
136
+ */
137
+ text: string;
138
+ /**
139
+ * Timestamp when the message was logged (milliseconds)
140
+ */
141
+ timestamp: number;
142
+ /**
143
+ * Type of console message
144
+ */
145
+ type: ConsoleMessageType;
146
+ }
147
+ /**
148
+ * Network request extracted from Playwright trace
149
+ * Matches the webapp's CheckRunNavigationTraceNetworkEntry type
150
+ */
151
+ interface NetworkRequest {
152
+ id: string;
153
+ url: string;
154
+ domain: string;
155
+ method: string;
156
+ resourceType: string;
157
+ statusCode: number;
158
+ statusText: string;
159
+ start: number;
160
+ startedAt: number;
161
+ finishedAt: number;
162
+ time: number;
163
+ hasFinished: boolean;
164
+ hasSucceeded: boolean;
165
+ requestHeaders: Record<string, string>;
166
+ responseHeaders: Record<string, string>;
167
+ transferBytes?: number;
168
+ resourceBytes?: number;
169
+ }
102
170
  /**
103
171
  * Checkly-specific extensions added to JSONReportTestResult
104
172
  */
@@ -107,6 +175,14 @@ interface ChecklyTestResultExtensions {
107
175
  * Warnings about the test result (e.g., missing traces)
108
176
  */
109
177
  warnings?: ChecklyWarning[];
178
+ /**
179
+ * Console messages extracted from the Playwright trace
180
+ */
181
+ console?: ConsoleMessage[];
182
+ /**
183
+ * Network requests extracted from the Playwright trace
184
+ */
185
+ network?: NetworkRequest[];
110
186
  }
111
187
  /**
112
188
  * Extended JSONReportTestResult with Checkly-specific data
@@ -168,6 +244,13 @@ declare class ChecklyReporter implements Reporter {
168
244
  private testCounts;
169
245
  private stepsMap;
170
246
  private warningsMap;
247
+ private tracePathsMap;
248
+ private consoleMessagesMap;
249
+ private networkRequestsMap;
250
+ /**
251
+ * Log a message if verbose mode is enabled
252
+ */
253
+ private log;
171
254
  constructor(options?: ChecklyReporterOptions);
172
255
  /**
173
256
  * Resolves the session name from options
@@ -176,6 +259,7 @@ declare class ChecklyReporter implements Reporter {
176
259
  private resolveSessionName;
177
260
  /**
178
261
  * Checks if test result has a trace attachment and adds context-aware warning if missing
262
+ * Also captures trace file path for later console message extraction
179
263
  * The warning type depends on the trace configuration and test result state
180
264
  */
181
265
  private checkTraceAttachment;
@@ -196,7 +280,12 @@ declare class ChecklyReporter implements Reporter {
196
280
  onEnd(): Promise<void>;
197
281
  private printSummary;
198
282
  /**
199
- * Injects captured steps and warnings into the JSON report
283
+ * Extracts console messages and network requests from all captured traces
284
+ * Called before injecting data into the report
285
+ */
286
+ private extractDataFromTraces;
287
+ /**
288
+ * Injects captured steps, warnings, console messages, and network requests into the JSON report
200
289
  * Traverses the report structure and matches by test ID + retry
201
290
  */
202
291
  private injectDataIntoReport;
@@ -216,4 +305,4 @@ declare class ChecklyReporter implements Reporter {
216
305
  onError(error: TestError): void;
217
306
  }
218
307
 
219
- export { ChecklyReporter, type ChecklyReporterOptions, type ChecklyTestResultExtensions, type ChecklyWarning, type ChecklyWarningType, type JSONReport, type JSONReportSpec, type JSONReportSuite, type JSONReportTest, type JSONReportTestResult, ChecklyReporter as default };
308
+ export { ChecklyReporter, type ChecklyReporterOptions, type ChecklyTestResultExtensions, type ChecklyWarning, type ChecklyWarningType, type ConsoleMessage, type ConsoleMessageLocation, type ConsoleMessageType, type JSONReport, type JSONReportSpec, type JSONReportSuite, type JSONReportTest, type JSONReportTestResult, type NetworkRequest, ChecklyReporter as default };
package/dist/index.js CHANGED
@@ -234,8 +234,261 @@ var AssetCollector = class {
234
234
  }
235
235
  };
236
236
 
237
- // ../utils/src/zipper.ts
237
+ // ../utils/src/console-adapter.ts
238
+ import { createHash } from "crypto";
239
+ function normalizeType(messageType) {
240
+ switch (messageType.toLowerCase()) {
241
+ case "debug":
242
+ return "debug";
243
+ case "error":
244
+ return "error";
245
+ case "info":
246
+ return "info";
247
+ case "warning":
248
+ case "warn":
249
+ return "warning";
250
+ default:
251
+ return "log";
252
+ }
253
+ }
254
+ function generateId(time, messageType, text, url) {
255
+ return createHash("sha256").update(`${time}-${messageType}-${text}-${url}`).digest("hex").substring(0, 16);
256
+ }
257
+ function toConsoleMessage(event) {
258
+ const url = event.location?.url || "";
259
+ return {
260
+ id: generateId(event.time, event.messageType, event.text, url),
261
+ location: {
262
+ url,
263
+ columnNumber: event.location?.columnNumber || 0,
264
+ lineNumber: event.location?.lineNumber || 0
265
+ },
266
+ text: event.text || "",
267
+ timestamp: event.time,
268
+ type: normalizeType(event.messageType)
269
+ };
270
+ }
271
+
272
+ // ../utils/src/network-adapter.ts
273
+ import { createHash as createHash2 } from "crypto";
274
+ function generateId2(url, method, startedAt) {
275
+ return createHash2("sha256").update(`${url}-${method}-${startedAt}`).digest("hex").substring(0, 16);
276
+ }
277
+ function extractDomain(url) {
278
+ try {
279
+ return new URL(url).hostname;
280
+ } catch {
281
+ return "";
282
+ }
283
+ }
284
+ function headersArrayToRecord(headers) {
285
+ const record = {};
286
+ for (const { name, value } of headers) {
287
+ record[name.toLowerCase()] = value;
288
+ }
289
+ return record;
290
+ }
291
+ function isSuccessStatus(status) {
292
+ return status >= 200 && status < 400;
293
+ }
294
+ function determineResourceType(snapshot) {
295
+ if (snapshot._resourceType) {
296
+ return snapshot._resourceType;
297
+ }
298
+ if (snapshot._apiRequest) {
299
+ return "fetch";
300
+ }
301
+ return "other";
302
+ }
303
+ function toNetworkRequest(event) {
304
+ const { snapshot } = event;
305
+ const startedAt = new Date(snapshot.startedDateTime).getTime();
306
+ const time = Math.round(snapshot.time);
307
+ const finishedAt = startedAt + time;
308
+ const statusCode = snapshot.response.status;
309
+ const url = snapshot.request.url;
310
+ const method = snapshot.request.method;
311
+ return {
312
+ id: generateId2(url, method, startedAt),
313
+ url,
314
+ domain: extractDomain(url),
315
+ method,
316
+ resourceType: determineResourceType(snapshot),
317
+ statusCode,
318
+ statusText: snapshot.response.statusText || "",
319
+ start: startedAt,
320
+ startedAt,
321
+ finishedAt,
322
+ time,
323
+ hasFinished: true,
324
+ hasSucceeded: isSuccessStatus(statusCode),
325
+ requestHeaders: headersArrayToRecord(snapshot.request.headers || []),
326
+ responseHeaders: headersArrayToRecord(snapshot.response.headers || []),
327
+ transferBytes: snapshot.response._transferSize,
328
+ resourceBytes: snapshot.response.content?.size
329
+ };
330
+ }
331
+
332
+ // ../utils/src/trace-reader.ts
238
333
  import * as fs2 from "fs";
334
+
335
+ // ../utils/src/zip-reader.ts
336
+ import { promisify } from "util";
337
+ import * as zlib from "zlib";
338
+ var gunzip2 = promisify(zlib.gunzip);
339
+ var inflateRaw2 = promisify(zlib.inflateRaw);
340
+ function parseZipEntries(zipBuffer) {
341
+ const EOCD_SIG = 101010256;
342
+ let eocdOffset = -1;
343
+ for (let i = zipBuffer.length - 22; i >= 0; i--) {
344
+ if (zipBuffer.readUInt32LE(i) === EOCD_SIG) {
345
+ eocdOffset = i;
346
+ break;
347
+ }
348
+ }
349
+ if (eocdOffset === -1) {
350
+ return [];
351
+ }
352
+ const cdOffset = zipBuffer.readUInt32LE(eocdOffset + 16);
353
+ const cdEntries = zipBuffer.readUInt16LE(eocdOffset + 10);
354
+ const entries = [];
355
+ const CD_SIG = 33639248;
356
+ let offset = cdOffset;
357
+ for (let i = 0; i < cdEntries; i++) {
358
+ if (zipBuffer.readUInt32LE(offset) !== CD_SIG) {
359
+ break;
360
+ }
361
+ const compressionMethod = zipBuffer.readUInt16LE(offset + 10);
362
+ const compressedSize = zipBuffer.readUInt32LE(offset + 20);
363
+ const fileNameLength = zipBuffer.readUInt16LE(offset + 28);
364
+ const extraFieldLength = zipBuffer.readUInt16LE(offset + 30);
365
+ const commentLength = zipBuffer.readUInt16LE(offset + 32);
366
+ const localHeaderOffset = zipBuffer.readUInt32LE(offset + 42);
367
+ const fileName = zipBuffer.subarray(offset + 46, offset + 46 + fileNameLength).toString("utf-8");
368
+ entries.push({
369
+ fileName,
370
+ compressionMethod,
371
+ compressedSize,
372
+ localHeaderOffset
373
+ });
374
+ offset += 46 + fileNameLength + extraFieldLength + commentLength;
375
+ }
376
+ return entries;
377
+ }
378
+ async function readZipEntryContent(zipBuffer, entry) {
379
+ const LOCAL_SIG = 67324752;
380
+ if (zipBuffer.readUInt32LE(entry.localHeaderOffset) !== LOCAL_SIG) {
381
+ return null;
382
+ }
383
+ const localFileNameLength = zipBuffer.readUInt16LE(entry.localHeaderOffset + 26);
384
+ const localExtraLength = zipBuffer.readUInt16LE(entry.localHeaderOffset + 28);
385
+ const dataOffset = entry.localHeaderOffset + 30 + localFileNameLength + localExtraLength;
386
+ const compressedData = zipBuffer.subarray(dataOffset, dataOffset + entry.compressedSize);
387
+ let buffer;
388
+ if (entry.compressionMethod === 0) {
389
+ buffer = compressedData;
390
+ } else if (entry.compressionMethod === 8) {
391
+ buffer = await inflateRaw2(compressedData);
392
+ } else {
393
+ return null;
394
+ }
395
+ if (buffer.length >= 2 && buffer[0] === 31 && buffer[1] === 139) {
396
+ const decompressed = await gunzip2(buffer);
397
+ return decompressed.toString("utf-8");
398
+ }
399
+ return buffer.toString("utf-8");
400
+ }
401
+
402
+ // ../utils/src/trace-reader.ts
403
+ var TraceReader = class {
404
+ constructor(tracePath) {
405
+ this.tracePath = tracePath;
406
+ }
407
+ zipBuffer = null;
408
+ traceEntries = [];
409
+ async open() {
410
+ if (!fs2.existsSync(this.tracePath)) {
411
+ return false;
412
+ }
413
+ try {
414
+ this.zipBuffer = fs2.readFileSync(this.tracePath);
415
+ const entries = parseZipEntries(this.zipBuffer);
416
+ this.traceEntries = entries.filter(
417
+ (e) => (/^\d+-trace\.trace$/.test(e.fileName) || /^\d+-trace\.network$/.test(e.fileName)) && !e.fileName.includes("/")
418
+ );
419
+ return this.traceEntries.length > 0;
420
+ } catch {
421
+ return false;
422
+ }
423
+ }
424
+ /**
425
+ * Extracts events matching a text filter.
426
+ *
427
+ * @param textFilter - Substring to match (e.g., '"type":"console"')
428
+ * @param adapter - Optional adapter to transform events
429
+ */
430
+ async extractEvents(textFilter, adapter) {
431
+ if (!this.zipBuffer) {
432
+ throw new Error("TraceReader not opened. Call open() first.");
433
+ }
434
+ const results = [];
435
+ for (const traceEntry of this.traceEntries) {
436
+ const content = await readZipEntryContent(this.zipBuffer, traceEntry);
437
+ if (!content) continue;
438
+ for (const line of content.split("\n")) {
439
+ if (line.indexOf(textFilter) !== -1) {
440
+ try {
441
+ const event = JSON.parse(line);
442
+ results.push(adapter ? adapter(event) : event);
443
+ } catch {
444
+ }
445
+ }
446
+ }
447
+ }
448
+ if (results.length > 0 && typeof results[0].time === "number") {
449
+ return results.sort((a, b) => a.time - b.time);
450
+ }
451
+ return results;
452
+ }
453
+ /**
454
+ * Extracts all events from the trace.
455
+ */
456
+ async extractAllEvents(adapter) {
457
+ if (!this.zipBuffer) {
458
+ throw new Error("TraceReader not opened. Call open() first.");
459
+ }
460
+ const results = [];
461
+ for (const traceEntry of this.traceEntries) {
462
+ const content = await readZipEntryContent(this.zipBuffer, traceEntry);
463
+ if (!content) continue;
464
+ for (const line of content.split("\n")) {
465
+ if (line.trim()) {
466
+ try {
467
+ const event = JSON.parse(line);
468
+ results.push(adapter ? adapter(event) : event);
469
+ } catch {
470
+ }
471
+ }
472
+ }
473
+ }
474
+ if (results.length > 0 && typeof results[0].time === "number") {
475
+ return results.sort((a, b) => a.time - b.time);
476
+ }
477
+ return results;
478
+ }
479
+ listFiles() {
480
+ if (!this.zipBuffer) {
481
+ return [];
482
+ }
483
+ return parseZipEntries(this.zipBuffer).map((e) => e.fileName);
484
+ }
485
+ isOpen() {
486
+ return this.zipBuffer !== null;
487
+ }
488
+ };
489
+
490
+ // ../utils/src/zipper.ts
491
+ import * as fs3 from "fs";
239
492
  import * as os from "os";
240
493
  import * as path2 from "path";
241
494
  import { ZipArchive } from "archiver";
@@ -254,7 +507,7 @@ var Zipper = class {
254
507
  const entries = [];
255
508
  return new Promise((resolve2, reject) => {
256
509
  try {
257
- const output = fs2.createWriteStream(this.outputPath);
510
+ const output = fs3.createWriteStream(this.outputPath);
258
511
  const archive = new ZipArchive({
259
512
  zlib: { level: 0 }
260
513
  });
@@ -284,14 +537,14 @@ var Zipper = class {
284
537
  reject(err);
285
538
  });
286
539
  archive.pipe(output);
287
- if (!fs2.existsSync(reportPath)) {
540
+ if (!fs3.existsSync(reportPath)) {
288
541
  reject(new Error(`Report file not found: ${reportPath}`));
289
542
  return;
290
543
  }
291
544
  const transformedReportPath = this.transformJsonReport(reportPath);
292
545
  archive.file(transformedReportPath, { name: "output/playwright-test-report.json" });
293
546
  for (const asset of assets) {
294
- if (!fs2.existsSync(asset.sourcePath)) {
547
+ if (!fs3.existsSync(asset.sourcePath)) {
295
548
  console.warn(`[Checkly Reporter] Skipping missing asset: ${asset.sourcePath}`);
296
549
  continue;
297
550
  }
@@ -310,11 +563,11 @@ var Zipper = class {
310
563
  * @returns Path to the transformed JSON report (in temp directory)
311
564
  */
312
565
  transformJsonReport(reportPath) {
313
- const reportContent = fs2.readFileSync(reportPath, "utf-8");
566
+ const reportContent = fs3.readFileSync(reportPath, "utf-8");
314
567
  const report = JSON.parse(reportContent);
315
568
  this.transformAttachmentPaths(report);
316
569
  const tempReportPath = path2.join(os.tmpdir(), `playwright-test-report-${Date.now()}.json`);
317
- fs2.writeFileSync(tempReportPath, JSON.stringify(report, null, 2));
570
+ fs3.writeFileSync(tempReportPath, JSON.stringify(report, null, 2));
318
571
  return tempReportPath;
319
572
  }
320
573
  /**
@@ -392,8 +645,8 @@ var Zipper = class {
392
645
  };
393
646
 
394
647
  // src/reporter.ts
395
- import * as fs3 from "fs";
396
- import { readFileSync as readFileSync3 } from "fs";
648
+ import * as fs4 from "fs";
649
+ import { readFileSync as readFileSync4 } from "fs";
397
650
  import * as path3 from "path";
398
651
  import { dirname, join as join3 } from "path";
399
652
  import { fileURLToPath } from "url";
@@ -658,7 +911,7 @@ var TestResults = class {
658
911
  // src/reporter.ts
659
912
  var __filename = fileURLToPath(import.meta.url);
660
913
  var __dirname = dirname(__filename);
661
- var packageJson = JSON.parse(readFileSync3(join3(__dirname, "..", "package.json"), "utf-8"));
914
+ var packageJson = JSON.parse(readFileSync4(join3(__dirname, "..", "package.json"), "utf-8"));
662
915
  var pkgVersion = packageJson.version;
663
916
  var pluralRules = new Intl.PluralRules("en-US");
664
917
  var projectForms = {
@@ -725,11 +978,30 @@ var ChecklyReporter = class {
725
978
  stepsMap = /* @__PURE__ */ new Map();
726
979
  // Store warnings per test result, keyed by "testId:retry"
727
980
  warningsMap = /* @__PURE__ */ new Map();
981
+ // Store trace file paths per test result, keyed by "testId:retry"
982
+ tracePathsMap = /* @__PURE__ */ new Map();
983
+ // Store console messages per test result, keyed by "testId:retry"
984
+ consoleMessagesMap = /* @__PURE__ */ new Map();
985
+ // Store network requests per test result, keyed by "testId:retry"
986
+ networkRequestsMap = /* @__PURE__ */ new Map();
987
+ /**
988
+ * Log a message if verbose mode is enabled
989
+ */
990
+ log(message, data) {
991
+ if (!this.options.verbose) return;
992
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
993
+ if (data) {
994
+ console.log(`[Checkly Reporter DEBUG ${timestamp}] ${message}`, JSON.stringify(data, null, 2));
995
+ } else {
996
+ console.log(`[Checkly Reporter DEBUG ${timestamp}] ${message}`);
997
+ }
998
+ }
728
999
  constructor(options = {}) {
729
1000
  const environment = getEnvironment(options);
730
1001
  const baseUrl = getApiUrl(environment);
731
1002
  const apiKey = process.env.CHECKLY_API_KEY || options.apiKey;
732
1003
  const accountId = process.env.CHECKLY_ACCOUNT_ID || options.accountId;
1004
+ const verbose = options.verbose ?? process.env.CHECKLY_REPORTER_VERBOSE === "true";
733
1005
  this.options = {
734
1006
  accountId,
735
1007
  apiKey,
@@ -737,7 +1009,8 @@ var ChecklyReporter = class {
737
1009
  jsonReportPath: options.jsonReportPath ?? "test-results/playwright-test-report.json",
738
1010
  testResultsDir: options.testResultsDir ?? "test-results",
739
1011
  dryRun: options.dryRun ?? false,
740
- sessionName: options.sessionName
1012
+ sessionName: options.sessionName,
1013
+ verbose
741
1014
  };
742
1015
  this.assetCollector = new AssetCollector(this.options.testResultsDir);
743
1016
  this.zipper = new Zipper({
@@ -751,6 +1024,20 @@ var ChecklyReporter = class {
751
1024
  });
752
1025
  this.testResults = new TestResults(client.getAxiosInstance());
753
1026
  }
1027
+ if (verbose) {
1028
+ console.log(`[Checkly Reporter DEBUG] Initialized with options:`, {
1029
+ environment,
1030
+ baseUrl,
1031
+ hasApiKey: !!apiKey,
1032
+ hasAccountId: !!accountId,
1033
+ outputPath: this.options.outputPath,
1034
+ jsonReportPath: this.options.jsonReportPath,
1035
+ testResultsDir: this.options.testResultsDir,
1036
+ dryRun: this.options.dryRun,
1037
+ verbose: this.options.verbose,
1038
+ hasTestResults: !!this.testResults
1039
+ });
1040
+ }
754
1041
  }
755
1042
  /**
756
1043
  * Resolves the session name from options
@@ -768,14 +1055,16 @@ var ChecklyReporter = class {
768
1055
  }
769
1056
  /**
770
1057
  * Checks if test result has a trace attachment and adds context-aware warning if missing
1058
+ * Also captures trace file path for later console message extraction
771
1059
  * The warning type depends on the trace configuration and test result state
772
1060
  */
773
1061
  checkTraceAttachment(test, result) {
774
- const warningsKey = `${test.id}:${result.retry}`;
775
- const hasTrace = result.attachments?.some(
1062
+ const key = `${test.id}:${result.retry}`;
1063
+ const traceAttachment = result.attachments?.find(
776
1064
  (attachment) => attachment.name === "trace" || attachment.contentType === "application/zip"
777
1065
  );
778
- if (hasTrace) {
1066
+ if (traceAttachment?.path) {
1067
+ this.tracePathsMap.set(key, traceAttachment.path);
779
1068
  return;
780
1069
  }
781
1070
  const traceConfig = test.parent?.project()?.use?.trace;
@@ -841,9 +1130,9 @@ var ChecklyReporter = class {
841
1130
  warningType = "trace-missing";
842
1131
  message = `No trace found. Trace mode "${traceMode}" may not be generating traces for this result.`;
843
1132
  }
844
- const warnings = this.warningsMap.get(warningsKey) || [];
1133
+ const warnings = this.warningsMap.get(key) || [];
845
1134
  warnings.push({ type: warningType, message });
846
- this.warningsMap.set(warningsKey, warnings);
1135
+ this.warningsMap.set(key, warnings);
847
1136
  }
848
1137
  /**
849
1138
  * Called once before running tests
@@ -919,9 +1208,16 @@ var ChecklyReporter = class {
919
1208
  * This is where we create the ZIP archive and upload results
920
1209
  */
921
1210
  async onEnd() {
1211
+ this.log("onEnd started", {
1212
+ testCounts: this.testCounts,
1213
+ stepsMapSize: this.stepsMap.size,
1214
+ warningsMapSize: this.warningsMap.size,
1215
+ hasTestSession: !!this.testSession,
1216
+ testSessionId: this.testSession?.testSessionId
1217
+ });
922
1218
  try {
923
1219
  const jsonReportPath = this.options.jsonReportPath;
924
- if (!fs3.existsSync(jsonReportPath)) {
1220
+ if (!fs4.existsSync(jsonReportPath)) {
925
1221
  console.error(`[Checkly Reporter] ERROR: JSON report not found at: ${jsonReportPath}`);
926
1222
  console.error("[Checkly Reporter] Make sure to configure the json reporter before the checkly reporter:");
927
1223
  console.error(
@@ -929,17 +1225,42 @@ var ChecklyReporter = class {
929
1225
  );
930
1226
  return;
931
1227
  }
932
- const reportContent = fs3.readFileSync(jsonReportPath, "utf-8");
1228
+ this.log("Reading JSON report", { path: jsonReportPath });
1229
+ const reportContent = fs4.readFileSync(jsonReportPath, "utf-8");
933
1230
  const report = JSON.parse(reportContent);
1231
+ this.log("JSON report parsed", {
1232
+ configVersion: report.config?.version,
1233
+ projectsCount: report.config?.projects?.length ?? 0,
1234
+ suitesCount: report.suites?.length ?? 0,
1235
+ rootDir: report.config?.rootDir
1236
+ });
1237
+ await this.extractDataFromTraces();
934
1238
  this.injectDataIntoReport(report);
935
- fs3.writeFileSync(jsonReportPath, JSON.stringify(report, null, 2), "utf-8");
1239
+ this.log("Data injected into report", {
1240
+ projectsCountAfterReconstruction: report.config?.projects?.length ?? 0
1241
+ });
1242
+ fs4.writeFileSync(jsonReportPath, JSON.stringify(report, null, 2), "utf-8");
1243
+ this.log("Enriched report written to disk");
1244
+ this.log("Collecting assets", { testResultsDir: this.options.testResultsDir });
936
1245
  const assets = await this.assetCollector.collectAssets(report);
1246
+ this.log("Assets collected", {
1247
+ count: assets.length,
1248
+ assets: assets.map((a) => ({ source: a.sourcePath, archive: a.archivePath, type: a.type }))
1249
+ });
1250
+ this.log("Creating ZIP archive", { outputPath: this.options.outputPath });
937
1251
  const result = await this.zipper.createZip(jsonReportPath, assets);
1252
+ this.log("ZIP created", {
1253
+ zipPath: result.zipPath,
1254
+ zipSize: result.size,
1255
+ entriesCount: result.entries.length,
1256
+ entries: result.entries.map((e) => ({ name: e.name, start: e.start, end: e.end }))
1257
+ });
938
1258
  if (this.testResults && this.testSession) {
1259
+ this.log("Uploading results", { testSessionId: this.testSession.testSessionId });
939
1260
  await this.uploadResults(report, result.zipPath, result.entries);
940
1261
  if (!this.options.dryRun) {
941
1262
  try {
942
- fs3.unlinkSync(result.zipPath);
1263
+ fs4.unlinkSync(result.zipPath);
943
1264
  } catch (cleanupError) {
944
1265
  console.warn(`[Checkly Reporter] Warning: Could not delete ZIP file: ${cleanupError}`);
945
1266
  }
@@ -962,7 +1283,33 @@ var ChecklyReporter = class {
962
1283
  console.log("\n======================================================");
963
1284
  }
964
1285
  /**
965
- * Injects captured steps and warnings into the JSON report
1286
+ * Extracts console messages and network requests from all captured traces
1287
+ * Called before injecting data into the report
1288
+ */
1289
+ async extractDataFromTraces() {
1290
+ const extractionPromises = [];
1291
+ for (const [key, tracePath] of this.tracePathsMap.entries()) {
1292
+ extractionPromises.push(
1293
+ (async () => {
1294
+ const reader = new TraceReader(tracePath);
1295
+ if (!await reader.open()) return;
1296
+ const messages = await reader.extractEvents('"type":"console"', toConsoleMessage);
1297
+ if (messages.length > 0) {
1298
+ this.consoleMessagesMap.set(key, messages);
1299
+ }
1300
+ const networkRequests = await reader.extractEvents('"type":"resource-snapshot"', toNetworkRequest);
1301
+ if (networkRequests.length > 0) {
1302
+ this.networkRequestsMap.set(key, networkRequests);
1303
+ }
1304
+ })().catch((error) => {
1305
+ console.error(`[Checkly Reporter] Failed to extract data from trace: ${error}`);
1306
+ })
1307
+ );
1308
+ }
1309
+ await Promise.all(extractionPromises);
1310
+ }
1311
+ /**
1312
+ * Injects captured steps, warnings, console messages, and network requests into the JSON report
966
1313
  * Traverses the report structure and matches by test ID + retry
967
1314
  */
968
1315
  injectDataIntoReport(report) {
@@ -976,8 +1323,15 @@ var ChecklyReporter = class {
976
1323
  result.steps = steps;
977
1324
  }
978
1325
  const warnings = this.warningsMap.get(key);
979
- if (warnings && warnings.length > 0) {
980
- result._checkly = { warnings };
1326
+ const consoleMessages = this.consoleMessagesMap.get(key);
1327
+ const networkRequests = this.networkRequestsMap.get(key);
1328
+ const hasData = warnings && warnings.length > 0 || consoleMessages && consoleMessages.length > 0 || networkRequests && networkRequests.length > 0;
1329
+ if (hasData) {
1330
+ result._checkly = {
1331
+ ...warnings && warnings.length > 0 ? { warnings } : {},
1332
+ ...consoleMessages && consoleMessages.length > 0 ? { console: consoleMessages } : {},
1333
+ ...networkRequests && networkRequests.length > 0 ? { network: networkRequests } : {}
1334
+ };
981
1335
  }
982
1336
  }
983
1337
  }
@@ -1000,6 +1354,13 @@ var ChecklyReporter = class {
1000
1354
  */
1001
1355
  reconstructProjectsFromTests(report) {
1002
1356
  const projectNames = /* @__PURE__ */ new Set();
1357
+ let testsWithMissingProjectId = 0;
1358
+ const configAny = report.config;
1359
+ const originalProjectsCount = configAny.projects?.length ?? 0;
1360
+ this.log("reconstructProjectsFromTests started", {
1361
+ originalProjectsCount,
1362
+ suitesCount: report.suites?.length ?? 0
1363
+ });
1003
1364
  const collectProjectNames = (suite) => {
1004
1365
  for (const spec of suite.specs) {
1005
1366
  for (const test of spec.tests) {
@@ -1009,6 +1370,7 @@ var ChecklyReporter = class {
1009
1370
  }
1010
1371
  if (testAny.projectName && !testAny.projectId) {
1011
1372
  testAny.projectId = testAny.projectName;
1373
+ testsWithMissingProjectId++;
1012
1374
  }
1013
1375
  }
1014
1376
  }
@@ -1021,19 +1383,37 @@ var ChecklyReporter = class {
1021
1383
  for (const suite of report.suites) {
1022
1384
  collectProjectNames(suite);
1023
1385
  }
1024
- const configAny = report.config;
1386
+ this.log("Project names collected from tests", {
1387
+ uniqueProjectNames: Array.from(projectNames),
1388
+ testsWithMissingProjectId
1389
+ });
1025
1390
  if ((!configAny.projects || configAny.projects.length === 0) && projectNames.size > 0) {
1026
1391
  configAny.projects = Array.from(projectNames).map((name) => ({
1027
1392
  id: name,
1028
1393
  name
1029
1394
  }));
1395
+ this.log("Reconstructed config.projects", {
1396
+ reconstructedProjectsCount: configAny.projects.length,
1397
+ projects: configAny.projects
1398
+ });
1399
+ } else {
1400
+ this.log("No project reconstruction needed", {
1401
+ reason: configAny.projects?.length > 0 ? "projects already present" : "no project names found in tests"
1402
+ });
1030
1403
  }
1031
1404
  }
1032
1405
  /**
1033
1406
  * Uploads test results to Checkly API
1034
1407
  */
1035
- async uploadResults(report, zipPath, entries) {
1408
+ async uploadResults(_report, zipPath, entries) {
1409
+ this.log("uploadResults started", {
1410
+ zipPath,
1411
+ entriesCount: entries.length,
1412
+ hasTestResults: !!this.testResults,
1413
+ hasTestSession: !!this.testSession
1414
+ });
1036
1415
  if (!this.testResults || !this.testSession) {
1416
+ this.log("uploadResults skipped - missing testResults or testSession");
1037
1417
  return;
1038
1418
  }
1039
1419
  try {
@@ -1042,25 +1422,42 @@ var ChecklyReporter = class {
1042
1422
  const isDegraded = failedCount === 0 && flakyCount > 0;
1043
1423
  const endTime = /* @__PURE__ */ new Date();
1044
1424
  const responseTime = this.startTime ? Math.max(0, endTime.getTime() - this.startTime.getTime()) : 0;
1045
- const zipSizeBytes = (await fs3.promises.stat(zipPath)).size;
1425
+ const zipSizeBytes = (await fs4.promises.stat(zipPath)).size;
1426
+ this.log("Upload metadata calculated", {
1427
+ testCounts: this.testCounts,
1428
+ overallStatus,
1429
+ isDegraded,
1430
+ responseTime,
1431
+ zipSizeBytes,
1432
+ testSessionId: this.testSession.testSessionId,
1433
+ testResultsCount: this.testSession.testResults.length
1434
+ });
1046
1435
  if (this.testSession.testResults.length > 0) {
1047
1436
  const firstResult = this.testSession.testResults[0];
1437
+ this.log("Using first test result for upload", {
1438
+ testResultId: firstResult.testResultId
1439
+ });
1048
1440
  let assetId;
1049
1441
  if (zipSizeBytes > 0) {
1442
+ this.log("Starting S3 asset upload", { zipSizeBytes });
1050
1443
  try {
1051
- const assets = fs3.createReadStream(zipPath);
1444
+ const assets = fs4.createReadStream(zipPath);
1052
1445
  const uploadResponse = await this.testResults.uploadTestResultAsset(
1053
1446
  this.testSession.testSessionId,
1054
1447
  firstResult.testResultId,
1055
1448
  assets
1056
1449
  );
1057
1450
  assetId = uploadResponse.assetId;
1451
+ this.log("S3 asset upload completed", { assetId });
1058
1452
  } catch (error) {
1059
1453
  const errorMessage = error instanceof Error ? error.message : String(error);
1060
1454
  console.error("[Checkly Reporter] Asset upload failed:", errorMessage);
1455
+ this.log("S3 asset upload failed", { error: errorMessage });
1061
1456
  }
1457
+ } else {
1458
+ this.log("Skipping S3 upload - ZIP is empty");
1062
1459
  }
1063
- await this.testResults.updateTestResult(this.testSession.testSessionId, firstResult.testResultId, {
1460
+ const updatePayload = {
1064
1461
  status: overallStatus,
1065
1462
  assetEntries: assetId ? entries : void 0,
1066
1463
  isDegraded,
@@ -1072,11 +1469,21 @@ var ChecklyReporter = class {
1072
1469
  s3PostTotalBytes: zipSizeBytes
1073
1470
  }
1074
1471
  }
1472
+ };
1473
+ this.log("Updating test result", {
1474
+ testResultId: firstResult.testResultId,
1475
+ payload: updatePayload,
1476
+ assetEntriesCount: assetId ? entries.length : 0
1075
1477
  });
1478
+ await this.testResults.updateTestResult(this.testSession.testSessionId, firstResult.testResultId, updatePayload);
1479
+ this.log("Test result updated successfully");
1480
+ } else {
1481
+ this.log("No test results in session to update");
1076
1482
  }
1077
1483
  } catch (error) {
1078
1484
  const errorMessage = error instanceof Error ? error.message : String(error);
1079
1485
  console.error("[Checkly Reporter] Failed to upload results:", errorMessage);
1486
+ this.log("uploadResults failed", { error: errorMessage });
1080
1487
  }
1081
1488
  }
1082
1489
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checkly/playwright-reporter",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "Playwright reporter that generates ZIP archives containing JSON reports and test assets",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",