playwright-e2e-mcp 0.1.0

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.
Files changed (67) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +497 -0
  3. package/dist/http.d.ts +27 -0
  4. package/dist/http.js +156 -0
  5. package/dist/http.js.map +1 -0
  6. package/dist/index.d.ts +12 -0
  7. package/dist/index.js +56 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/server.d.ts +23 -0
  10. package/dist/server.js +225 -0
  11. package/dist/server.js.map +1 -0
  12. package/dist/tools/compare-visual-state.d.ts +77 -0
  13. package/dist/tools/compare-visual-state.js +223 -0
  14. package/dist/tools/compare-visual-state.js.map +1 -0
  15. package/dist/tools/diagnose-flaky.d.ts +87 -0
  16. package/dist/tools/diagnose-flaky.js +258 -0
  17. package/dist/tools/diagnose-flaky.js.map +1 -0
  18. package/dist/tools/generate-e2e-test.d.ts +51 -0
  19. package/dist/tools/generate-e2e-test.js +274 -0
  20. package/dist/tools/generate-e2e-test.js.map +1 -0
  21. package/dist/tools/get-failure.d.ts +26 -0
  22. package/dist/tools/get-failure.js +220 -0
  23. package/dist/tools/get-failure.js.map +1 -0
  24. package/dist/tools/inspect-page.d.ts +57 -0
  25. package/dist/tools/inspect-page.js +124 -0
  26. package/dist/tools/inspect-page.js.map +1 -0
  27. package/dist/tools/list-tests.d.ts +35 -0
  28. package/dist/tools/list-tests.js +88 -0
  29. package/dist/tools/list-tests.js.map +1 -0
  30. package/dist/tools/run-test.d.ts +70 -0
  31. package/dist/tools/run-test.js +169 -0
  32. package/dist/tools/run-test.js.map +1 -0
  33. package/dist/tools/shared.d.ts +86 -0
  34. package/dist/tools/shared.js +490 -0
  35. package/dist/tools/shared.js.map +1 -0
  36. package/dist/tools/validate-selector.d.ts +32 -0
  37. package/dist/tools/validate-selector.js +93 -0
  38. package/dist/tools/validate-selector.js.map +1 -0
  39. package/dist/types/index.d.ts +344 -0
  40. package/dist/types/index.js +51 -0
  41. package/dist/types/index.js.map +1 -0
  42. package/dist/utils/change-analyzer.d.ts +46 -0
  43. package/dist/utils/change-analyzer.js +290 -0
  44. package/dist/utils/change-analyzer.js.map +1 -0
  45. package/dist/utils/image-diff.d.ts +61 -0
  46. package/dist/utils/image-diff.js +373 -0
  47. package/dist/utils/image-diff.js.map +1 -0
  48. package/dist/utils/logger.d.ts +21 -0
  49. package/dist/utils/logger.js +132 -0
  50. package/dist/utils/logger.js.map +1 -0
  51. package/dist/utils/path-utils.d.ts +55 -0
  52. package/dist/utils/path-utils.js +211 -0
  53. package/dist/utils/path-utils.js.map +1 -0
  54. package/dist/utils/playwright-runner.d.ts +63 -0
  55. package/dist/utils/playwright-runner.js +594 -0
  56. package/dist/utils/playwright-runner.js.map +1 -0
  57. package/dist/utils/project-detector.d.ts +43 -0
  58. package/dist/utils/project-detector.js +255 -0
  59. package/dist/utils/project-detector.js.map +1 -0
  60. package/dist/utils/report-parser.d.ts +60 -0
  61. package/dist/utils/report-parser.js +501 -0
  62. package/dist/utils/report-parser.js.map +1 -0
  63. package/dist/utils/trace-reader.d.ts +109 -0
  64. package/dist/utils/trace-reader.js +608 -0
  65. package/dist/utils/trace-reader.js.map +1 -0
  66. package/examples/sample-test.spec.ts +49 -0
  67. package/package.json +66 -0
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Tool 7 — compare-visual-state: the blueprint's visual regression tool.
3
+ *
4
+ * Capture a screenshot of a page (or one element), compare it against a
5
+ * stored baseline, and report *what* changed: how many pixels, where
6
+ * (merged regions), and the average color shift — e.g. "the changed
7
+ * area went from blue rgb(37,99,235) to red rgb(220,38,38)".
8
+ */
9
+ import { z } from 'zod';
10
+ import { mkdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
11
+ import { dirname } from 'node:path';
12
+ import { PlaywrightMcpError } from '../types/index.js';
13
+ import { decodePng, diffImages, encodePng } from '../utils/image-diff.js';
14
+ import { relativeToRoot, resolvePath, tempFilePath } from '../utils/path-utils.js';
15
+ import { diagnoseOutput } from '../utils/report-parser.js';
16
+ import { assertHttpUrl, guard, resolveProjectRoot, runBrowserScript, toolError, toolText, } from './shared.js';
17
+ const compareInput = z.object({
18
+ url: z.string().describe('Full URL of the page (or element target) to capture'),
19
+ name: z
20
+ .string()
21
+ .min(1)
22
+ .max(64)
23
+ .regex(/^[A-Za-z0-9._-]+$/, 'letters, digits, dot, underscore, dash only')
24
+ .describe('Baseline id, e.g. "checkout-page" or "submit-button"'),
25
+ projectRoot: z.string().optional().describe('Project directory; defaults to the server working directory'),
26
+ selector: z.string().optional().describe('Capture only this element instead of the whole page'),
27
+ fullPage: z.boolean().optional().describe('Capture the full scrollable page (default false)'),
28
+ waitUntil: z.enum(['load', 'domcontentloaded', 'networkidle']).optional(),
29
+ waitFor: z.string().optional().describe('Wait for this selector before capturing'),
30
+ timeoutMs: z.number().int().min(1_000).max(300_000).optional(),
31
+ action: z
32
+ .enum(['compare', 'baseline'])
33
+ .optional()
34
+ .describe("'baseline' overwrites the stored baseline; 'compare' (default) compares against it"),
35
+ tolerance: z
36
+ .number()
37
+ .min(0)
38
+ .max(100)
39
+ .optional()
40
+ .describe('Percent of pixels that may differ before reporting a change (default 0.1)'),
41
+ pixelThreshold: z
42
+ .number()
43
+ .int()
44
+ .min(1)
45
+ .max(765)
46
+ .optional()
47
+ .describe('Combined per-pixel channel delta considered different (default 60)'),
48
+ });
49
+ export const compareVisualStateSchema = compareInput;
50
+ const COLOR_NAMES = [
51
+ { name: 'white', rgb: { r: 255, g: 255, b: 255 } },
52
+ { name: 'black', rgb: { r: 0, g: 0, b: 0 } },
53
+ { name: 'red', rgb: { r: 220, g: 38, b: 38 } },
54
+ { name: 'orange', rgb: { r: 234, g: 88, b: 12 } },
55
+ { name: 'yellow', rgb: { r: 234, g: 179, b: 8 } },
56
+ { name: 'green', rgb: { r: 22, g: 163, b: 74 } },
57
+ { name: 'cyan', rgb: { r: 8, g: 145, b: 178 } },
58
+ { name: 'blue', rgb: { r: 37, g: 99, b: 235 } },
59
+ { name: 'purple', rgb: { r: 126, g: 34, b: 206 } },
60
+ { name: 'pink', rgb: { r: 219, g: 39, b: 119 } },
61
+ { name: 'brown', rgb: { r: 120, g: 53, b: 15 } },
62
+ { name: 'gray', rgb: { r: 120, g: 113, b: 108 } },
63
+ ];
64
+ function colorName(rgb) {
65
+ let best = COLOR_NAMES[0];
66
+ let bestDistance = Number.POSITIVE_INFINITY;
67
+ for (const candidate of COLOR_NAMES) {
68
+ const distance = (rgb.r - candidate.rgb.r) ** 2 + (rgb.g - candidate.rgb.g) ** 2 + (rgb.b - candidate.rgb.b) ** 2;
69
+ if (distance < bestDistance) {
70
+ bestDistance = distance;
71
+ best = candidate;
72
+ }
73
+ }
74
+ return best.name;
75
+ }
76
+ function rgbText(rgb) {
77
+ return `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`;
78
+ }
79
+ async function exists(p) {
80
+ return stat(p).then((info) => info.isFile(), () => false);
81
+ }
82
+ function decodeScreenshot(buf) {
83
+ try {
84
+ return decodePng(buf);
85
+ }
86
+ catch (err) {
87
+ throw new PlaywrightMcpError(`The screenshot could not be decoded: ${err instanceof Error ? err.message : String(err)}`, 'UNKNOWN', { hint: 'Playwright produced an unexpected image format; re-run the capture.' });
88
+ }
89
+ }
90
+ export const compareVisualStateTool = {
91
+ name: 'compare-visual-state',
92
+ description: 'Visual regression check: screenshot a live page (or element), compare it to a stored baseline, and report how many pixels changed, WHERE they changed (bounding regions), and the average color shift ("blue → red"). First call saves the baseline; later calls compare. Writes a red-highlighted diff image for review.',
93
+ inputSchema: compareVisualStateSchema,
94
+ handler: async (args, ctx) => guard('compare-visual-state', async () => {
95
+ const root = await resolveProjectRoot(args.projectRoot, ctx);
96
+ const url = assertHttpUrl(args.url);
97
+ const tolerance = args.tolerance ?? 0.1;
98
+ const timeoutMs = args.timeoutMs ?? 45_000;
99
+ const visualDir = resolvePath(root, '.pw-mcp/visual');
100
+ const baselinePath = resolvePath(visualDir, `${args.name}.png`);
101
+ const metaPath = resolvePath(visualDir, `${args.name}.json`);
102
+ // 1. Capture a fresh screenshot.
103
+ const shotPath = tempFilePath('pw-shot', '.png');
104
+ try {
105
+ const outcome = await runBrowserScript({
106
+ mode: 'screenshot',
107
+ projectRoot: root,
108
+ url,
109
+ selector: args.selector,
110
+ waitFor: args.waitFor,
111
+ waitUntil: args.waitUntil,
112
+ includeHtml: false,
113
+ fullPage: args.fullPage,
114
+ screenshotPath: shotPath,
115
+ gotoTimeout: Math.max(5_000, timeoutMs - 8_000),
116
+ waitTimeout: Math.min(10_000, Math.max(3_000, timeoutMs - 8_000)),
117
+ startedAt: Date.now(),
118
+ }, { timeoutMs, signal: ctx.signal });
119
+ if (!outcome.ok) {
120
+ const diagnosis = outcome.stderrTail ? diagnoseOutput(outcome.stderrTail) : null;
121
+ return toolError(outcome.kind ?? 'UNKNOWN', `Could not capture ${url}: ${outcome.error ?? 'unknown error'}`, outcome.hint ?? diagnosis?.hint, outcome.stderrTail);
122
+ }
123
+ const shot = await readFile(shotPath);
124
+ const current = decodeScreenshot(shot);
125
+ const meta = {
126
+ url,
127
+ savedAt: new Date().toISOString(),
128
+ width: current.width,
129
+ height: current.height,
130
+ selector: args.selector,
131
+ };
132
+ // 2. First call (or action: 'baseline') stores the baseline.
133
+ const hasBaseline = await exists(baselinePath);
134
+ if (args.action === 'baseline' || !hasBaseline) {
135
+ try {
136
+ await mkdir(dirname(baselinePath), { recursive: true });
137
+ await writeFile(baselinePath, shot);
138
+ await writeFile(metaPath, JSON.stringify(meta, null, 2), 'utf8');
139
+ }
140
+ catch (err) {
141
+ const code = err.code;
142
+ const kind = code === 'ENOSPC' ? 'DISK_FULL' : 'UNKNOWN';
143
+ const diagnosis = diagnoseOutput(`${code ?? ''} ${err instanceof Error ? err.message : ''}`);
144
+ throw new PlaywrightMcpError(`Could not save the baseline at ${relativeToRoot(root, baselinePath)}`, kind, {
145
+ hint: diagnosis?.hint ??
146
+ 'Check that the project directory is writable and has free space.',
147
+ });
148
+ }
149
+ const verb = args.action === 'baseline' ? 'overwritten' : 'created';
150
+ return toolText([
151
+ `## 📸 Baseline ${verb} — "${args.name}"`,
152
+ '',
153
+ `**URL:** ${url}`,
154
+ `**Size:** ${current.width}×${current.height}${args.selector ? ` (element: \`${args.selector}\`)` : ''}`,
155
+ `**Saved:** \`${relativeToRoot(root, baselinePath)}\``,
156
+ '',
157
+ hasBaseline && args.action === 'baseline'
158
+ ? '_Future `compare` calls will diff against this new baseline._'
159
+ : '_Make your UI change, then call compare-visual-state again with the same `name` (and `action: "compare"` or omitted)._',
160
+ ].join('\n'));
161
+ }
162
+ // 3. Compare against the baseline.
163
+ const baselinePng = await readFile(baselinePath);
164
+ const baseline = decodeScreenshot(baselinePng);
165
+ const baselineMeta = await readFile(metaPath, 'utf8')
166
+ .then((text) => JSON.parse(text))
167
+ .catch(() => null);
168
+ const result = diffImages(baseline, current, { pixelThreshold: args.pixelThreshold });
169
+ const lines = [];
170
+ if (result.diffPixels === 0 || result.percent <= tolerance) {
171
+ lines.push(`## ✅ No visual change — "${args.name}"`, '', `**Changed:** 0 pixel(s)${result.diffPixels > 0 ? ` (${result.percent}% — within tolerance ${tolerance}%)` : ''}`, `**Size:** ${result.width}×${result.height} · **Baseline:** ${baselineMeta?.savedAt ?? 'unknown date'}`, '', '_Nothing worth flagging — the render matches the baseline._');
172
+ return toolText(lines.join('\n'));
173
+ }
174
+ // Write the annotated diff image for review.
175
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-');
176
+ const diffPath = resolvePath(visualDir, `diffs/${args.name}-${stamp}.png`);
177
+ try {
178
+ await mkdir(dirname(diffPath), { recursive: true });
179
+ await writeFile(diffPath, encodePng(result.diffImage));
180
+ }
181
+ catch {
182
+ // Diff image is a nice-to-have; the numbers below still stand.
183
+ }
184
+ lines.push(`## ⚠️ Visual change detected — "${args.name}"`, '');
185
+ lines.push(`**URL:** ${url}`);
186
+ lines.push(`**Changed:** ${result.percent}% of pixels (${result.diffPixels.toLocaleString('en-US')} px) — tolerance ${tolerance}%`);
187
+ lines.push(`**Size:** baseline ${baseline.width}×${baseline.height} → now ${current.width}×${current.height}`);
188
+ if (result.dimensionsChanged) {
189
+ lines.push('> ⚠️ Dimensions changed between baseline and capture — layout shifted.');
190
+ }
191
+ if (baselineMeta && baselineMeta.url !== url) {
192
+ lines.push(`> ⚠️ This baseline was captured for a different URL (${baselineMeta.url}) on ${baselineMeta.savedAt}.`);
193
+ }
194
+ lines.push('');
195
+ if (result.regions.length > 0) {
196
+ lines.push('### Changed regions (largest first)', '');
197
+ for (const region of result.regions) {
198
+ lines.push(`- (${region.x}, ${region.y}) ${region.width}×${region.height} — ${region.pixels.toLocaleString('en-US')} px`);
199
+ }
200
+ lines.push('');
201
+ }
202
+ if (result.beforeAvg && result.afterAvg) {
203
+ const beforeName = colorName(result.beforeAvg);
204
+ const afterName = colorName(result.afterAvg);
205
+ const shift = beforeName === afterName
206
+ ? `stayed ${afterName} but shifted in shade`
207
+ : `${beforeName} → ${afterName}`;
208
+ lines.push('### Color shift in changed area', '');
209
+ lines.push(`- Average: ${rgbText(result.beforeAvg)} (${beforeName}) → ${rgbText(result.afterAvg)} (${afterName})`);
210
+ lines.push(`- **${shift}**`);
211
+ lines.push('');
212
+ }
213
+ lines.push(`**Diff image:** \`${relativeToRoot(root, diffPath)}\` (gray = unchanged, red = changed)`, '');
214
+ lines.push('### Next steps', '');
215
+ lines.push('1. If the change is intentional, re-baseline: `compare-visual-state` with `action: "baseline"`.', '2. If not, review the CSS/markdown files you touched — the regions above point at the affected area.', '3. Re-run the affected E2E tests with `run-test` to confirm behavior still matches.');
216
+ return toolText(lines.join('\n'));
217
+ }
218
+ finally {
219
+ await rm(shotPath, { force: true }).catch(() => undefined);
220
+ }
221
+ }),
222
+ };
223
+ //# sourceMappingURL=compare-visual-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compare-visual-state.js","sourceRoot":"","sources":["../../src/tools/compare-visual-state.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Tool 8 — diagnose-flaky: run a failing test several times with
3
+ * retries disabled and decide — from evidence — whether it is
4
+ * genuinely flaky, consistently broken, or no longer reproducible.
5
+ *
6
+ * The verdict logic is a pure function (exported for unit tests).
7
+ */
8
+ import { z } from 'zod';
9
+ import type { TestFailure, ToolContext, ToolResponse } from '../types/index.js';
10
+ declare const diagnoseFlakyInput: z.ZodObject<{
11
+ projectRoot: z.ZodOptional<z.ZodString>;
12
+ testFiles: z.ZodOptional<z.ZodArray<z.ZodString>>;
13
+ runs: z.ZodOptional<z.ZodNumber>;
14
+ browser: z.ZodOptional<z.ZodEnum<{
15
+ chromium: "chromium";
16
+ firefox: "firefox";
17
+ webkit: "webkit";
18
+ }>>;
19
+ headed: z.ZodOptional<z.ZodBoolean>;
20
+ workers: z.ZodOptional<z.ZodNumber>;
21
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
22
+ config: z.ZodOptional<z.ZodString>;
23
+ }, z.core.$strip>;
24
+ export type DiagnoseFlakyInput = z.infer<typeof diagnoseFlakyInput>;
25
+ export declare const diagnoseFlakySchema: z.ZodObject<{
26
+ projectRoot: z.ZodOptional<z.ZodString>;
27
+ testFiles: z.ZodOptional<z.ZodArray<z.ZodString>>;
28
+ runs: z.ZodOptional<z.ZodNumber>;
29
+ browser: z.ZodOptional<z.ZodEnum<{
30
+ chromium: "chromium";
31
+ firefox: "firefox";
32
+ webkit: "webkit";
33
+ }>>;
34
+ headed: z.ZodOptional<z.ZodBoolean>;
35
+ workers: z.ZodOptional<z.ZodNumber>;
36
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
37
+ config: z.ZodOptional<z.ZodString>;
38
+ }, z.core.$strip>;
39
+ export interface FlakyRunOutcome {
40
+ ok: boolean;
41
+ durationMs: number;
42
+ /** Normalized error signature of the first failure, when one occurred. */
43
+ signature?: string;
44
+ /** First line of the failure message, for display. */
45
+ headline?: string;
46
+ /** No tests executed (bad filter, empty suite, crashed reporter). */
47
+ noTests?: boolean;
48
+ /** Run-level error (timeout, spawn failure…) rather than a test failure. */
49
+ errorKind?: string;
50
+ }
51
+ export type FlakyVerdict = 'NOT REPRODUCING' | 'FLAKY' | 'CONSISTENTLY FAILING' | 'NO TESTS RAN';
52
+ export interface FlakySummary {
53
+ verdict: FlakyVerdict;
54
+ passed: number;
55
+ failed: number;
56
+ /** Number of distinct normalized error signatures across failed runs. */
57
+ distinctSignatures: number;
58
+ /** True when every failed run failed with the same signature. */
59
+ sameError: boolean;
60
+ }
61
+ /**
62
+ * Normalize a failure message into a comparable signature: first line,
63
+ * lowercased, digits folded, whitespace collapsed.
64
+ */
65
+ export declare function failureSignature(failure: TestFailure | undefined): string | undefined;
66
+ /** Decide the verdict from the per-run outcomes. */
67
+ export declare function summarizeFlaky(runs: FlakyRunOutcome[]): FlakySummary;
68
+ export declare const diagnoseFlakyTool: {
69
+ name: string;
70
+ description: string;
71
+ inputSchema: z.ZodObject<{
72
+ projectRoot: z.ZodOptional<z.ZodString>;
73
+ testFiles: z.ZodOptional<z.ZodArray<z.ZodString>>;
74
+ runs: z.ZodOptional<z.ZodNumber>;
75
+ browser: z.ZodOptional<z.ZodEnum<{
76
+ chromium: "chromium";
77
+ firefox: "firefox";
78
+ webkit: "webkit";
79
+ }>>;
80
+ headed: z.ZodOptional<z.ZodBoolean>;
81
+ workers: z.ZodOptional<z.ZodNumber>;
82
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
83
+ config: z.ZodOptional<z.ZodString>;
84
+ }, z.core.$strip>;
85
+ handler: (args: DiagnoseFlakyInput, ctx: ToolContext) => Promise<ToolResponse>;
86
+ };
87
+ export {};
@@ -0,0 +1,258 @@
1
+ /**
2
+ * Tool 8 — diagnose-flaky: run a failing test several times with
3
+ * retries disabled and decide — from evidence — whether it is
4
+ * genuinely flaky, consistently broken, or no longer reproducible.
5
+ *
6
+ * The verdict logic is a pure function (exported for unit tests).
7
+ */
8
+ import { z } from 'zod';
9
+ import { PlaywrightMcpError } from '../types/index.js';
10
+ import { detectProject, missingPlaywrightMessage } from '../utils/project-detector.js';
11
+ import { runTests } from '../utils/playwright-runner.js';
12
+ import { relativeToRoot } from '../utils/path-utils.js';
13
+ import { clipLines, formatDuration, guard, resolveConfigSelection, resolveProjectRoot, sanitizeTestPathArgument, testPathExists, toolText, } from './shared.js';
14
+ const diagnoseFlakyInput = z.object({
15
+ projectRoot: z.string().optional().describe('Project directory; defaults to the server working directory'),
16
+ testFiles: z
17
+ .array(z.string())
18
+ .optional()
19
+ .describe('Tests to diagnose ("file:line" supported). Defaults to the tests that failed in the most recent run.'),
20
+ runs: z
21
+ .number()
22
+ .int()
23
+ .min(2)
24
+ .max(10)
25
+ .optional()
26
+ .describe('How many times to run the tests (default 3)'),
27
+ browser: z
28
+ .enum(['chromium', 'firefox', 'webkit'])
29
+ .optional()
30
+ .describe('Playwright project name to run'),
31
+ headed: z.boolean().optional().describe('Run with a visible browser window'),
32
+ workers: z.number().int().min(1).max(64).optional(),
33
+ timeoutMs: z
34
+ .number()
35
+ .int()
36
+ .min(1_000)
37
+ .max(3_600_000)
38
+ .optional()
39
+ .describe('Hard wall-clock limit per run (default 120000)'),
40
+ config: z
41
+ .string()
42
+ .optional()
43
+ .describe('playwright.config path or 1-based index when the project has several configs'),
44
+ });
45
+ export const diagnoseFlakySchema = diagnoseFlakyInput;
46
+ /**
47
+ * Normalize a failure message into a comparable signature: first line,
48
+ * lowercased, digits folded, whitespace collapsed.
49
+ */
50
+ export function failureSignature(failure) {
51
+ if (!failure)
52
+ return undefined;
53
+ const firstLine = (failure.message ?? '').split('\n')[0] ?? '';
54
+ const normalized = firstLine
55
+ .toLowerCase()
56
+ .replace(/\d+/g, '#')
57
+ .replace(/\s+/g, ' ')
58
+ .trim()
59
+ .slice(0, 200);
60
+ return normalized === '' ? undefined : normalized;
61
+ }
62
+ /** Decide the verdict from the per-run outcomes. */
63
+ export function summarizeFlaky(runs) {
64
+ const ranAnything = runs.some((r) => !r.noTests);
65
+ const passed = runs.filter((r) => r.ok && !r.noTests).length;
66
+ const failed = runs.filter((r) => !r.ok && !r.noTests).length;
67
+ const signatures = new Set(runs.map((r) => r.signature).filter((s) => s !== undefined));
68
+ let verdict;
69
+ if (!ranAnything)
70
+ verdict = 'NO TESTS RAN';
71
+ else if (failed === 0)
72
+ verdict = 'NOT REPRODUCING';
73
+ else if (passed === 0)
74
+ verdict = 'CONSISTENTLY FAILING';
75
+ else
76
+ verdict = 'FLAKY';
77
+ return {
78
+ verdict,
79
+ passed,
80
+ failed,
81
+ distinctSignatures: signatures.size,
82
+ sameError: signatures.size <= 1,
83
+ };
84
+ }
85
+ /* ------------------------------------------------------------------ */
86
+ /* Rendering */
87
+ /* ------------------------------------------------------------------ */
88
+ function verdictBlock(summary) {
89
+ const { verdict } = summary;
90
+ const total = summary.passed + summary.failed;
91
+ switch (verdict) {
92
+ case 'CONSISTENTLY FAILING':
93
+ return [
94
+ `> ❌ **CONSISTENTLY FAILING** — failed all ${total} run(s)${summary.sameError ? ' with the same error' : `, but the errors differ (${summary.distinctSignatures} distinct signatures)`}. This is **not flaky**: there is a real, reproducible bug. Use \`get-failure\` for the full trace and fix it.`,
95
+ ].join('\n');
96
+ case 'FLAKY':
97
+ return [
98
+ `> ⚠️ **FLAKY** — ${summary.passed} of ${total} run(s) passed${summary.sameError
99
+ ? ' and the failures share one signature (a real intermittent bug, not random noise)'
100
+ : ` and failures vary (${summary.distinctSignatures} distinct signatures — often timing/environment instability)`}. Quarantine or stabilize it: re-run under load, check for missing \`await\`s, race conditions and shared state.`,
101
+ ].join('\n');
102
+ case 'NOT REPRODUCING':
103
+ return [
104
+ `> ✅ **NOT REPRODUCING** — passed all ${total} run(s). The failure you saw was one-off (transient environment, network, or a change already fixed it). Re-run the full suite with \`run-test\` to confirm.`,
105
+ ].join('\n');
106
+ case 'NO TESTS RAN':
107
+ default:
108
+ return [
109
+ '> ⚠️ **NO TESTS RAN** — the filter matched nothing (or the run crashed before executing). Check `testFiles` with `list-tests`.',
110
+ ].join('\n');
111
+ }
112
+ }
113
+ function renderDiagnosis(target, runs, summary, runsRequested) {
114
+ const lines = [
115
+ `## Flaky diagnosis — ${target.map((t) => `\`${t}\``).join(', ')} × ${runsRequested} runs`,
116
+ '',
117
+ '**Mode:** Playwright retries disabled (`--retries=0`, no auto-retry) so each run is honest evidence.',
118
+ '',
119
+ '| run | status | duration | first failure |',
120
+ '| ---: | --- | --- | --- |',
121
+ ];
122
+ runs.forEach((run, i) => {
123
+ const status = run.noTests
124
+ ? '∅ no tests'
125
+ : run.ok
126
+ ? '✅ passed'
127
+ : run.errorKind
128
+ ? `🛑 ${run.errorKind}`
129
+ : '❌ failed';
130
+ const failure = run.headline ? clipLines(run.headline, 1, 160).replace(/\|/g, '\\|') : '—';
131
+ lines.push(`| ${i + 1} | ${status} | ${formatDuration(run.durationMs)} | ${failure} |`);
132
+ });
133
+ lines.push('', verdictBlock(summary), '');
134
+ if (summary.verdict === 'FLAKY' || summary.verdict === 'CONSISTENTLY FAILING') {
135
+ lines.push('**Next steps:**', '');
136
+ lines.push('1. `get-failure` for the full error, DOM snapshot and failed requests of the last run.');
137
+ lines.push('2. Re-run only these tests after your fix: `run-test` with `testFiles` and `lastFailed: true`.');
138
+ if (summary.verdict === 'FLAKY') {
139
+ lines.push('3. Diagnose again with `runs: 5` for stronger evidence before quarantining the test.');
140
+ }
141
+ lines.push('');
142
+ }
143
+ return lines.join('\n');
144
+ }
145
+ /* ------------------------------------------------------------------ */
146
+ /* Tool */
147
+ /* ------------------------------------------------------------------ */
148
+ function ranNoTests(result) {
149
+ const s = result.stats;
150
+ return (s !== null &&
151
+ s.expected === 0 &&
152
+ s.unexpected === 0 &&
153
+ s.flaky === 0 &&
154
+ s.skipped === 0);
155
+ }
156
+ export const diagnoseFlakyTool = {
157
+ name: 'diagnose-flaky',
158
+ description: 'Run a failing test multiple times with retries disabled and return an evidence-based verdict: CONSISTENTLY FAILING (real bug), FLAKY (intermittent — includes pass/fail counts and error-signature variance) or NOT REPRODUCING (one-off). Defaults to the tests that failed in the most recent run.',
159
+ inputSchema: diagnoseFlakySchema,
160
+ handler: async (args, ctx) => guard('diagnose-flaky', async () => {
161
+ const root = await resolveProjectRoot(args.projectRoot, ctx);
162
+ const detection = await detectProject(root);
163
+ if (!detection.hasPlaywright) {
164
+ const { message, hint } = missingPlaywrightMessage(detection);
165
+ return toolText([`## Flaky diagnosis unavailable`, '', `**Error:** ${message}`, '', `> ${hint}`].join('\n'));
166
+ }
167
+ const configPath = resolveConfigSelection(detection, args.config);
168
+ // Target set: explicit, or the failures of the most recent run.
169
+ let target = args.testFiles ?? [];
170
+ if (target.length === 0) {
171
+ const last = ctx.store.lastRun;
172
+ const failedFiles = [
173
+ ...new Set((last?.result.failures ?? [])
174
+ .map((f) => f.file)
175
+ .filter((f) => typeof f === 'string' && f !== '')),
176
+ ];
177
+ if (failedFiles.length === 0) {
178
+ throw new PlaywrightMcpError('No tests to diagnose', 'INVALID_PATH', {
179
+ hint: 'Pass `testFiles`, or call `run-test` first so the previously failed tests can be reused as the target set.',
180
+ });
181
+ }
182
+ target = failedFiles;
183
+ }
184
+ const sanitized = [];
185
+ const missing = [];
186
+ for (const raw of target) {
187
+ const absolute = sanitizeTestPathArgument(raw, root);
188
+ sanitized.push(absolute);
189
+ if (!(await testPathExists(absolute, root)))
190
+ missing.push(raw);
191
+ }
192
+ if (missing.length > 0) {
193
+ throw new PlaywrightMcpError(`Test path(s) not found: ${missing.join(', ')}`, 'INVALID_PATH', {
194
+ hint: `Paths resolve inside ${root}. Call list-tests to see the available tests.`,
195
+ });
196
+ }
197
+ const runsRequested = args.runs ?? 3;
198
+ const outcomeList = [];
199
+ let lastResult;
200
+ for (let i = 0; i < runsRequested; i += 1) {
201
+ if (ctx.signal?.aborted)
202
+ break;
203
+ ctx.logger.info('diagnose-flaky run', { run: i + 1, of: runsRequested, target: sanitized });
204
+ const result = await runTests({
205
+ projectRoot: root,
206
+ configPath,
207
+ testFiles: sanitized,
208
+ browser: args.browser,
209
+ headed: args.headed,
210
+ workers: args.workers,
211
+ timeoutMs: args.timeoutMs,
212
+ retries: 0,
213
+ retryOnFailure: false,
214
+ signal: ctx.signal,
215
+ });
216
+ lastResult = result;
217
+ const first = result.failures[0];
218
+ const noTests = ranNoTests(result);
219
+ outcomeList.push({
220
+ ok: result.ok && !noTests,
221
+ durationMs: result.durationMs,
222
+ signature: failureSignature(first),
223
+ headline: first ? first.message.split('\n')[0] : undefined,
224
+ noTests,
225
+ errorKind: !result.ok && result.failures.length === 0 ? result.errorKind : undefined,
226
+ });
227
+ // A run-level hard error (spawn/timeout) will repeat — stop early.
228
+ if (!result.ok && result.failures.length === 0 && result.errorKind && result.errorKind !== 'REPORT_MISSING') {
229
+ break;
230
+ }
231
+ }
232
+ const summary = summarizeFlaky(outcomeList);
233
+ if (lastResult) {
234
+ ctx.store.setLastRun({ projectRoot: root, result: lastResult, at: Date.now() });
235
+ }
236
+ ctx.logger.info('diagnose-flaky finished', {
237
+ verdict: summary.verdict,
238
+ runs: outcomeList.length,
239
+ passed: summary.passed,
240
+ failed: summary.failed,
241
+ });
242
+ if (outcomeList.every((r) => r.errorKind)) {
243
+ const kinds = [...new Set(outcomeList.map((r) => r.errorKind))].join(', ');
244
+ return toolText([
245
+ '## Flaky diagnosis could not run',
246
+ '',
247
+ `Every attempt failed at the run level (\`${kinds}\`) before any test executed.`,
248
+ '',
249
+ lastResult?.errorMessage ? clipLines(lastResult.errorMessage, 4, 600) : '',
250
+ lastResult?.hint ? `> ${lastResult.hint}` : '',
251
+ ]
252
+ .filter(Boolean)
253
+ .join('\n'));
254
+ }
255
+ return toolText(renderDiagnosis(sanitized.map((file) => relativeToRoot(root, file)), outcomeList, summary, runsRequested));
256
+ }),
257
+ };
258
+ //# sourceMappingURL=diagnose-flaky.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnose-flaky.js","sourceRoot":"","sources":["../../src/tools/diagnose-flaky.ts"],"names":[],"mappings":"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"}