@arghajit/dummy 0.1.2-beta-9 → 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,333 +69,212 @@ 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";
130
+ if (this.options.base64Images === undefined) {
131
+ this.options.base64Images = false;
171
132
  }
172
- else {
173
- stepStatus = convertStatus(step.error ? "failed" : "passed", testCase);
174
- }
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
- * Groups all run attempts for a single logical test case and creates consolidated test results.
324
- * This matches Playwright's default structure where retry attempts are grouped under one test entry.
325
- * @param allAttempts An array of all individual test run attempts.
326
- * @returns An array of ConsolidatedTestResult objects, where each object represents one logical test with all its retry attempts.
327
- */
328
231
  _getFinalizedResults(allAttempts) {
329
232
  const groupedResults = new Map();
330
233
  for (const attempt of allAttempts) {
331
- const baseTestId = this._getBaseTestId(attempt.id);
234
+ const baseTestId = attempt.id;
332
235
  if (!groupedResults.has(baseTestId)) {
333
236
  groupedResults.set(baseTestId, []);
334
237
  }
335
238
  groupedResults.get(baseTestId).push(attempt);
336
239
  }
337
240
  const finalResults = [];
338
- for (const [baseId, runs] of groupedResults.entries()) {
241
+ for (const [baseId, attempts] of groupedResults.entries()) {
242
+ attempts.sort((a, b) => a.retry - b.retry);
339
243
  let overallStatus = "passed";
340
- if (runs.length > 1) {
341
- const hasPassedRun = runs.some((run) => run.status === "passed");
342
- const hasFailedRun = runs.some((run) => run.status === "failed");
343
- if (hasPassedRun && hasFailedRun) {
344
- overallStatus = "flaky";
345
- runs.forEach((run) => {
346
- if (run.status === "passed" || run.status === "failed") {
347
- run.status = "flaky";
348
- }
349
- });
350
- }
351
- else if (hasFailedRun) {
352
- overallStatus = "failed";
353
- }
354
- else if (runs.some((run) => run.status === "skipped")) {
355
- overallStatus = "skipped";
356
- }
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";
357
255
  }
358
256
  else {
359
- overallStatus = runs[0].status;
257
+ overallStatus = "passed";
360
258
  }
361
- // Sort runs to find the best representative run for metadata
362
- runs.sort((a, b) => this._getStatusOrder(a.status) - this._getStatusOrder(b.status));
363
- const bestRun = runs[0];
364
- // Calculate total duration from the earliest start to the latest end time of all runs
365
- const startTimes = runs.map((run) => run.startTime.getTime());
366
- const endTimes = runs.map((run) => run.endTime.getTime());
259
+ const startTimes = attempts.map((a) => a.startTime.getTime());
260
+ const endTimes = attempts.map((a) => a.endTime.getTime());
367
261
  const overallDuration = Math.max(...endTimes) - Math.min(...startTimes);
262
+ const baseAttempt = attempts[0];
368
263
  finalResults.push({
369
264
  id: baseId,
370
- name: bestRun.name,
371
- suiteName: bestRun.suiteName,
265
+ name: baseAttempt.name,
266
+ suiteName: baseAttempt.suiteName,
372
267
  status: overallStatus,
373
268
  duration: overallDuration,
374
269
  startTime: new Date(Math.min(...startTimes)),
375
270
  endTime: new Date(Math.max(...endTimes)),
376
- browser: bestRun.browser,
377
- tags: bestRun.tags,
378
- runs: runs.sort((a, b) => a.retries - b.retries), // Sort runs chronologically for the report
271
+ browser: baseAttempt.browser,
272
+ tags: baseAttempt.tags,
273
+ results: attempts,
379
274
  });
380
275
  }
381
276
  return finalResults;
382
277
  }
383
- /**
384
- * Helper method to get summary statistics from consolidated results
385
- */
386
278
  _getSummaryStats(consolidatedResults) {
387
279
  let passed = 0;
388
280
  let failed = 0;
@@ -413,273 +305,161 @@ class PlaywrightPulseReporter {
413
305
  };
414
306
  }
415
307
  onError(error) {
416
- var _a;
417
- 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);
418
- if (error === null || error === void 0 ? void 0 : error.stack) {
419
- console.error(error.stack);
420
- }
308
+ console.error("Pulse Reporter: Error occurred:", error);
421
309
  }
