@sentinelqa/playwright-reporter 0.1.28 → 0.1.30
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/README.md +2 -0
- package/dist/localReport.d.ts +2 -0
- package/dist/localReport.js +91 -54
- package/dist/quickDiagnosis.d.ts +1 -0
- package/dist/quickDiagnosis.js +36 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,6 +12,8 @@ Works locally out of the box with no account required.
|
|
|
12
12
|
Optionally upload runs to Sentinel Cloud for CI history and AI failure analysis.
|
|
13
13
|
|
|
14
14
|

|
|
15
|
+

|
|
16
|
+

|
|
15
17
|
|
|
16
18
|
## Features
|
|
17
19
|
|
package/dist/localReport.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ type CopiedArtifact = {
|
|
|
21
21
|
};
|
|
22
22
|
type ReportTest = {
|
|
23
23
|
id: string;
|
|
24
|
+
matchKey: string;
|
|
24
25
|
title: string;
|
|
25
26
|
titlePath: string[];
|
|
26
27
|
file: string | null;
|
|
@@ -51,6 +52,7 @@ type RunSnapshot = {
|
|
|
51
52
|
totals: LocalReportSummary;
|
|
52
53
|
tests: Array<{
|
|
53
54
|
id: string;
|
|
55
|
+
matchKey: string;
|
|
54
56
|
title: string;
|
|
55
57
|
status: string;
|
|
56
58
|
signal: string | null;
|
package/dist/localReport.js
CHANGED
|
@@ -95,6 +95,23 @@ const safeSlug = (value) => {
|
|
|
95
95
|
.replace(/^-+|-+$/g, "")
|
|
96
96
|
.slice(0, 64) || "artifact");
|
|
97
97
|
};
|
|
98
|
+
const buildTitlePath = (baseTitles, test) => {
|
|
99
|
+
const title = typeof test?.title === "string" ? test.title : null;
|
|
100
|
+
if (!title)
|
|
101
|
+
return baseTitles.filter(Boolean);
|
|
102
|
+
if (baseTitles[baseTitles.length - 1] === title)
|
|
103
|
+
return baseTitles.filter(Boolean);
|
|
104
|
+
return [...baseTitles, title].filter(Boolean);
|
|
105
|
+
};
|
|
106
|
+
const buildTestIdentity = (test, titlePath) => {
|
|
107
|
+
const file = test?.location?.file || "unknown";
|
|
108
|
+
const project = test?.projectName || "default";
|
|
109
|
+
const joined = titlePath.join(" > ");
|
|
110
|
+
return {
|
|
111
|
+
id: [file, project, joined].join("::"),
|
|
112
|
+
matchKey: [file, joined].join("::")
|
|
113
|
+
};
|
|
114
|
+
};
|
|
98
115
|
const formatDuration = (durationMs) => {
|
|
99
116
|
if (!Number.isFinite(durationMs) || durationMs <= 0)
|
|
100
117
|
return "0 ms";
|
|
@@ -233,7 +250,7 @@ const copyArtifact = (sourcePath, kind, reportDir, usedRelativePaths, testId) =>
|
|
|
233
250
|
testId
|
|
234
251
|
};
|
|
235
252
|
};
|
|
236
|
-
const createReportTest = (test, titlePath
|
|
253
|
+
const createReportTest = (test, titlePath) => {
|
|
237
254
|
const results = Array.isArray(test?.results) ? test.results : [];
|
|
238
255
|
const lastResult = results.length > 0 ? results[results.length - 1] : null;
|
|
239
256
|
const errors = results.flatMap((result) => Array.isArray(result?.errors)
|
|
@@ -242,30 +259,31 @@ const createReportTest = (test, titlePath, diagnosisById) => {
|
|
|
242
259
|
.filter(Boolean)
|
|
243
260
|
: []);
|
|
244
261
|
const duration = results.reduce((total, result) => total + (Number(result?.duration) || 0), 0);
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
titlePath.join(" > ")
|
|
249
|
-
].join("::");
|
|
262
|
+
const identity = buildTestIdentity(test, titlePath);
|
|
263
|
+
const status = normalizeTestStatus(test?.status || lastResult?.status || "unknown");
|
|
264
|
+
const primaryError = errors[0] || "";
|
|
250
265
|
return {
|
|
251
|
-
id,
|
|
266
|
+
id: identity.id,
|
|
267
|
+
matchKey: identity.matchKey,
|
|
252
268
|
title: test?.title || titlePath[titlePath.length - 1] || "Untitled test",
|
|
253
269
|
titlePath,
|
|
254
270
|
file: test?.location?.file || null,
|
|
255
271
|
projectName: test?.projectName || null,
|
|
256
|
-
status
|
|
272
|
+
status,
|
|
257
273
|
duration,
|
|
258
274
|
errors,
|
|
259
|
-
diagnosis:
|
|
275
|
+
diagnosis: ["failed", "timedOut", "interrupted"].includes(status) && primaryError
|
|
276
|
+
? (0, quickDiagnosis_1.parseFailureFacts)(test?.title || titlePath[titlePath.length - 1] || "Untitled test", titlePath, primaryError, status)
|
|
277
|
+
: null,
|
|
260
278
|
artifacts: []
|
|
261
279
|
};
|
|
262
280
|
};
|
|
263
|
-
const collectTests = (node,
|
|
281
|
+
const collectTests = (node, parentTitles = []) => {
|
|
264
282
|
const nextTitles = node?.title ? [...parentTitles, node.title] : parentTitles;
|
|
265
283
|
const collected = [];
|
|
266
284
|
if (Array.isArray(node?.tests)) {
|
|
267
285
|
for (const test of node.tests) {
|
|
268
|
-
collected.push(createReportTest(test,
|
|
286
|
+
collected.push(createReportTest(test, buildTitlePath(nextTitles, test)));
|
|
269
287
|
}
|
|
270
288
|
}
|
|
271
289
|
if (Array.isArray(node?.specs)) {
|
|
@@ -273,13 +291,13 @@ const collectTests = (node, diagnosisById, parentTitles = []) => {
|
|
|
273
291
|
const specTitles = [...nextTitles, spec?.title].filter(Boolean);
|
|
274
292
|
const specTests = Array.isArray(spec?.tests) ? spec.tests : [];
|
|
275
293
|
for (const test of specTests) {
|
|
276
|
-
collected.push(createReportTest(test, specTitles,
|
|
294
|
+
collected.push(createReportTest(test, buildTitlePath(specTitles, test)));
|
|
277
295
|
}
|
|
278
296
|
}
|
|
279
297
|
}
|
|
280
298
|
if (Array.isArray(node?.suites)) {
|
|
281
299
|
for (const suite of node.suites) {
|
|
282
|
-
collected.push(...collectTests(suite,
|
|
300
|
+
collected.push(...collectTests(suite, nextTitles));
|
|
283
301
|
}
|
|
284
302
|
}
|
|
285
303
|
return collected;
|
|
@@ -289,12 +307,8 @@ const collectTestRefs = (node, parentTitles = []) => {
|
|
|
289
307
|
const refs = [];
|
|
290
308
|
if (Array.isArray(node?.tests)) {
|
|
291
309
|
for (const test of node.tests) {
|
|
292
|
-
const titlePath =
|
|
293
|
-
const id =
|
|
294
|
-
test?.location?.file || "unknown",
|
|
295
|
-
test?.projectName || "default",
|
|
296
|
-
titlePath.join(" > ")
|
|
297
|
-
].join("::");
|
|
310
|
+
const titlePath = buildTitlePath(nextTitles, test);
|
|
311
|
+
const id = buildTestIdentity(test, titlePath).id;
|
|
298
312
|
refs.push({ id, resultList: Array.isArray(test?.results) ? test.results : [] });
|
|
299
313
|
}
|
|
300
314
|
}
|
|
@@ -302,11 +316,7 @@ const collectTestRefs = (node, parentTitles = []) => {
|
|
|
302
316
|
for (const spec of node.specs) {
|
|
303
317
|
const titlePath = [...nextTitles, spec?.title].filter(Boolean);
|
|
304
318
|
for (const test of Array.isArray(spec?.tests) ? spec.tests : []) {
|
|
305
|
-
const id =
|
|
306
|
-
test?.location?.file || "unknown",
|
|
307
|
-
test?.projectName || "default",
|
|
308
|
-
titlePath.join(" > ")
|
|
309
|
-
].join("::");
|
|
319
|
+
const id = buildTestIdentity(test, buildTitlePath(titlePath, test)).id;
|
|
310
320
|
refs.push({ id, resultList: Array.isArray(test?.results) ? test.results : [] });
|
|
311
321
|
}
|
|
312
322
|
}
|
|
@@ -331,14 +341,6 @@ const summarizeTests = (tests) => {
|
|
|
331
341
|
return summary;
|
|
332
342
|
}, { total: 0, failed: 0, passed: 0, skipped: 0 });
|
|
333
343
|
};
|
|
334
|
-
const buildDiagnosisMap = (playwrightJsonPath) => {
|
|
335
|
-
const failures = (0, quickDiagnosis_1.collectFailureFacts)(playwrightJsonPath);
|
|
336
|
-
const map = new Map();
|
|
337
|
-
for (const failure of failures) {
|
|
338
|
-
map.set(failure.titlePath.join(" > "), failure);
|
|
339
|
-
}
|
|
340
|
-
return map;
|
|
341
|
-
};
|
|
342
344
|
const getFailureTests = (tests) => tests.filter((test) => ["failed", "timedOut", "interrupted"].includes(test.status));
|
|
343
345
|
const groupSimilarFailures = (tests) => {
|
|
344
346
|
const groups = new Map();
|
|
@@ -356,6 +358,22 @@ const groupSimilarFailures = (tests) => {
|
|
|
356
358
|
}
|
|
357
359
|
groups.get(key).tests.push(test);
|
|
358
360
|
}
|
|
361
|
+
return Array.from(groups.values())
|
|
362
|
+
.filter((group) => group.tests.length > 1)
|
|
363
|
+
.sort((a, b) => b.tests.length - a.tests.length);
|
|
364
|
+
};
|
|
365
|
+
const groupFailureDigest = (tests) => {
|
|
366
|
+
const groups = new Map();
|
|
367
|
+
for (const test of getFailureTests(tests)) {
|
|
368
|
+
const summary = test.diagnosis
|
|
369
|
+
? (0, quickDiagnosis_1.describeFailure)(test.diagnosis)
|
|
370
|
+
: (test.errors[0]?.split(/\r?\n/)[0]?.trim() || "Open the failure details to inspect the exact Playwright error.");
|
|
371
|
+
const key = summary;
|
|
372
|
+
if (!groups.has(key)) {
|
|
373
|
+
groups.set(key, { key, summary, tests: [] });
|
|
374
|
+
}
|
|
375
|
+
groups.get(key).tests.push(test);
|
|
376
|
+
}
|
|
359
377
|
return Array.from(groups.values()).sort((a, b) => b.tests.length - a.tests.length);
|
|
360
378
|
};
|
|
361
379
|
const buildRunSnapshot = (tests, summary) => ({
|
|
@@ -365,6 +383,7 @@ const buildRunSnapshot = (tests, summary) => ({
|
|
|
365
383
|
totals: summary,
|
|
366
384
|
tests: tests.map((test) => ({
|
|
367
385
|
id: test.id,
|
|
386
|
+
matchKey: test.matchKey,
|
|
368
387
|
title: test.titlePath.join(" > ") || test.title,
|
|
369
388
|
status: test.status,
|
|
370
389
|
signal: test.diagnosis?.signal || null,
|
|
@@ -407,18 +426,21 @@ const buildRunDiff = (tests, snapshot) => {
|
|
|
407
426
|
|| readSnapshot(path_1.default.resolve(process.cwd(), ".sentinel", "latest.json"));
|
|
408
427
|
if (!previous || previous.generatedAt === snapshot.generatedAt)
|
|
409
428
|
return null;
|
|
410
|
-
const
|
|
429
|
+
const currentById = new Map(tests.map((test) => [test.id, test]));
|
|
430
|
+
const currentByMatchKey = new Map(tests.map((test) => [test.matchKey, test]));
|
|
411
431
|
const currentFailures = getFailureTests(tests);
|
|
412
432
|
const previousFailures = previous.tests.filter((test) => ["failed", "timedOut", "interrupted"].includes(test.status));
|
|
413
433
|
const previousFailureIds = new Set(previousFailures.map((test) => test.id));
|
|
434
|
+
const previousFailureMatchKeys = new Set(previousFailures.map((test) => (typeof test.matchKey === "string" ? test.matchKey : test.id)));
|
|
414
435
|
return {
|
|
415
436
|
label: previous.branch === snapshot.branch ? `Compared to previous ${snapshot.branch} run` : "Compared to previous run",
|
|
416
|
-
newFailures: currentFailures.filter((test) => !previousFailureIds.has(test.id)),
|
|
437
|
+
newFailures: currentFailures.filter((test) => !previousFailureIds.has(test.id) && !previousFailureMatchKeys.has(test.matchKey)),
|
|
417
438
|
fixedTests: previousFailures.filter((test) => {
|
|
418
|
-
const current =
|
|
439
|
+
const current = currentById.get(test.id) ||
|
|
440
|
+
currentByMatchKey.get(typeof test.matchKey === "string" ? test.matchKey : test.id);
|
|
419
441
|
return current && !["failed", "timedOut", "interrupted"].includes(current.status);
|
|
420
442
|
}),
|
|
421
|
-
stillFailing: currentFailures.filter((test) => previousFailureIds.has(test.id))
|
|
443
|
+
stillFailing: currentFailures.filter((test) => previousFailureIds.has(test.id) || previousFailureMatchKeys.has(test.matchKey))
|
|
422
444
|
};
|
|
423
445
|
};
|
|
424
446
|
const renderArtifact = (artifact) => {
|
|
@@ -593,22 +615,30 @@ const renderFailureDigest = (tests) => {
|
|
|
593
615
|
if (!failedTests.length) {
|
|
594
616
|
return `<div class="empty-state">No failed tests were detected in this run.</div>`;
|
|
595
617
|
}
|
|
618
|
+
const digestGroups = groupFailureDigest(tests);
|
|
596
619
|
return `
|
|
597
620
|
<div class="digest-grid">
|
|
598
|
-
${
|
|
599
|
-
.map((
|
|
600
|
-
const
|
|
601
|
-
const
|
|
602
|
-
const
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
621
|
+
${digestGroups
|
|
622
|
+
.map((group) => {
|
|
623
|
+
const primary = group.tests[0];
|
|
624
|
+
const diagnosis = primary.diagnosis;
|
|
625
|
+
const debugSummary = escapeHtml(group.tests.length > 1
|
|
626
|
+
? [
|
|
627
|
+
`Grouped failure summary: ${group.summary}`,
|
|
628
|
+
...group.tests.map((test) => `- ${test.title}`)
|
|
629
|
+
].join("\n")
|
|
630
|
+
: diagnosis
|
|
631
|
+
? (0, quickDiagnosis_1.buildDebugSummary)(diagnosis)
|
|
632
|
+
: `Test: ${primary.title}\nDiagnosis: Review trace and error details in the expanded card.`);
|
|
633
|
+
const uniqueLocators = Array.from(new Set(group.tests.map((test) => test.diagnosis?.locator).filter(Boolean)));
|
|
606
634
|
return `
|
|
607
635
|
<article class="digest-card">
|
|
608
636
|
<div class="digest-head">
|
|
609
637
|
<div>
|
|
610
|
-
<span class="artifact-kind">${escapeHtml(
|
|
611
|
-
<h3>${
|
|
638
|
+
<span class="artifact-kind">${escapeHtml(primary.status)}</span>
|
|
639
|
+
<h3>${group.tests.length > 1
|
|
640
|
+
? `${group.tests.length} tests share this failure`
|
|
641
|
+
: escapeHtml(primary.title)}</h3>
|
|
612
642
|
</div>
|
|
613
643
|
<button
|
|
614
644
|
type="button"
|
|
@@ -619,12 +649,20 @@ const renderFailureDigest = (tests) => {
|
|
|
619
649
|
Copy summary
|
|
620
650
|
</button>
|
|
621
651
|
</div>
|
|
622
|
-
<p>${summary}</p>
|
|
652
|
+
<p>${escapeHtml(group.summary)}</p>
|
|
623
653
|
<div class="fact-row">
|
|
624
|
-
${diagnosis?.
|
|
625
|
-
|
|
626
|
-
|
|
654
|
+
${diagnosis?.expected && diagnosis?.received
|
|
655
|
+
? `<span class="fact-chip">Expected: ${escapeHtml(diagnosis.expected)}</span><span class="fact-chip">Observed: ${escapeHtml(diagnosis.received)}</span>`
|
|
656
|
+
: ""}
|
|
657
|
+
${uniqueLocators.length === 1
|
|
658
|
+
? `<span class="fact-chip">Locator: ${escapeHtml(uniqueLocators[0])}</span>`
|
|
659
|
+
: uniqueLocators.length > 1
|
|
660
|
+
? `<span class="fact-chip">${uniqueLocators.length} locators involved</span>`
|
|
661
|
+
: ""}
|
|
627
662
|
</div>
|
|
663
|
+
${group.tests.length > 1
|
|
664
|
+
? `<ul class="group-list">${group.tests.map((test) => `<li>${escapeHtml(test.title)}</li>`).join("\n")}</ul>`
|
|
665
|
+
: ""}
|
|
628
666
|
</article>
|
|
629
667
|
`;
|
|
630
668
|
})
|
|
@@ -634,7 +672,7 @@ const renderFailureDigest = (tests) => {
|
|
|
634
672
|
};
|
|
635
673
|
const renderSimilarFailureGroups = (groups) => {
|
|
636
674
|
if (!groups.length) {
|
|
637
|
-
return `<div class="empty-state">No failure
|
|
675
|
+
return `<div class="empty-state">No repeated failure fingerprint was detected in this run.</div>`;
|
|
638
676
|
}
|
|
639
677
|
return groups
|
|
640
678
|
.map((group) => `
|
|
@@ -1223,7 +1261,7 @@ const buildHtml = (tests, summary, extraArtifacts, runDiff) => {
|
|
|
1223
1261
|
<h2>Similar Failures</h2>
|
|
1224
1262
|
<div class="failed-count">${similarGroups.length} groups</div>
|
|
1225
1263
|
</div>
|
|
1226
|
-
<p>Failures are grouped
|
|
1264
|
+
<p>Failures are grouped only when they share the same fingerprint, so one repeated issue is easier to spot.</p>
|
|
1227
1265
|
${renderSimilarFailureGroups(similarGroups)}
|
|
1228
1266
|
</section>
|
|
1229
1267
|
|
|
@@ -1400,8 +1438,7 @@ function generateLocalDebugReport(options) {
|
|
|
1400
1438
|
const reportJsonRaw = fs_1.default.readFileSync(options.playwrightJsonPath, "utf8");
|
|
1401
1439
|
const reportJson = JSON.parse(reportJsonRaw);
|
|
1402
1440
|
const reportRoot = { suites: reportJson?.suites || [] };
|
|
1403
|
-
const
|
|
1404
|
-
const tests = collectTests(reportRoot, diagnosisById);
|
|
1441
|
+
const tests = collectTests(reportRoot);
|
|
1405
1442
|
const testsById = new Map(tests.map((test) => [test.id, test]));
|
|
1406
1443
|
const claimedSourcePaths = new Set();
|
|
1407
1444
|
const attachArtifactToTest = (sourcePath, testId) => {
|
package/dist/quickDiagnosis.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export type FailureFacts = {
|
|
|
15
15
|
status: string;
|
|
16
16
|
};
|
|
17
17
|
export declare const collectFailureFacts: (playwrightJsonPath: string) => FailureFacts[];
|
|
18
|
+
export declare const parseFailureFacts: (title: string, titlePath: string[], message: string, status: string) => FailureFacts;
|
|
18
19
|
export declare const describeFailure: (failure: FailureFacts) => string;
|
|
19
20
|
export declare const buildDebugSummary: (failure: FailureFacts) => string;
|
|
20
21
|
export declare const buildSimilarityKey: (failure: FailureFacts) => string;
|
package/dist/quickDiagnosis.js
CHANGED
|
@@ -3,8 +3,18 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.buildQuickDiagnosis = exports.summarizeSignal = exports.buildSimilarityKey = exports.buildDebugSummary = exports.describeFailure = exports.collectFailureFacts = void 0;
|
|
6
|
+
exports.buildQuickDiagnosis = exports.summarizeSignal = exports.buildSimilarityKey = exports.buildDebugSummary = exports.describeFailure = exports.parseFailureFacts = exports.collectFailureFacts = void 0;
|
|
7
7
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const normalizeMessageFingerprint = (message) => stripAnsi(message)
|
|
9
|
+
.split(/\r?\n/)
|
|
10
|
+
.map((line) => line.trim())
|
|
11
|
+
.filter(Boolean)
|
|
12
|
+
.slice(0, 3)
|
|
13
|
+
.join(" | ")
|
|
14
|
+
.replace(/\b\d+ms\b/gi, "<ms>")
|
|
15
|
+
.replace(/:\d+:\d+/g, ":<line>:<col>")
|
|
16
|
+
.replace(/\s+/g, " ")
|
|
17
|
+
.slice(0, 200);
|
|
8
18
|
const stripAnsi = (value) => value.replace(/\u001b\[[0-9;]*m/g, "");
|
|
9
19
|
const toMessage = (result) => {
|
|
10
20
|
const direct = result.error?.message ||
|
|
@@ -18,11 +28,11 @@ const toMessage = (result) => {
|
|
|
18
28
|
};
|
|
19
29
|
const classifySignal = (message) => {
|
|
20
30
|
const lower = message.toLowerCase();
|
|
21
|
-
if (/timeout|timed out|waiting for/.test(lower))
|
|
22
|
-
return "timeout";
|
|
23
31
|
if (/expected substring|expected string|received string|tohavetext|tocontaintext/.test(lower)) {
|
|
24
32
|
return "assertion_mismatch";
|
|
25
33
|
}
|
|
34
|
+
if (/timeout|timed out|waiting for/.test(lower))
|
|
35
|
+
return "timeout";
|
|
26
36
|
if (/resolved to 0 elements|locator.*not found|never appeared|strict mode violation/.test(lower)) {
|
|
27
37
|
return "locator_not_found";
|
|
28
38
|
}
|
|
@@ -120,24 +130,26 @@ const collectFailureFacts = (playwrightJsonPath) => {
|
|
|
120
130
|
const raw = node_fs_1.default.readFileSync(playwrightJsonPath, "utf8");
|
|
121
131
|
const parsed = JSON.parse(raw);
|
|
122
132
|
const failedCases = flattenFailedCases(parsed);
|
|
123
|
-
return failedCases.map((failed) => (
|
|
124
|
-
title: shortenTitle(failed.title),
|
|
125
|
-
titlePath: failed.title.split(" > ").filter(Boolean),
|
|
126
|
-
message: failed.message,
|
|
127
|
-
signal: failed.signal,
|
|
128
|
-
locator: extractLocator(failed.message),
|
|
129
|
-
expected: extractExpected(failed.message),
|
|
130
|
-
received: extractReceived(failed.message),
|
|
131
|
-
timeoutMs: extractTimeoutMs(failed.message),
|
|
132
|
-
lastUrl: extractLastUrl(failed.message),
|
|
133
|
-
status: "failed"
|
|
134
|
-
}));
|
|
133
|
+
return failedCases.map((failed) => (0, exports.parseFailureFacts)(shortenTitle(failed.title), failed.title.split(" > ").filter(Boolean), failed.message, "failed"));
|
|
135
134
|
}
|
|
136
135
|
catch {
|
|
137
136
|
return [];
|
|
138
137
|
}
|
|
139
138
|
};
|
|
140
139
|
exports.collectFailureFacts = collectFailureFacts;
|
|
140
|
+
const parseFailureFacts = (title, titlePath, message, status) => ({
|
|
141
|
+
title,
|
|
142
|
+
titlePath,
|
|
143
|
+
message,
|
|
144
|
+
signal: classifySignal(message),
|
|
145
|
+
locator: extractLocator(message),
|
|
146
|
+
expected: extractExpected(message),
|
|
147
|
+
received: extractReceived(message),
|
|
148
|
+
timeoutMs: extractTimeoutMs(message),
|
|
149
|
+
lastUrl: extractLastUrl(message),
|
|
150
|
+
status
|
|
151
|
+
});
|
|
152
|
+
exports.parseFailureFacts = parseFailureFacts;
|
|
141
153
|
const describeFailure = (failure) => {
|
|
142
154
|
if (failure.signal === "assertion_mismatch" && failure.locator && failure.expected && failure.received) {
|
|
143
155
|
return `${failure.locator} showed "${failure.received}" instead of "${failure.expected}" before timeout.`;
|
|
@@ -179,9 +191,15 @@ const buildDebugSummary = (failure) => {
|
|
|
179
191
|
};
|
|
180
192
|
exports.buildDebugSummary = buildDebugSummary;
|
|
181
193
|
const buildSimilarityKey = (failure) => {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
194
|
+
if (failure.locator || failure.expected || failure.received) {
|
|
195
|
+
return [
|
|
196
|
+
failure.signal,
|
|
197
|
+
failure.locator || "unknown-locator",
|
|
198
|
+
failure.expected || "unknown-expected",
|
|
199
|
+
failure.received || "unknown-received"
|
|
200
|
+
].join("|");
|
|
201
|
+
}
|
|
202
|
+
return `${failure.signal}|${normalizeMessageFingerprint(failure.message)}`;
|
|
185
203
|
};
|
|
186
204
|
exports.buildSimilarityKey = buildSimilarityKey;
|
|
187
205
|
exports.summarizeSignal = signalSummary;
|