playwright-e2e-mcp 0.5.1 → 0.6.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.
Files changed (82) hide show
  1. package/README.md +55 -20
  2. package/dist/cli/ci.d.ts +5 -0
  3. package/dist/cli/ci.js +97 -5
  4. package/dist/cli/ci.js.map +1 -1
  5. package/dist/cli/doctor.d.ts +31 -0
  6. package/dist/cli/doctor.js +200 -0
  7. package/dist/cli/doctor.js.map +1 -0
  8. package/dist/cli/init.js +2 -1
  9. package/dist/cli/init.js.map +1 -1
  10. package/dist/cli/review.d.ts +50 -0
  11. package/dist/cli/review.js +148 -0
  12. package/dist/cli/review.js.map +1 -0
  13. package/dist/http.d.ts +14 -1
  14. package/dist/http.js +43 -8
  15. package/dist/http.js.map +1 -1
  16. package/dist/index.js +10 -0
  17. package/dist/index.js.map +1 -1
  18. package/dist/oauth.d.ts +10 -0
  19. package/dist/oauth.js +29 -1
  20. package/dist/oauth.js.map +1 -1
  21. package/dist/server.d.ts +2 -2
  22. package/dist/server.js +65 -5
  23. package/dist/server.js.map +1 -1
  24. package/dist/tools/audit-accessibility.d.ts +24 -0
  25. package/dist/tools/audit-accessibility.js +8 -1
  26. package/dist/tools/audit-accessibility.js.map +1 -1
  27. package/dist/tools/audit-performance.d.ts +24 -0
  28. package/dist/tools/audit-performance.js +14 -2
  29. package/dist/tools/audit-performance.js.map +1 -1
  30. package/dist/tools/audit-tests.d.ts +62 -0
  31. package/dist/tools/audit-tests.js +209 -0
  32. package/dist/tools/audit-tests.js.map +1 -0
  33. package/dist/tools/bisect-failure.d.ts +42 -0
  34. package/dist/tools/bisect-failure.js +291 -0
  35. package/dist/tools/bisect-failure.js.map +1 -0
  36. package/dist/tools/compare-visual-state.d.ts +24 -0
  37. package/dist/tools/diagnose-flaky.js +20 -3
  38. package/dist/tools/diagnose-flaky.js.map +1 -1
  39. package/dist/tools/generate-e2e-test.js +1 -1
  40. package/dist/tools/generate-e2e-test.js.map +1 -1
  41. package/dist/tools/get-failure.js +11 -2
  42. package/dist/tools/get-failure.js.map +1 -1
  43. package/dist/tools/get-run-status.js +4 -3
  44. package/dist/tools/get-run-status.js.map +1 -1
  45. package/dist/tools/inspect-page.d.ts +24 -0
  46. package/dist/tools/inspect-page.js +9 -5
  47. package/dist/tools/inspect-page.js.map +1 -1
  48. package/dist/tools/list-tests.js +5 -1
  49. package/dist/tools/list-tests.js.map +1 -1
  50. package/dist/tools/probe-script.js +87 -9
  51. package/dist/tools/probe-script.js.map +1 -1
  52. package/dist/tools/run-test.js +3 -2
  53. package/dist/tools/run-test.js.map +1 -1
  54. package/dist/tools/shared.d.ts +23 -1
  55. package/dist/tools/shared.js +91 -26
  56. package/dist/tools/shared.js.map +1 -1
  57. package/dist/tools/structured.d.ts +334 -0
  58. package/dist/tools/structured.js +176 -0
  59. package/dist/tools/structured.js.map +1 -0
  60. package/dist/tools/suggest-fix.js +15 -5
  61. package/dist/tools/suggest-fix.js.map +1 -1
  62. package/dist/tools/validate-selector.d.ts +27 -0
  63. package/dist/tools/validate-selector.js +28 -5
  64. package/dist/tools/validate-selector.js.map +1 -1
  65. package/dist/types/index.d.ts +1 -1
  66. package/dist/types/index.js.map +1 -1
  67. package/dist/utils/attempt-limiter.d.ts +31 -0
  68. package/dist/utils/attempt-limiter.js +58 -0
  69. package/dist/utils/attempt-limiter.js.map +1 -0
  70. package/dist/utils/bisect.d.ts +31 -0
  71. package/dist/utils/bisect.js +47 -0
  72. package/dist/utils/bisect.js.map +1 -0
  73. package/dist/utils/flake-rules.d.ts +46 -0
  74. package/dist/utils/flake-rules.js +327 -0
  75. package/dist/utils/flake-rules.js.map +1 -0
  76. package/dist/utils/locator-expr.d.ts +3 -1
  77. package/dist/utils/locator-expr.js +25 -7
  78. package/dist/utils/locator-expr.js.map +1 -1
  79. package/package.json +5 -4
  80. package/skill/playwright-e2e-mcp/SKILL.md +19 -2
  81. package/skill/playwright-e2e-mcp/references/setup.md +2 -2
  82. package/skill/playwright-e2e-mcp/references/tools.md +34 -1
