@sentinelqa/playwright-reporter 0.1.50 → 0.1.51
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/quickDiagnosis.d.ts +1 -1
- package/dist/quickDiagnosis.js +24 -35
- package/dist/reporter.js +41 -61
- package/dist/runHistory.d.ts +7 -0
- package/dist/runHistory.js +12 -1
- package/dist/terminalSummary.d.ts +0 -1
- package/dist/terminalSummary.js +15 -46
- package/package.json +1 -1
package/dist/quickDiagnosis.d.ts
CHANGED
package/dist/quickDiagnosis.js
CHANGED
|
@@ -860,31 +860,25 @@ const buildQuickDiagnosis = (playwrightJsonPath) => {
|
|
|
860
860
|
const failed = failures[0];
|
|
861
861
|
const suspects = commitWindow.trusted ? rankCommitsForFailure(failed, commitWindow) : [];
|
|
862
862
|
const top = suspects[0];
|
|
863
|
-
const
|
|
864
|
-
const lines = [`${formatTitle(failed.title)} failed.`, `What broke: ${(0, exports.describeFailure)(failed)}`];
|
|
863
|
+
const lines = [`Test: ${shortenTitle(failed.title)}`, `Likely cause: ${(0, exports.describeFailure)(failed)}`];
|
|
865
864
|
const primaryLocation = buildLocationLine(failed);
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
if (
|
|
869
|
-
lines.push(`
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
if (top) {
|
|
873
|
-
lines.push(compactCommitLine(top));
|
|
874
|
-
lines.push(`Why Sentinel picked it: ${compactWhyLine(top)}.`);
|
|
875
|
-
if (top.touchedFiles.length) {
|
|
876
|
-
lines.push(`Touched files: ${top.touchedFiles.map((file) => basename(file)).join(", ")}`);
|
|
865
|
+
const confidence = top ? confidenceLabel(top.score).toLowerCase() : "medium";
|
|
866
|
+
lines.push(`Confidence: ${confidence}`);
|
|
867
|
+
if (top && top.score >= 0.62) {
|
|
868
|
+
lines.push(`Likely introduced in: "${top.commit.message}"`);
|
|
869
|
+
if (top.reasons.length) {
|
|
870
|
+
lines.push(`Reason: ${compactWhyLine(top)}.`);
|
|
877
871
|
}
|
|
878
|
-
if (alt)
|
|
879
|
-
lines.push(alternateCommitLine(alt));
|
|
880
872
|
}
|
|
881
|
-
lines.push(
|
|
873
|
+
lines.push("Check first:");
|
|
874
|
+
lines.push(`- ${checkFirst(failed)}`);
|
|
875
|
+
if (primaryLocation)
|
|
876
|
+
lines.push(`Where: ${withoutPrefix(withoutPrefix(primaryLocation, "Error location:"), "Likely file:")}`);
|
|
877
|
+
if (failed.codeContext?.action)
|
|
878
|
+
lines.push(`Failing step: ${failed.codeContext.action}`);
|
|
882
879
|
return {
|
|
883
880
|
lines,
|
|
884
|
-
footer: [
|
|
885
|
-
...(top ? [`Confidence: ${confidenceLabel(top.score)}`] : []),
|
|
886
|
-
...(commitWindow.trusted ? [`Commits analyzed: ${commitWindow.commits.length}`] : [`Commit blame skipped: ${commitWindow.reason}`])
|
|
887
|
-
]
|
|
881
|
+
footer: []
|
|
888
882
|
};
|
|
889
883
|
}
|
|
890
884
|
const clusterMap = new Map();
|
|
@@ -916,29 +910,24 @@ const buildQuickDiagnosis = (playwrightJsonPath) => {
|
|
|
916
910
|
const locationValue = clusterLocation
|
|
917
911
|
? withoutPrefix(clusterLocation, clusterLocation.startsWith("Error locations:") ? "Error locations:" : "Error location:")
|
|
918
912
|
: null;
|
|
919
|
-
const rootCause = compactRootCauseSummary(cluster);
|
|
920
|
-
const errorLine = compactErrorLine(cluster.sample);
|
|
913
|
+
const rootCause = cluster.count === 1 ? (0, exports.describeFailure)(cluster.sample) : compactRootCauseSummary(cluster);
|
|
921
914
|
lines.push(`[${index + 1}] ${rootCauseLabel(cluster.sample)} (${cluster.count} tests)`);
|
|
922
|
-
lines.push(`
|
|
923
|
-
if (
|
|
924
|
-
lines.push(`
|
|
915
|
+
lines.push(` Likely cause: ${rootCause}`);
|
|
916
|
+
if (top && top.score >= 0.62) {
|
|
917
|
+
lines.push(` Likely introduced in: "${top.commit.message}"`);
|
|
918
|
+
if (top.reasons.length) {
|
|
919
|
+
lines.push(` Reason: ${compactWhyLine(top)}.`);
|
|
920
|
+
}
|
|
925
921
|
}
|
|
922
|
+
lines.push(` Check first: ${clusterCheckFirst(cluster)}.`);
|
|
926
923
|
if (locationValue) {
|
|
927
924
|
lines.push(` Where: ${locationValue}`);
|
|
928
925
|
}
|
|
929
|
-
|
|
930
|
-
lines.push(` Changed files: ${top.touchedFiles.map((file) => basename(file)).join(", ")}`);
|
|
931
|
-
}
|
|
932
|
-
lines.push(` Check first: ${clusterCheckFirst(cluster)}.`);
|
|
926
|
+
lines.push(` Shared impact: ${cluster.count} test${cluster.count === 1 ? "" : "s"} failing with ${cluster.count === 1 ? "this" : "same"} root cause`);
|
|
933
927
|
}
|
|
934
928
|
return {
|
|
935
929
|
lines,
|
|
936
|
-
footer: topCluster
|
|
937
|
-
? [
|
|
938
|
-
...(topCluster.suspects[0] ? [`Top confidence: ${confidenceLabel(topCluster.suspects[0].score)}`] : []),
|
|
939
|
-
...(commitWindow.trusted ? [`Commits analyzed: ${commitWindow.commits.length}`] : [`Commit blame skipped: ${commitWindow.reason}`])
|
|
940
|
-
]
|
|
941
|
-
: []
|
|
930
|
+
footer: topCluster?.suspects[0] ? [`Confidence: ${confidenceLabel(topCluster.suspects[0].score).toLowerCase()}`] : []
|
|
942
931
|
};
|
|
943
932
|
};
|
|
944
933
|
exports.buildQuickDiagnosis = buildQuickDiagnosis;
|
package/dist/reporter.js
CHANGED
|
@@ -14,6 +14,7 @@ const colorize = (value, code) => {
|
|
|
14
14
|
const green = (value) => colorize(value, "32");
|
|
15
15
|
const yellow = (value) => colorize(value, "33");
|
|
16
16
|
const dim = (value) => colorize(value, "2");
|
|
17
|
+
const divider = () => "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━";
|
|
17
18
|
const readFinalFailedCount = (playwrightJsonPath) => {
|
|
18
19
|
try {
|
|
19
20
|
const parsed = JSON.parse(require("node:fs").readFileSync(playwrightJsonPath, "utf8"));
|
|
@@ -94,21 +95,14 @@ class SentinelReporter {
|
|
|
94
95
|
console.log("");
|
|
95
96
|
if (passingSummary) {
|
|
96
97
|
console.log(green("Sentinel run summary"));
|
|
98
|
+
console.log(divider());
|
|
99
|
+
console.log("");
|
|
97
100
|
for (const line of passingSummary.lines) {
|
|
98
|
-
console.log(
|
|
99
|
-
}
|
|
100
|
-
if (passingSummary.risks.length > 0) {
|
|
101
|
-
console.log("");
|
|
102
|
-
console.log(yellow("Potential risks"));
|
|
103
|
-
for (const line of passingSummary.risks) {
|
|
104
|
-
console.log(` ${line}`);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
if (!hasWorkspaceToken) {
|
|
108
|
-
console.log("");
|
|
109
|
-
console.log("Activate a free workspace at sentinelqa.com/register");
|
|
101
|
+
console.log(line);
|
|
110
102
|
}
|
|
111
103
|
console.log("");
|
|
104
|
+
console.log(divider());
|
|
105
|
+
console.log("");
|
|
112
106
|
}
|
|
113
107
|
if (effectiveFailedCount > 0) {
|
|
114
108
|
(0, telemetry_1.emitFailedRunTelemetry)();
|
|
@@ -118,31 +112,38 @@ class SentinelReporter {
|
|
|
118
112
|
return;
|
|
119
113
|
}
|
|
120
114
|
if (hasWorkspaceToken && !hasCiEnv && !localUploadEnabled) {
|
|
121
|
-
console.log(
|
|
122
|
-
|
|
123
|
-
"Reason: Local workspace uploads require SENTINEL_UPLOAD_LOCAL=1.",
|
|
124
|
-
"",
|
|
125
|
-
"Next step:",
|
|
126
|
-
"- Set SENTINEL_UPLOAD_LOCAL=1 to allow a local workspace upload.",
|
|
127
|
-
"- Or remove SENTINEL_TOKEN to generate a free public hosted report instead."
|
|
128
|
-
].join("\n"));
|
|
115
|
+
console.log("Uploading debug report skipped");
|
|
116
|
+
console.log("Set SENTINEL_UPLOAD_LOCAL=1 for local workspace uploads.");
|
|
129
117
|
return;
|
|
130
118
|
}
|
|
131
|
-
if (
|
|
119
|
+
if (quickDiagnosis?.lines.length) {
|
|
120
|
+
console.log(yellow("Sentinel diagnosis"));
|
|
121
|
+
console.log(divider());
|
|
132
122
|
console.log("");
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
console.log("
|
|
123
|
+
if (failedRunHistory?.passStreakBeforeFailure && failedRunHistory.passStreakBeforeFailure > 0) {
|
|
124
|
+
console.log(`NEW FAILURE after ${failedRunHistory.passStreakBeforeFailure} passing runs`);
|
|
125
|
+
console.log("");
|
|
126
|
+
}
|
|
127
|
+
else if ((failedRunHistory?.recurringCount || 0) > 0) {
|
|
128
|
+
console.log(`RECURRING FAILURE (${failedRunHistory?.recurringCount} previous runs)`);
|
|
129
|
+
console.log("");
|
|
130
|
+
}
|
|
131
|
+
for (const line of quickDiagnosis.lines) {
|
|
132
|
+
console.log(line);
|
|
133
|
+
}
|
|
134
|
+
console.log("");
|
|
135
|
+
console.log(divider());
|
|
136
|
+
console.log("");
|
|
137
|
+
if (failedRunHistory?.newFailures && failedRunHistory.newFailures > 0) {
|
|
138
|
+
console.log(`Impact: ${failedRunHistory.newFailures} newly failing in this run`);
|
|
139
|
+
console.log("");
|
|
140
|
+
}
|
|
141
|
+
else if (failedRunHistory?.stillFailing && failedRunHistory.stillFailing > 0) {
|
|
142
|
+
console.log(`Impact: ${failedRunHistory.stillFailing} tests still failing`);
|
|
143
|
+
console.log("");
|
|
144
|
+
}
|
|
145
145
|
}
|
|
146
|
+
console.log("Uploading debug report...");
|
|
146
147
|
console.log("");
|
|
147
148
|
const upload = (await (0, node_1.runSentinelUpload)({
|
|
148
149
|
playwrightJsonPath: this.options.playwrightJsonPath,
|
|
@@ -159,43 +160,22 @@ class SentinelReporter {
|
|
|
159
160
|
if (upload.exitCode !== 0) {
|
|
160
161
|
throw new Error(`Sentinel upload failed with exit code ${upload.exitCode}`);
|
|
161
162
|
}
|
|
162
|
-
|
|
163
|
-
const diagnosis = backendReady ? upload.diagnosis : quickDiagnosis;
|
|
164
|
-
if (diagnosis?.lines.length) {
|
|
165
|
-
console.log("");
|
|
163
|
+
if (!quickDiagnosis?.lines.length && upload.diagnosis?.lines.length) {
|
|
166
164
|
console.log(yellow("Sentinel diagnosis"));
|
|
167
|
-
|
|
168
|
-
console.log(` ${dim(line)}`);
|
|
169
|
-
}
|
|
170
|
-
if (diagnosis.footer?.length) {
|
|
171
|
-
console.log("");
|
|
172
|
-
for (const line of diagnosis.footer) {
|
|
173
|
-
console.log(` ${dim(line)}`);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
const historyLines = failedRunHistory?.lines || [];
|
|
178
|
-
const normalizedHistory = backendReady
|
|
179
|
-
? historyLines.filter((line) => !line.includes("Seen before:"))
|
|
180
|
-
: historyLines;
|
|
181
|
-
if (normalizedHistory.length) {
|
|
165
|
+
console.log(divider());
|
|
182
166
|
console.log("");
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
console.log(` ${dim(line)}`);
|
|
167
|
+
for (const line of upload.diagnosis.lines) {
|
|
168
|
+
console.log(line);
|
|
186
169
|
}
|
|
170
|
+
console.log("");
|
|
171
|
+
console.log(divider());
|
|
187
172
|
}
|
|
188
173
|
console.log("");
|
|
189
|
-
console.log("
|
|
174
|
+
console.log("Debug report ready");
|
|
190
175
|
console.log(` ${upload.shareRunUrl || upload.internalRunUrl}`);
|
|
191
176
|
if (upload.shareLabel) {
|
|
192
177
|
console.log(` ${dim(upload.shareLabel)}`);
|
|
193
178
|
}
|
|
194
|
-
if (!hasWorkspaceToken) {
|
|
195
|
-
console.log("");
|
|
196
|
-
console.log("Create a free workspace to keep reports private, compare runs, and unlock deeper AI debugging");
|
|
197
|
-
console.log(` ${dim("https://sentinelqa.com/register")}`);
|
|
198
|
-
}
|
|
199
179
|
}
|
|
200
180
|
}
|
|
201
181
|
module.exports = SentinelReporter;
|
package/dist/runHistory.d.ts
CHANGED
|
@@ -5,6 +5,13 @@ type RunDiffSummary = {
|
|
|
5
5
|
};
|
|
6
6
|
export type FailedRunHistorySummary = {
|
|
7
7
|
lines: string[];
|
|
8
|
+
passStreakBeforeFailure: number;
|
|
9
|
+
previousWasGreen: boolean;
|
|
10
|
+
newFailures: number;
|
|
11
|
+
fixedTests: number;
|
|
12
|
+
stillFailing: number;
|
|
13
|
+
recurringCount: number;
|
|
14
|
+
recurringTitle: string | null;
|
|
8
15
|
};
|
|
9
16
|
export declare const buildRunDiffSummary: (playwrightJsonPath: string) => RunDiffSummary | null;
|
|
10
17
|
export declare const buildFailedRunHistorySummary: (playwrightJsonPath: string) => FailedRunHistorySummary | null;
|
package/dist/runHistory.js
CHANGED
|
@@ -221,6 +221,17 @@ const buildFailedRunHistorySummary = (playwrightJsonPath) => {
|
|
|
221
221
|
if (topRecurring) {
|
|
222
222
|
lines.push(`- Recurring across ${topRecurring.occurrences + 1} recorded failed runs in local history (${topRecurring.failure.title})`);
|
|
223
223
|
}
|
|
224
|
-
return lines.length
|
|
224
|
+
return lines.length
|
|
225
|
+
? {
|
|
226
|
+
lines,
|
|
227
|
+
passStreakBeforeFailure,
|
|
228
|
+
previousWasGreen: Boolean(previousRun && (previousRun.failedCount || 0) === 0),
|
|
229
|
+
newFailures,
|
|
230
|
+
fixedTests,
|
|
231
|
+
stillFailing,
|
|
232
|
+
recurringCount: topRecurring ? topRecurring.occurrences : 0,
|
|
233
|
+
recurringTitle: topRecurring?.failure.title || null
|
|
234
|
+
}
|
|
235
|
+
: null;
|
|
225
236
|
};
|
|
226
237
|
exports.buildFailedRunHistorySummary = buildFailedRunHistorySummary;
|
package/dist/terminalSummary.js
CHANGED
|
@@ -343,56 +343,25 @@ const buildPassingRunSummary = (playwrightJsonPath, options) => {
|
|
|
343
343
|
const hasActiveRisk = flakyLookingCount > 0;
|
|
344
344
|
const totalDurationMs = snapshot.tests.reduce((sum, test) => sum + test.durationMs, 0);
|
|
345
345
|
const displayedRunDurationMs = options?.observedRunDurationMs || snapshot.wallDurationMs || totalDurationMs;
|
|
346
|
-
const lines = [
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
lines.push(`- Flaky-looking tests: ${flakyLookingCount}`);
|
|
353
|
-
if (topNearFailures[0]) {
|
|
354
|
-
lines.push(`- Most at risk next: ${topNearFailures[0].title} (${topNearFailures[0].primaryReason})`);
|
|
355
|
-
}
|
|
356
|
-
if (passStreak > 1)
|
|
357
|
-
lines.push(`- Pass streak: ${passStreak} runs`);
|
|
346
|
+
const lines = ["All tests passed", `${snapshot.passedCount} tests in ${formatDuration(displayedRunDurationMs)}`];
|
|
347
|
+
if (hasActiveRisk && topNearFailures[0]) {
|
|
348
|
+
lines.push(`At risk: ${topNearFailures[0].title} ${topNearFailures[0].primaryReason}`);
|
|
349
|
+
lines.push(topNearFailures[0].retries > 0 || topNearFailures[0].historicalRetries >= 2
|
|
350
|
+
? "Why this matters: Similar timing and retry patterns often turn into flaky failures"
|
|
351
|
+
: "Why this matters: Performance regressions often lead to flaky failures");
|
|
358
352
|
if (lastFailureRunsAgo !== null)
|
|
359
|
-
lines.push(
|
|
353
|
+
lines.push(`Last failure: ${lastFailureRunsAgo} runs ago`);
|
|
354
|
+
lines.push(topNearFailures[0].retries > 0
|
|
355
|
+
? "Recommendation: inspect retry behavior or timing around this test"
|
|
356
|
+
: "Recommendation: monitor the next runs or investigate the slowdown");
|
|
360
357
|
}
|
|
361
358
|
else {
|
|
362
|
-
lines.push(
|
|
363
|
-
if (passStreak > 1)
|
|
364
|
-
lines.push(`- Pass streak: ${passStreak} runs`);
|
|
359
|
+
lines.push("No anomalies detected");
|
|
365
360
|
if (lastFailureRunsAgo !== null)
|
|
366
|
-
lines.push(
|
|
367
|
-
|
|
368
|
-
lines.push(`- Sentinel active: traces, screenshots, video enabled`);
|
|
369
|
-
const risks = [];
|
|
370
|
-
for (const candidate of topNearFailures) {
|
|
371
|
-
if (candidate.ratio && candidate.ratio >= 1.8) {
|
|
372
|
-
risks.push(`- ${candidate.title} took ${formatShortDuration(candidate.currentDurationMs)} vs ${formatShortDuration(candidate.medianDurationMs)} recent median (${candidate.ratio.toFixed(1)}x)`);
|
|
373
|
-
}
|
|
374
|
-
if (candidate.retries > 0) {
|
|
375
|
-
risks.push(`- ${candidate.title} passed after ${candidate.retries} retr${candidate.retries === 1 ? 'y' : 'ies'}`);
|
|
376
|
-
}
|
|
377
|
-
if (candidate.historicalRetries >= 2) {
|
|
378
|
-
risks.push(`- ${candidate.title} has needed retries in ${candidate.historicalRetries} of the last 10 passing runs`);
|
|
379
|
-
}
|
|
380
|
-
if (candidate.repeatedSlowPasses >= 3) {
|
|
381
|
-
risks.push(`- ${candidate.title} has been unusually slow in ${candidate.repeatedSlowPasses} of the last 10 passing runs`);
|
|
382
|
-
}
|
|
383
|
-
if (candidate.recentTrendRatio && candidate.recentTrendRatio >= 1.5) {
|
|
384
|
-
risks.push(`- ${candidate.title} is trending slower over recent runs`);
|
|
385
|
-
}
|
|
386
|
-
if (candidate.timeoutUtilization && candidate.timeoutUtilization >= 0.65) {
|
|
387
|
-
risks.push(`- ${candidate.title} used ${(candidate.timeoutUtilization * 100).toFixed(0)}% of its timeout budget`);
|
|
388
|
-
}
|
|
389
|
-
if (candidate.historicalFailures >= 5 &&
|
|
390
|
-
lastFailureRunsAgo !== null &&
|
|
391
|
-
lastFailureRunsAgo <= 3 &&
|
|
392
|
-
(candidate.currentRunSignal || candidate.historicalRetries >= 2 || candidate.repeatedSlowPasses >= 3)) {
|
|
393
|
-
risks.push(`- ${candidate.title} failed in ${candidate.historicalFailures} of the last 10 runs`);
|
|
394
|
-
}
|
|
361
|
+
lines.push(`Last failure: ${lastFailureRunsAgo} runs ago`);
|
|
362
|
+
lines.push("Status: Stable");
|
|
395
363
|
}
|
|
396
|
-
|
|
364
|
+
lines.push("Artifacts ready: traces, screenshots, video");
|
|
365
|
+
return { lines };
|
|
397
366
|
};
|
|
398
367
|
exports.buildPassingRunSummary = buildPassingRunSummary;
|