@arghajit/dummy 0.1.2-beta-3 → 0.1.2

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.
@@ -20,14 +20,8 @@ export declare class PlaywrightPulseReporter implements Reporter {
20
20
  private getBrowserDetails;
21
21
  private processStep;
22
22
  onTestEnd(test: TestCase, result: PwTestResult): Promise<void>;
23
- private _getBaseTestId;
24
- private _getStatusOrder;
25
- /**
26
- * Refactored to group all run attempts for a single logical test case.
27
- * @param allAttempts An array of all individual test run attempts.
28
- * @returns An array of ConsolidatedTestResult objects, where each object represents one logical test and contains an array of all its runs.
29
- */
30
23
  private _getFinalizedResults;
24
+ private _getStatusOrder;
31
25
  onError(error: any): void;
32
26
  private _getEnvDetails;
33
27
  private _writeShardResults;
@@ -40,10 +40,8 @@ const crypto_1 = require("crypto");
40
40
  const ua_parser_js_1 = require("ua-parser-js");
41
41
  const os = __importStar(require("os"));
42
42
  const convertStatus = (status, testCase, retryCount = 0) => {
43
- if (status === "passed" && retryCount > 0) {
44
- return "flaky";
45
- }
46
43
  if ((testCase === null || testCase === void 0 ? void 0 : testCase.expectedStatus) === "failed") {
44
+ // If expected to fail but passed, it's flaky
47
45
  if (status === "passed")
48
46
  return "flaky";
49
47
  return "failed";
@@ -51,6 +49,10 @@ const convertStatus = (status, testCase, retryCount = 0) => {
51
49
  if ((testCase === null || testCase === void 0 ? void 0 : testCase.expectedStatus) === "skipped") {
52
50
  return "skipped";
53
51
  }
52
+ // If a test passes on a retry, it's considered flaky
53
+ if (status === "passed" && retryCount > 0) {
54
+ return "flaky";
55
+ }
54
56
  switch (status) {
55
57
  case "passed":
56
58
  return "passed";
@@ -69,12 +71,11 @@ const INDIVIDUAL_REPORTS_SUBDIR = "pulse-results";
69
71
  class PlaywrightPulseReporter {
70
72
  constructor(options = {}) {
71
73
  var _a, _b, _c;
72
- // This will now store all individual run attempts for all tests using our new local type.
73
74
  this.results = [];
74
75
  this.baseOutputFile = "playwright-pulse-report.json";
75
76
  this.isSharded = false;
76
77
  this.shardIndex = undefined;
77
- this.currentRunId = "";
78
+ this.currentRunId = ""; // Added to store the overall run ID
78
79
  this.options = options;
79
80
  this.baseOutputFile = (_a = options.outputFile) !== null && _a !== void 0 ? _a : this.baseOutputFile;
80
81
  this.outputDir = (_b = options.outputDir) !== null && _b !== void 0 ? _b : "pulse-report";
@@ -89,6 +90,7 @@ class PlaywrightPulseReporter {
89
90
  this.config = config;
90
91
  this.suite = suite;
91
92
  this.runStartTime = Date.now();
93
+ // Generate the overall runId once at the beginning
92
94
  this.currentRunId = `run-${this.runStartTime}-${(0, crypto_1.randomUUID)()}`;
93
95
  const configDir = this.config.rootDir;
94
96
  const configFileDir = this.config.configFile
@@ -114,7 +116,9 @@ class PlaywrightPulseReporter {
114
116
  })
115
117
  .catch((err) => console.error("Pulse Reporter: Error during initialization:", err));
116
118
  }
117
- onTestBegin(test) { }
119
+ onTestBegin(test) {
120
+ // console.log(`Starting test: ${test.title}`); // Removed for brevity in final output
121
+ }
118
122
  getBrowserDetails(test) {
119
123
  var _a, _b, _c, _d;
120
124
  const project = (_a = test.parent) === null || _a === void 0 ? void 0 : _a.project();
@@ -165,7 +169,8 @@ class PlaywrightPulseReporter {
165
169
  }
166
170
  return finalString.trim();
167
171
  }
168
- async processStep(step, testId, browserDetails, testCase, retryCount = 0) {
172
+ async processStep(step, testId, browserDetails, testCase, retryCount = 0 // Pass retryCount to convertStatus for steps
173
+ ) {
169
174
  var _a, _b, _c, _d;
170
175
  let stepStatus = "passed";
171
176
  let errorMessage = ((_a = step.error) === null || _a === void 0 ? void 0 : _a.message) || undefined;
@@ -173,6 +178,7 @@ class PlaywrightPulseReporter {
173
178
  stepStatus = "skipped";
174
179
  }
175
180
  else {
181
+ // Use the extended convertStatus
176
182
  stepStatus = convertStatus(step.error ? "failed" : "passed", testCase, retryCount);
177
183
  }
178
184
  const duration = step.duration;
@@ -206,13 +212,15 @@ class PlaywrightPulseReporter {
206
212
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
207
213
  const project = (_a = test.parent) === null || _a === void 0 ? void 0 : _a.project();
208
214
  const browserDetails = this.getBrowserDetails(test);
215
+ // Use the extended convertStatus, passing result.retry
209
216
  const testStatus = convertStatus(result.status, test, result.retry);
210
217
  const startTime = new Date(result.startTime);
211
218
  const endTime = new Date(startTime.getTime() + result.duration);
212
219
  const processAllSteps = async (steps) => {
213
220
  let processed = [];
214
221
  for (const step of steps) {
215
- const processedStep = await this.processStep(step, test.id, browserDetails, test, result.retry);
222
+ const processedStep = await this.processStep(step, test.id, browserDetails, test, result.retry // Pass retryCount to processStep
223
+ );
216
224
  processed.push(processedStep);
217
225
  if (step.steps && step.steps.length > 0) {
218
226
  processedStep.steps = await processAllSteps(step.steps);
@@ -244,11 +252,11 @@ class PlaywrightPulseReporter {
244
252
  ? JSON.stringify(this.config.metadata)
245
253
  : undefined,
246
254
  };
247
- // Correctly handle the ID for each run attempt.
248
- const testIdWithRunCounter = `${test.id}-run-${result.retry}`;
255
+ // Modify test.id for retries
256
+ const testIdWithRunCounter = result.retry > 0 ? `${test.id}-${result.retry}` : test.id;
249
257
  const pulseResult = {
250
- id: testIdWithRunCounter,
251
- runId: this.currentRunId,
258
+ id: testIdWithRunCounter, // Use the modified ID
259
+ runId: this.currentRunId, // Assign the overall run ID
252
260
  name: test.titlePath().join(" > "),
253
261
  suiteName: (project === null || project === void 0 ? void 0 : project.name) || ((_e = this.config.projects[0]) === null || _e === void 0 ? void 0 : _e.name) || "Default Suite",
254
262
  status: testStatus,
@@ -256,8 +264,8 @@ class PlaywrightPulseReporter {
256
264
  startTime: startTime,
257
265
  endTime: endTime,
258
266
  browser: browserDetails,
259
- retries: result.retry,
260
- runCounter: result.retry,
267
+ retries: result.retry, // This remains the Playwright retry count (0 for first run, 1 for first retry, etc.)
268
+ runCounter: result.retry, // This is your 'runCounter'
261
269
  steps: ((_f = result.steps) === null || _f === void 0 ? void 0 : _f.length) ? await processAllSteps(result.steps) : [],
262
270
  errorMessage: (_g = result.error) === null || _g === void 0 ? void 0 : _g.message,
263
271
  stackTrace: (_h = result.error) === null || _h === void 0 ? void 0 : _h.stack,
@@ -276,6 +284,7 @@ class PlaywrightPulseReporter {
276
284
  if (!attachment.path)
277
285
  continue;
278
286
  try {
287
+ // Use the new testIdWithRunCounter for the subfolder
279
288
  const testSubfolder = testIdWithRunCounter.replace(/[^a-zA-Z0-9_-]/g, "_");
280
289
  const safeAttachmentName = path
281
290
  .basename(attachment.path)
@@ -308,10 +317,34 @@ class PlaywrightPulseReporter {
308
317
  }
309
318
  this.results.push(pulseResult);
310
319
  }
311
- // New method to extract the base test ID, ignoring the run-counter suffix
312
- _getBaseTestId(testResultId) {
313
- const parts = testResultId.split("-run-");
314
- return parts[0];
320
+ _getFinalizedResults(allResults) {
321
+ const finalResultsMap = new Map();
322
+ for (const result of allResults) {
323
+ // The key for de-duplication should now be the base test ID (without the run counter suffix)
324
+ // This ensures that all runs of a single logical test are considered together.
325
+ const baseTestId = result.id.split("-").slice(0, -1).join("-"); // Remove '-${runCounter}'
326
+ const existing = finalResultsMap.get(baseTestId);
327
+ // We want to keep the "most successful" run for the final report.
328
+ // Priority: passed > flaky > failed > skipped.
329
+ // If statuses are equal, prefer the one with higher retry count (latest attempt).
330
+ if (!existing) {
331
+ finalResultsMap.set(baseTestId, result);
332
+ }
333
+ else {
334
+ const currentStatusOrder = this._getStatusOrder(result.status);
335
+ const existingStatusOrder = this._getStatusOrder(existing.status);
336
+ if (currentStatusOrder < existingStatusOrder) {
337
+ // Current result is "better" (e.g., passed over failed)
338
+ finalResultsMap.set(baseTestId, result);
339
+ }
340
+ else if (currentStatusOrder === existingStatusOrder &&
341
+ result.retries > existing.retries) {
342
+ // Same status, but current is a later retry, so prefer it
343
+ finalResultsMap.set(baseTestId, result);
344
+ }
345
+ }
346
+ }
347
+ return Array.from(finalResultsMap.values());
315
348
  }
316
349
  _getStatusOrder(status) {
317
350
  switch (status) {
@@ -324,47 +357,9 @@ class PlaywrightPulseReporter {
324
357
  case "skipped":
325
358
  return 4;
326
359
  default:
327
- return 99;
360
+ return 99; // Unknown status
328
361
  }
329
362
  }
330
- /**
331
- * Refactored to group all run attempts for a single logical test case.
332
- * @param allAttempts An array of all individual test run attempts.
333
- * @returns An array of ConsolidatedTestResult objects, where each object represents one logical test and contains an array of all its runs.
334
- */
335
- _getFinalizedResults(allAttempts) {
336
- const groupedResults = new Map();
337
- for (const attempt of allAttempts) {
338
- const baseTestId = this._getBaseTestId(attempt.id);
339
- if (!groupedResults.has(baseTestId)) {
340
- groupedResults.set(baseTestId, []);
341
- }
342
- groupedResults.get(baseTestId).push(attempt);
343
- }
344
- const finalResults = [];
345
- for (const [baseId, runs] of groupedResults.entries()) {
346
- // Sort runs to find the best status
347
- runs.sort((a, b) => this._getStatusOrder(a.status) - this._getStatusOrder(b.status));
348
- const bestRun = runs[0];
349
- // Calculate total duration from the earliest start to the latest end time of all runs
350
- const startTimes = runs.map((run) => run.startTime.getTime());
351
- const endTimes = runs.map((run) => run.endTime.getTime());
352
- const overallDuration = Math.max(...endTimes) - Math.min(...startTimes);
353
- finalResults.push({
354
- id: baseId,
355
- name: bestRun.name,
356
- suiteName: bestRun.suiteName,
357
- status: bestRun.status,
358
- duration: overallDuration,
359
- startTime: new Date(Math.min(...startTimes)),
360
- endTime: new Date(Math.max(...endTimes)),
361
- browser: bestRun.browser,
362
- tags: bestRun.tags,
363
- runs: runs.sort((a, b) => a.runCounter - b.runCounter), // Sort runs chronologically for the report
364
- });
365
- }
366
- return finalResults;
367
- }
368
363
  onError(error) {
369
364
  var _a;
370
365
  console.error(`PlaywrightPulseReporter: Error encountered (Shard: ${(_a = this.shardIndex) !== null && _a !== void 0 ? _a : "Main"}):`, (error === null || error === void 0 ? void 0 : error.message) || error);
@@ -399,7 +394,7 @@ class PlaywrightPulseReporter {
399
394
  }
400
395
  }
401
396
  async _mergeShardResults(finalRunData) {
402
- let allShardRawResults = [];
397
+ let allShardRawResults = []; // Store raw results before final de-duplication
403
398
  const totalShards = this.config.shard ? this.config.shard.total : 1;
404
399
  for (let i = 0; i < totalShards; i++) {
405
400
  const tempFilePath = path.join(this.outputDir, `${TEMP_SHARD_FILE_PREFIX}${i}.json`);
@@ -417,10 +412,13 @@ class PlaywrightPulseReporter {
417
412
  }
418
413
  }
419
414
  }
415
+ // Apply _getFinalizedResults after all raw shard results are collected
420
416
  const finalResultsList = this._getFinalizedResults(allShardRawResults);
417
+ finalResultsList.forEach((r) => (r.runId = finalRunData.id));
421
418
  finalRunData.passed = finalResultsList.filter((r) => r.status === "passed").length;
422
419
  finalRunData.failed = finalResultsList.filter((r) => r.status === "failed").length;
423
420
  finalRunData.skipped = finalResultsList.filter((r) => r.status === "skipped").length;
421
+ // Add flaky count
424
422
  finalRunData.flaky = finalResultsList.filter((r) => r.status === "flaky").length;
425
423
  finalRunData.totalTests = finalResultsList.length;
426
424
  const reviveDates = (key, value) => {
@@ -468,9 +466,12 @@ class PlaywrightPulseReporter {
468
466
  await this._writeShardResults();
469
467
  return;
470
468
  }
469
+ // `this.results` now contains all individual run attempts.
470
+ // _getFinalizedResults will select the "best" run for each logical test.
471
471
  const finalResults = this._getFinalizedResults(this.results);
472
472
  const runEndTime = Date.now();
473
473
  const duration = runEndTime - this.runStartTime;
474
+ // Use the stored overall runId
474
475
  const runId = this.currentRunId;
475
476
  const environmentDetails = this._getEnvDetails();
476
477
  const runData = {
@@ -480,18 +481,21 @@ class PlaywrightPulseReporter {
480
481
  passed: finalResults.filter((r) => r.status === "passed").length,
481
482
  failed: finalResults.filter((r) => r.status === "failed").length,
482
483
  skipped: finalResults.filter((r) => r.status === "skipped").length,
483
- flaky: finalResults.filter((r) => r.status === "flaky").length,
484
+ flaky: finalResults.filter((r) => r.status === "flaky").length, // Add flaky count
484
485
  duration,
485
486
  environment: environmentDetails,
486
487
  };
488
+ // Ensure all final results have the correct overall runId
489
+ finalResults.forEach((r) => (r.runId = runId));
487
490
  let finalReport = undefined;
488
491
  if (this.isSharded) {
492
+ // _mergeShardResults will now perform the final de-duplication across shards
489
493
  finalReport = await this._mergeShardResults(runData);
490
494
  }
491
495
  else {
492
496
  finalReport = {
493
497
  run: runData,
494
- results: finalResults, // Cast to any to bypass the type mismatch
498
+ results: finalResults, // Use the de-duplicated results for a non-sharded run
495
499
  metadata: { generatedAt: new Date().toISOString() },
496
500
  };
497
501
  }
@@ -565,6 +569,8 @@ class PlaywrightPulseReporter {
565
569
  const allResultsFromAllFiles = [];
566
570
  let latestTimestamp = new Date(0);
567
571
  let lastRunEnvironment = undefined;
572
+ // We can't simply sum durations across merged files, as the tests might overlap.
573
+ // The final duration will be derived from the range of start/end times in the final results.
568
574
  let earliestStartTime = Date.now();
569
575
  let latestEndTime = 0;
570
576
  for (const file of reportFiles) {
@@ -572,26 +578,24 @@ class PlaywrightPulseReporter {
572
578
  try {
573
579
  const content = await fs.readFile(filePath, "utf-8");
574
580
  const json = JSON.parse(content);
575
- // This is the tricky part. We need to handle both old and new report formats.
576
- // Assuming the `results` array might contain the old, single-run objects or the new, consolidated ones.
581
+ if (json.run) {
582
+ const runTimestamp = new Date(json.run.timestamp);
583
+ if (runTimestamp > latestTimestamp) {
584
+ latestTimestamp = runTimestamp;
585
+ lastRunEnvironment = json.run.environment || undefined;
586
+ }
587
+ }
577
588
  if (json.results) {
578
- json.results.forEach((testResult) => {
579
- // Check if the TestResult has a 'runs' array (new format)
580
- if ("runs" in testResult && Array.isArray(testResult.runs)) {
581
- allResultsFromAllFiles.push(...testResult.runs);
582
- }
583
- else {
584
- // This is the old format (single run). We'll treat it as a single attempt.
585
- allResultsFromAllFiles.push(testResult); // Cast to any to get properties
586
- }
587
- });
589
+ allResultsFromAllFiles.push(...json.results);
588
590
  }
589
591
  }
590
592
  catch (err) {
591
593
  console.warn(`Pulse Reporter: Could not parse report file ${filePath}. Skipping. Error: ${err.message}`);
592
594
  }
593
595
  }
596
+ // De-duplicate the results from ALL merged files using the helper function
594
597
  const finalMergedResults = this._getFinalizedResults(allResultsFromAllFiles);
598
+ // Calculate overall duration from the earliest start and latest end of the final merged results
595
599
  for (const res of finalMergedResults) {
596
600
  if (res.startTime.getTime() < earliestStartTime)
597
601
  earliestStartTime = res.startTime.getTime();
@@ -607,7 +611,7 @@ class PlaywrightPulseReporter {
607
611
  passed: finalMergedResults.filter((r) => r.status === "passed").length,
608
612
  failed: finalMergedResults.filter((r) => r.status === "failed").length,
609
613
  skipped: finalMergedResults.filter((r) => r.status === "skipped").length,
610
- flaky: finalMergedResults.filter((r) => r.status === "flaky").length,
614
+ flaky: finalMergedResults.filter((r) => r.status === "flaky").length, // Add flaky count
611
615
  duration: totalDuration,
612
616
  };
613
617
  const finalReport = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arghajit/dummy",
3
- "version": "0.1.2-beta-3",
3
+ "version": "0.1.2",
4
4
  "description": "A Playwright reporter and dashboard for visualizing test results.",
5
5
  "homepage": "https://playwright-pulse-report.netlify.app/",
6
6
  "keywords": [
@@ -50,8 +50,7 @@
50
50
  "report:merge": "node ./scripts/merge-pulse-report.js",
51
51
  "report:email": "node ./scripts/sendReport.mjs",
52
52
  "report:minify": "node ./scripts/generate-email-report.mjs",
53
- "generate-trend": "node ./scripts/generate-trend.mjs",
54
- "deploy": "npm publish --access -public"
53
+ "generate-trend": "node ./scripts/generate-trend.mjs"
55
54
  },
56
55
  "dependencies": {
57
56
  "archiver": "^7.0.1",