@@ -0,0 +1,334 @@
1
+ /**
2
+ * Structured results: every tool returns, next to its Markdown, a JSON
3
+ * object (`structuredContent`) described by an output schema, so agents
4
+ * and apps can read verdicts, counts and file:line positions without
5
+ * parsing prose.
6
+ *
7
+ * All results share an envelope (`ok`, and `error` when the call failed);
8
+ * the tools agents chain most also fill in their own fields. Every field
9
+ * past `ok` is optional, so a tool may add detail without breaking older
10
+ * readers.
11
+ */
12
+ import { z } from 'zod';
13
+ import type { RunTestResult, TestFailure } from '../types/index.js';
14
+ declare const failureSchema: z.ZodObject<{
15
+ file: z.ZodString;
16
+ line: z.ZodOptional<z.ZodNumber>;
17
+ title: z.ZodString;
18
+ project: z.ZodOptional<z.ZodString>;
19
+ kind: z.ZodString;
20
+ message: z.ZodString;
21
+ tracePath: z.ZodOptional<z.ZodString>;
22
+ }, z.core.$strip>;
23
+ declare const runSchema: z.ZodObject<{
24
+ passed: z.ZodNumber;
25
+ failed: z.ZodNumber;
26
+ flaky: z.ZodNumber;
27
+ skipped: z.ZodNumber;
28
+ durationMs: z.ZodNumber;
29
+ exitCode: z.ZodNullable<z.ZodNumber>;
30
+ partial: z.ZodBoolean;
31
+ failures: z.ZodArray<z.ZodObject<{
32
+ file: z.ZodString;
33
+ line: z.ZodOptional<z.ZodNumber>;
34
+ title: z.ZodString;
35
+ project: z.ZodOptional<z.ZodString>;
36
+ kind: z.ZodString;
37
+ message: z.ZodString;
38
+ tracePath: z.ZodOptional<z.ZodString>;
39
+ }, z.core.$strip>>;
40
+ failuresTruncated: z.ZodOptional<z.ZodBoolean>;
41
+ }, z.core.$strip>;
42
+ /** Output schema shared by every tool: the envelope plus each tool's fields. */
43
+ export declare const toolOutputSchema: z.ZodObject<{
44
+ ok: z.ZodBoolean;
45
+ error: z.ZodOptional<z.ZodObject<{
46
+ kind: z.ZodString;
47
+ message: z.ZodString;
48
+ hint: z.ZodOptional<z.ZodString>;
49
+ }, z.core.$strip>>;
50
+ run: z.ZodOptional<z.ZodObject<{
51
+ passed: z.ZodNumber;
52
+ failed: z.ZodNumber;
53
+ flaky: z.ZodNumber;
54
+ skipped: z.ZodNumber;
55
+ durationMs: z.ZodNumber;
56
+ exitCode: z.ZodNullable<z.ZodNumber>;
57
+ partial: z.ZodBoolean;
58
+ failures: z.ZodArray<z.ZodObject<{
59
+ file: z.ZodString;
60
+ line: z.ZodOptional<z.ZodNumber>;
61
+ title: z.ZodString;
62
+ project: z.ZodOptional<z.ZodString>;
63
+ kind: z.ZodString;
64
+ message: z.ZodString;
65
+ tracePath: z.ZodOptional<z.ZodString>;
66
+ }, z.core.$strip>>;
67
+ failuresTruncated: z.ZodOptional<z.ZodBoolean>;
68
+ }, z.core.$strip>>;
69
+ runId: z.ZodOptional<z.ZodString>;
70
+ status: z.ZodOptional<z.ZodString>;
71
+ failure: z.ZodOptional<z.ZodObject<{
72
+ file: z.ZodString;
73
+ line: z.ZodOptional<z.ZodNumber>;
74
+ title: z.ZodString;
75
+ project: z.ZodOptional<z.ZodString>;
76
+ kind: z.ZodString;
77
+ message: z.ZodString;
78
+ tracePath: z.ZodOptional<z.ZodString>;
79
+ expected: z.ZodOptional<z.ZodString>;
80
+ actual: z.ZodOptional<z.ZodString>;
81
+ }, z.core.$strip>>;
82
+ index: z.ZodOptional<z.ZodNumber>;
83
+ total: z.ZodOptional<z.ZodNumber>;
84
+ tests: z.ZodOptional<z.ZodArray<z.ZodObject<{
85
+ file: z.ZodString;
86
+ line: z.ZodOptional<z.ZodNumber>;
87
+ title: z.ZodString;
88
+ project: z.ZodOptional<z.ZodString>;
89
+ verdict: z.ZodOptional<z.ZodEnum<{
90
+ "CONSISTENTLY FAILING": "CONSISTENTLY FAILING";
91
+ FLAKY: "FLAKY";
92
+ "NO TESTS RAN": "NO TESTS RAN";
93
+ "NOT REPRODUCING": "NOT REPRODUCING";
94
+ }>>;
95
+ passed: z.ZodOptional<z.ZodNumber>;
96
+ failed: z.ZodOptional<z.ZodNumber>;
97
+ }, z.core.$strip>>>;
98
+ truncated: z.ZodOptional<z.ZodBoolean>;
99
+ verdict: z.ZodOptional<z.ZodEnum<{
100
+ "CONSISTENTLY FAILING": "CONSISTENTLY FAILING";
101
+ FLAKY: "FLAKY";
102
+ "NO TESTS RAN": "NO TESTS RAN";
103
+ "NOT REPRODUCING": "NOT REPRODUCING";
104
+ }>>;
105
+ fix: z.ZodOptional<z.ZodObject<{
106
+ file: z.ZodString;
107
+ line: z.ZodNumber;
108
+ before: z.ZodString;
109
+ after: z.ZodString;
110
+ kind: z.ZodEnum<{
111
+ expectation: "expectation";
112
+ locator: "locator";
113
+ }>;
114
+ confidence: z.ZodString;
115
+ why: z.ZodString;
116
+ alternatives: z.ZodOptional<z.ZodArray<z.ZodString>>;
117
+ }, z.core.$strip>>;
118
+ applied: z.ZodOptional<z.ZodBoolean>;
119
+ verified: z.ZodOptional<z.ZodBoolean>;
120
+ violations: z.ZodOptional<z.ZodArray<z.ZodObject<{
121
+ id: z.ZodString;
122
+ impact: z.ZodString;
123
+ help: z.ZodString;
124
+ helpUrl: z.ZodString;
125
+ count: z.ZodNumber;
126
+ }, z.core.$strip>>>;
127
+ metrics: z.ZodOptional<z.ZodArray<z.ZodObject<{
128
+ id: z.ZodEnum<{
129
+ cls: "cls";
130
+ fcp: "fcp";
131
+ inp: "inp";
132
+ lcp: "lcp";
133
+ tbt: "tbt";
134
+ ttfb: "ttfb";
135
+ }>;
136
+ value: z.ZodOptional<z.ZodNumber>;
137
+ unit: z.ZodEnum<{
138
+ ms: "ms";
139
+ score: "score";
140
+ }>;
141
+ rating: z.ZodOptional<z.ZodEnum<{
142
+ good: "good";
143
+ "needs-improvement": "needs-improvement";
144
+ poor: "poor";
145
+ }>>;
146
+ budget: z.ZodOptional<z.ZodNumber>;
147
+ overBudget: z.ZodBoolean;
148
+ }, z.core.$strip>>>;
149
+ url: z.ZodOptional<z.ZodString>;
150
+ findings: z.ZodOptional<z.ZodArray<z.ZodObject<{
151
+ file: z.ZodString;
152
+ line: z.ZodNumber;
153
+ rule: z.ZodString;
154
+ severity: z.ZodEnum<{
155
+ high: "high";
156
+ low: "low";
157
+ medium: "medium";
158
+ }>;
159
+ message: z.ZodString;
160
+ fix: z.ZodOptional<z.ZodString>;
161
+ test: z.ZodOptional<z.ZodString>;
162
+ }, z.core.$strip>>>;
163
+ firstBad: z.ZodOptional<z.ZodObject<{
164
+ sha: z.ZodString;
165
+ subject: z.ZodString;
166
+ author: z.ZodString;
167
+ date: z.ZodString;
168
+ files: z.ZodArray<z.ZodString>;
169
+ }, z.core.$strip>>;
170
+ steps: z.ZodOptional<z.ZodArray<z.ZodObject<{
171
+ commit: z.ZodString;
172
+ verdict: z.ZodEnum<{
173
+ bad: "bad";
174
+ good: "good";
175
+ skip: "skip";
176
+ }>;
177
+ note: z.ZodOptional<z.ZodString>;
178
+ }, z.core.$strip>>>;
179
+ valid: z.ZodOptional<z.ZodBoolean>;
180
+ matches: z.ZodOptional<z.ZodNumber>;
181
+ locator: z.ZodOptional<z.ZodString>;
182
+ httpStatus: z.ZodOptional<z.ZodNumber>;
183
+ }, z.core.$strip>;
184
+ export type StructuredResult = z.infer<typeof toolOutputSchema>;
185
+ export declare function failureData(failure: TestFailure): z.infer<typeof failureSchema>;
186
+ export declare function runData(result: RunTestResult): z.infer<typeof runSchema>;
187
+ /**
188
+ * The output schema for one tool: the envelope plus only that tool's fields,
189
+ * so tools/list does not repeat every tool's fields sixteen times. Tools
190
+ * with no fields of their own declare none (their results still carry the
191
+ * envelope): each schema is context a client pays for on every request.
192
+ */
193
+ export declare function outputSchemaFor(toolName: string): z.ZodObject<{
194
+ ok: z.ZodBoolean;
195
+ error: z.ZodOptional<z.ZodObject<{
196
+ kind: z.ZodString;
197
+ message: z.ZodString;
198
+ hint: z.ZodOptional<z.ZodString>;
199
+ }, z.core.$strip>>;
200
+ run: z.ZodOptional<z.ZodObject<{
201
+ passed: z.ZodNumber;
202
+ failed: z.ZodNumber;
203
+ flaky: z.ZodNumber;
204
+ skipped: z.ZodNumber;
205
+ durationMs: z.ZodNumber;
206
+ exitCode: z.ZodNullable<z.ZodNumber>;
207
+ partial: z.ZodBoolean;
208
+ failures: z.ZodArray<z.ZodObject<{
209
+ file: z.ZodString;
210
+ line: z.ZodOptional<z.ZodNumber>;
211
+ title: z.ZodString;
212
+ project: z.ZodOptional<z.ZodString>;
213
+ kind: z.ZodString;
214
+ message: z.ZodString;
215
+ tracePath: z.ZodOptional<z.ZodString>;
216
+ }, z.core.$strip>>;
217
+ failuresTruncated: z.ZodOptional<z.ZodBoolean>;
218
+ }, z.core.$strip>>;
219
+ runId: z.ZodOptional<z.ZodString>;
220
+ status: z.ZodOptional<z.ZodString>;
221
+ failure: z.ZodOptional<z.ZodObject<{
222
+ file: z.ZodString;
223
+ line: z.ZodOptional<z.ZodNumber>;
224
+ title: z.ZodString;
225
+ project: z.ZodOptional<z.ZodString>;
226
+ kind: z.ZodString;
227
+ message: z.ZodString;
228
+ tracePath: z.ZodOptional<z.ZodString>;
229
+ expected: z.ZodOptional<z.ZodString>;
230
+ actual: z.ZodOptional<z.ZodString>;
231
+ }, z.core.$strip>>;
232
+ index: z.ZodOptional<z.ZodNumber>;
233
+ total: z.ZodOptional<z.ZodNumber>;
234
+ tests: z.ZodOptional<z.ZodArray<z.ZodObject<{
235
+ file: z.ZodString;
236
+ line: z.ZodOptional<z.ZodNumber>;
237
+ title: z.ZodString;
238
+ project: z.ZodOptional<z.ZodString>;
239
+ verdict: z.ZodOptional<z.ZodEnum<{
240
+ "CONSISTENTLY FAILING": "CONSISTENTLY FAILING";
241
+ FLAKY: "FLAKY";
242
+ "NO TESTS RAN": "NO TESTS RAN";
243
+ "NOT REPRODUCING": "NOT REPRODUCING";
244
+ }>>;
245
+ passed: z.ZodOptional<z.ZodNumber>;
246
+ failed: z.ZodOptional<z.ZodNumber>;
247
+ }, z.core.$strip>>>;
248
+ truncated: z.ZodOptional<z.ZodBoolean>;
249
+ verdict: z.ZodOptional<z.ZodEnum<{
250
+ "CONSISTENTLY FAILING": "CONSISTENTLY FAILING";
251
+ FLAKY: "FLAKY";
252
+ "NO TESTS RAN": "NO TESTS RAN";
253
+ "NOT REPRODUCING": "NOT REPRODUCING";
254
+ }>>;
255
+ fix: z.ZodOptional<z.ZodObject<{
256
+ file: z.ZodString;
257
+ line: z.ZodNumber;
258
+ before: z.ZodString;
259
+ after: z.ZodString;
260
+ kind: z.ZodEnum<{
261
+ expectation: "expectation";
262
+ locator: "locator";
263
+ }>;
264
+ confidence: z.ZodString;
265
+ why: z.ZodString;
266
+ alternatives: z.ZodOptional<z.ZodArray<z.ZodString>>;
267
+ }, z.core.$strip>>;
268
+ applied: z.ZodOptional<z.ZodBoolean>;
269
+ verified: z.ZodOptional<z.ZodBoolean>;
270
+ violations: z.ZodOptional<z.ZodArray<z.ZodObject<{
271
+ id: z.ZodString;
272
+ impact: z.ZodString;
273
+ help: z.ZodString;
274
+ helpUrl: z.ZodString;
275
+ count: z.ZodNumber;
276
+ }, z.core.$strip>>>;
277
+ metrics: z.ZodOptional<z.ZodArray<z.ZodObject<{
278
+ id: z.ZodEnum<{
279
+ cls: "cls";
280
+ fcp: "fcp";
281
+ inp: "inp";
282
+ lcp: "lcp";
283
+ tbt: "tbt";
284
+ ttfb: "ttfb";
285
+ }>;
286
+ value: z.ZodOptional<z.ZodNumber>;
287
+ unit: z.ZodEnum<{
288
+ ms: "ms";
289
+ score: "score";
290
+ }>;
291
+ rating: z.ZodOptional<z.ZodEnum<{
292
+ good: "good";
293
+ "needs-improvement": "needs-improvement";
294
+ poor: "poor";
295
+ }>>;
296
+ budget: z.ZodOptional<z.ZodNumber>;
297
+ overBudget: z.ZodBoolean;
298
+ }, z.core.$strip>>>;
299
+ url: z.ZodOptional<z.ZodString>;
300
+ findings: z.ZodOptional<z.ZodArray<z.ZodObject<{
301
+ file: z.ZodString;
302
+ line: z.ZodNumber;
303
+ rule: z.ZodString;
304
+ severity: z.ZodEnum<{
305
+ high: "high";
306
+ low: "low";
307
+ medium: "medium";
308
+ }>;
309
+ message: z.ZodString;
310
+ fix: z.ZodOptional<z.ZodString>;
311
+ test: z.ZodOptional<z.ZodString>;
312
+ }, z.core.$strip>>>;
313
+ firstBad: z.ZodOptional<z.ZodObject<{
314
+ sha: z.ZodString;
315
+ subject: z.ZodString;
316
+ author: z.ZodString;
317
+ date: z.ZodString;
318
+ files: z.ZodArray<z.ZodString>;
319
+ }, z.core.$strip>>;
320
+ steps: z.ZodOptional<z.ZodArray<z.ZodObject<{
321
+ commit: z.ZodString;
322
+ verdict: z.ZodEnum<{
323
+ bad: "bad";
324
+ good: "good";
325
+ skip: "skip";
326
+ }>;
327
+ note: z.ZodOptional<z.ZodString>;
328
+ }, z.core.$strip>>>;
329
+ valid: z.ZodOptional<z.ZodBoolean>;
330
+ matches: z.ZodOptional<z.ZodNumber>;
331
+ locator: z.ZodOptional<z.ZodString>;
332
+ httpStatus: z.ZodOptional<z.ZodNumber>;
333
+ }, z.core.$strip> | undefined;
334
+ export {};
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Structured results: every tool returns, next to its Markdown, a JSON
3
+ * object (`structuredContent`) described by an output schema, so agents
4
+ * and apps can read verdicts, counts and file:line positions without
5
+ * parsing prose.
6
+ *
7
+ * All results share an envelope (`ok`, and `error` when the call failed);
8
+ * the tools agents chain most also fill in their own fields. Every field
9
+ * past `ok` is optional, so a tool may add detail without breaking older
10
+ * readers.
11
+ */
12
+ import { z } from 'zod';
13
+ const failureSchema = z.object({
14
+ file: z.string(),
15
+ line: z.number().int().optional(),
16
+ title: z.string(),
17
+ project: z.string().optional(),
18
+ kind: z.string(),
19
+ message: z.string(),
20
+ tracePath: z.string().optional(),
21
+ });
22
+ const runSchema = z.object({
23
+ passed: z.number().int(),
24
+ failed: z.number().int(),
25
+ flaky: z.number().int(),
26
+ skipped: z.number().int(),
27
+ durationMs: z.number(),
28
+ exitCode: z.number().int().nullable(),
29
+ partial: z.boolean(),
30
+ failures: z.array(failureSchema),
31
+ failuresTruncated: z.boolean().optional(),
32
+ });
33
+ const errorSchema = z.object({ kind: z.string(), message: z.string(), hint: z.string().optional() });
34
+ const verdictSchema = z.enum(['NOT REPRODUCING', 'FLAKY', 'CONSISTENTLY FAILING', 'NO TESTS RAN']);
35
+ const fixSchema = z.object({
36
+ file: z.string(),
37
+ line: z.number().int(),
38
+ before: z.string(),
39
+ after: z.string(),
40
+ kind: z.enum(['locator', 'expectation']),
41
+ confidence: z.string(),
42
+ why: z.string(),
43
+ alternatives: z.array(z.string()).optional(),
44
+ });
45
+ /** Output schema shared by every tool: the envelope plus each tool's fields. */
46
+ export const toolOutputSchema = z.object({
47
+ ok: z.boolean().describe('false when the call failed (see error) or the tests it ran failed'),
48
+ error: errorSchema.optional(),
49
+ // run-test, get-run-status
50
+ run: runSchema.optional(),
51
+ runId: z.string().optional(),
52
+ status: z.string().optional(),
53
+ // get-failure
54
+ failure: failureSchema.extend({ expected: z.string().optional(), actual: z.string().optional() }).optional(),
55
+ index: z.number().int().optional(),
56
+ total: z.number().int().optional(),
57
+ // list-tests
58
+ tests: z
59
+ .array(z.object({
60
+ file: z.string(),
61
+ line: z.number().int().optional(),
62
+ title: z.string(),
63
+ project: z.string().optional(),
64
+ verdict: verdictSchema.optional(),
65
+ passed: z.number().int().optional(),
66
+ failed: z.number().int().optional(),
67
+ }))
68
+ .optional(),
69
+ truncated: z.boolean().optional(),
70
+ // diagnose-flaky
71
+ verdict: verdictSchema.optional(),
72
+ // suggest-fix
73
+ fix: fixSchema.optional(),
74
+ applied: z.boolean().optional(),
75
+ verified: z.boolean().optional(),
76
+ // audit-accessibility
77
+ violations: z
78
+ .array(z.object({ id: z.string(), impact: z.string(), help: z.string(), helpUrl: z.string(), count: z.number().int() }))
79
+ .optional(),
80
+ // audit-performance
81
+ metrics: z
82
+ .array(z.object({
83
+ id: z.enum(['lcp', 'cls', 'inp', 'tbt', 'fcp', 'ttfb']),
84
+ value: z.number().optional(),
85
+ unit: z.enum(['ms', 'score']),
86
+ rating: z.enum(['good', 'needs-improvement', 'poor']).optional(),
87
+ budget: z.number().optional(),
88
+ overBudget: z.boolean(),
89
+ }))
90
+ .optional(),
91
+ url: z.string().optional(),
92
+ // audit-tests
93
+ findings: z
94
+ .array(z.object({
95
+ file: z.string(),
96
+ line: z.number().int(),
97
+ rule: z.string(),
98
+ severity: z.enum(['high', 'medium', 'low']),
99
+ message: z.string(),
100
+ fix: z.string().optional().describe('the replacement line, when the fix is mechanical'),
101
+ test: z.string().optional(),
102
+ }))
103
+ .optional(),
104
+ // bisect-failure
105
+ firstBad: z
106
+ .object({ sha: z.string(), subject: z.string(), author: z.string(), date: z.string(), files: z.array(z.string()) })
107
+ .optional(),
108
+ steps: z
109
+ .array(z.object({ commit: z.string(), verdict: z.enum(['good', 'bad', 'skip']), note: z.string().optional() }))
110
+ .optional(),
111
+ // validate-selector, inspect-page
112
+ valid: z.boolean().optional().describe('validate-selector: the selector parsed and ran'),
113
+ matches: z.number().int().optional().describe('elements matched; actions need exactly 1'),
114
+ locator: z.string().optional().describe('a verified unique locator for the first match'),
115
+ httpStatus: z.number().int().optional().describe("the page's HTTP status"),
116
+ });
117
+ /** Keep long messages short: an agent that wants all of it calls get-failure. */
118
+ function firstLines(text, lines = 6, chars = 600) {
119
+ const head = text.split('\n').slice(0, lines).join('\n');
120
+ return head.length > chars ? `${head.slice(0, chars)}…` : head;
121
+ }
122
+ export function failureData(failure) {
123
+ return {
124
+ file: failure.file,
125
+ ...(failure.line !== undefined ? { line: failure.line } : {}),
126
+ title: failure.title,
127
+ ...(failure.project ? { project: failure.project } : {}),
128
+ kind: failure.failureKind,
129
+ message: firstLines(failure.message ?? ''),
130
+ ...(failure.tracePath ? { tracePath: failure.tracePath } : {}),
131
+ };
132
+ }
133
+ export function runData(result) {
134
+ const stats = result.stats;
135
+ return {
136
+ passed: stats?.expected ?? 0,
137
+ failed: stats?.unexpected ?? result.failures.length,
138
+ flaky: stats?.flaky ?? 0,
139
+ skipped: stats?.skipped ?? 0,
140
+ durationMs: Math.round(result.durationMs),
141
+ exitCode: result.exitCode,
142
+ partial: result.partial,
143
+ failures: result.failures.map(failureData),
144
+ ...(result.failuresTruncated ? { failuresTruncated: true } : {}),
145
+ };
146
+ }
147
+ /** The fields each tool fills in, beyond the envelope (ok, error). */
148
+ const TOOL_FIELDS = {
149
+ 'run-test': ['run', 'runId', 'status'],
150
+ 'get-run-status': ['run', 'runId', 'status'],
151
+ 'get-failure': ['failure', 'index', 'total'],
152
+ 'list-tests': ['tests', 'total', 'truncated'],
153
+ 'diagnose-flaky': ['verdict', 'tests'],
154
+ 'suggest-fix': ['fix', 'applied', 'verified'],
155
+ 'audit-accessibility': ['url', 'violations'],
156
+ 'audit-performance': ['url', 'metrics'],
157
+ 'audit-tests': ['findings', 'total', 'truncated', 'applied', 'verified'],
158
+ 'bisect-failure': ['status', 'firstBad', 'steps'],
159
+ 'validate-selector': ['url', 'valid', 'matches', 'locator', 'httpStatus'],
160
+ };
161
+ /**
162
+ * The output schema for one tool: the envelope plus only that tool's fields,
163
+ * so tools/list does not repeat every tool's fields sixteen times. Tools
164
+ * with no fields of their own declare none (their results still carry the
165
+ * envelope): each schema is context a client pays for on every request.
166
+ */
167
+ export function outputSchemaFor(toolName) {
168
+ const fields = TOOL_FIELDS[toolName];
169
+ if (!fields)
170
+ return undefined;
171
+ const mask = { ok: true, error: true };
172
+ for (const field of fields)
173
+ mask[field] = true;
174
+ return toolOutputSchema.pick(mask);
175
+ }
176
+ //# sourceMappingURL=structured.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"structured.js","sourceRoot":"","sources":["../../src/tools/structured.ts"],"names":[],"mappings":"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"}
@@ -437,7 +437,7 @@ export const suggestFixTool = {
437
437
  : picked;
438
438
  const listing = strict.matches.map((m, i) => `${i + 1}. \`${m.locator}\` · \`${clipLines(m.html, 1, 120)}\``);
439
439
  if (args.candidate !== undefined && !chosen) {
440
- throw new PlaywrightMcpError(`candidate ${args.candidate} is out of range: Playwright listed ${strict.matches.length} match(es)`, 'INVALID_PATH', {
440
+ throw new PlaywrightMcpError(`candidate ${args.candidate} is out of range: Playwright listed ${strict.matches.length} match(es)`, 'INVALID_INPUT', {
441
441
  hint: 'Call suggest-fix without candidate to see the list.',
442
442
  });
443
443
  }
@@ -550,6 +550,16 @@ export const suggestFixTool = {
550
550
  return toolText(out.join('\n'));
551
551
  }
552
552
  const diff = unifiedDiff(failure.file, lines, proposal.lineNo, proposal.after);
553
+ const fix = {
554
+ file: failure.file,
555
+ line: proposal.lineNo,
556
+ before: proposal.before,
557
+ after: proposal.after,
558
+ kind: proposal.kind,
559
+ confidence: proposal.confidence,
560
+ why: proposal.why,
561
+ ...(proposal.alternatives?.length ? { alternatives: proposal.alternatives } : {}),
562
+ };
553
563
  out.push(`### Proposed ${proposal.kind === 'locator' ? 'locator fix' : 'expectation update'} — confidence **${proposal.confidence}**`, '', proposal.why, '', codeFence(diff, 'diff'), '');
554
564
  if (proposal.alternatives && proposal.alternatives.length > 0) {
555
565
  out.push(`Other candidates: ${proposal.alternatives.map((a) => `\`${a}\``).join(', ')}`, '');
@@ -559,11 +569,11 @@ export const suggestFixTool = {
559
569
  out.push(mayApply
560
570
  ? '> Next: call **suggest-fix** again with `apply: true` to write this change and re-run the test to verify it.'
561
571
  : '> This changes what the test asserts. Apply it by hand, or pass `apply: true, allowExpectationUpdate: true` if the new text is intended.');
562
- return toolText(out.join('\n'));
572
+ return toolText(out.join('\n'), { fix, applied: false });
563
573
  }
564
574
  if (!mayApply) {
565
575
  out.push('> ⛔ Not applied: expectation updates need `allowExpectationUpdate: true`, because they can hide a real regression.');
566
- return toolText(out.join('\n'));
576
+ return toolText(out.join('\n'), { fix, applied: false });
567
577
  }
568
578
  // Apply: refuse if the file changed since we read it.
569
579
  const current = await readFile(specPath, 'utf8');
@@ -578,7 +588,7 @@ export const suggestFixTool = {
578
588
  out.push(`**Applied** to \`${failure.file}:${proposal.lineNo}\`.`, '');
579
589
  if (args.verify === false) {
580
590
  out.push('> Not verified (verify: false). Re-run with **run-test** to check it.');
581
- return toolText(out.join('\n'));
591
+ return toolText(out.join('\n'), { fix, applied: true });
582
592
  }
583
593
  const detection = await detectProject(root);
584
594
  const configPath = resolveConfigSelection(detection, args.config);
@@ -611,7 +621,7 @@ export const suggestFixTool = {
611
621
  const first = result.failures[0];
612
622
  out.push(`### ❌ Still failing — the change was reverted`, '', first ? codeFence(clipLines(first.message, 8, 1_200), 'text') : (result.errorMessage ?? 'The run failed without a test failure.'), '', '> The original file is restored. Call **get-failure** for the new failure, or try another candidate by hand.');
613
623
  }
614
- return toolText(out.join('\n'));
624
+ return toolText(out.join('\n'), { ok: result.ok, fix, applied: result.ok, verified: result.ok });
615
625
  }),
616
626
  };
617
627
  //# sourceMappingURL=suggest-fix.js.map