@arghajit/playwright-pulse-report 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,273 +0,0 @@
1
- // generate-trend-excel.mjs
2
- import * as fs from "fs/promises";
3
- import path from "path";
4
- import * as XLSX from "xlsx";
5
-
6
- // Use dynamic import for chalk as it's ESM only for prettier console logs
7
- let chalk;
8
- try {
9
- chalk = (await import("chalk")).default;
10
- } catch (e) {
11
- chalk = { green: (t) => t, red: (t) => t, yellow: (t) => t, blue: (t) => t }; // Basic fallback
12
- }
13
-
14
- const DEFAULT_OUTPUT_DIR = "pulse-report"; // Should match reporter's outputDir
15
- const DEFAULT_JSON_FILE = "playwright-pulse-report.json";
16
- const TREND_EXCEL_FILE_NAME = "trend.xls";
17
-
18
- class ExcelTrendManager {
19
- _excelFilePath;
20
- _maxRuns = 5;
21
-
22
- constructor(outputDir, excelFileName = TREND_EXCEL_FILE_NAME) {
23
- this._excelFilePath = path.join(outputDir, excelFileName);
24
- }
25
-
26
- getExcelFilePath() {
27
- return this._excelFilePath;
28
- }
29
-
30
- async _readExistingData() {
31
- try {
32
- await fs.access(this._excelFilePath);
33
- const buffer = await fs.readFile(this._excelFilePath);
34
- return XLSX.read(buffer, { type: "buffer" });
35
- } catch {
36
- return null;
37
- }
38
- }
39
-
40
- _shiftOverallRuns(data, currentNumericRunId) {
41
- const validData = Array.isArray(data) ? data : [];
42
- const pastOrCurrentData = validData.filter(
43
- (row) =>
44
- row.hasOwnProperty("RUN_ID") &&
45
- typeof row.RUN_ID === "number" &&
46
- row.RUN_ID <= currentNumericRunId
47
- );
48
- const sortedData = [...pastOrCurrentData].sort(
49
- (a, b) => a.RUN_ID - b.RUN_ID
50
- );
51
- if (sortedData.length > this._maxRuns) {
52
- return sortedData.slice(sortedData.length - this._maxRuns);
53
- }
54
- return sortedData;
55
- }
56
-
57
- async updateTrendData(
58
- runIdFromReport,
59
- timestamp,
60
- totalTests,
61
- passed,
62
- failed,
63
- skipped,
64
- duration,
65
- testResultsForThisRun
66
- ) {
67
- let workbook = await this._readExistingData();
68
- const numericRunId = Math.floor(timestamp / 1000);
69
-
70
- if (!workbook) {
71
- workbook = XLSX.utils.book_new();
72
- // If the workbook is new, SheetNames will be empty.
73
- // We need to initialize it if it doesn't exist
74
- if (!workbook.SheetNames) {
75
- workbook.SheetNames = [];
76
- }
77
- } else {
78
- // Ensure SheetNames exists even for existing workbooks (should, but defensive)
79
- if (!workbook.SheetNames) {
80
- workbook.SheetNames = [];
81
- }
82
- }
83
-
84
- // --- Overall Data ---
85
- let existingOverallData = [];
86
- if (workbook.Sheets["overall"]) {
87
- try {
88
- existingOverallData = XLSX.utils.sheet_to_json(
89
- workbook.Sheets["overall"]
90
- );
91
- } catch (e) {
92
- console.warn(
93
- chalk.yellow(
94
- "Could not parse existing 'overall' sheet. Starting fresh. Error:",
95
- e.message
96
- )
97
- );
98
- existingOverallData = [];
99
- }
100
- }
101
- existingOverallData = existingOverallData.filter(
102
- (row) => row.RUN_ID !== numericRunId
103
- );
104
- const newOverallRow = {
105
- RUN_ID: numericRunId,
106
- DURATION: duration,
107
- TIMESTAMP: timestamp,
108
- TOTAL_TESTS: totalTests,
109
- PASSED: passed,
110
- FAILED: failed,
111
- SKIPPED: skipped,
112
- };
113
- let updatedOverallData = [...existingOverallData, newOverallRow];
114
- updatedOverallData = this._shiftOverallRuns(
115
- updatedOverallData,
116
- numericRunId
117
- );
118
-
119
- const overallSheet = XLSX.utils.json_to_sheet(updatedOverallData);
120
-
121
- // UPDATED: Use book_append_sheet for new sheets, or replace existing
122
- if (!workbook.SheetNames.includes("overall")) {
123
- XLSX.utils.book_append_sheet(workbook, overallSheet, "overall");
124
- // Move "overall" to the beginning if it was just added and not already first
125
- const overallIndex = workbook.SheetNames.indexOf("overall");
126
- if (overallIndex > 0) {
127
- const sheetName = workbook.SheetNames.splice(overallIndex, 1)[0];
128
- workbook.SheetNames.unshift(sheetName);
129
- }
130
- } else {
131
- workbook.Sheets["overall"] = overallSheet; // Replace existing
132
- }
133
- XLSX.utils.book_set_sheet_visibility(workbook, "overall", 0);
134
-
135
- // --- Per-Test Data Sheet for the Current Run ---
136
- const runKey = `test run ${numericRunId}`;
137
- const currentRunTestData = testResultsForThisRun.map((test) => ({
138
- TEST_NAME: test.name,
139
- DURATION: test.duration,
140
- STATUS: test.status,
141
- TIMESTAMP: timestamp,
142
- }));
143
- const testRunSheet = XLSX.utils.json_to_sheet(currentRunTestData);
144
-
145
- // UPDATED: Logic to add or replace the sheet and ensure it's in SheetNames
146
- if (!workbook.SheetNames.includes(runKey)) {
147
- XLSX.utils.book_append_sheet(workbook, testRunSheet, runKey); // This adds to Sheets and SheetNames
148
- } else {
149
- workbook.Sheets[runKey] = testRunSheet; // Just replace the sheet data
150
- }
151
- // Now that the sheet is guaranteed to be in SheetNames and workbook.Sheets, set visibility
152
- XLSX.utils.book_set_sheet_visibility(workbook, runKey, 0);
153
-
154
- // --- Maintain Max Sheet Count for Individual Test Runs ---
155
- let testRunSheetNames = workbook.SheetNames.filter(
156
- (name) => name.toLowerCase().startsWith("test run ") && name !== "overall"
157
- );
158
- testRunSheetNames.sort((a, b) => {
159
- const matchA = a.match(/test run (\d+)$/i);
160
- const matchB = b.match(/test run (\d+)$/i);
161
- const idA = matchA && matchA[1] ? parseInt(matchA[1], 10) : 0;
162
- const idB = matchB && matchB[1] ? parseInt(matchB[1], 10) : 0;
163
- return idA - idB;
164
- });
165
-
166
- if (testRunSheetNames.length > this._maxRuns) {
167
- const sheetsToRemoveCount = testRunSheetNames.length - this._maxRuns;
168
- const removedSheetNames = [];
169
- for (let i = 0; i < sheetsToRemoveCount; i++) {
170
- const oldestSheetName = testRunSheetNames[i];
171
- // Remove from workbook.Sheets
172
- delete workbook.Sheets[oldestSheetName];
173
- removedSheetNames.push(oldestSheetName);
174
- }
175
- // Rebuild SheetNames array without the removed sheets
176
- workbook.SheetNames = workbook.SheetNames.filter(
177
- (name) => !removedSheetNames.includes(name)
178
- );
179
- }
180
-
181
- // --- Write Workbook ---
182
- try {
183
- const buffer = XLSX.write(workbook, { bookType: "xls", type: "buffer" });
184
- await fs.writeFile(this._excelFilePath, buffer);
185
- console.log(
186
- chalk.green(
187
- `Excel trend report updated successfully at ${this._excelFilePath}`
188
- )
189
- );
190
- } catch (writeError) {
191
- console.error(
192
- chalk.red(`Failed to write Excel file at ${this._excelFilePath}`)
193
- );
194
- console.error(chalk.red("Write Error Details:"), writeError);
195
- throw writeError;
196
- }
197
- }
198
- }
199
-
200
- async function generateTrendExcel() {
201
- const outputDir = path.resolve(process.cwd(), DEFAULT_OUTPUT_DIR);
202
- const jsonReportPath = path.join(outputDir, DEFAULT_JSON_FILE);
203
-
204
- // Ensure output directory exists before any file operations
205
- try {
206
- await fs.mkdir(outputDir, { recursive: true });
207
- } catch (mkdirError) {
208
- console.error(
209
- chalk.red(`Failed to create output directory ${outputDir}:`),
210
- mkdirError
211
- );
212
- process.exit(1);
213
- }
214
-
215
- console.log(chalk.blue(`Reading JSON report from: ${jsonReportPath}`));
216
- let reportData;
217
- try {
218
- const jsonData = await fs.readFile(jsonReportPath, "utf-8");
219
- reportData = JSON.parse(jsonData);
220
- if (!reportData || !reportData.run || !Array.isArray(reportData.results)) {
221
- throw new Error(
222
- "Invalid JSON report structure. Missing 'run' or 'results' data."
223
- );
224
- }
225
- } catch (error) {
226
- console.error(
227
- chalk.red(`Error reading or parsing JSON report: ${error.message}`)
228
- );
229
- console.error(chalk.red("JSON Read/Parse Error Details:"), error);
230
- process.exit(1);
231
- }
232
-
233
- const { run, results } = reportData;
234
- if (!run.timestamp || isNaN(new Date(run.timestamp).getTime())) {
235
- console.error(
236
- chalk.red(`Invalid or missing run.timestamp in JSON: ${run.timestamp}`)
237
- );
238
- process.exit(1);
239
- }
240
- const runTimestamp = new Date(run.timestamp).getTime();
241
-
242
- const testResultsForExcel = results.map((r) => ({
243
- name: r.name,
244
- duration: r.duration,
245
- status: r.status,
246
- }));
247
-
248
- const excelManager = new ExcelTrendManager(outputDir);
249
- try {
250
- await excelManager.updateTrendData(
251
- run.id,
252
- runTimestamp,
253
- run.totalTests,
254
- run.passed,
255
- run.failed,
256
- run.skipped,
257
- run.duration,
258
- testResultsForExcel
259
- );
260
- } catch (excelError) {
261
- console.error(chalk.red("Aborting due to error during Excel generation."));
262
- console.error(chalk.red("Excel Generation Error Details:"), excelError);
263
- process.exit(1);
264
- }
265
- }
266
-
267
- generateTrendExcel().catch((error) => {
268
- console.error(
269
- chalk.red("An unexpected error occurred in generate-trend-excel:"),
270
- error
271
- );
272
- process.exit(1);
273
- });