@arghajit/dummy 0.1.2-beta-8 → 0.1.2-beta-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.
@@ -1,53 +1,66 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  Object.defineProperty(exports, "__esModule", { value: true });
36
3
  exports.PlaywrightPulseReporter = void 0;
37
- const fs = __importStar(require("fs/promises"));
38
- const path = __importStar(require("path"));
39
- const crypto_1 = require("crypto");
40
- const ua_parser_js_1 = require("ua-parser-js");
41
- const os = __importStar(require("os"));
42
- const convertStatus = (status, testCase) => {
43
- if ((testCase === null || testCase === void 0 ? void 0 : testCase.expectedStatus) === "failed") {
44
- if (status === "passed")
45
- return "flaky";
46
- return "failed";
47
- }
48
- if ((testCase === null || testCase === void 0 ? void 0 : testCase.expectedStatus) === "skipped") {
49
- return "skipped";
4
+ const fs = {
5
+ promises: {
6
+ writeFile: async (path, data) => {
7
+ console.log(`Would write to ${path}`);
8
+ },
9
+ readFile: async (path, encoding) => {
10
+ console.log(`Would read from ${path}`);
11
+ return "{}";
12
+ },
13
+ readdir: async (path) => {
14
+ console.log(`Would read directory ${path}`);
15
+ return [];
16
+ },
17
+ mkdir: async (path, options) => {
18
+ console.log(`Would create directory ${path}`);
19
+ },
20
+ unlink: async (path) => {
21
+ console.log(`Would delete ${path}`);
22
+ },
23
+ },
24
+ };
25
+ const path = {
26
+ resolve: (...paths) => paths.join("/"),
27
+ join: (...paths) => paths.join("/"),
28
+ relative: (from, to) => to,
29
+ dirname: (p) => p.split("/").slice(0, -1).join("/"),
30
+ basename: (p, ext) => {
31
+ const base = p.split("/").pop() || "";
32
+ return ext ? base.replace(ext, "") : base;
33
+ },
34
+ extname: (p) => {
35
+ const parts = p.split(".");
36
+ return parts.length > 1 ? "." + parts.pop() : "";
37
+ },
38
+ };
39
+ const randomUUID = () => "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
40
+ const r = (Math.random() * 16) | 0;
41
+ const v = c == "x" ? r : (r & 0x3) | 0x8;
42
+ return v.toString(16);
43
+ });
44
+ const UAParser = class {
45
+ getBrowser() {
46
+ return { name: "unknown", version: "unknown" };
50
47
  }
48
+ };
49
+ const os = {
50
+ type: () => "Linux",
51
+ release: () => "5.4.0",
52
+ arch: () => "x64",
53
+ hostname: () => "localhost",
54
+ platform: () => "linux",
55
+ cpus: () => [{ model: "Intel" }],
56
+ totalmem: () => 8589934592,
57
+ };
58
+ const process = {
59
+ cwd: () => "/current/working/directory",
60
+ version: "v18.0.0",
61
+ versions: { v8: "10.0.0" },
62
+ };
63
+ function convertStatus(status) {
51
64
  switch (status) {
52
65
  case "passed":
53
66
  return "passed";
@@ -56,314 +69,219 @@ const convertStatus = (status, testCase) => {
56
69
  case "interrupted":
57
70
  return "failed";
58
71
  case "skipped":
59
- default:
60
72
  return "skipped";
73
+ default:
74
+ return "failed";
61
75
  }
62
- };
63
- const TEMP_SHARD_FILE_PREFIX = ".pulse-shard-results-";
64
- const ATTACHMENTS_SUBDIR = "attachments";
65
- const INDIVIDUAL_REPORTS_SUBDIR = "pulse-results";
76
+ }
77
+ function attachFiles(testId, pwResult, pulseResult, config) {
78
+ const baseReportDir = config.outputDir || "pulse-report";
79
+ const attachmentsBaseDir = path.resolve(baseReportDir, "attachments");
80
+ const attachmentsSubFolder = `${testId}-retry-${pwResult.retry || 0}`.replace(/[^a-zA-Z0-9_-]/g, "_");
81
+ pulseResult.screenshots = [];
82
+ pulseResult.videoPath = [];
83
+ pulseResult.attachments = [];
84
+ if (!pwResult.attachments)
85
+ return;
86
+ pwResult.attachments.forEach((attachment) => {
87
+ var _a, _b, _c;
88
+ const { contentType, name, path: attachmentPath } = attachment;
89
+ if (!attachmentPath)
90
+ return;
91
+ const relativePath = path.join("attachments", attachmentsSubFolder, path.basename(attachmentPath));
92
+ if (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith("image/")) {
93
+ (_a = pulseResult.screenshots) === null || _a === void 0 ? void 0 : _a.push(relativePath);
94
+ }
95
+ else if (name === "video" || (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith("video/"))) {
96
+ (_b = pulseResult.videoPath) === null || _b === void 0 ? void 0 : _b.push(relativePath);
97
+ }
98
+ else if (name === "trace" || contentType === "application/zip") {
99
+ pulseResult.tracePath = relativePath;
100
+ }
101
+ else {
102
+ (_c = pulseResult.attachments) === null || _c === void 0 ? void 0 : _c.push({
103
+ name: attachment.name,
104
+ path: relativePath,
105
+ contentType: attachment.contentType,
106
+ });
107
+ }
108
+ });
109
+ }
66
110
  class PlaywrightPulseReporter {
67
111
  constructor(options = {}) {
68
- var _a, _b, _c;
69
- // This will now store all individual run attempts for all tests using our new local type.
70
- this.results = [];
71
- this.baseOutputFile = "playwright-pulse-report.json";
72
- this.isSharded = false;
73
- this.shardIndex = undefined;
112
+ this.testResults = [];
74
113
  this.currentRunId = "";
114
+ this.outputDir = "";
115
+ this.totalWorkers = 0;
75
116
  this.options = options;
76
- this.baseOutputFile = (_a = options.outputFile) !== null && _a !== void 0 ? _a : this.baseOutputFile;
77
- this.outputDir = (_b = options.outputDir) !== null && _b !== void 0 ? _b : "pulse-report";
78
- this.attachmentsDir = path.join(this.outputDir, ATTACHMENTS_SUBDIR);
79
- this.resetOnEachRun = (_c = options.resetOnEachRun) !== null && _c !== void 0 ? _c : true;
80
117
  }
81
118
  printsToStdio() {
82
- return this.shardIndex === undefined || this.shardIndex === 0;
119
+ return false;
83
120
  }
84
121
  onBegin(config, suite) {
85
122
  var _a;
86
- this.config = config;
87
- this.suite = suite;
88
- this.runStartTime = Date.now();
89
- this.currentRunId = `run-${this.runStartTime}-${(0, crypto_1.randomUUID)()}`;
90
- const configDir = this.config.rootDir;
91
- const configFileDir = this.config.configFile
92
- ? path.dirname(this.config.configFile)
93
- : configDir;
94
- this.outputDir = path.resolve(configFileDir, (_a = this.options.outputDir) !== null && _a !== void 0 ? _a : "pulse-report");
95
- this.attachmentsDir = path.resolve(this.outputDir, ATTACHMENTS_SUBDIR);
96
- this.options.outputDir = this.outputDir;
97
- const totalShards = this.config.shard ? this.config.shard.total : 1;
98
- this.isSharded = totalShards > 1;
99
- this.shardIndex = this.config.shard
100
- ? this.config.shard.current - 1
101
- : undefined;
102
- this._ensureDirExists(this.outputDir)
103
- .then(() => {
104
- if (this.printsToStdio()) {
105
- console.log(`PlaywrightPulseReporter: Starting test run with ${suite.allTests().length} tests${this.isSharded ? ` across ${totalShards} shards` : ""}. Pulse outputting to ${this.outputDir}`);
106
- if (this.shardIndex === undefined ||
107
- (this.isSharded && this.shardIndex === 0)) {
108
- return this._cleanupTemporaryFiles();
109
- }
110
- }
111
- })
112
- .catch((err) => console.error("Pulse Reporter: Error during initialization:", err));
113
- }
114
- onTestBegin(test) { }
115
- getBrowserDetails(test) {
116
- var _a, _b, _c, _d;
117
- const project = (_a = test.parent) === null || _a === void 0 ? void 0 : _a.project();
118
- const projectConfig = project === null || project === void 0 ? void 0 : project.use;
119
- const userAgent = projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.userAgent;
120
- const configuredBrowserType = (_b = projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.browserName) === null || _b === void 0 ? void 0 : _b.toLowerCase();
121
- const parser = new ua_parser_js_1.UAParser(userAgent);
122
- const result = parser.getResult();
123
- let browserName = result.browser.name;
124
- const browserVersion = result.browser.version
125
- ? ` v${result.browser.version.split(".")[0]}`
126
- : "";
127
- const osName = result.os.name ? ` on ${result.os.name}` : "";
128
- const osVersion = result.os.version
129
- ? ` ${result.os.version.split(".")[0]}`
130
- : "";
131
- const deviceType = result.device.type;
132
- let finalString;
133
- if (browserName === undefined) {
134
- browserName = configuredBrowserType;
135
- finalString = `${browserName}`;
136
- }
137
- else {
138
- if (deviceType === "mobile" || deviceType === "tablet") {
139
- if ((_c = result.os.name) === null || _c === void 0 ? void 0 : _c.toLowerCase().includes("android")) {
140
- if (browserName.toLowerCase().includes("chrome"))
141
- browserName = "Chrome Mobile";
142
- else if (browserName.toLowerCase().includes("firefox"))
143
- browserName = "Firefox Mobile";
144
- else if (result.engine.name === "Blink" && !result.browser.name)
145
- browserName = "Android WebView";
146
- else if (browserName &&
147
- !browserName.toLowerCase().includes("mobile")) {
148
- // Keep it as is
149
- }
150
- else {
151
- browserName = "Android Browser";
152
- }
153
- }
154
- else if ((_d = result.os.name) === null || _d === void 0 ? void 0 : _d.toLowerCase().includes("ios")) {
155
- browserName = "Mobile Safari";
156
- }
157
- }
158
- else if (browserName === "Electron") {
159
- browserName = "Electron App";
160
- }
161
- finalString = `${browserName}${browserVersion}${osName}${osVersion}`;
123
+ this.currentRunId = randomUUID();
124
+ this.totalWorkers = config.workers;
125
+ this.shardIndex = (_a = config.shard) === null || _a === void 0 ? void 0 : _a.current;
126
+ this.outputDir = path.resolve(this.options.outputDir || config.outputDir || "pulse-report");
127
+ if (this.options.open === undefined) {
128
+ this.options.open = false;
162
129
  }
163
- return finalString.trim();
164
- }
165
- async processStep(step, testId, browserDetails, testCase) {
166
- var _a, _b, _c, _d;
167
- let stepStatus = "passed";
168
- let errorMessage = ((_a = step.error) === null || _a === void 0 ? void 0 : _a.message) || undefined;
169
- if ((_c = (_b = step.error) === null || _b === void 0 ? void 0 : _b.message) === null || _c === void 0 ? void 0 : _c.startsWith("Test is skipped:")) {
170
- stepStatus = "skipped";
171
- }
172
- else {
173
- stepStatus = convertStatus(step.error ? "failed" : "passed", testCase);
130
+ if (this.options.base64Images === undefined) {
131
+ this.options.base64Images = false;
174
132
  }
175
- const duration = step.duration;
176
- const startTime = new Date(step.startTime);
177
- const endTime = new Date(startTime.getTime() + Math.max(0, duration));
178
- let codeLocation = "";
179
- if (step.location) {
180
- codeLocation = `${path.relative(this.config.rootDir, step.location.file)}:${step.location.line}:${step.location.column}`;
133
+ console.log(`Pulse Reporter: Starting test run with ID: ${this.currentRunId}`);
134
+ if (this.shardIndex !== undefined) {
135
+ console.log(`Pulse Reporter: Running shard ${this.shardIndex}`);
181
136
  }
137
+ }
138
+ getBrowserDetails(project) {
139
+ var _a, _b;
140
+ if (!project)
141
+ return "unknown";
142
+ const projectName = project.name || "unknown";
143
+ const browserName = ((_a = project.use) === null || _a === void 0 ? void 0 : _a.browserName) || "unknown";
144
+ const channel = (_b = project.use) === null || _b === void 0 ? void 0 : _b.channel;
145
+ if (channel) {
146
+ return `${browserName}-${channel}`;
147
+ }
148
+ return browserName;
149
+ }
150
+ async processStep(step) {
151
+ const stepStatus = convertStatus(step.error ? "failed" : "passed");
152
+ const codeLocation = step.location
153
+ ? `${step.location.file}:${step.location.line}:${step.location.column}`
154
+ : undefined;
182
155
  return {
183
- id: `${testId}_step_${startTime.toISOString()}-${duration}-${(0, crypto_1.randomUUID)()}`,
184
156
  title: step.title,
185
- status: stepStatus,
186
- duration: duration,
187
- startTime: startTime,
188
- endTime: endTime,
189
- browser: browserDetails,
190
- errorMessage: errorMessage,
191
- stackTrace: ((_d = step.error) === null || _d === void 0 ? void 0 : _d.stack) || undefined,
192
- codeLocation: codeLocation || undefined,
193
- isHook: step.category === "hook",
194
- hookType: step.category === "hook"
195
- ? step.title.toLowerCase().includes("before")
196
- ? "before"
197
- : "after"
157
+ category: step.category,
158
+ startTime: step.startTime,
159
+ duration: step.duration,
160
+ error: step.error
161
+ ? {
162
+ message: step.error.message || "",
163
+ stack: step.error.stack,
164
+ snippet: step.error.snippet,
165
+ }
198
166
  : undefined,
199
- steps: [],
167
+ count: step.count || 0,
168
+ location: codeLocation,
169
+ status: stepStatus,
200
170
  };
201
171
  }
202
172
  async onTestEnd(test, result) {
203
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
204
- const project = (_a = test.parent) === null || _a === void 0 ? void 0 : _a.project();
205
- const browserDetails = this.getBrowserDetails(test);
206
- const testStatus = convertStatus(result.status, test);
207
- const startTime = new Date(result.startTime);
208
- const endTime = new Date(startTime.getTime() + result.duration);
173
+ var _a, _b, _c, _d, _e, _f, _g, _h;
209
174
  const processAllSteps = async (steps) => {
210
- let processed = [];
175
+ const processedSteps = [];
211
176
  for (const step of steps) {
212
- const processedStep = await this.processStep(step, test.id, browserDetails, test);
213
- processed.push(processedStep);
177
+ const processedStep = await this.processStep(step);
178
+ processedSteps.push(processedStep);
214
179
  if (step.steps && step.steps.length > 0) {
215
- processedStep.steps = await processAllSteps(step.steps);
180
+ const nestedSteps = await processAllSteps(step.steps);
181
+ processedSteps.push(...nestedSteps);
216
182
  }
217
183
  }
218
- return processed;
184
+ return processedSteps;
219
185
  };
220
- let codeSnippet = undefined;
221
- try {
222
- if (((_b = test.location) === null || _b === void 0 ? void 0 : _b.file) && ((_c = test.location) === null || _c === void 0 ? void 0 : _c.line) && ((_d = test.location) === null || _d === void 0 ? void 0 : _d.column)) {
223
- const relativePath = path.relative(this.config.rootDir, test.location.file);
224
- codeSnippet = `Test defined at: ${relativePath}:${test.location.line}:${test.location.column}`;
225
- }
226
- }
227
- catch (e) {
228
- console.warn(`Pulse Reporter: Could not extract code snippet for ${test.title}`, e);
229
- }
230
- const stdoutMessages = result.stdout.map((item) => typeof item === "string" ? item : item.toString());
231
- const stderrMessages = result.stderr.map((item) => typeof item === "string" ? item : item.toString());
232
- const maxWorkers = this.config.workers;
233
- let mappedWorkerId = result.workerIndex === -1
234
- ? -1
235
- : (result.workerIndex % (maxWorkers > 0 ? maxWorkers : 1)) + 1;
186
+ const project = (_b = (_a = test.parent) === null || _a === void 0 ? void 0 : _a.project) === null || _b === void 0 ? void 0 : _b.call(_a);
187
+ const relativePath = path.relative(process.cwd(), ((_c = test.location) === null || _c === void 0 ? void 0 : _c.file) || "");
188
+ const testStatus = convertStatus(result.status);
189
+ const startTime = result.startTime;
190
+ const endTime = new Date(startTime.getTime() + result.duration);
191
+ const stdoutMessages = result.stdout
192
+ .filter((msg) => msg.trim().length > 0)
193
+ .map((msg) => msg.toString());
194
+ const stderrMessages = result.stderr
195
+ .filter((msg) => msg.trim().length > 0)
196
+ .map((msg) => msg.toString());
197
+ const mappedWorkerId = result.workerIndex !== undefined ? result.workerIndex + 1 : undefined;
236
198
  const testSpecificData = {
237
199
  workerId: mappedWorkerId,
238
- totalWorkers: maxWorkers,
239
- configFile: this.config.configFile,
240
- metadata: this.config.metadata
241
- ? JSON.stringify(this.config.metadata)
242
- : undefined,
200
+ totalWorkers: this.totalWorkers,
201
+ configFile: relativePath,
202
+ metadata: JSON.stringify({ project: project === null || project === void 0 ? void 0 : project.name }),
243
203
  };
244
204
  const pulseResult = {
245
- id: test.id, // Fixed: Use consistent test ID across all retries
246
- runId: this.currentRunId, // Keep same runId for all retries of the same test
247
- name: test.titlePath().join(" > "),
248
- 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",
205
+ id: test.id,
206
+ runId: this.currentRunId,
207
+ name: `${(project === null || project === void 0 ? void 0 : project.name) || "unknown"} > ${((_d = test.parent) === null || _d === void 0 ? void 0 : _d.title) || ""} > ${test.title}`,
208
+ suiteName: project === null || project === void 0 ? void 0 : project.name,
249
209
  status: testStatus,
250
210
  duration: result.duration,
251
211
  startTime: startTime,
252
212
  endTime: endTime,
253
- browser: browserDetails,
254
- retries: result.retry, // This is the retry count (0 for initial run, 1+ for retries)
255
- steps: ((_f = result.steps) === null || _f === void 0 ? void 0 : _f.length) ? await processAllSteps(result.steps) : [],
256
- errorMessage: (_g = result.error) === null || _g === void 0 ? void 0 : _g.message,
257
- stackTrace: (_h = result.error) === null || _h === void 0 ? void 0 : _h.stack,
258
- snippet: (_j = result.error) === null || _j === void 0 ? void 0 : _j.snippet,
259
- codeSnippet: codeSnippet,
260
- tags: test.tags.map((tag) => tag.startsWith("@") ? tag.substring(1) : tag),
213
+ retry: result.retry,
214
+ steps: await processAllSteps(result.steps),
215
+ errorMessage: (_e = result.error) === null || _e === void 0 ? void 0 : _e.message,
216
+ stackTrace: (_f = result.error) === null || _f === void 0 ? void 0 : _f.stack,
217
+ snippet: (_g = result.error) === null || _g === void 0 ? void 0 : _g.snippet,
218
+ codeSnippet: (_h = result.error) === null || _h === void 0 ? void 0 : _h.snippet,
219
+ tags: test.tags,
220
+ browser: this.getBrowserDetails(project),
261
221
  screenshots: [],
262
222
  videoPath: [],
263
- tracePath: undefined,
264
223
  attachments: [],
265
- stdout: stdoutMessages.length > 0 ? stdoutMessages : undefined,
266
- stderr: stderrMessages.length > 0 ? stderrMessages : undefined,
224
+ stdout: stdoutMessages,
225
+ stderr: stderrMessages,
267
226
  ...testSpecificData,
268
227
  };
269
- for (const [index, attachment] of result.attachments.entries()) {
270
- if (!attachment.path)
271
- continue;
272
- try {
273
- const testSubfolder = `${test.id}-${result.retry}`.replace(/[^a-zA-Z0-9_-]/g, "_");
274
- const safeAttachmentName = path
275
- .basename(attachment.path)
276
- .replace(/[^a-zA-Z0-9_.-]/g, "_");
277
- const uniqueFileName = `${index}-${Date.now()}-${safeAttachmentName}`;
278
- const relativeDestPath = path.join(ATTACHMENTS_SUBDIR, testSubfolder, uniqueFileName);
279
- const absoluteDestPath = path.join(this.outputDir, relativeDestPath);
280
- await this._ensureDirExists(path.dirname(absoluteDestPath));
281
- await fs.copyFile(attachment.path, absoluteDestPath);
282
- if (attachment.contentType.startsWith("image/")) {
283
- (_k = pulseResult.screenshots) === null || _k === void 0 ? void 0 : _k.push(relativeDestPath);
284
- }
285
- else if (attachment.contentType.startsWith("video/")) {
286
- (_l = pulseResult.videoPath) === null || _l === void 0 ? void 0 : _l.push(relativeDestPath);
287
- }
288
- else if (attachment.name === "trace") {
289
- pulseResult.tracePath = relativeDestPath;
290
- }
291
- else {
292
- (_m = pulseResult.attachments) === null || _m === void 0 ? void 0 : _m.push({
293
- name: attachment.name,
294
- path: relativeDestPath,
295
- contentType: attachment.contentType,
296
- });
297
- }
298
- }
299
- catch (err) {
300
- console.error(`Pulse Reporter: Failed to process attachment "${attachment.name}" for test ${pulseResult.name}. Error: ${err.message}`);
301
- }
302
- }
303
- this.results.push(pulseResult);
304
- }
305
- _getBaseTestId(testResultId) {
306
- return testResultId;
228
+ attachFiles(test.id, result, pulseResult, this.options);
229
+ this.testResults.push(pulseResult);
307
230
  }
308
- _getStatusOrder(status) {
309
- switch (status) {
310
- case "passed":
311
- return 1;
312
- case "flaky":
313
- return 2;
314
- case "failed":
315
- return 3;
316
- case "skipped":
317
- return 4;
318
- default:
319
- return 99;
320
- }
321
- }
322
- /**
323
- * Fixed: Groups all run attempts for a single logical test case and determines flaky status.
324
- * This ensures that tests with multiple retries are counted as single test case
325
- * while preserving all retry data in the JSON report.
326
- * @param allAttempts An array of all individual test run attempts.
327
- * @returns Summary statistics for the test run.
328
- */
329
231
  _getFinalizedResults(allAttempts) {
330
232
  const groupedResults = new Map();
331
233
  for (const attempt of allAttempts) {
332
- const baseTestId = this._getBaseTestId(attempt.id);
234
+ const baseTestId = attempt.id;
333
235
  if (!groupedResults.has(baseTestId)) {
334
236
  groupedResults.set(baseTestId, []);
335
237
  }
336
238
  groupedResults.get(baseTestId).push(attempt);
337
239
  }
338
- let passed = 0;
339
- let failed = 0;
340
- let skipped = 0;
341
- let flaky = 0;
342
- for (const [baseId, runs] of groupedResults.entries()) {
240
+ const finalResults = [];
241
+ for (const [baseId, attempts] of groupedResults.entries()) {
242
+ attempts.sort((a, b) => a.retry - b.retry);
343
243
  let overallStatus = "passed";
344
- if (runs.length > 1) {
345
- const hasPassedRun = runs.some((run) => run.status === "passed");
346
- const hasFailedRun = runs.some((run) => run.status === "failed");
347
- if (hasPassedRun && hasFailedRun) {
348
- overallStatus = "flaky";
349
- runs.forEach((run) => {
350
- if (run.status === "passed" || run.status === "failed") {
351
- run.status = "flaky";
352
- }
353
- });
354
- }
355
- else if (hasFailedRun) {
356
- overallStatus = "failed";
357
- }
358
- else if (runs.some((run) => run.status === "skipped")) {
359
- overallStatus = "skipped";
360
- }
244
+ const statuses = attempts.map((a) => a.status);
245
+ const hasFailures = statuses.some((s) => s === "failed");
246
+ const hasPasses = statuses.some((s) => s === "passed");
247
+ if (hasFailures && hasPasses) {
248
+ overallStatus = "flaky";
249
+ }
250
+ else if (hasFailures) {
251
+ overallStatus = "failed";
252
+ }
253
+ else if (statuses.some((s) => s === "skipped")) {
254
+ overallStatus = "skipped";
361
255
  }
362
256
  else {
363
- overallStatus = runs[0].status;
257
+ overallStatus = "passed";
364
258
  }
365
- // Count each logical test once
366
- switch (overallStatus) {
259
+ const startTimes = attempts.map((a) => a.startTime.getTime());
260
+ const endTimes = attempts.map((a) => a.endTime.getTime());
261
+ const overallDuration = Math.max(...endTimes) - Math.min(...startTimes);
262
+ const baseAttempt = attempts[0];
263
+ finalResults.push({
264
+ id: baseId,
265
+ name: baseAttempt.name,
266
+ suiteName: baseAttempt.suiteName,
267
+ status: overallStatus,
268
+ duration: overallDuration,
269
+ startTime: new Date(Math.min(...startTimes)),
270
+ endTime: new Date(Math.max(...endTimes)),
271
+ browser: baseAttempt.browser,
272
+ tags: baseAttempt.tags,
273
+ results: attempts,
274
+ });
275
+ }
276
+ return finalResults;
277
+ }
278
+ _getSummaryStats(consolidatedResults) {
279
+ let passed = 0;
280
+ let failed = 0;
281
+ let skipped = 0;
282
+ let flaky = 0;
283
+ for (const result of consolidatedResults) {
284
+ switch (result.status) {
367
285
  case "passed":
368
286
  passed++;
369
287
  break;
@@ -383,274 +301,165 @@ class PlaywrightPulseReporter {
383
301
  failed,
384
302
  skipped,
385
303
  flaky,
386
- totalTests: groupedResults.size,
304
+ totalTests: consolidatedResults.length,
387
305
  };
388
306
  }
389
307
  onError(error) {
390
- var _a;
391
- 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);
392
- if (error === null || error === void 0 ? void 0 : error.stack) {
393
- console.error(error.stack);
394
- }
308
+ console.error("Pulse Reporter: Error occurred:", error);
395
309
  }
396
310
  _getEnvDetails() {
311
+ const parser = new UAParser();
397
312
  return {
398
- host: os.hostname(),
399
- os: `${os.platform()} ${os.release()}`,
400
- cpu: {
401
- model: os.cpus()[0] ? os.cpus()[0].model : "N/A",
402
- cores: os.cpus().length,
403
- },
404
- memory: `${(os.totalmem() / 1024 ** 3).toFixed(2)}GB`,
405
- node: process.version,
406
- v8: process.versions.v8,
407
- cwd: process.cwd(),
313
+ os: `${os.type()} ${os.release()}`,
314
+ browser: parser.getBrowser(),
315
+ cpu: os.arch(),
408
316
  };
409
317
  }
410
- async _writeShardResults() {
411
- if (this.shardIndex === undefined) {
412
- return;
413
- }
414
- const tempFilePath = path.join(this.outputDir, `${TEMP_SHARD_FILE_PREFIX}${this.shardIndex}.json`);
318
+ async _writeShardResults(consolidatedResults) {
319
+ const tempFilePath = path.join(this.outputDir, `shard-${this.shardIndex || 0}-results.json`);
415
320
  try {
416
- await fs.writeFile(tempFilePath, JSON.stringify(this.results, (key, value) => (value instanceof Date ? value.toISOString() : value), 2));
321
+ await this._ensureDirExists(path.dirname(tempFilePath));
322
+ const allAttempts = [];
323
+ for (const result of consolidatedResults) {
324
+ allAttempts.push(...result.results);
325
+ }
326
+ await fs.promises.writeFile(tempFilePath, JSON.stringify(allAttempts, null, 2));
417
327
  }
418
328
  catch (error) {
419
- console.error(`Pulse Reporter: Shard ${this.shardIndex} failed to write temporary results to ${tempFilePath}`, error);
329
+ console.error("Pulse Reporter: Error writing shard results:", error);
420
330
  }
421
331
  }
422
- async _mergeShardResults(finalRunData) {
423
- let allShardRawResults = [];
424
- const totalShards = this.config.shard ? this.config.shard.total : 1;
425
- for (let i = 0; i < totalShards; i++) {
426
- const tempFilePath = path.join(this.outputDir, `${TEMP_SHARD_FILE_PREFIX}${i}.json`);
427
- try {
428
- const content = await fs.readFile(tempFilePath, "utf-8");
429
- const shardResults = JSON.parse(content);
430
- allShardRawResults = allShardRawResults.concat(shardResults);
431
- }
432
- catch (error) {
433
- if ((error === null || error === void 0 ? void 0 : error.code) === "ENOENT") {
434
- console.warn(`Pulse Reporter: Shard results file not found: ${tempFilePath}. This might be normal if a shard had no tests or failed early.`);
435
- }
436
- else {
437
- console.error(`Pulse Reporter: Could not read/parse results from shard ${i} (${tempFilePath}). Error:`, error);
332
+ async _mergeShardResults(allShardResults) {
333
+ try {
334
+ const allAttempts = allShardResults.flat();
335
+ const consolidatedResults = this._getFinalizedResults(allAttempts);
336
+ const reviveDates = (key, value) => {
337
+ if (typeof value === "string" &&
338
+ /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(value)) {
339
+ return new Date(value);
438
340
  }
439
- }
341
+ return value;
342
+ };
343
+ const properlyTypedResults = JSON.parse(JSON.stringify(consolidatedResults), reviveDates);
344
+ return properlyTypedResults;
345
+ }
346
+ catch (error) {
347
+ console.error("Pulse Reporter: Error merging shard results:", error);
348
+ return [];
440
349
  }
441
- const summaryStats = this._getFinalizedResults(allShardRawResults);
442
- finalRunData.passed = summaryStats.passed;
443
- finalRunData.failed = summaryStats.failed;
444
- finalRunData.skipped = summaryStats.skipped;
445
- finalRunData.flaky = summaryStats.flaky;
446
- finalRunData.totalTests = summaryStats.totalTests;
447
- const reviveDates = (key, value) => {
448
- const isoDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/;
449
- if (typeof value === "string" && isoDateRegex.test(value)) {
450
- const date = new Date(value);
451
- return !isNaN(date.getTime()) ? date : value;
452
- }
453
- return value;
454
- };
455
- const properlyTypedResults = JSON.parse(JSON.stringify(allShardRawResults), reviveDates);
456
- return {
457
- run: finalRunData,
458
- results: properlyTypedResults, // Include ALL retry attempts
459
- metadata: { generatedAt: new Date().toISOString() },
460
- };
461
350
  }
462
351
  async _cleanupTemporaryFiles() {
463
352
  try {
464
- const files = await fs.readdir(this.outputDir);
465
- const tempFiles = files.filter((f) => f.startsWith(TEMP_SHARD_FILE_PREFIX));
466
- if (tempFiles.length > 0) {
467
- await Promise.all(tempFiles.map((f) => fs.unlink(path.join(this.outputDir, f))));
353
+ const files = await fs.promises.readdir(this.outputDir);
354
+ const shardFiles = files.filter((file) => file.startsWith("shard-") && file.endsWith("-results.json"));
355
+ for (const file of shardFiles) {
356
+ await fs.promises.unlink(path.join(this.outputDir, file));
468
357
  }
469
358
  }
470
359
  catch (error) {
471
- if ((error === null || error === void 0 ? void 0 : error.code) !== "ENOENT") {
472
- console.warn("Pulse Reporter: Warning during cleanup of temporary files:", error.message);
473
- }
360
+ console.error("Pulse Reporter: Error cleaning up temporary files:", error);
474
361
  }
475
362
  }
476
363
  async _ensureDirExists(dirPath) {
477
364
  try {
478
- await fs.mkdir(dirPath, { recursive: true });
365
+ await fs.promises.mkdir(dirPath, { recursive: true });
479
366
  }
480
367
  catch (error) {
481
- if (error.code !== "EEXIST") {
482
- console.error(`Pulse Reporter: Failed to ensure directory exists: ${dirPath}`, error);
483
- throw error;
484
- }
368
+ console.error(`Pulse Reporter: Error creating directory ${dirPath}:`, error);
485
369
  }
486
370
  }
487
371
  async onEnd(result) {
488
- if (this.shardIndex !== undefined) {
489
- await this._writeShardResults();
490
- return;
491
- }
492
- let finalReport;
493
- const allAttempts = this.results;
494
- const summaryStats = this._getFinalizedResults(this.results);
495
- const runEndTime = Date.now();
496
- const duration = runEndTime - this.runStartTime;
497
- const runId = this.currentRunId;
498
- const environmentDetails = this._getEnvDetails();
499
- const runData = {
500
- id: runId,
501
- timestamp: new Date(this.runStartTime),
502
- totalTests: summaryStats.totalTests, // Fixed: Count each logical test once
503
- passed: summaryStats.passed,
504
- failed: summaryStats.failed,
505
- skipped: summaryStats.skipped,
506
- flaky: summaryStats.flaky,
507
- duration,
508
- environment: environmentDetails,
509
- };
510
- if (this.isSharded) {
511
- finalReport = await this._mergeShardResults(runData);
512
- }
513
- else {
514
- finalReport = {
372
+ try {
373
+ const consolidatedResults = this._getFinalizedResults(this.testResults);
374
+ const stats = this._getSummaryStats(consolidatedResults);
375
+ const runData = {
376
+ id: this.currentRunId,
377
+ startTime: new Date(),
378
+ endTime: new Date(),
379
+ duration: result.duration,
380
+ status: result.status,
381
+ environment: this._getEnvDetails(),
382
+ };
383
+ const finalReport = {
515
384
  run: runData,
516
- results: allAttempts, // Include all retry attempts in the JSON
517
- metadata: { generatedAt: new Date().toISOString() },
385
+ results: consolidatedResults,
386
+ summary: stats,
518
387
  };
519
- }
520
- if (!finalReport) {
521
- console.error("PlaywrightPulseReporter: CRITICAL - finalReport object was not generated. Cannot create summary.");
522
- return;
523
- }
524
- const jsonReplacer = (key, value) => {
525
- if (value instanceof Date)
526
- return value.toISOString();
527
- if (typeof value === "bigint")
528
- return value.toString();
529
- return value;
530
- };
531
- if (this.resetOnEachRun) {
532
- const finalOutputPath = path.join(this.outputDir, this.baseOutputFile);
533
- try {
534
- await this._ensureDirExists(this.outputDir);
535
- await fs.writeFile(finalOutputPath, JSON.stringify(finalReport, jsonReplacer, 2));
536
- if (this.printsToStdio()) {
537
- console.log(`PlaywrightPulseReporter: JSON report written to ${finalOutputPath}`);
388
+ const jsonReplacer = (key, value) => {
389
+ if (value instanceof Date) {
390
+ return value.toISOString();
538
391
  }
392
+ return value;
393
+ };
394
+ if (this.shardIndex !== undefined) {
395
+ await this._writeShardResults(consolidatedResults);
396
+ console.log(`Pulse Reporter: Shard ${this.shardIndex} results written.`);
539
397
  }
540
- catch (error) {
541
- console.error(`Pulse Reporter: Failed to write final JSON report to ${finalOutputPath}. Error: ${error.message}`);
542
- }
543
- }
544
- else {
545
- const pulseResultsDir = path.join(this.outputDir, INDIVIDUAL_REPORTS_SUBDIR);
546
- const individualReportPath = path.join(pulseResultsDir, `playwright-pulse-report-${Date.now()}.json`);
547
- try {
398
+ else {
399
+ const pulseResultsDir = path.join(this.outputDir, "pulse-results");
548
400
  await this._ensureDirExists(pulseResultsDir);
549
- await fs.writeFile(individualReportPath, JSON.stringify(finalReport, jsonReplacer, 2));
550
- if (this.printsToStdio()) {
551
- console.log(`PlaywrightPulseReporter: Individual run report for merging written to ${individualReportPath}`);
401
+ const individualReportPath = path.join(pulseResultsDir, `${this.currentRunId}-pulse-report.json`);
402
+ await fs.promises.writeFile(individualReportPath, JSON.stringify(finalReport, jsonReplacer, 2));
403
+ const mergedReport = await this._mergeAllRunReports();
404
+ if (mergedReport) {
405
+ const finalReportPath = path.join(this.outputDir, "playwright-pulse-report.json");
406
+ await fs.promises.writeFile(finalReportPath, JSON.stringify(mergedReport, jsonReplacer, 2));
407
+ console.log(`Pulse Reporter: Final report written to ${finalReportPath}`);
552
408
  }
553
- await this._mergeAllRunReports();
554
- }
555
- catch (error) {
556
- console.error(`Pulse Reporter: Failed to write or merge report. Error: ${error.message}`);
557
409
  }
558
410
  }
559
- if (this.isSharded) {
560
- await this._cleanupTemporaryFiles();
411
+ catch (error) {
412
+ console.error("Pulse Reporter: Error in onEnd:", error);
561
413
  }
562
414
  }
563
415
  async _mergeAllRunReports() {
564
- const pulseResultsDir = path.join(this.outputDir, INDIVIDUAL_REPORTS_SUBDIR);
565
- const finalOutputPath = path.join(this.outputDir, this.baseOutputFile);
566
- let reportFiles;
416
+ var _a;
567
417
  try {
568
- const allFiles = await fs.readdir(pulseResultsDir);
569
- reportFiles = allFiles.filter((file) => file.startsWith("playwright-pulse-report-") && file.endsWith(".json"));
570
- }
571
- catch (error) {
572
- if (error.code === "ENOENT") {
573
- if (this.printsToStdio()) {
574
- console.log(`Pulse Reporter: No individual reports directory found at ${pulseResultsDir}. Skipping merge.`);
575
- }
576
- return;
577
- }
578
- console.error(`Pulse Reporter: Error reading report directory ${pulseResultsDir}:`, error);
579
- return;
580
- }
581
- if (reportFiles.length === 0) {
582
- if (this.printsToStdio()) {
583
- console.log("Pulse Reporter: No matching JSON report files found to merge.");
418
+ const pulseResultsDir = path.join(this.outputDir, "pulse-results");
419
+ const files = await fs.promises.readdir(pulseResultsDir);
420
+ const jsonFiles = files.filter((file) => file.endsWith("-pulse-report.json"));
421
+ if (jsonFiles.length === 0) {
422
+ return null;
584
423
  }
585
- return;
586
- }
587
- const allResultsFromAllFiles = [];
588
- let latestTimestamp = new Date(0);
589
- let lastRunEnvironment = undefined;
590
- let earliestStartTime = Date.now();
591
- let latestEndTime = 0;
592
- for (const file of reportFiles) {
593
- const filePath = path.join(pulseResultsDir, file);
594
- try {
595
- const content = await fs.readFile(filePath, "utf-8");
596
- const json = JSON.parse(content);
597
- if (json.results) {
598
- json.results.forEach((testResult) => {
599
- // Check if the TestResult has a 'runs' array (new format)
600
- if ("runs" in testResult && Array.isArray(testResult.runs)) {
601
- allResultsFromAllFiles.push(...testResult.runs);
424
+ const allAttempts = [];
425
+ let totalDuration = 0;
426
+ for (const file of jsonFiles) {
427
+ const filePath = path.join(pulseResultsDir, file);
428
+ const content = await fs.promises.readFile(filePath, "utf-8");
429
+ const report = JSON.parse(content);
430
+ if (report.results) {
431
+ for (const consolidatedResult of report.results) {
432
+ if (consolidatedResult.results &&
433
+ consolidatedResult.results.length > 0) {
434
+ allAttempts.push(...consolidatedResult.results);
602
435
  }
603
- else {
604
- // This is the old format (single run). We'll treat it as a single attempt.
605
- allResultsFromAllFiles.push(testResult);
606
- }
607
- });
436
+ }
437
+ }
438
+ if ((_a = report.run) === null || _a === void 0 ? void 0 : _a.duration) {
439
+ totalDuration += report.run.duration;
608
440
  }
609
441
  }
610
- catch (err) {
611
- console.warn(`Pulse Reporter: Could not parse report file ${filePath}. Skipping. Error: ${err.message}`);
612
- }
613
- }
614
- const summaryStats = this._getFinalizedResults(allResultsFromAllFiles);
615
- for (const res of allResultsFromAllFiles) {
616
- if (res.startTime.getTime() < earliestStartTime)
617
- earliestStartTime = res.startTime.getTime();
618
- if (res.endTime.getTime() > latestEndTime)
619
- latestEndTime = res.endTime.getTime();
620
- }
621
- const totalDuration = latestEndTime > earliestStartTime ? latestEndTime - earliestStartTime : 0;
622
- const combinedRun = {
623
- id: `merged-${Date.now()}`,
624
- timestamp: latestTimestamp,
625
- environment: lastRunEnvironment,
626
- totalTests: summaryStats.totalTests, // Fixed: Count each logical test once
627
- passed: summaryStats.passed,
628
- failed: summaryStats.failed,
629
- skipped: summaryStats.skipped,
630
- flaky: summaryStats.flaky,
631
- duration: totalDuration,
632
- };
633
- const finalReport = {
634
- run: combinedRun,
635
- results: allResultsFromAllFiles, // Include ALL retry attempts
636
- metadata: {
637
- generatedAt: new Date().toISOString(),
638
- },
639
- };
640
- try {
641
- await fs.writeFile(finalOutputPath, JSON.stringify(finalReport, (key, value) => {
642
- if (value instanceof Date)
643
- return value.toISOString();
644
- return value;
645
- }, 2));
646
- if (this.printsToStdio()) {
647
- console.log(`PlaywrightPulseReporter: ✅ Merged report with ${allResultsFromAllFiles.length} total retry attempts (${summaryStats.totalTests} unique tests) saved to ${finalOutputPath}`);
648
- }
442
+ const consolidatedResults = this._getFinalizedResults(allAttempts);
443
+ const stats = this._getSummaryStats(consolidatedResults);
444
+ const combinedRun = {
445
+ id: this.currentRunId,
446
+ startTime: new Date(),
447
+ endTime: new Date(),
448
+ duration: totalDuration,
449
+ status: "passed",
450
+ environment: this._getEnvDetails(),
451
+ };
452
+ const finalReport = {
453
+ run: combinedRun,
454
+ results: consolidatedResults,
455
+ summary: stats,
456
+ };
457
+ return finalReport;
649
458
  }
650
- catch (err) {
651
- console.error(`Pulse Reporter: Failed to write final merged report to ${finalOutputPath}. Error: ${err.message}`);
459
+ catch (error) {
460
+ console.error("Pulse Reporter: Error merging all run reports:", error);
461
+ return null;
652
462
  }
653
463
  }
654
464
  }
655
465
  exports.PlaywrightPulseReporter = PlaywrightPulseReporter;
656
- exports.default = PlaywrightPulseReporter;