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,93 @@
1
+ /**
2
+ * Tool 5 — validate-selector: verify a CSS selector against a live page:
3
+ * is it syntactically valid, how many elements match, and what do the
4
+ * matches look like?
5
+ */
6
+ import { z } from 'zod';
7
+ import { assertHttpUrl, clipLines, guard, resolveProjectRoot, runBrowserScript, toolError, toolText, } from './shared.js';
8
+ const validateSelectorInput = z.object({
9
+ url: z.string().describe('Full URL of the live page to test against (http/https)'),
10
+ selector: z.string().min(1).describe('CSS selector to validate'),
11
+ projectRoot: z
12
+ .string()
13
+ .optional()
14
+ .describe("Project whose Playwright install launches the browser; defaults to the server working directory"),
15
+ timeoutMs: z
16
+ .number()
17
+ .int()
18
+ .min(1_000)
19
+ .max(300_000)
20
+ .optional()
21
+ .describe('Overall limit in ms (default 45000)'),
22
+ });
23
+ export const validateSelectorSchema = validateSelectorInput;
24
+ const ENGINE_RE = /^(text|xpath|id|css|pierce|nth|visible|has-text)=|>>|:has-text\(|:text\(/i;
25
+ export const validateSelectorTool = {
26
+ name: 'validate-selector',
27
+ description: 'Check a CSS selector against a live page: syntax validity, match count, and a sample of matched elements (tag, visibility, box, text). Also flags Playwright-only selector engines (text=, xpath=, >>, :has-text()) that are not valid CSS.',
28
+ inputSchema: validateSelectorSchema,
29
+ handler: async (args, ctx) => guard('validate-selector', async () => {
30
+ const url = assertHttpUrl(args.url);
31
+ const selector = args.selector.trim();
32
+ if (ENGINE_RE.test(selector)) {
33
+ return toolText([
34
+ `## ⚠️ \`${selector}\` is not a CSS selector`,
35
+ '',
36
+ 'It uses a **Playwright selector engine** (`text=`, `xpath=`, `>>`, `:has-text()` …). Those are valid *inside Playwright tests* but cannot be validated as CSS here, and Playwright CSS syntax is otherwise a subset of what the browser accepts.',
37
+ '',
38
+ '**How to fix:** rewrite it as plain CSS (e.g. `text=Login` → `[role="button"]:has-text("Login")` is still Playwright-only — prefer `[data-testid="login-button"]`), then re-validate.',
39
+ ].join('\n'));
40
+ }
41
+ const timeoutMs = args.timeoutMs ?? 45_000;
42
+ const root = await resolveProjectRoot(args.projectRoot, ctx);
43
+ const outcome = await runBrowserScript({
44
+ mode: 'validate',
45
+ projectRoot: root,
46
+ url,
47
+ selector,
48
+ gotoTimeout: Math.max(5_000, timeoutMs - 5_000),
49
+ startedAt: Date.now(),
50
+ }, { timeoutMs, signal: ctx.signal });
51
+ if (!outcome.ok) {
52
+ return toolError(outcome.kind ?? 'UNKNOWN', `Could not validate "${selector}" on ${url}: ${outcome.error ?? 'unknown error'}`, outcome.hint, outcome.stderrTail);
53
+ }
54
+ const data = outcome.data;
55
+ if (data.parseError) {
56
+ return toolText([
57
+ `## ❌ INVALID — \`${selector}\``,
58
+ '',
59
+ `**Parse error:** \`${data.parseError}\``,
60
+ '',
61
+ '**How to fix:** only CSS selectors are supported (`document.querySelectorAll` semantics). Common mistakes: unescaped `#`/`.` in ids/classes, stray `>`, or a Playwright engine prefix like `text=`.',
62
+ '',
63
+ 'Use **inspect-page** to see the actual selectors of the live elements.',
64
+ ].join('\n'));
65
+ }
66
+ const lines = [];
67
+ if (data.matchCount === 0) {
68
+ lines.push(`## ✅ VALID — 0 matches`, '', `\`${selector}\` is syntactically valid but matched nothing on ${url}.`, '');
69
+ lines.push('**How to fix:** confirm the element exists at this URL (app may render after an interaction). Run **inspect-page** with the same URL to list real selectors, or add `waitFor` there.');
70
+ return toolText(lines.join('\n'));
71
+ }
72
+ lines.push(`## ✅ VALID — ${data.matchCount} match${data.matchCount === 1 ? '' : 'es'}`, '');
73
+ lines.push(`**URL:** ${url}`, '');
74
+ const matches = data.elements;
75
+ for (let i = 0; i < matches.length; i += 1) {
76
+ const element = matches[i];
77
+ const box = element.box ? `${element.box.width}×${element.box.height} at (${element.box.x},${element.box.y})` : 'no box';
78
+ lines.push(`${i + 1}. \`${element.selector}\` — **${element.tag}** · ${element.visible ? 'visible' : 'hidden'} · ${box}`);
79
+ if (element.text)
80
+ lines.push(` text: "${clipLines(element.text, 1, 140)}"`);
81
+ }
82
+ lines.push('');
83
+ if (data.matchCount > 1) {
84
+ lines.push(`> ${data.matchCount} elements match. If you need exactly one, scope it: \`#id ${selector}\` or use the unique selector shown above.`, '');
85
+ }
86
+ const hidden = matches.filter((element) => !element.visible).length;
87
+ if (hidden > 0 && hidden === matches.length) {
88
+ lines.push('> ⚠️ All sampled matches are **hidden** — `toBeVisible()` will fail. Check parent elements for `display: none` / `visibility: hidden`.', '');
89
+ }
90
+ return toolText(lines.join('\n'));
91
+ }),
92
+ };
93
+ //# sourceMappingURL=validate-selector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-selector.js","sourceRoot":"","sources":["../../src/tools/validate-selector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,344 @@
1
+ /**
2
+ * Shared TypeScript interfaces and error types for playwright-e2e-mcp.
3
+ *
4
+ * Everything that other modules need to agree on lives here: tool
5
+ * contracts, Playwright JSON report shapes, run/inspect results, and the
6
+ * error taxonomy used to turn failures into actionable MCP responses.
7
+ */
8
+ export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent';
9
+ export type LogContext = Record<string, unknown>;
10
+ export interface Logger {
11
+ readonly level: LogLevel;
12
+ debug(message: string, context?: LogContext): void;
13
+ info(message: string, context?: LogContext): void;
14
+ warn(message: string, context?: LogContext): void;
15
+ error(message: string, context?: LogContext): void;
16
+ child(bindings: LogContext): Logger;
17
+ }
18
+ /**
19
+ * Machine-readable failure categories. Tools map these to clear user
20
+ * facing messages instead of dumping raw stack traces.
21
+ */
22
+ export type ErrorKind = 'NO_PLAYWRIGHT' | 'NO_CONFIG' | 'MULTIPLE_CONFIGS' | 'INVALID_PATH' | 'SERVER_NOT_RUNNING' | 'TIMEOUT' | 'BROWSER_CRASH' | 'SYNTAX_ERROR' | 'DISK_FULL' | 'CLIENT_DISCONNECT' | 'SPAWN_FAILED' | 'REPORT_MISSING' | 'UNKNOWN';
23
+ export interface PlaywrightMcpErrorOptions {
24
+ hint?: string;
25
+ details?: string;
26
+ cause?: unknown;
27
+ }
28
+ /**
29
+ * Error type carrying an ErrorKind so the MCP layer can render a
30
+ * helpful remediation hint instead of a raw exception.
31
+ */
32
+ export declare class PlaywrightMcpError extends Error {
33
+ readonly kind: ErrorKind;
34
+ readonly hint?: string;
35
+ readonly details?: string;
36
+ constructor(message: string, kind?: ErrorKind, options?: PlaywrightMcpErrorOptions);
37
+ }
38
+ /** Wrap an unknown thrown value into a PlaywrightMcpError. */
39
+ export declare function toPlaywrightMcpError(err: unknown, fallbackKind?: ErrorKind): PlaywrightMcpError;
40
+ /** Result of the most recent run-test invocation (feeds get-failure). */
41
+ export interface LastRunRecord {
42
+ projectRoot: string;
43
+ result: RunTestResult;
44
+ at: number;
45
+ }
46
+ /** Minimal cross-tool state held by the server. */
47
+ export interface ToolStore {
48
+ lastRun: LastRunRecord | null;
49
+ setLastRun(record: LastRunRecord): void;
50
+ }
51
+ /** Per-invocation context passed from the server into each tool. */
52
+ export interface ToolContext {
53
+ logger: Logger;
54
+ /**
55
+ * Aborted when the MCP client disconnects or cancels the request.
56
+ * Tools must kill child processes / browsers when this fires.
57
+ */
58
+ signal?: AbortSignal;
59
+ store: ToolStore;
60
+ /** Default project root (server cwd or PW_MCP_PROJECT_ROOT). */
61
+ projectRoot: string;
62
+ }
63
+ /**
64
+ * MCP tool return value (content blocks + optional structured payload).
65
+ * The index signature keeps it assignable to the SDK's CallToolResult.
66
+ */
67
+ export interface ToolResponse {
68
+ [key: string]: unknown;
69
+ content: Array<{
70
+ type: 'text';
71
+ text: string;
72
+ }>;
73
+ isError?: boolean;
74
+ structuredContent?: Record<string, unknown>;
75
+ }
76
+ export type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun' | 'unknown';
77
+ export interface ProjectDetection {
78
+ /** Directory the search started from (normalized, absolute). */
79
+ startDir: string;
80
+ /** Project root: directory containing playwright.config.* if found. */
81
+ root: string;
82
+ /** Absolute path of the selected config, or null when none exists. */
83
+ configPath: string | null;
84
+ /** Every config found in the chosen directory (ask user if >1). */
85
+ configCandidates: string[];
86
+ /** Whether @playwright/test or playwright is resolvable in node_modules. */
87
+ hasPlaywright: boolean;
88
+ /** Which package satisfied the check: '@playwright/test' | 'playwright'. */
89
+ playwrightPackage: string | null;
90
+ playwrightVersion: string | null;
91
+ /** Best-effort testDir parsed from the config file text. */
92
+ testDir: string | null;
93
+ packageManager: PackageManager;
94
+ }
95
+ export interface ReportStats {
96
+ expected: number;
97
+ unexpected: number;
98
+ flaky: number;
99
+ skipped: number;
100
+ duration: number;
101
+ }
102
+ export interface ReportError {
103
+ message?: string;
104
+ stack?: string;
105
+ codeframe?: string;
106
+ expected?: unknown;
107
+ actual?: unknown;
108
+ location?: {
109
+ file?: string;
110
+ line?: number;
111
+ column?: number;
112
+ };
113
+ }
114
+ export interface ReportSpec {
115
+ title: string;
116
+ file?: string;
117
+ line?: number;
118
+ column?: number;
119
+ ok?: boolean;
120
+ tags?: string[];
121
+ tests?: ReportTestCase[];
122
+ }
123
+ export interface ReportTestCase {
124
+ projectName?: string;
125
+ status?: string;
126
+ results?: ReportTestResult[];
127
+ }
128
+ export interface ReportTestResult {
129
+ status?: string;
130
+ retry?: number;
131
+ workerIndex?: number;
132
+ duration?: number;
133
+ startTime?: string;
134
+ error?: ReportError;
135
+ errors?: ReportError[];
136
+ expected?: string;
137
+ expectedStatus?: string;
138
+ actual?: string;
139
+ /** Trace/screenshot/console attachments produced for this attempt. */
140
+ attachments?: Array<{
141
+ name?: string;
142
+ path?: string;
143
+ contentType?: string;
144
+ }>;
145
+ /** Raw test console output captured by the JSON reporter. */
146
+ stdout?: unknown[];
147
+ stderr?: unknown[];
148
+ }
149
+ export interface ReportSuite {
150
+ title?: string;
151
+ file?: string;
152
+ line?: number;
153
+ suites?: ReportSuite[];
154
+ specs?: ReportSpec[];
155
+ }
156
+ export interface PlaywrightJsonReport {
157
+ config?: {
158
+ rootDir?: string;
159
+ configFile?: string;
160
+ projects?: Array<{
161
+ name?: string;
162
+ }>;
163
+ };
164
+ suites?: ReportSuite[];
165
+ errors?: ReportError[];
166
+ stats?: ReportStats;
167
+ }
168
+ export type BrowserName = 'chromium' | 'firefox' | 'webkit';
169
+ export interface RunTestOptions {
170
+ /** Absolute project root (already sanitized by the caller). */
171
+ projectRoot: string;
172
+ configPath?: string;
173
+ /** Test files/paths relative to the project root, already sanitized. */
174
+ testFiles?: string[];
175
+ /** Playwright --grep expression. */
176
+ grep?: string;
177
+ browser?: BrowserName;
178
+ headed?: boolean;
179
+ /** Hard wall-clock limit for the whole run; process group is killed. */
180
+ timeoutMs?: number;
181
+ /** Per-test timeout passed to Playwright (--timeout). */
182
+ testTimeoutMs?: number;
183
+ workers?: number;
184
+ retries?: number;
185
+ /** Extra CLI args appended verbatim (caller-sanitized). */
186
+ extraArgs?: string[];
187
+ /**
188
+ * Automatically retry failed tests once before reporting failure
189
+ * (default true — injected as --retries=1 when the config does not
190
+ * already enable retries).
191
+ */
192
+ retryOnFailure?: boolean;
193
+ /**
194
+ * Playwright --last-failed: only re-run tests that failed in the
195
+ * previous run — the fast fix → re-run loop.
196
+ */
197
+ lastFailed?: boolean;
198
+ /** Abort when the MCP client disconnects. */
199
+ signal?: AbortSignal;
200
+ }
201
+ export type TestStatus = 'passed' | 'failed' | 'timedOut' | 'skipped' | 'flaky' | 'unknown';
202
+ export type FailureKind = 'assertion' | 'timeout' | 'browser-crash' | 'syntax' | 'config' | 'server-unreachable' | 'unknown';
203
+ export interface TestFailure {
204
+ /** Project-relative file path, posix separators. */
205
+ file: string;
206
+ line?: number;
207
+ /** Full test title path joined with ' › '. */
208
+ title: string;
209
+ project?: string;
210
+ status: TestStatus;
211
+ failureKind: FailureKind;
212
+ retry?: number;
213
+ message: string;
214
+ stack?: string;
215
+ codeframe?: string;
216
+ expected?: string;
217
+ actual?: string;
218
+ durationMs?: number;
219
+ /** Absolute path of the Playwright trace.zip captured for this attempt. */
220
+ tracePath?: string;
221
+ /** Test console output (stdout) captured for this attempt. */
222
+ stdout?: string;
223
+ }
224
+ export interface RunTestResult {
225
+ ok: boolean;
226
+ exitCode: number | null;
227
+ signalName: string | null;
228
+ durationMs: number;
229
+ /** The wrapper timeout fired: process was killed, results are partial. */
230
+ timedOut: boolean;
231
+ killed: boolean;
232
+ /** True when results are incomplete (timeout/crash/missing report). */
233
+ partial: boolean;
234
+ stats: ReportStats | null;
235
+ failures: TestFailure[];
236
+ stdoutTail: string;
237
+ stderrTail: string;
238
+ truncatedOutput: boolean;
239
+ reportPath?: string;
240
+ configPath?: string;
241
+ command: string;
242
+ /** True when more failures occurred than were included in `failures`. */
243
+ failuresTruncated?: boolean;
244
+ /** Server injected --retries=1 so flaky tests pass before reporting. */
245
+ autoRetry?: boolean;
246
+ /** Run was restricted to previously failed tests (--last-failed). */
247
+ lastFailed?: boolean;
248
+ errorKind?: ErrorKind;
249
+ errorMessage?: string;
250
+ hint?: string;
251
+ }
252
+ export interface ListTestsOptions {
253
+ projectRoot: string;
254
+ configPath?: string;
255
+ testDir?: string | null;
256
+ /** Case-insensitive substring filter applied to "file › title". */
257
+ filter?: string;
258
+ limit?: number;
259
+ signal?: AbortSignal;
260
+ }
261
+ export interface DiscoveredTest {
262
+ file: string;
263
+ title: string;
264
+ line?: number;
265
+ projects?: string[];
266
+ }
267
+ export interface ListTestsResult {
268
+ tests: DiscoveredTest[];
269
+ total: number;
270
+ truncated: boolean;
271
+ testDir: string | null;
272
+ source: 'playwright-list' | 'file-scan';
273
+ /** Why Playwright could not list tests, when the fallback scan was used. */
274
+ error?: {
275
+ kind: ErrorKind;
276
+ message: string;
277
+ hint?: string;
278
+ };
279
+ }
280
+ export interface ElementInfo {
281
+ /** Stable CSS selector that uniquely addresses the element. */
282
+ selector: string;
283
+ tag: string;
284
+ id?: string;
285
+ classes: string[];
286
+ role?: string;
287
+ text?: string;
288
+ attributes: Record<string, string>;
289
+ visible: boolean;
290
+ box?: {
291
+ x: number;
292
+ y: number;
293
+ width: number;
294
+ height: number;
295
+ };
296
+ }
297
+ export interface ConsoleMessageInfo {
298
+ type: string;
299
+ text: string;
300
+ }
301
+ export interface InspectPageOptions {
302
+ projectRoot: string;
303
+ url: string;
304
+ /** When set, inspect matches for this selector instead of the whole DOM. */
305
+ selector?: string;
306
+ /** Extra wait condition: CSS selector or "text=..." to await first. */
307
+ waitFor?: string;
308
+ waitUntil?: 'load' | 'domcontentloaded' | 'networkidle';
309
+ includeHtml?: boolean;
310
+ maxHtmlChars?: number;
311
+ timeoutMs?: number;
312
+ signal?: AbortSignal;
313
+ }
314
+ export interface PageInspection {
315
+ requestedUrl: string;
316
+ finalUrl: string;
317
+ title: string;
318
+ elementCount: number;
319
+ elements: ElementInfo[];
320
+ html?: string;
321
+ htmlTruncated?: boolean;
322
+ consoleMessages: ConsoleMessageInfo[];
323
+ viewport: {
324
+ width: number;
325
+ height: number;
326
+ };
327
+ durationMs: number;
328
+ }
329
+ export interface ValidateSelectorOptions {
330
+ projectRoot: string;
331
+ url: string;
332
+ selector: string;
333
+ timeoutMs?: number;
334
+ signal?: AbortSignal;
335
+ }
336
+ export interface ValidateSelectorResult {
337
+ selector: string;
338
+ valid: boolean;
339
+ matchCount: number;
340
+ /** First few matches, capped for readability. */
341
+ matches: ElementInfo[];
342
+ parseError?: string;
343
+ hint?: string;
344
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Shared TypeScript interfaces and error types for playwright-e2e-mcp.
3
+ *
4
+ * Everything that other modules need to agree on lives here: tool
5
+ * contracts, Playwright JSON report shapes, run/inspect results, and the
6
+ * error taxonomy used to turn failures into actionable MCP responses.
7
+ */
8
+ /**
9
+ * Error type carrying an ErrorKind so the MCP layer can render a
10
+ * helpful remediation hint instead of a raw exception.
11
+ */
12
+ export class PlaywrightMcpError extends Error {
13
+ kind;
14
+ hint;
15
+ details;
16
+ constructor(message, kind = 'UNKNOWN', options = {}) {
17
+ super(message, options.cause !== undefined ? { cause: options.cause } : undefined);
18
+ this.name = 'PlaywrightMcpError';
19
+ this.kind = kind;
20
+ this.hint = options.hint;
21
+ this.details = options.details;
22
+ }
23
+ }
24
+ /** Wrap an unknown thrown value into a PlaywrightMcpError. */
25
+ export function toPlaywrightMcpError(err, fallbackKind = 'UNKNOWN') {
26
+ if (err instanceof PlaywrightMcpError)
27
+ return err;
28
+ if (err instanceof Error) {
29
+ const wrapped = new PlaywrightMcpError(err.message, classifyMessage(err.message, fallbackKind), {
30
+ cause: err,
31
+ });
32
+ wrapped.stack = err.stack;
33
+ return wrapped;
34
+ }
35
+ return new PlaywrightMcpError(String(err), fallbackKind);
36
+ }
37
+ function classifyMessage(message, fallbackKind) {
38
+ const m = message.toLowerCase();
39
+ if (m.includes('enoent') && m.includes('playwright'))
40
+ return 'NO_PLAYWRIGHT';
41
+ if (m.includes('syntaxerror') || m.includes('unexpected token'))
42
+ return 'SYNTAX_ERROR';
43
+ if (m.includes('enospc') || m.includes('no space left'))
44
+ return 'DISK_FULL';
45
+ if (m.includes('timed out') || m.includes('timeout'))
46
+ return 'TIMEOUT';
47
+ if (m.includes('target closed') || m.includes('browser has been closed'))
48
+ return 'BROWSER_CRASH';
49
+ return fallbackKind;
50
+ }
51
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAgDH;;;GAGG;AACH,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAClC,IAAI,CAAY;IAChB,IAAI,CAAU;IACd,OAAO,CAAU;IAE1B,YACE,OAAe,EACf,OAAkB,SAAS,EAC3B,UAAqC,EAAE;QAEvC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QACnF,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;QACjC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;QACzB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IACjC,CAAC;CACF;AAED,8DAA8D;AAC9D,MAAM,UAAU,oBAAoB,CAClC,GAAY,EACZ,eAA0B,SAAS;IAEnC,IAAI,GAAG,YAAY,kBAAkB;QAAE,OAAO,GAAG,CAAC;IAClD,IAAI,GAAG,YAAY,KAAK,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,IAAI,kBAAkB,CAAC,GAAG,CAAC,OAAO,EAAE,eAAe,CAAC,GAAG,CAAC,OAAO,EAAE,YAAY,CAAC,EAAE;YAC9F,KAAK,EAAE,GAAG;SACX,CAAC,CAAC;QACH,OAAO,CAAC,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC;QAC1B,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,OAAO,IAAI,kBAAkB,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,YAAY,CAAC,CAAC;AAC3D,CAAC;AAED,SAAS,eAAe,CAAC,OAAe,EAAE,YAAuB;IAC/D,MAAM,CAAC,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAChC,IAAI,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC;QAAE,OAAO,eAAe,CAAC;IAC7E,IAAI,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,kBAAkB,CAAC;QAAE,OAAO,cAAc,CAAC;IACvF,IAAI,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,eAAe,CAAC;QAAE,OAAO,WAAW,CAAC;IAC5E,IAAI,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC;QAAE,OAAO,SAAS,CAAC;IACvE,IAAI,CAAC,CAAC,QAAQ,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,yBAAyB,CAAC;QAAE,OAAO,eAAe,CAAC;IACjG,OAAO,YAAY,CAAC;AACtB,CAAC"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Change + selector analysis for the generate-e2e-test tool.
3
+ *
4
+ * "Look at the agent's recent file changes, identify the components,
5
+ * and scaffold a test using the correct selectors" (blueprint). This
6
+ * module answers the first two parts: which files changed (git working
7
+ * tree, last commit, or mtime fallback) and which locators those files
8
+ * actually declare (data-testid, roles, labels, ids…).
9
+ */
10
+ export type SelectorKind = 'test-id' | 'role' | 'aria' | 'placeholder' | 'id' | 'name' | 'text';
11
+ export interface SelectorCandidate {
12
+ kind: SelectorKind;
13
+ /** The raw value (test id, label, role name…). */
14
+ value: string;
15
+ /** Ready-to-paste Playwright locator fragment. */
16
+ locator: string;
17
+ file: string;
18
+ line: number;
19
+ }
20
+ export interface ChangedFile {
21
+ /** Project-relative posix path. */
22
+ path: string;
23
+ source: 'git' | 'mtime';
24
+ }
25
+ export interface ChangeAnalysis {
26
+ changeSource: 'git-status' | 'git-last-commit' | 'mtime';
27
+ files: ChangedFile[];
28
+ selectors: SelectorCandidate[];
29
+ scanned: number;
30
+ truncated: boolean;
31
+ }
32
+ /** Extract locators declared in one source file. */
33
+ export declare function extractSelectors(source: string, file: string): SelectorCandidate[];
34
+ export interface AnalyzeOptions {
35
+ /** Max files to read and scan. */
36
+ limit?: number;
37
+ /** mtime window for the non-git fallback. */
38
+ windowMs?: number;
39
+ }
40
+ export declare function analyzeRecentChanges(projectRoot: string, options?: AnalyzeOptions): Promise<ChangeAnalysis>;
41
+ export interface EntryUrl {
42
+ baseURL?: string;
43
+ webServerUrl?: string;
44
+ }
45
+ /** Pull `baseURL` / `webServer.url` out of a playwright.config source. */
46
+ export declare function detectEntryUrl(configSource: string): EntryUrl;