@arghajit/dummy 0.1.1 → 0.1.2-beta-1
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.
|
@@ -12,6 +12,7 @@ export declare class PlaywrightPulseReporter implements Reporter {
|
|
|
12
12
|
private isSharded;
|
|
13
13
|
private shardIndex;
|
|
14
14
|
private resetOnEachRun;
|
|
15
|
+
private currentRunId;
|
|
15
16
|
constructor(options?: PlaywrightPulseReporterOptions);
|
|
16
17
|
printsToStdio(): boolean;
|
|
17
18
|
onBegin(config: FullConfig, suite: Suite): void;
|
|
@@ -19,7 +20,9 @@ export declare class PlaywrightPulseReporter implements Reporter {
|
|
|
19
20
|
private getBrowserDetails;
|
|
20
21
|
private processStep;
|
|
21
22
|
onTestEnd(test: TestCase, result: PwTestResult): Promise<void>;
|
|
23
|
+
private _getBaseTestId;
|
|
22
24
|
private _getFinalizedResults;
|
|
25
|
+
private _getStatusOrder;
|
|
23
26
|
onError(error: any): void;
|
|
24
27
|
private _getEnvDetails;
|
|
25
28
|
private _writeShardResults;
|
|
@@ -39,13 +39,23 @@ const path = __importStar(require("path"));
|
|
|
39
39
|
const crypto_1 = require("crypto");
|
|
40
40
|
const ua_parser_js_1 = require("ua-parser-js");
|
|
41
41
|
const os = __importStar(require("os"));
|
|
42
|
-
const convertStatus = (status, testCase) => {
|
|
42
|
+
const convertStatus = (status, testCase, retryCount = 0) => {
|
|
43
|
+
// If a test passes on a retry, it's considered flaky regardless of expected status.
|
|
44
|
+
// This is the most critical check for flaky tests.
|
|
45
|
+
if (status === "passed" && retryCount > 0) {
|
|
46
|
+
return "flaky";
|
|
47
|
+
}
|
|
48
|
+
// Handle expected statuses for the final result.
|
|
43
49
|
if ((testCase === null || testCase === void 0 ? void 0 : testCase.expectedStatus) === "failed") {
|
|
50
|
+
// If expected to fail but passed, it's flaky.
|
|
51
|
+
if (status === "passed")
|
|
52
|
+
return "flaky";
|
|
44
53
|
return "failed";
|
|
45
54
|
}
|
|
46
55
|
if ((testCase === null || testCase === void 0 ? void 0 : testCase.expectedStatus) === "skipped") {
|
|
47
56
|
return "skipped";
|
|
48
57
|
}
|
|
58
|
+
// Default Playwright status mapping
|
|
49
59
|
switch (status) {
|
|
50
60
|
case "passed":
|
|
51
61
|
return "passed";
|
|
@@ -64,10 +74,12 @@ const INDIVIDUAL_REPORTS_SUBDIR = "pulse-results";
|
|
|
64
74
|
class PlaywrightPulseReporter {
|
|
65
75
|
constructor(options = {}) {
|
|
66
76
|
var _a, _b, _c;
|
|
77
|
+
// This will now store all individual run attempts for all tests.
|
|
67
78
|
this.results = [];
|
|
68
79
|
this.baseOutputFile = "playwright-pulse-report.json";
|
|
69
80
|
this.isSharded = false;
|
|
70
81
|
this.shardIndex = undefined;
|
|
82
|
+
this.currentRunId = ""; // Added to store the overall run ID
|
|
71
83
|
this.options = options;
|
|
72
84
|
this.baseOutputFile = (_a = options.outputFile) !== null && _a !== void 0 ? _a : this.baseOutputFile;
|
|
73
85
|
this.outputDir = (_b = options.outputDir) !== null && _b !== void 0 ? _b : "pulse-report";
|
|
@@ -82,6 +94,8 @@ class PlaywrightPulseReporter {
|
|
|
82
94
|
this.config = config;
|
|
83
95
|
this.suite = suite;
|
|
84
96
|
this.runStartTime = Date.now();
|
|
97
|
+
// Generate the overall runId once at the beginning
|
|
98
|
+
this.currentRunId = `run-${this.runStartTime}-${(0, crypto_1.randomUUID)()}`;
|
|
85
99
|
const configDir = this.config.rootDir;
|
|
86
100
|
const configFileDir = this.config.configFile
|
|
87
101
|
? path.dirname(this.config.configFile)
|
|
@@ -107,7 +121,7 @@ class PlaywrightPulseReporter {
|
|
|
107
121
|
.catch((err) => console.error("Pulse Reporter: Error during initialization:", err));
|
|
108
122
|
}
|
|
109
123
|
onTestBegin(test) {
|
|
110
|
-
console.log(`Starting test: ${test.title}`);
|
|
124
|
+
// console.log(`Starting test: ${test.title}`); // Removed for brevity in final output
|
|
111
125
|
}
|
|
112
126
|
getBrowserDetails(test) {
|
|
113
127
|
var _a, _b, _c, _d;
|
|
@@ -159,7 +173,8 @@ class PlaywrightPulseReporter {
|
|
|
159
173
|
}
|
|
160
174
|
return finalString.trim();
|
|
161
175
|
}
|
|
162
|
-
async processStep(step, testId, browserDetails, testCase
|
|
176
|
+
async processStep(step, testId, browserDetails, testCase, retryCount = 0 // Pass retryCount to convertStatus for steps
|
|
177
|
+
) {
|
|
163
178
|
var _a, _b, _c, _d;
|
|
164
179
|
let stepStatus = "passed";
|
|
165
180
|
let errorMessage = ((_a = step.error) === null || _a === void 0 ? void 0 : _a.message) || undefined;
|
|
@@ -167,7 +182,8 @@ class PlaywrightPulseReporter {
|
|
|
167
182
|
stepStatus = "skipped";
|
|
168
183
|
}
|
|
169
184
|
else {
|
|
170
|
-
|
|
185
|
+
// Use the extended convertStatus
|
|
186
|
+
stepStatus = convertStatus(step.error ? "failed" : "passed", testCase, retryCount);
|
|
171
187
|
}
|
|
172
188
|
const duration = step.duration;
|
|
173
189
|
const startTime = new Date(step.startTime);
|
|
@@ -200,13 +216,15 @@ class PlaywrightPulseReporter {
|
|
|
200
216
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
201
217
|
const project = (_a = test.parent) === null || _a === void 0 ? void 0 : _a.project();
|
|
202
218
|
const browserDetails = this.getBrowserDetails(test);
|
|
203
|
-
|
|
219
|
+
// Use the extended convertStatus, passing result.retry
|
|
220
|
+
const testStatus = convertStatus(result.status, test, result.retry);
|
|
204
221
|
const startTime = new Date(result.startTime);
|
|
205
222
|
const endTime = new Date(startTime.getTime() + result.duration);
|
|
206
223
|
const processAllSteps = async (steps) => {
|
|
207
224
|
let processed = [];
|
|
208
225
|
for (const step of steps) {
|
|
209
|
-
const processedStep = await this.processStep(step, test.id, browserDetails, test
|
|
226
|
+
const processedStep = await this.processStep(step, test.id, browserDetails, test, result.retry // Pass retryCount to processStep
|
|
227
|
+
);
|
|
210
228
|
processed.push(processedStep);
|
|
211
229
|
if (step.steps && step.steps.length > 0) {
|
|
212
230
|
processedStep.steps = await processAllSteps(step.steps);
|
|
@@ -238,9 +256,11 @@ class PlaywrightPulseReporter {
|
|
|
238
256
|
? JSON.stringify(this.config.metadata)
|
|
239
257
|
: undefined,
|
|
240
258
|
};
|
|
259
|
+
// Correctly handle the ID for each run. A unique ID per attempt is crucial.
|
|
260
|
+
const testIdWithRunCounter = `${test.id}-run-${result.retry}`;
|
|
241
261
|
const pulseResult = {
|
|
242
|
-
id:
|
|
243
|
-
runId:
|
|
262
|
+
id: testIdWithRunCounter, // Use the modified ID
|
|
263
|
+
runId: this.currentRunId, // Assign the overall run ID
|
|
244
264
|
name: test.titlePath().join(" > "),
|
|
245
265
|
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",
|
|
246
266
|
status: testStatus,
|
|
@@ -248,7 +268,8 @@ class PlaywrightPulseReporter {
|
|
|
248
268
|
startTime: startTime,
|
|
249
269
|
endTime: endTime,
|
|
250
270
|
browser: browserDetails,
|
|
251
|
-
retries: result.retry,
|
|
271
|
+
retries: result.retry, // This is the Playwright retry count (0 for first run, 1 for first retry, etc.)
|
|
272
|
+
runCounter: result.retry, // This is your 'runCounter'
|
|
252
273
|
steps: ((_f = result.steps) === null || _f === void 0 ? void 0 : _f.length) ? await processAllSteps(result.steps) : [],
|
|
253
274
|
errorMessage: (_g = result.error) === null || _g === void 0 ? void 0 : _g.message,
|
|
254
275
|
stackTrace: (_h = result.error) === null || _h === void 0 ? void 0 : _h.stack,
|
|
@@ -267,7 +288,8 @@ class PlaywrightPulseReporter {
|
|
|
267
288
|
if (!attachment.path)
|
|
268
289
|
continue;
|
|
269
290
|
try {
|
|
270
|
-
|
|
291
|
+
// Use the new testIdWithRunCounter for the subfolder
|
|
292
|
+
const testSubfolder = testIdWithRunCounter.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
271
293
|
const safeAttachmentName = path
|
|
272
294
|
.basename(attachment.path)
|
|
273
295
|
.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
@@ -299,17 +321,68 @@ class PlaywrightPulseReporter {
|
|
|
299
321
|
}
|
|
300
322
|
this.results.push(pulseResult);
|
|
301
323
|
}
|
|
324
|
+
// New method to extract the base test ID, ignoring the run-counter suffix
|
|
325
|
+
_getBaseTestId(testResultId) {
|
|
326
|
+
const parts = testResultId.split("-run-");
|
|
327
|
+
return parts[0];
|
|
328
|
+
}
|
|
302
329
|
_getFinalizedResults(allResults) {
|
|
303
330
|
const finalResultsMap = new Map();
|
|
331
|
+
const allRunsMap = new Map();
|
|
332
|
+
// First, group all run attempts by their base test ID
|
|
304
333
|
for (const result of allResults) {
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
334
|
+
const baseTestId = this._getBaseTestId(result.id);
|
|
335
|
+
if (!allRunsMap.has(baseTestId)) {
|
|
336
|
+
allRunsMap.set(baseTestId, []);
|
|
337
|
+
}
|
|
338
|
+
allRunsMap.get(baseTestId).push(result);
|
|
339
|
+
}
|
|
340
|
+
// Now, iterate through the grouped runs to determine the final state
|
|
341
|
+
for (const [baseTestId, runs] of allRunsMap.entries()) {
|
|
342
|
+
let finalResult = undefined;
|
|
343
|
+
// Sort runs to process them in chronological order
|
|
344
|
+
runs.sort((a, b) => a.runCounter - b.runCounter);
|
|
345
|
+
for (const currentRun of runs) {
|
|
346
|
+
if (!finalResult) {
|
|
347
|
+
finalResult = currentRun;
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
// Compare the current run to the best result found so far
|
|
351
|
+
const currentStatusOrder = this._getStatusOrder(currentRun.status);
|
|
352
|
+
const finalStatusOrder = this._getStatusOrder(finalResult.status);
|
|
353
|
+
if (currentStatusOrder < finalStatusOrder) {
|
|
354
|
+
// Current run is "better" (e.g., passed over failed)
|
|
355
|
+
finalResult = currentRun;
|
|
356
|
+
}
|
|
357
|
+
else if (currentStatusOrder === finalStatusOrder &&
|
|
358
|
+
currentRun.retries > finalResult.retries) {
|
|
359
|
+
// Same status, but prefer the latest attempt
|
|
360
|
+
finalResult = currentRun;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
if (finalResult) {
|
|
365
|
+
// Ensure the ID of the final result is the base test ID for de-duplication
|
|
366
|
+
finalResult.id = baseTestId;
|
|
367
|
+
finalResultsMap.set(baseTestId, finalResult);
|
|
309
368
|
}
|
|
310
369
|
}
|
|
311
370
|
return Array.from(finalResultsMap.values());
|
|
312
371
|
}
|
|
372
|
+
_getStatusOrder(status) {
|
|
373
|
+
switch (status) {
|
|
374
|
+
case "passed":
|
|
375
|
+
return 1;
|
|
376
|
+
case "flaky":
|
|
377
|
+
return 2;
|
|
378
|
+
case "failed":
|
|
379
|
+
return 3;
|
|
380
|
+
case "skipped":
|
|
381
|
+
return 4;
|
|
382
|
+
default:
|
|
383
|
+
return 99; // Unknown status
|
|
384
|
+
}
|
|
385
|
+
}
|
|
313
386
|
onError(error) {
|
|
314
387
|
var _a;
|
|
315
388
|
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);
|
|
@@ -344,15 +417,14 @@ class PlaywrightPulseReporter {
|
|
|
344
417
|
}
|
|
345
418
|
}
|
|
346
419
|
async _mergeShardResults(finalRunData) {
|
|
347
|
-
let
|
|
420
|
+
let allShardRawResults = []; // Store raw results before final de-duplication
|
|
348
421
|
const totalShards = this.config.shard ? this.config.shard.total : 1;
|
|
349
422
|
for (let i = 0; i < totalShards; i++) {
|
|
350
423
|
const tempFilePath = path.join(this.outputDir, `${TEMP_SHARD_FILE_PREFIX}${i}.json`);
|
|
351
424
|
try {
|
|
352
425
|
const content = await fs.readFile(tempFilePath, "utf-8");
|
|
353
426
|
const shardResults = JSON.parse(content);
|
|
354
|
-
|
|
355
|
-
allShardProcessedResults.concat(shardResults);
|
|
427
|
+
allShardRawResults = allShardRawResults.concat(shardResults);
|
|
356
428
|
}
|
|
357
429
|
catch (error) {
|
|
358
430
|
if ((error === null || error === void 0 ? void 0 : error.code) === "ENOENT") {
|
|
@@ -363,11 +435,14 @@ class PlaywrightPulseReporter {
|
|
|
363
435
|
}
|
|
364
436
|
}
|
|
365
437
|
}
|
|
366
|
-
|
|
438
|
+
// Apply _getFinalizedResults after all raw shard results are collected
|
|
439
|
+
const finalResultsList = this._getFinalizedResults(allShardRawResults);
|
|
367
440
|
finalResultsList.forEach((r) => (r.runId = finalRunData.id));
|
|
368
441
|
finalRunData.passed = finalResultsList.filter((r) => r.status === "passed").length;
|
|
369
442
|
finalRunData.failed = finalResultsList.filter((r) => r.status === "failed").length;
|
|
370
443
|
finalRunData.skipped = finalResultsList.filter((r) => r.status === "skipped").length;
|
|
444
|
+
// Add flaky count
|
|
445
|
+
finalRunData.flaky = finalResultsList.filter((r) => r.status === "flaky").length;
|
|
371
446
|
finalRunData.totalTests = finalResultsList.length;
|
|
372
447
|
const reviveDates = (key, value) => {
|
|
373
448
|
const isoDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/;
|
|
@@ -414,34 +489,36 @@ class PlaywrightPulseReporter {
|
|
|
414
489
|
await this._writeShardResults();
|
|
415
490
|
return;
|
|
416
491
|
}
|
|
417
|
-
//
|
|
492
|
+
// Now, `this.results` contains all individual run attempts.
|
|
493
|
+
// _getFinalizedResults will select the "best" run for each logical test.
|
|
418
494
|
const finalResults = this._getFinalizedResults(this.results);
|
|
419
495
|
const runEndTime = Date.now();
|
|
420
496
|
const duration = runEndTime - this.runStartTime;
|
|
421
|
-
|
|
497
|
+
// Use the stored overall runId
|
|
498
|
+
const runId = this.currentRunId;
|
|
422
499
|
const environmentDetails = this._getEnvDetails();
|
|
423
500
|
const runData = {
|
|
424
501
|
id: runId,
|
|
425
502
|
timestamp: new Date(this.runStartTime),
|
|
426
|
-
// Use the length of the de-duplicated array for all counts
|
|
427
503
|
totalTests: finalResults.length,
|
|
428
504
|
passed: finalResults.filter((r) => r.status === "passed").length,
|
|
429
505
|
failed: finalResults.filter((r) => r.status === "failed").length,
|
|
430
506
|
skipped: finalResults.filter((r) => r.status === "skipped").length,
|
|
507
|
+
flaky: finalResults.filter((r) => r.status === "flaky").length, // Add flaky count
|
|
431
508
|
duration,
|
|
432
509
|
environment: environmentDetails,
|
|
433
510
|
};
|
|
511
|
+
// Ensure all final results have the correct overall runId
|
|
434
512
|
finalResults.forEach((r) => (r.runId = runId));
|
|
435
513
|
let finalReport = undefined;
|
|
436
514
|
if (this.isSharded) {
|
|
437
|
-
//
|
|
515
|
+
// _mergeShardResults will now perform the final de-duplication across shards
|
|
438
516
|
finalReport = await this._mergeShardResults(runData);
|
|
439
517
|
}
|
|
440
518
|
else {
|
|
441
519
|
finalReport = {
|
|
442
520
|
run: runData,
|
|
443
|
-
// Use the de-duplicated results
|
|
444
|
-
results: finalResults,
|
|
521
|
+
results: finalResults, // Use the de-duplicated results for a non-sharded run
|
|
445
522
|
metadata: { generatedAt: new Date().toISOString() },
|
|
446
523
|
};
|
|
447
524
|
}
|
|
@@ -470,7 +547,6 @@ class PlaywrightPulseReporter {
|
|
|
470
547
|
}
|
|
471
548
|
}
|
|
472
549
|
else {
|
|
473
|
-
// Logic for appending/merging reports
|
|
474
550
|
const pulseResultsDir = path.join(this.outputDir, INDIVIDUAL_REPORTS_SUBDIR);
|
|
475
551
|
const individualReportPath = path.join(pulseResultsDir, `playwright-pulse-report-${Date.now()}.json`);
|
|
476
552
|
try {
|
|
@@ -516,7 +592,10 @@ class PlaywrightPulseReporter {
|
|
|
516
592
|
const allResultsFromAllFiles = [];
|
|
517
593
|
let latestTimestamp = new Date(0);
|
|
518
594
|
let lastRunEnvironment = undefined;
|
|
519
|
-
|
|
595
|
+
// We can't simply sum durations across merged files, as the tests might overlap.
|
|
596
|
+
// The final duration will be derived from the range of start/end times in the final results.
|
|
597
|
+
let earliestStartTime = Date.now();
|
|
598
|
+
let latestEndTime = 0;
|
|
520
599
|
for (const file of reportFiles) {
|
|
521
600
|
const filePath = path.join(pulseResultsDir, file);
|
|
522
601
|
try {
|
|
@@ -539,22 +618,28 @@ class PlaywrightPulseReporter {
|
|
|
539
618
|
}
|
|
540
619
|
// De-duplicate the results from ALL merged files using the helper function
|
|
541
620
|
const finalMergedResults = this._getFinalizedResults(allResultsFromAllFiles);
|
|
542
|
-
//
|
|
543
|
-
|
|
621
|
+
// Calculate overall duration from the earliest start and latest end of the final merged results
|
|
622
|
+
for (const res of finalMergedResults) {
|
|
623
|
+
if (res.startTime.getTime() < earliestStartTime)
|
|
624
|
+
earliestStartTime = res.startTime.getTime();
|
|
625
|
+
if (res.endTime.getTime() > latestEndTime)
|
|
626
|
+
latestEndTime = res.endTime.getTime();
|
|
627
|
+
}
|
|
628
|
+
const totalDuration = latestEndTime > earliestStartTime ? latestEndTime - earliestStartTime : 0;
|
|
544
629
|
const combinedRun = {
|
|
545
630
|
id: `merged-${Date.now()}`,
|
|
546
631
|
timestamp: latestTimestamp,
|
|
547
632
|
environment: lastRunEnvironment,
|
|
548
|
-
// Recalculate counts based on the truly final, de-duplicated list
|
|
549
633
|
totalTests: finalMergedResults.length,
|
|
550
634
|
passed: finalMergedResults.filter((r) => r.status === "passed").length,
|
|
551
635
|
failed: finalMergedResults.filter((r) => r.status === "failed").length,
|
|
552
636
|
skipped: finalMergedResults.filter((r) => r.status === "skipped").length,
|
|
637
|
+
flaky: finalMergedResults.filter((r) => r.status === "flaky").length, // Add flaky count
|
|
553
638
|
duration: totalDuration,
|
|
554
639
|
};
|
|
555
640
|
const finalReport = {
|
|
556
641
|
run: combinedRun,
|
|
557
|
-
results: finalMergedResults,
|
|
642
|
+
results: finalMergedResults,
|
|
558
643
|
metadata: {
|
|
559
644
|
generatedAt: new Date().toISOString(),
|
|
560
645
|
},
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { LucideIcon } from "lucide-react";
|
|
2
2
|
export type TestStatus = "passed" | "failed" | "skipped" | "expected-failure" | "unexpected-success" | "explicitly-skipped";
|
|
3
|
+
export type PulseTestStatus = TestStatus | "flaky";
|
|
3
4
|
export interface TestStep {
|
|
4
5
|
id: string;
|
|
5
6
|
title: string;
|
|
6
|
-
status:
|
|
7
|
+
status: PulseTestStatus;
|
|
7
8
|
duration: number;
|
|
8
9
|
startTime: Date;
|
|
9
10
|
endTime: Date;
|
|
@@ -18,11 +19,12 @@ export interface TestStep {
|
|
|
18
19
|
export interface TestResult {
|
|
19
20
|
id: string;
|
|
20
21
|
name: string;
|
|
21
|
-
status:
|
|
22
|
+
status: PulseTestStatus;
|
|
22
23
|
duration: number;
|
|
23
24
|
startTime: Date;
|
|
24
25
|
endTime: Date;
|
|
25
26
|
retries: number;
|
|
27
|
+
runCounter: number;
|
|
26
28
|
steps: TestStep[];
|
|
27
29
|
errorMessage?: string;
|
|
28
30
|
stackTrace?: string;
|
|
@@ -54,6 +56,7 @@ export interface TestRun {
|
|
|
54
56
|
passed: number;
|
|
55
57
|
failed: number;
|
|
56
58
|
skipped: number;
|
|
59
|
+
flaky: number;
|
|
57
60
|
duration: number;
|
|
58
61
|
environment?: EnvDetails;
|
|
59
62
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arghajit/dummy",
|
|
3
|
-
"
|
|
4
|
-
"version": "0.1.1",
|
|
3
|
+
"version": "0.1.2-beta-1",
|
|
5
4
|
"description": "A Playwright reporter and dashboard for visualizing test results.",
|
|
6
5
|
"homepage": "https://playwright-pulse-report.netlify.app/",
|
|
7
6
|
"keywords": [
|
|
@@ -1850,43 +1850,53 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1850
1850
|
: ""
|
|
1851
1851
|
}<button class="copy-error-btn" onclick="copyErrorToClipboard(this)">Copy Error Prompt</button></div>`
|
|
1852
1852
|
: ""
|
|
1853
|
-
}${
|
|
1854
|
-
(
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1853
|
+
}${(() => {
|
|
1854
|
+
if (!step.attachments || step.attachments.length === 0)
|
|
1855
|
+
return "";
|
|
1856
|
+
return `<div class="attachments-section"><h4>Step Attachments</h4><div class="attachments-grid">${step.attachments
|
|
1857
|
+
.map((attachment) => {
|
|
1858
|
+
try {
|
|
1859
|
+
const attachmentPath = path.resolve(
|
|
1860
|
+
DEFAULT_OUTPUT_DIR,
|
|
1861
|
+
attachment.path
|
|
1862
|
+
);
|
|
1863
|
+
if (!fsExistsSync(attachmentPath)) {
|
|
1864
|
+
return `<div class="attachment-item error">Attachment not found: ${sanitizeHTML(
|
|
1865
|
+
attachment.name
|
|
1866
|
+
)}</div>`;
|
|
1867
|
+
}
|
|
1868
|
+
const attachmentBase64 =
|
|
1869
|
+
readFileSync(attachmentPath).toString("base64");
|
|
1870
|
+
const attachmentDataUri = `data:${attachment.contentType};base64,${attachmentBase64}`;
|
|
1871
|
+
return `<div class="attachment-item generic-attachment">
|
|
1872
|
+
<div class="attachment-icon">${getAttachmentIcon(
|
|
1873
|
+
attachment.contentType
|
|
1874
|
+
)}</div>
|
|
1872
1875
|
<div class="attachment-caption">
|
|
1873
|
-
<span class="attachment-name" title="${sanitizeHTML(
|
|
1874
|
-
|
|
1876
|
+
<span class="attachment-name" title="${sanitizeHTML(
|
|
1877
|
+
attachment.name
|
|
1878
|
+
)}">${sanitizeHTML(attachment.name)}</span>
|
|
1879
|
+
<span class="attachment-type">${sanitizeHTML(
|
|
1880
|
+
attachment.contentType
|
|
1881
|
+
)}</span>
|
|
1875
1882
|
</div>
|
|
1876
1883
|
<div class="attachment-info">
|
|
1877
1884
|
<div class="trace-actions">
|
|
1878
1885
|
<a href="#" data-href="${attachmentDataUri}" class="view-full lazy-load-attachment" target="_blank">View</a>
|
|
1879
|
-
<a href="#" data-href="${attachmentDataUri}" class="lazy-load-attachment" download="${sanitizeHTML(
|
|
1886
|
+
<a href="#" data-href="${attachmentDataUri}" class="lazy-load-attachment" download="${sanitizeHTML(
|
|
1887
|
+
attachment.name
|
|
1888
|
+
)}">Download</a>
|
|
1880
1889
|
</div>
|
|
1881
1890
|
</div>
|
|
1882
1891
|
</div>`;
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1892
|
+
} catch (e) {
|
|
1893
|
+
return `<div class="attachment-item error">Failed to load attachment: ${sanitizeHTML(
|
|
1894
|
+
attachment.name
|
|
1895
|
+
)}</div>`;
|
|
1896
|
+
}
|
|
1897
|
+
})
|
|
1898
|
+
.join("")}</div></div>`;
|
|
1899
|
+
})()}${
|
|
1890
1900
|
hasNestedSteps
|
|
1891
1901
|
? `<div class="nested-steps">${generateStepsHTML(
|
|
1892
1902
|
step.steps,
|
|
@@ -1903,7 +1913,9 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1903
1913
|
test.tags || []
|
|
1904
1914
|
)
|
|
1905
1915
|
.join(",")
|
|
1906
|
-
.toLowerCase()}" data-test-id="${sanitizeHTML(
|
|
1916
|
+
.toLowerCase()}" data-test-id="${sanitizeHTML(
|
|
1917
|
+
String(test.id || testIndex)
|
|
1918
|
+
)}">
|
|
1907
1919
|
<div class="test-case-header" role="button" aria-expanded="false"><div class="test-case-summary"><span class="status-badge ${getStatusClass(
|
|
1908
1920
|
test.status
|
|
1909
1921
|
)}">${String(
|
|
@@ -1970,7 +1982,9 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1970
1982
|
${
|
|
1971
1983
|
test.stderr && test.stderr.length > 0
|
|
1972
1984
|
? (() => {
|
|
1973
|
-
const logId = `stderr-log-${
|
|
1985
|
+
const logId = `stderr-log-${
|
|
1986
|
+
test.id || testIndex
|
|
1987
|
+
}`;
|
|
1974
1988
|
return `<div class="console-output-section"><h4>Console Output (stderr)</h4><pre id="${logId}" class="console-log stderr-log">${test.stderr
|
|
1975
1989
|
.map((line) => sanitizeHTML(line))
|
|
1976
1990
|
.join("\\n")}</pre></div>`;
|
|
@@ -1984,7 +1998,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
1984
1998
|
test.screenshots.length === 0
|
|
1985
1999
|
)
|
|
1986
2000
|
return "";
|
|
1987
|
-
return `<div class="attachments-section"><h4>Screenshots</h4><div class="attachments-grid">${test.screenshots
|
|
2001
|
+
return `<div class="attachments-section"><h4>Screenshots (Click to load Images)</h4><div class="attachments-grid">${test.screenshots
|
|
1988
2002
|
.map((screenshotPath, index) => {
|
|
1989
2003
|
try {
|
|
1990
2004
|
const imagePath = path.resolve(
|
|
@@ -2384,7 +2398,7 @@ aspect-ratio: 16 / 9;
|
|
|
2384
2398
|
@media (max-width: 992px) { .dashboard-bottom-row { grid-template-columns: 1fr; } .pie-chart-wrapper div[id^="pieChart-"] { max-width: 350px; margin: 0 auto; } .filters input { min-width: 180px; } .filters select { min-width: 150px; } }
|
|
2385
2399
|
@media (max-width: 768px) { body { font-size: 15px; } .container { margin: 10px; padding: 20px; } .header { flex-direction: column; align-items: flex-start; gap: 15px; } .header h1 { font-size: 1.6em; } .run-info { text-align: left; font-size:0.9em; } .tabs { margin-bottom: 25px;} .tab-button { padding: 12px 20px; font-size: 1.05em;} .dashboard-grid { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 18px;} .summary-card .value {font-size: 2em;} .summary-card h3 {font-size: 0.95em;} .filters { flex-direction: column; padding: 18px; gap: 12px;} .filters input, .filters select, .filters button {width: 100%; box-sizing: border-box;} .test-case-header { flex-direction: column; align-items: flex-start; gap: 10px; padding: 14px; } .test-case-summary {gap: 10px;} .test-case-title {font-size: 1.05em;} .test-case-meta { flex-direction: row; flex-wrap: wrap; gap: 8px; margin-top: 8px;} .attachments-grid {grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 18px;} .test-history-grid {grid-template-columns: 1fr;} .pie-chart-wrapper {min-height: auto;} .ai-failure-cards-grid { grid-template-columns: 1fr; } .ai-analyzer-stats { flex-direction: column; gap: 15px; text-align: center; } .failure-header { flex-direction: column; align-items: stretch; gap: 15px; } .failure-main-info { text-align: center; } .failure-meta { justify-content: center; } .compact-ai-btn { justify-content: center; padding: 12px 20px; } }
|
|
2386
2400
|
@media (max-width: 480px) { body {font-size: 14px;} .container {padding: 15px;} .header h1 {font-size: 1.4em;} #report-logo { height: 35px; width: 45px; } .tab-button {padding: 10px 15px; font-size: 1em;} .summary-card .value {font-size: 1.8em;} .attachments-grid {grid-template-columns: 1fr;} .step-item {padding-left: calc(var(--depth, 0) * 18px);} .test-case-content, .step-details {padding: 15px;} .trend-charts-row {gap: 20px;} .trend-chart {padding: 20px;} .stat-item .stat-number { font-size: 1.5em; } .failure-header { padding: 15px; } .failure-error-preview, .full-error-details { padding-left: 15px; padding-right: 15px; } }
|
|
2387
|
-
.trace-actions a { text-decoration: none;
|
|
2401
|
+
.trace-actions a { text-decoration: none; font-weight: 500; font-size: 0.9em; }
|
|
2388
2402
|
.generic-attachment { text-align: center; padding: 1rem; justify-content: center; }
|
|
2389
2403
|
.attachment-icon { font-size: 2.5rem; display: block; margin-bottom: 0.75rem; }
|
|
2390
2404
|
.attachment-caption { display: flex; flex-direction: column; align-items: center; justify-content: center; flex-grow: 1; }
|