@arghajit/dummy 0.1.2-beta-9 → 0.1.2-beta-11
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 -29
- package/dist/reporter/playwright-pulse-reporter.js +344 -532
- 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,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
|
-
|
|
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}`;
|
|
162
|
-
}
|
|
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";
|
|
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;
|
|
171
129
|
}
|
|
172
|
-
|
|
173
|
-
|
|
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);
|
|
228
|
+
attachFiles(test.id, result, pulseResult, this.options);
|
|
229
|
+
this.testResults.push(pulseResult);
|
|
304
230
|
}
|
|
305
|
-
_getBaseTestId(testResultId) {
|
|
306
|
-
return testResultId;
|
|
307
|
-
}
|
|
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 =
|
|
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,
|
|
241
|
+
for (const [baseId, attempts] of groupedResults.entries()) {
|
|
242
|
+
attempts.sort((a, b) => a.retry - b.retry);
|
|
339
243
|
let overallStatus = "passed";
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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 =
|
|
257
|
+
overallStatus = "passed";
|
|
360
258
|
}
|
|
361
|
-
|
|
362
|
-
|
|
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:
|
|
371
|
-
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:
|
|
377
|
-
tags:
|
|
378
|
-
|
|
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;
|
|
@@ -412,274 +304,194 @@ class PlaywrightPulseReporter {
|
|
|
412
304
|
totalTests: consolidatedResults.length,
|
|
413
305
|
};
|
|
414
306
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
307
|
+
_getSummaryStatsFromAttempts(attempts) {
|
|
308
|
+
let passed = 0;
|
|
309
|
+
let failed = 0;
|
|
310
|
+
let skipped = 0;
|
|
311
|
+
let flaky = 0;
|
|
312
|
+
const groupedByTest = new Map();
|
|
313
|
+
for (const attempt of attempts) {
|
|
314
|
+
const baseId = attempt.id.replace(/-\d+$/, "");
|
|
315
|
+
if (!groupedByTest.has(baseId)) {
|
|
316
|
+
groupedByTest.set(baseId, []);
|
|
317
|
+
}
|
|
318
|
+
groupedByTest.get(baseId).push(attempt);
|
|
319
|
+
}
|
|
320
|
+
for (const attempt of attempts) {
|
|
321
|
+
const baseId = attempt.id.replace(/-\d+$/, "");
|
|
322
|
+
const testAttempts = groupedByTest.get(baseId);
|
|
323
|
+
const hasFailures = testAttempts.some((a) => a.status === "failed");
|
|
324
|
+
const hasPasses = testAttempts.some((a) => a.status === "passed");
|
|
325
|
+
if (hasFailures && hasPasses) {
|
|
326
|
+
flaky++;
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
switch (attempt.status) {
|
|
330
|
+
case "passed":
|
|
331
|
+
passed++;
|
|
332
|
+
break;
|
|
333
|
+
case "failed":
|
|
334
|
+
failed++;
|
|
335
|
+
break;
|
|
336
|
+
case "skipped":
|
|
337
|
+
skipped++;
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
420
341
|
}
|
|
342
|
+
return {
|
|
343
|
+
passed,
|
|
344
|
+
failed,
|
|
345
|
+
skipped,
|
|
346
|
+
flaky,
|
|
347
|
+
totalTests: attempts.length,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
onError(error) {
|
|
351
|
+
console.error("Pulse Reporter: Error occurred:", error);
|
|
421
352
|
}
|
|
422
353
|
_getEnvDetails() {
|
|
354
|
+
const parser = new UAParser();
|
|
423
355
|
return {
|
|
424
|
-
|
|
425
|
-
|
|
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(),
|
|
356
|
+
os: `${os.type()} ${os.release()}`,
|
|
357
|
+
browser: parser.getBrowser(),
|
|
358
|
+
cpu: os.arch(),
|
|
434
359
|
};
|
|
435
360
|
}
|
|
436
|
-
async _writeShardResults() {
|
|
437
|
-
|
|
438
|
-
return;
|
|
439
|
-
}
|
|
440
|
-
const tempFilePath = path.join(this.outputDir, `${TEMP_SHARD_FILE_PREFIX}${this.shardIndex}.json`);
|
|
361
|
+
async _writeShardResults(individualResults) {
|
|
362
|
+
const tempFilePath = path.join(this.outputDir, `shard-${this.shardIndex || 0}-results.json`);
|
|
441
363
|
try {
|
|
442
|
-
await
|
|
364
|
+
await this._ensureDirExists(path.dirname(tempFilePath));
|
|
365
|
+
await fs.promises.writeFile(tempFilePath, JSON.stringify(individualResults, null, 2));
|
|
443
366
|
}
|
|
444
367
|
catch (error) {
|
|
445
|
-
console.error(
|
|
368
|
+
console.error("Pulse Reporter: Error writing shard results:", error);
|
|
446
369
|
}
|
|
447
370
|
}
|
|
448
|
-
async _mergeShardResults(
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
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);
|
|
371
|
+
async _mergeShardResults(allShardResults) {
|
|
372
|
+
try {
|
|
373
|
+
const allAttempts = allShardResults.flat();
|
|
374
|
+
const reviveDates = (key, value) => {
|
|
375
|
+
if (typeof value === "string" &&
|
|
376
|
+
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(value)) {
|
|
377
|
+
return new Date(value);
|
|
464
378
|
}
|
|
465
|
-
|
|
379
|
+
return value;
|
|
380
|
+
};
|
|
381
|
+
const properlyTypedResults = JSON.parse(JSON.stringify(allAttempts), reviveDates);
|
|
382
|
+
return properlyTypedResults;
|
|
383
|
+
}
|
|
384
|
+
catch (error) {
|
|
385
|
+
console.error("Pulse Reporter: Error merging shard results:", error);
|
|
386
|
+
return [];
|
|
466
387
|
}
|
|
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
388
|
}
|
|
489
389
|
async _cleanupTemporaryFiles() {
|
|
490
390
|
try {
|
|
491
|
-
const files = await fs.readdir(this.outputDir);
|
|
492
|
-
const
|
|
493
|
-
|
|
494
|
-
await
|
|
391
|
+
const files = await fs.promises.readdir(this.outputDir);
|
|
392
|
+
const shardFiles = files.filter((file) => file.startsWith("shard-") && file.endsWith("-results.json"));
|
|
393
|
+
for (const file of shardFiles) {
|
|
394
|
+
await fs.promises.unlink(path.join(this.outputDir, file));
|
|
495
395
|
}
|
|
496
396
|
}
|
|
497
397
|
catch (error) {
|
|
498
|
-
|
|
499
|
-
console.warn("Pulse Reporter: Warning during cleanup of temporary files:", error.message);
|
|
500
|
-
}
|
|
398
|
+
console.error("Pulse Reporter: Error cleaning up temporary files:", error);
|
|
501
399
|
}
|
|
502
400
|
}
|
|
503
401
|
async _ensureDirExists(dirPath) {
|
|
504
402
|
try {
|
|
505
|
-
await fs.mkdir(dirPath, { recursive: true });
|
|
403
|
+
await fs.promises.mkdir(dirPath, { recursive: true });
|
|
506
404
|
}
|
|
507
405
|
catch (error) {
|
|
508
|
-
|
|
509
|
-
console.error(`Pulse Reporter: Failed to ensure directory exists: ${dirPath}`, error);
|
|
510
|
-
throw error;
|
|
511
|
-
}
|
|
406
|
+
console.error(`Pulse Reporter: Error creating directory ${dirPath}:`, error);
|
|
512
407
|
}
|
|
513
408
|
}
|
|
514
409
|
async onEnd(result) {
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
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 = {
|
|
410
|
+
try {
|
|
411
|
+
const individualResults = this.testResults;
|
|
412
|
+
const stats = this._getSummaryStatsFromAttempts(individualResults);
|
|
413
|
+
const runData = {
|
|
414
|
+
id: this.currentRunId,
|
|
415
|
+
startTime: new Date(),
|
|
416
|
+
endTime: new Date(),
|
|
417
|
+
duration: result.duration,
|
|
418
|
+
status: result.status,
|
|
419
|
+
environment: this._getEnvDetails(),
|
|
420
|
+
};
|
|
421
|
+
const finalReport = {
|
|
543
422
|
run: runData,
|
|
544
|
-
results:
|
|
545
|
-
|
|
423
|
+
results: individualResults,
|
|
424
|
+
summary: stats,
|
|
546
425
|
};
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
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}`);
|
|
426
|
+
const jsonReplacer = (key, value) => {
|
|
427
|
+
if (value instanceof Date) {
|
|
428
|
+
return value.toISOString();
|
|
566
429
|
}
|
|
430
|
+
return value;
|
|
431
|
+
};
|
|
432
|
+
if (this.shardIndex !== undefined) {
|
|
433
|
+
await this._writeShardResults(individualResults);
|
|
434
|
+
console.log(`Pulse Reporter: Shard ${this.shardIndex} results written.`);
|
|
567
435
|
}
|
|
568
|
-
|
|
569
|
-
|
|
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 {
|
|
436
|
+
else {
|
|
437
|
+
const pulseResultsDir = path.join(this.outputDir, "pulse-results");
|
|
576
438
|
await this._ensureDirExists(pulseResultsDir);
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
439
|
+
const individualReportPath = path.join(pulseResultsDir, `${this.currentRunId}-pulse-report.json`);
|
|
440
|
+
await fs.promises.writeFile(individualReportPath, JSON.stringify(finalReport, jsonReplacer, 2));
|
|
441
|
+
const mergedReport = await this._mergeAllRunReports();
|
|
442
|
+
if (mergedReport) {
|
|
443
|
+
const finalReportPath = path.join(this.outputDir, "playwright-pulse-report.json");
|
|
444
|
+
await fs.promises.writeFile(finalReportPath, JSON.stringify(mergedReport, jsonReplacer, 2));
|
|
445
|
+
console.log(`Pulse Reporter: Final report written to ${finalReportPath}`);
|
|
580
446
|
}
|
|
581
|
-
await this._mergeAllRunReports();
|
|
582
|
-
}
|
|
583
|
-
catch (error) {
|
|
584
|
-
console.error(`Pulse Reporter: Failed to write or merge report. Error: ${error.message}`);
|
|
585
447
|
}
|
|
586
448
|
}
|
|
587
|
-
|
|
588
|
-
|
|
449
|
+
catch (error) {
|
|
450
|
+
console.error("Pulse Reporter: Error in onEnd:", error);
|
|
589
451
|
}
|
|
590
452
|
}
|
|
591
453
|
async _mergeAllRunReports() {
|
|
592
|
-
|
|
593
|
-
const finalOutputPath = path.join(this.outputDir, this.baseOutputFile);
|
|
594
|
-
let reportFiles;
|
|
454
|
+
var _a;
|
|
595
455
|
try {
|
|
596
|
-
const
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
if (this.printsToStdio()) {
|
|
602
|
-
console.log(`Pulse Reporter: No individual reports directory found at ${pulseResultsDir}. Skipping merge.`);
|
|
603
|
-
}
|
|
604
|
-
return;
|
|
456
|
+
const pulseResultsDir = path.join(this.outputDir, "pulse-results");
|
|
457
|
+
const files = await fs.promises.readdir(pulseResultsDir);
|
|
458
|
+
const jsonFiles = files.filter((file) => file.endsWith("-pulse-report.json"));
|
|
459
|
+
if (jsonFiles.length === 0) {
|
|
460
|
+
return null;
|
|
605
461
|
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
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);
|
|
634
|
-
}
|
|
635
|
-
});
|
|
462
|
+
const allAttempts = [];
|
|
463
|
+
let totalDuration = 0;
|
|
464
|
+
for (const file of jsonFiles) {
|
|
465
|
+
const filePath = path.join(pulseResultsDir, file);
|
|
466
|
+
const content = await fs.promises.readFile(filePath, "utf-8");
|
|
467
|
+
const report = JSON.parse(content);
|
|
468
|
+
if (report.results) {
|
|
469
|
+
allAttempts.push(...report.results);
|
|
470
|
+
}
|
|
471
|
+
if ((_a = report.run) === null || _a === void 0 ? void 0 : _a.duration) {
|
|
472
|
+
totalDuration += report.run.duration;
|
|
636
473
|
}
|
|
637
474
|
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
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
|
-
}
|
|
475
|
+
const stats = this._getSummaryStatsFromAttempts(allAttempts);
|
|
476
|
+
const combinedRun = {
|
|
477
|
+
id: this.currentRunId,
|
|
478
|
+
startTime: new Date(),
|
|
479
|
+
endTime: new Date(),
|
|
480
|
+
duration: totalDuration,
|
|
481
|
+
status: "passed",
|
|
482
|
+
environment: this._getEnvDetails(),
|
|
483
|
+
};
|
|
484
|
+
const finalReport = {
|
|
485
|
+
run: combinedRun,
|
|
486
|
+
results: allAttempts,
|
|
487
|
+
summary: stats,
|
|
488
|
+
};
|
|
489
|
+
return finalReport;
|
|
678
490
|
}
|
|
679
|
-
catch (
|
|
680
|
-
console.error(
|
|
491
|
+
catch (error) {
|
|
492
|
+
console.error("Pulse Reporter: Error merging all run reports:", error);
|
|
493
|
+
return null;
|
|
681
494
|
}
|
|
682
495
|
}
|
|
683
496
|
}
|
|
684
497
|
exports.PlaywrightPulseReporter = PlaywrightPulseReporter;
|
|
685
|
-
exports.default = PlaywrightPulseReporter;
|