422
310
  _getEnvDetails() {
311
+ const parser = new UAParser();
423
312
  return {
424
- host: os.hostname(),
425
- os: `${os.platform()} ${os.release()}`,
426
- cpu: {
427
- model: os.cpus()[0] ? os.cpus()[0].model : "N/A",
428
- cores: os.cpus().length,
429
- },
430
- memory: `${(os.totalmem() / 1024 ** 3).toFixed(2)}GB`,
431
- node: process.version,
432
- v8: process.versions.v8,
433
- cwd: process.cwd(),
313
+ os: `${os.type()} ${os.release()}`,
314
+ browser: parser.getBrowser(),
315
+ cpu: os.arch(),
434
316
  };
435
317
  }
436
- async _writeShardResults() {
437
- if (this.shardIndex === undefined) {
438
- return;
439
- }
440
- 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`);
441
320
  try {
442
- 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));
443
327
  }
444
328
  catch (error) {
445
- 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);
446
330
  }
447
331
  }
448
- async _mergeShardResults(finalRunData) {
449
- let allShardRawResults = [];
450
- const totalShards = this.config.shard ? this.config.shard.total : 1;
451
- for (let i = 0; i < totalShards; i++) {
452
- const tempFilePath = path.join(this.outputDir, `${TEMP_SHARD_FILE_PREFIX}${i}.json`);
453
- try {
454
- const content = await fs.readFile(tempFilePath, "utf-8");
455
- const shardResults = JSON.parse(content);
456
- allShardRawResults = allShardRawResults.concat(shardResults);
457
- }
458
- catch (error) {
459
- if ((error === null || error === void 0 ? void 0 : error.code) === "ENOENT") {
460
- console.warn(`Pulse Reporter: Shard results file not found: ${tempFilePath}. This might be normal if a shard had no tests or failed early.`);
461
- }
462
- else {
463
- 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);
464
340
  }
465
- }
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 [];
466
349
  }
467
- const consolidatedResults = this._getFinalizedResults(allShardRawResults);
468
- const summaryStats = this._getSummaryStats(consolidatedResults);
469
- finalRunData.passed = summaryStats.passed;
470
- finalRunData.failed = summaryStats.failed;
471
- finalRunData.skipped = summaryStats.skipped;
472
- finalRunData.flaky = summaryStats.flaky;
473
- finalRunData.totalTests = summaryStats.totalTests;
474
- const reviveDates = (key, value) => {
475
- const isoDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/;
476
- if (typeof value === "string" && isoDateRegex.test(value)) {
477
- const date = new Date(value);
478
- return !isNaN(date.getTime()) ? date : value;
479
- }
480
- return value;
481
- };
482
- const properlyTypedResults = JSON.parse(JSON.stringify(consolidatedResults), reviveDates);
483
- return {
484
- run: finalRunData,
485
- results: properlyTypedResults, // Use consolidated results that group retry attempts
486
- metadata: { generatedAt: new Date().toISOString() },
487
- };
488
350
  }
489
351
  async _cleanupTemporaryFiles() {
490
352
  try {
491
- const files = await fs.readdir(this.outputDir);
492
- const tempFiles = files.filter((f) => f.startsWith(TEMP_SHARD_FILE_PREFIX));
493
- if (tempFiles.length > 0) {
494
- 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));
495
357
  }
496
358
  }
497
359
  catch (error) {
498
- if ((error === null || error === void 0 ? void 0 : error.code) !== "ENOENT") {
499
- console.warn("Pulse Reporter: Warning during cleanup of temporary files:", error.message);
500
- }
360
+ console.error("Pulse Reporter: Error cleaning up temporary files:", error);
501
361
  }
502
362
  }
503
363
  async _ensureDirExists(dirPath) {
504
364
  try {
505
- await fs.mkdir(dirPath, { recursive: true });
365
+ await fs.promises.mkdir(dirPath, { recursive: true });
506
366
  }
507
367
  catch (error) {
508
- if (error.code !== "EEXIST") {
509
- console.error(`Pulse Reporter: Failed to ensure directory exists: ${dirPath}`, error);
510
- throw error;
511
- }
368
+ console.error(`Pulse Reporter: Error creating directory ${dirPath}:`, error);
512
369
  }
513
370
  }
514
371
  async onEnd(result) {
515
- if (this.shardIndex !== undefined) {
516
- await this._writeShardResults();
517
- return;
518
- }
519
- let finalReport;
520
- const allAttempts = this.results;
521
- const consolidatedResults = this._getFinalizedResults(this.results);
522
- const summaryStats = this._getSummaryStats(consolidatedResults);
523
- const runEndTime = Date.now();
524
- const duration = runEndTime - this.runStartTime;
525
- const runId = this.currentRunId;
526
- const environmentDetails = this._getEnvDetails();
527
- const runData = {
528
- id: runId,
529
- timestamp: new Date(this.runStartTime),
530
- totalTests: summaryStats.totalTests,
531
- passed: summaryStats.passed,
532
- failed: summaryStats.failed,
533
- skipped: summaryStats.skipped,
534
- flaky: summaryStats.flaky,
535
- duration,
536
- environment: environmentDetails,
537
- };
538
- if (this.isSharded) {
539
- finalReport = await this._mergeShardResults(runData);
540
- }
541
- else {
542
- 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 = {
543
384
  run: runData,
544
- results: consolidatedResults, // Use consolidated results that group retry attempts
545
- metadata: { generatedAt: new Date().toISOString() },
385
+ results: consolidatedResults,
386
+ summary: stats,
546
387
  };
547
- }
548
- if (!finalReport) {
549
- console.error("PlaywrightPulseReporter: CRITICAL - finalReport object was not generated. Cannot create summary.");
550
- return;
551
- }
552
- const jsonReplacer = (key, value) => {
553
- if (value instanceof Date)
554
- return value.toISOString();
555
- if (typeof value === "bigint")
556
- return value.toString();
557
- return value;
558
- };
559
- if (this.resetOnEachRun) {
560
- const finalOutputPath = path.join(this.outputDir, this.baseOutputFile);
561
- try {
562
- await this._ensureDirExists(this.outputDir);
563
- await fs.writeFile(finalOutputPath, JSON.stringify(finalReport, jsonReplacer, 2));
564
- if (this.printsToStdio()) {
565
- console.log(`PlaywrightPulseReporter: JSON report written to ${finalOutputPath}`);
388
+ const jsonReplacer = (key, value) => {
389
+ if (value instanceof Date) {
390
+ return value.toISOString();
566
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.`);
567
397
  }
