@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.
- package/dist/reporter/attachment-utils.d.ts +27 -9
- package/dist/reporter/attachment-utils.js +34 -62
- package/dist/reporter/index.d.ts +0 -2
- package/dist/reporter/index.js +0 -3
- package/dist/reporter/playwright-pulse-reporter.d.ts +111 -27
- package/dist/reporter/playwright-pulse-reporter.js +331 -522
- package/package.json +1 -1
|
@@ -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 =
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
64
|
-
const
|
|
65
|
-
const
|
|
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
|
-
|
|
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
|
|
119
|
+
return false;
|
|
83
120
|
}
|
|
84
121
|
onBegin(config, suite) {
|
|
85
122
|
var _a;
|
|
86
|
-
this.
|
|
87
|
-
this.
|
|
88
|
-
this.
|
|
89
|
-
this.
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
175
|
+
const processedSteps = [];
|
|
211
176
|
for (const step of steps) {
|
|
212
|
-
const processedStep = await this.processStep(step
|
|
213
|
-
|
|
177
|
+
const processedStep = await this.processStep(step);
|
|
178
|
+
processedSteps.push(processedStep);
|
|
214
179
|
if (step.steps && step.steps.length > 0) {
|
|
215
|
-
|
|
180
|
+
const nestedSteps = await processAllSteps(step.steps);
|
|
181
|
+
processedSteps.push(...nestedSteps);
|
|
216
182
|
}
|
|
217
183
|
}
|
|
218
|
-
return
|
|
184
|
+
return processedSteps;
|
|
219
185
|
};
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const
|
|
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:
|
|
239
|
-
configFile:
|
|
240
|
-
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,
|
|
246
|
-
runId: this.currentRunId,
|
|
247
|
-
name: test.
|
|
248
|
-
suiteName:
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
|
266
|
-
stderr: stderrMessages
|
|
224
|
+
stdout: stdoutMessages,
|
|
225
|
+
stderr: stderrMessages,
|
|
267
226
|
...testSpecificData,
|
|
268
227
|
};
|
|
269
|
-
|
|
270
|
-
|
|
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 =
|
|
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
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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 =
|
|
257
|
+
overallStatus = "passed";
|
|
364
258
|
}
|
|
365
|
-
|
|
366
|
-
|
|
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:
|
|
304
|
+
totalTests: consolidatedResults.length,
|
|
387
305
|
};
|
|
388
306
|
}
|
|
389
307
|
onError(error) {
|
|
390
|
-
|
|
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
|
-
|
|
399
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
329
|
+
console.error("Pulse Reporter: Error writing shard results:", error);
|
|
420
330
|
}
|
|
421
331
|
}
|
|
422
|
-
async _mergeShardResults(
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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
|
|
466
|
-
|
|
467
|
-
await
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
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:
|
|
517
|
-
|
|
385
|
+
results: consolidatedResults,
|
|
386
|
+
summary: stats,
|
|
518
387
|
};
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
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
|
-
|
|
541
|
-
|
|
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
|
-
|
|
550
|
-
|
|
551
|
-
|
|
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
|
-
|
|
560
|
-
|
|
411
|
+
catch (error) {
|
|
412
|
+
console.error("Pulse Reporter: Error in onEnd:", error);
|
|
561
413
|
}
|
|
562
414
|
}
|
|
563
415
|
async _mergeAllRunReports() {
|
|
564
|
-
|
|
565
|
-
const finalOutputPath = path.join(this.outputDir, this.baseOutputFile);
|
|
566
|
-
let reportFiles;
|
|
416
|
+
var _a;
|
|
567
417
|
try {
|
|
568
|
-
const
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
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
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
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
|
-
|
|
604
|
-
|
|
605
|
-
|
|
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
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
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 (
|
|
651
|
-
console.error(
|
|
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;
|