568
- catch (error) {
569
- console.error(`Pulse Reporter: Failed to write final JSON report to ${finalOutputPath}. Error: ${error.message}`);
570
- }
571
- }
572
- else {
573
- const pulseResultsDir = path.join(this.outputDir, INDIVIDUAL_REPORTS_SUBDIR);
574
- const individualReportPath = path.join(pulseResultsDir, `playwright-pulse-report-${Date.now()}.json`);
575
- try {
398
+ else {
399
+ const pulseResultsDir = path.join(this.outputDir, "pulse-results");
576
400
  await this._ensureDirExists(pulseResultsDir);
577
- await fs.writeFile(individualReportPath, JSON.stringify(finalReport, jsonReplacer, 2));
578
- if (this.printsToStdio()) {
579
- 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}`);
580
408
  }
581
- await this._mergeAllRunReports();
582
- }
583
- catch (error) {
584
- console.error(`Pulse Reporter: Failed to write or merge report. Error: ${error.message}`);
585
409
  }
586
410
  }
587
- if (this.isSharded) {
588
- await this._cleanupTemporaryFiles();
411
+ catch (error) {
412
+ console.error("Pulse Reporter: Error in onEnd:", error);
589
413
  }
590
414
  }
591
415
  async _mergeAllRunReports() {
592
- const pulseResultsDir = path.join(this.outputDir, INDIVIDUAL_REPORTS_SUBDIR);
593
- const finalOutputPath = path.join(this.outputDir, this.baseOutputFile);
594
- let reportFiles;
416
+ var _a;
595
417
  try {
596
- const allFiles = await fs.readdir(pulseResultsDir);
597
- reportFiles = allFiles.filter((file) => file.startsWith("playwright-pulse-report-") && file.endsWith(".json"));
598
- }
599
- catch (error) {
600
- if (error.code === "ENOENT") {
601
- if (this.printsToStdio()) {
602
- console.log(`Pulse Reporter: No individual reports directory found at ${pulseResultsDir}. Skipping merge.`);
603
- }
604
- return;
605
- }
606
- console.error(`Pulse Reporter: Error reading report directory ${pulseResultsDir}:`, error);
607
- return;
608
- }
609
- if (reportFiles.length === 0) {
610
- if (this.printsToStdio()) {
611
- 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;
612
423
  }
613
- return;
614
- }
615
- const allResultsFromAllFiles = [];
616
- let latestTimestamp = new Date(0);
617
- let lastRunEnvironment = undefined;
618
- let earliestStartTime = Date.now();
619
- let latestEndTime = 0;
620
- for (const file of reportFiles) {
621
- const filePath = path.join(pulseResultsDir, file);
622
- try {
623
- const content = await fs.readFile(filePath, "utf-8");
624
- const json = JSON.parse(content);
625
- if (json.results) {
626
- json.results.forEach((testResult) => {
627
- // Check if the TestResult has a 'runs' array (consolidated format)
628
- if ("runs" in testResult && Array.isArray(testResult.runs)) {
629
- allResultsFromAllFiles.push(...testResult.runs);
630
- }
631
- else {
632
- // This is the old format (single run). We'll treat it as a single attempt.
633
- allResultsFromAllFiles.push(testResult);
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);
634
435
  }
635
- });
436
+ }
437
+ }
438
+ if ((_a = report.run) === null || _a === void 0 ? void 0 : _a.duration) {
439
+ totalDuration += report.run.duration;
636
440
  }
637
441
  }
638
- catch (err) {
639
- console.warn(`Pulse Reporter: Could not parse report file ${filePath}. Skipping. Error: ${err.message}`);
640
- }
641
- }
642
- const consolidatedResults = this._getFinalizedResults(allResultsFromAllFiles);
643
- const summaryStats = this._getSummaryStats(consolidatedResults);
644
- for (const res of allResultsFromAllFiles) {
645
- if (res.startTime.getTime() < earliestStartTime)
646
- earliestStartTime = res.startTime.getTime();
647
- if (res.endTime.getTime() > latestEndTime)
648
- latestEndTime = res.endTime.getTime();
649
- }
650
- const totalDuration = latestEndTime > earliestStartTime ? latestEndTime - earliestStartTime : 0;
651
- const combinedRun = {
652
- id: `merged-${Date.now()}`,
653
- timestamp: latestTimestamp,
654
- environment: lastRunEnvironment,
655
- totalTests: summaryStats.totalTests,
656
- passed: summaryStats.passed,
657
- failed: summaryStats.failed,
658
- skipped: summaryStats.skipped,
659
- flaky: summaryStats.flaky,
660
- duration: totalDuration,
661
- };
662
- const finalReport = {
663
- run: combinedRun,
664
- results: consolidatedResults, // Use consolidated results that group retry attempts
665
- metadata: {
666
- generatedAt: new Date().toISOString(),
667
- },
668
- };
669
- try {
670
- await fs.writeFile(finalOutputPath, JSON.stringify(finalReport, (key, value) => {
671
- if (value instanceof Date)
672
- return value.toISOString();
673
- return value;
674
- }, 2));
675
- if (this.printsToStdio()) {
676
- console.log(`PlaywrightPulseReporter: ✅ Merged report with ${allResultsFromAllFiles.length} total retry attempts (${summaryStats.totalTests} unique tests) saved to ${finalOutputPath}`);
677
- }
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;
678
458
  }
679
- catch (err) {
680
- 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;
681
462
  }
682
463
  }
683
464
  }
684
465
  exports.PlaywrightPulseReporter = PlaywrightPulseReporter;
685
- exports.default = PlaywrightPulseReporter;