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.
- package/LICENSE +21 -0
- package/README.md +497 -0
- package/dist/http.d.ts +27 -0
- package/dist/http.js +156 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +56 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +23 -0
- package/dist/server.js +225 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/compare-visual-state.d.ts +77 -0
- package/dist/tools/compare-visual-state.js +223 -0
- package/dist/tools/compare-visual-state.js.map +1 -0
- package/dist/tools/diagnose-flaky.d.ts +87 -0
- package/dist/tools/diagnose-flaky.js +258 -0
- package/dist/tools/diagnose-flaky.js.map +1 -0
- package/dist/tools/generate-e2e-test.d.ts +51 -0
- package/dist/tools/generate-e2e-test.js +274 -0
- package/dist/tools/generate-e2e-test.js.map +1 -0
- package/dist/tools/get-failure.d.ts +26 -0
- package/dist/tools/get-failure.js +220 -0
- package/dist/tools/get-failure.js.map +1 -0
- package/dist/tools/inspect-page.d.ts +57 -0
- package/dist/tools/inspect-page.js +124 -0
- package/dist/tools/inspect-page.js.map +1 -0
- package/dist/tools/list-tests.d.ts +35 -0
- package/dist/tools/list-tests.js +88 -0
- package/dist/tools/list-tests.js.map +1 -0
- package/dist/tools/run-test.d.ts +70 -0
- package/dist/tools/run-test.js +169 -0
- package/dist/tools/run-test.js.map +1 -0
- package/dist/tools/shared.d.ts +86 -0
- package/dist/tools/shared.js +490 -0
- package/dist/tools/shared.js.map +1 -0
- package/dist/tools/validate-selector.d.ts +32 -0
- package/dist/tools/validate-selector.js +93 -0
- package/dist/tools/validate-selector.js.map +1 -0
- package/dist/types/index.d.ts +344 -0
- package/dist/types/index.js +51 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/change-analyzer.d.ts +46 -0
- package/dist/utils/change-analyzer.js +290 -0
- package/dist/utils/change-analyzer.js.map +1 -0
- package/dist/utils/image-diff.d.ts +61 -0
- package/dist/utils/image-diff.js +373 -0
- package/dist/utils/image-diff.js.map +1 -0
- package/dist/utils/logger.d.ts +21 -0
- package/dist/utils/logger.js +132 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/path-utils.d.ts +55 -0
- package/dist/utils/path-utils.js +211 -0
- package/dist/utils/path-utils.js.map +1 -0
- package/dist/utils/playwright-runner.d.ts +63 -0
- package/dist/utils/playwright-runner.js +594 -0
- package/dist/utils/playwright-runner.js.map +1 -0
- package/dist/utils/project-detector.d.ts +43 -0
- package/dist/utils/project-detector.js +255 -0
- package/dist/utils/project-detector.js.map +1 -0
- package/dist/utils/report-parser.d.ts +60 -0
- package/dist/utils/report-parser.js +501 -0
- package/dist/utils/report-parser.js.map +1 -0
- package/dist/utils/trace-reader.d.ts +109 -0
- package/dist/utils/trace-reader.js +608 -0
- package/dist/utils/trace-reader.js.map +1 -0
- package/examples/sample-test.spec.ts +49 -0
- package/package.json +66 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared plumbing for the five MCP tools: response formatting, project
|
|
3
|
+
* root/config resolution, failure rendering, and the browser-inspection
|
|
4
|
+
* child script used by inspect-page and validate-selector.
|
|
5
|
+
*
|
|
6
|
+
* (This file is an implementation detail; the five tool modules remain
|
|
7
|
+
* the public surface documented in the README.)
|
|
8
|
+
*/
|
|
9
|
+
import type { ErrorKind, FailureKind, ProjectDetection, TestFailure, ToolContext, ToolResponse, ToolStore } from '../types/index.js';
|
|
10
|
+
export declare function createToolStore(): ToolStore;
|
|
11
|
+
export declare function toolText(markdown: string): ToolResponse;
|
|
12
|
+
export declare function toolError(kind: ErrorKind | 'ERROR', message: string, hint?: string, details?: string): ToolResponse;
|
|
13
|
+
/** Run a tool body, converting any thrown error into a tool response. */
|
|
14
|
+
export declare function guard(label: string, fn: () => Promise<ToolResponse>): Promise<ToolResponse>;
|
|
15
|
+
/**
|
|
16
|
+
* Resolve the project root: defaults to the server's working directory;
|
|
17
|
+
* an explicit value is resolved against it and must exist.
|
|
18
|
+
*/
|
|
19
|
+
export declare function resolveProjectRoot(input: string | undefined, ctx: ToolContext): Promise<string>;
|
|
20
|
+
/**
|
|
21
|
+
* Choose the playwright.config for a run/list. Throws MULTIPLE_CONFIGS
|
|
22
|
+
* (with a numbered menu) when the project has several and none chosen.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveConfigSelection(detection: ProjectDetection, selection?: string): string | undefined;
|
|
25
|
+
/** Validate a user-supplied test path (supports the `file:line` suffix). */
|
|
26
|
+
export declare function sanitizeTestPathArgument(input: string, root: string): string;
|
|
27
|
+
/** True when the path portion of a `file:line` argument exists. */
|
|
28
|
+
export declare function testPathExists(argument: string, root: string): Promise<boolean>;
|
|
29
|
+
export declare function isInsideRoot(path: string, root: string): boolean;
|
|
30
|
+
export declare function formatDuration(ms: number): string;
|
|
31
|
+
export declare function codeFence(text: string, lang?: string): string;
|
|
32
|
+
export declare function clipLines(text: string, maxLines: number, maxChars?: number): string;
|
|
33
|
+
export declare function renderFailure(failure: TestFailure, index: number, total: number): string;
|
|
34
|
+
export declare function failureHint(kind: FailureKind): string;
|
|
35
|
+
export interface BrowserScriptConfig {
|
|
36
|
+
mode: 'inspect' | 'validate' | 'screenshot';
|
|
37
|
+
projectRoot: string;
|
|
38
|
+
url: string;
|
|
39
|
+
selector?: string;
|
|
40
|
+
waitFor?: string;
|
|
41
|
+
waitUntil?: 'load' | 'domcontentloaded' | 'networkidle';
|
|
42
|
+
includeHtml?: boolean;
|
|
43
|
+
maxHtmlChars?: number;
|
|
44
|
+
gotoTimeout?: number;
|
|
45
|
+
waitTimeout?: number;
|
|
46
|
+
/** Screenshot target path (mode: 'screenshot'). */
|
|
47
|
+
screenshotPath?: string;
|
|
48
|
+
/** Capture the full scrollable page (mode: 'screenshot'). */
|
|
49
|
+
fullPage?: boolean;
|
|
50
|
+
startedAt: number;
|
|
51
|
+
}
|
|
52
|
+
export interface BrowserScriptData {
|
|
53
|
+
title?: string;
|
|
54
|
+
finalUrl?: string;
|
|
55
|
+
screenshotPath?: string;
|
|
56
|
+
elementCount: number;
|
|
57
|
+
matchCount: number;
|
|
58
|
+
elements: import('../types/index.js').ElementInfo[];
|
|
59
|
+
html?: string;
|
|
60
|
+
htmlTruncated?: boolean;
|
|
61
|
+
viewport?: {
|
|
62
|
+
width: number;
|
|
63
|
+
height: number;
|
|
64
|
+
};
|
|
65
|
+
parseError?: string;
|
|
66
|
+
consoleMessages?: import('../types/index.js').ConsoleMessageInfo[];
|
|
67
|
+
durationMs?: number;
|
|
68
|
+
}
|
|
69
|
+
export interface BrowserScriptOutcome {
|
|
70
|
+
ok: boolean;
|
|
71
|
+
data?: BrowserScriptData;
|
|
72
|
+
kind?: ErrorKind;
|
|
73
|
+
error?: string;
|
|
74
|
+
hint?: string;
|
|
75
|
+
stderrTail?: string;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Write the probe script to a temp file and run it with the project's
|
|
79
|
+
* Playwright, killing the whole tree on timeout or client disconnect.
|
|
80
|
+
*/
|
|
81
|
+
export declare function runBrowserScript(config: BrowserScriptConfig, options: {
|
|
82
|
+
timeoutMs?: number;
|
|
83
|
+
signal?: AbortSignal;
|
|
84
|
+
}): Promise<BrowserScriptOutcome>;
|
|
85
|
+
/** Accept only http(s) URLs for page-facing tools. */
|
|
86
|
+
export declare function assertHttpUrl(url: string): string;
|
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared plumbing for the five MCP tools: response formatting, project
|
|
3
|
+
* root/config resolution, failure rendering, and the browser-inspection
|
|
4
|
+
* child script used by inspect-page and validate-selector.
|
|
5
|
+
*
|
|
6
|
+
* (This file is an implementation detail; the five tool modules remain
|
|
7
|
+
* the public surface documented in the README.)
|
|
8
|
+
*/
|
|
9
|
+
import { rm, stat, writeFile } from 'node:fs/promises';
|
|
10
|
+
import { PlaywrightMcpError, toPlaywrightMcpError } from '../types/index.js';
|
|
11
|
+
import { isPathInside, normalizePath, resolvePath, sanitizeUserPath, tempFilePath, } from '../utils/path-utils.js';
|
|
12
|
+
import { selectConfig } from '../utils/project-detector.js';
|
|
13
|
+
import { runProcess } from '../utils/playwright-runner.js';
|
|
14
|
+
import { extractJsonFromText } from '../utils/report-parser.js';
|
|
15
|
+
/* ------------------------------------------------------------------ */
|
|
16
|
+
/* State + responses */
|
|
17
|
+
/* ------------------------------------------------------------------ */
|
|
18
|
+
export function createToolStore() {
|
|
19
|
+
let lastRun = null;
|
|
20
|
+
return {
|
|
21
|
+
get lastRun() {
|
|
22
|
+
return lastRun;
|
|
23
|
+
},
|
|
24
|
+
setLastRun(record) {
|
|
25
|
+
lastRun = record;
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export function toolText(markdown) {
|
|
30
|
+
return { content: [{ type: 'text', text: markdown }] };
|
|
31
|
+
}
|
|
32
|
+
export function toolError(kind, message, hint, details) {
|
|
33
|
+
const lines = [`## ❌ ${kind}`, '', message];
|
|
34
|
+
if (hint)
|
|
35
|
+
lines.push('', '**How to fix:**', hint);
|
|
36
|
+
if (details)
|
|
37
|
+
lines.push('', '```text', details.trim(), '```');
|
|
38
|
+
return { content: [{ type: 'text', text: lines.join('\n') }], isError: true };
|
|
39
|
+
}
|
|
40
|
+
/** Run a tool body, converting any thrown error into a tool response. */
|
|
41
|
+
export async function guard(label, fn) {
|
|
42
|
+
try {
|
|
43
|
+
return await fn();
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
const error = toPlaywrightMcpError(err);
|
|
47
|
+
return toolError(error.kind, `**${label}** failed: ${error.message}`, error.hint, error.details);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/* ------------------------------------------------------------------ */
|
|
51
|
+
/* Project root + config resolution */
|
|
52
|
+
/* ------------------------------------------------------------------ */
|
|
53
|
+
async function isDirectory(p) {
|
|
54
|
+
try {
|
|
55
|
+
return (await stat(p)).isDirectory();
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Resolve the project root: defaults to the server's working directory;
|
|
63
|
+
* an explicit value is resolved against it and must exist.
|
|
64
|
+
*/
|
|
65
|
+
export async function resolveProjectRoot(input, ctx) {
|
|
66
|
+
const base = normalizePath(ctx.projectRoot);
|
|
67
|
+
if (input === undefined || input.trim() === '')
|
|
68
|
+
return base;
|
|
69
|
+
const resolved = normalizePath(resolvePath(base, input.trim()));
|
|
70
|
+
if (!(await isDirectory(resolved))) {
|
|
71
|
+
throw new PlaywrightMcpError(`Project root not found: ${resolved}`, 'INVALID_PATH', {
|
|
72
|
+
hint: 'Pass `projectRoot` as an existing directory (absolute, or relative to the server working directory).',
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return resolved;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Choose the playwright.config for a run/list. Throws MULTIPLE_CONFIGS
|
|
79
|
+
* (with a numbered menu) when the project has several and none chosen.
|
|
80
|
+
*/
|
|
81
|
+
export function resolveConfigSelection(detection, selection) {
|
|
82
|
+
const candidates = detection.configCandidates;
|
|
83
|
+
if (candidates.length === 0) {
|
|
84
|
+
if (selection) {
|
|
85
|
+
throw new PlaywrightMcpError(`No config file exists to match "${selection}"`, 'NO_CONFIG', {
|
|
86
|
+
hint: `This project has no playwright.config.* file. Looked in ${detection.root}.`,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
if (selection !== undefined && selection.trim() !== '') {
|
|
92
|
+
const trimmed = selection.trim();
|
|
93
|
+
// Numeric selections are 1-based indexes into the candidate list.
|
|
94
|
+
const numeric = /^\d+$/.test(trimmed) ? Number(trimmed) : undefined;
|
|
95
|
+
const picked = numeric !== undefined ? selectConfig(candidates, numeric) : selectConfig(candidates, trimmed);
|
|
96
|
+
if (!picked) {
|
|
97
|
+
const sanitized = sanitizeUserPath(trimmed, detection.root);
|
|
98
|
+
if (!candidates.includes(sanitized)) {
|
|
99
|
+
throw new PlaywrightMcpError(`No config file matches "${selection}"`, 'NO_CONFIG', {
|
|
100
|
+
hint: `Available configs:\n${candidates.map((c, i) => ` ${i + 1}. ${normalizePath(c)}`).join('\n')}`,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
return sanitized;
|
|
104
|
+
}
|
|
105
|
+
return picked;
|
|
106
|
+
}
|
|
107
|
+
if (candidates.length === 1)
|
|
108
|
+
return candidates[0];
|
|
109
|
+
const menu = candidates
|
|
110
|
+
.map((c, i) => `${i + 1}. ${normalizePath(c).replace(`${detection.root}/`, '')}`)
|
|
111
|
+
.join('\n');
|
|
112
|
+
throw new PlaywrightMcpError(`Multiple playwright.config files found in ${detection.root}`, 'MULTIPLE_CONFIGS', {
|
|
113
|
+
hint: `Pass \`config\` with the number or path of the one to use:\n${menu}`,
|
|
114
|
+
details: candidates.join('\n'),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
/** Validate a user-supplied test path (supports the `file:line` suffix). */
|
|
118
|
+
export function sanitizeTestPathArgument(input, root) {
|
|
119
|
+
const trimmed = input.trim();
|
|
120
|
+
const lineMatch = /:(\d+)$/.exec(trimmed);
|
|
121
|
+
const pathPart = lineMatch ? trimmed.slice(0, -lineMatch[0].length) : trimmed;
|
|
122
|
+
const suffix = lineMatch ? lineMatch[0] : '';
|
|
123
|
+
const absolute = sanitizeUserPath(pathPart, root);
|
|
124
|
+
return `${absolute}${suffix}`;
|
|
125
|
+
}
|
|
126
|
+
/** True when the path portion of a `file:line` argument exists. */
|
|
127
|
+
export async function testPathExists(argument, root) {
|
|
128
|
+
const lineMatch = /:(\d+)$/.exec(argument);
|
|
129
|
+
const pathPart = lineMatch ? argument.slice(0, -lineMatch[0].length) : argument;
|
|
130
|
+
try {
|
|
131
|
+
await stat(resolvePath(root, pathPart));
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
export function isInsideRoot(path, root) {
|
|
139
|
+
return isPathInside(path, root);
|
|
140
|
+
}
|
|
141
|
+
/* ------------------------------------------------------------------ */
|
|
142
|
+
/* Rendering helpers */
|
|
143
|
+
/* ------------------------------------------------------------------ */
|
|
144
|
+
export function formatDuration(ms) {
|
|
145
|
+
if (ms < 1_000)
|
|
146
|
+
return `${Math.round(ms)}ms`;
|
|
147
|
+
if (ms < 60_000)
|
|
148
|
+
return `${(ms / 1_000).toFixed(1)}s`;
|
|
149
|
+
const minutes = Math.floor(ms / 60_000);
|
|
150
|
+
const seconds = Math.round((ms % 60_000) / 1_000);
|
|
151
|
+
return `${minutes}m ${seconds}s`;
|
|
152
|
+
}
|
|
153
|
+
export function codeFence(text, lang = '') {
|
|
154
|
+
const body = text.trim();
|
|
155
|
+
const safe = body.includes('```') ? body.replace(/```/g, "'''") : body;
|
|
156
|
+
return ['```' + lang, safe, '```'].join('\n');
|
|
157
|
+
}
|
|
158
|
+
export function clipLines(text, maxLines, maxChars = 4_000) {
|
|
159
|
+
const lines = text.split(/\r?\n/);
|
|
160
|
+
const clipped = lines.length > maxLines ? lines.slice(0, maxLines).join('\n') : text;
|
|
161
|
+
const final = lines.length > maxLines
|
|
162
|
+
? `${clipped}\n… (${lines.length - maxLines} more line${lines.length - maxLines === 1 ? '' : 's'})`
|
|
163
|
+
: clipped;
|
|
164
|
+
return final.length > maxChars ? `${final.slice(0, maxChars)}…` : final;
|
|
165
|
+
}
|
|
166
|
+
export function renderFailure(failure, index, total) {
|
|
167
|
+
const location = failure.file ? `\`${failure.file}${failure.line ? `:${failure.line}` : ''}\`` : '_config_';
|
|
168
|
+
const meta = [
|
|
169
|
+
failure.status,
|
|
170
|
+
failure.failureKind !== 'unknown' ? failure.failureKind : undefined,
|
|
171
|
+
failure.project,
|
|
172
|
+
failure.retry !== undefined && failure.retry > 0 ? `retry ${failure.retry + 1}` : undefined,
|
|
173
|
+
]
|
|
174
|
+
.filter(Boolean)
|
|
175
|
+
.join(' · ');
|
|
176
|
+
const lines = [
|
|
177
|
+
`### ${index + 1} of ${total}. ${failure.title}`,
|
|
178
|
+
`**File:** ${location} | **${meta}**`,
|
|
179
|
+
];
|
|
180
|
+
lines.push('', codeFence(clipLines(failure.message, 25), 'text'));
|
|
181
|
+
if (failure.expected !== undefined || failure.actual !== undefined) {
|
|
182
|
+
lines.push('', `- **Expected:** ${failure.expected === undefined ? '—' : `\`${clipLines(failure.expected, 5, 300)}\``}`, `- **Actual:** ${failure.actual === undefined ? '—' : `\`${clipLines(failure.actual, 5, 300)}\``}`);
|
|
183
|
+
}
|
|
184
|
+
return lines.join('\n');
|
|
185
|
+
}
|
|
186
|
+
const KIND_HINTS = {
|
|
187
|
+
assertion: 'The assertion failed. Compare expected vs actual above, and check the code frame for the exact line.',
|
|
188
|
+
timeout: 'The step never completed. Look for a missing `await`, an element that never appears, or a page that hangs — raise the timeout only if the app really needs longer.',
|
|
189
|
+
'browser-crash': 'The browser crashed or closed early. Retry the run; if it persists run `npx playwright install --force` and close other automation browsers.',
|
|
190
|
+
syntax: 'Fix the syntax error in the file shown above, then re-run.',
|
|
191
|
+
config: 'Playwright rejected the configuration or found no tests. Check `playwright.config.*` (projects, testDir, grep) and try again.',
|
|
192
|
+
'server-unreachable': 'The app under test was not reachable. Start your dev server (e.g. `npm run dev`) and keep it running, or configure `webServer` in playwright.config.*.',
|
|
193
|
+
unknown: 'Inspect the stack trace and run the single test with `headed: true` to watch it live.',
|
|
194
|
+
};
|
|
195
|
+
export function failureHint(kind) {
|
|
196
|
+
return KIND_HINTS[kind] ?? KIND_HINTS.unknown;
|
|
197
|
+
}
|
|
198
|
+
/* ------------------------------------------------------------------ */
|
|
199
|
+
/* Browser child script (inspect-page / validate-selector) */
|
|
200
|
+
/* ------------------------------------------------------------------ */
|
|
201
|
+
/**
|
|
202
|
+
* Runs inside a child `node` process with cwd = project root so that
|
|
203
|
+
* `createRequire(projectRoot/package.json)` resolves the *user's*
|
|
204
|
+
* Playwright install (browsers included). Writes one JSON document to
|
|
205
|
+
* stdout and always exits, so it can never wedge the server.
|
|
206
|
+
*/
|
|
207
|
+
const PROBE_SCRIPT = String.raw `
|
|
208
|
+
'use strict';
|
|
209
|
+
const cfg = JSON.parse(process.argv[2]);
|
|
210
|
+
|
|
211
|
+
function send(payload) {
|
|
212
|
+
try { process.stdout.write(JSON.stringify(payload)); } catch (err) { /* ignore */ }
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function classifyError(message) {
|
|
216
|
+
const m = String(message);
|
|
217
|
+
if (/Executable doesn't exist|Please run the following command to install/i.test(m)) {
|
|
218
|
+
return { kind: 'NO_PLAYWRIGHT', hint: 'Playwright browsers are not installed for this project. Run: npx playwright install chromium' };
|
|
219
|
+
}
|
|
220
|
+
if (/ECONNREFUSED|ERR_CONNECTION_REFUSED|ERR_NAME_NOT_RESOLVED|ERR_EMPTY_RESPONSE|getaddrinfo|net::ERR_/i.test(m)) {
|
|
221
|
+
return { kind: 'SERVER_NOT_RUNNING', hint: 'The URL is not reachable. Start your dev server (e.g. npm run dev / npm start) and retry.' };
|
|
222
|
+
}
|
|
223
|
+
if (/Timeout \d+ms exceeded|timed out/i.test(m)) {
|
|
224
|
+
return { kind: 'TIMEOUT', hint: 'Raise timeoutMs, or make the page load faster.' };
|
|
225
|
+
}
|
|
226
|
+
if (/Target closed|browser has been closed|browser has crashed|Page crashed/i.test(m)) {
|
|
227
|
+
return { kind: 'BROWSER_CRASH', hint: 'The browser crashed during inspection. Retry; if it persists run: npx playwright install --force' };
|
|
228
|
+
}
|
|
229
|
+
if (/Unknown selector|error evaluating selector/i.test(m)) {
|
|
230
|
+
return { kind: 'INVALID_PATH', hint: 'The selector could not be evaluated.' };
|
|
231
|
+
}
|
|
232
|
+
return { kind: 'UNKNOWN', hint: undefined };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
(async () => {
|
|
236
|
+
const { createRequire } = require('node:module');
|
|
237
|
+
const req = createRequire(cfg.projectRoot + '/package.json');
|
|
238
|
+
let pw = null;
|
|
239
|
+
try {
|
|
240
|
+
pw = req('playwright');
|
|
241
|
+
} catch (errA) {
|
|
242
|
+
try {
|
|
243
|
+
pw = req('@playwright/test');
|
|
244
|
+
} catch (errB) {
|
|
245
|
+
send({
|
|
246
|
+
ok: false,
|
|
247
|
+
kind: 'NO_PLAYWRIGHT',
|
|
248
|
+
error: 'Playwright is not installed in ' + cfg.projectRoot,
|
|
249
|
+
hint: 'Run: npm install -D @playwright/test && npx playwright install',
|
|
250
|
+
});
|
|
251
|
+
process.exit(1);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
let browser = null;
|
|
256
|
+
try {
|
|
257
|
+
browser = await pw.chromium.launch({ headless: true });
|
|
258
|
+
const context = await browser.newContext({ viewport: { width: 1280, height: 720 } });
|
|
259
|
+
const page = await context.newPage();
|
|
260
|
+
|
|
261
|
+
const consoleMessages = [];
|
|
262
|
+
page.on('console', (msg) => {
|
|
263
|
+
if (consoleMessages.length < 50) consoleMessages.push({ type: msg.type(), text: String(msg.text()).slice(0, 300) });
|
|
264
|
+
});
|
|
265
|
+
page.on('pageerror', (err) => {
|
|
266
|
+
if (consoleMessages.length < 50) consoleMessages.push({ type: 'pageerror', text: String(err).slice(0, 300) });
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
await page.goto(cfg.url, { waitUntil: cfg.waitUntil || 'domcontentloaded', timeout: cfg.gotoTimeout || 15000 });
|
|
270
|
+
if (cfg.waitFor) await page.waitForSelector(cfg.waitFor, { timeout: cfg.waitTimeout || 5000 });
|
|
271
|
+
|
|
272
|
+
if (cfg.mode === 'screenshot') {
|
|
273
|
+
if (cfg.selector) {
|
|
274
|
+
await page.locator(cfg.selector).first().screenshot({ path: cfg.screenshotPath });
|
|
275
|
+
} else {
|
|
276
|
+
await page.screenshot({ path: cfg.screenshotPath, fullPage: !!cfg.fullPage });
|
|
277
|
+
}
|
|
278
|
+
send({
|
|
279
|
+
ok: true,
|
|
280
|
+
data: {
|
|
281
|
+
screenshotPath: cfg.screenshotPath,
|
|
282
|
+
title: await page.title(),
|
|
283
|
+
finalUrl: page.url(),
|
|
284
|
+
viewport: page.viewportSize() || { width: 1280, height: 720 },
|
|
285
|
+
elementCount: 0,
|
|
286
|
+
matchCount: 0,
|
|
287
|
+
elements: [],
|
|
288
|
+
durationMs: Date.now() - cfg.startedAt,
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
await browser.close();
|
|
292
|
+
process.exit(0);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const data = await page.evaluate((input) => {
|
|
296
|
+
function uniqueSelector(el) {
|
|
297
|
+
if (el.id) {
|
|
298
|
+
const css = '#' + (window.CSS && CSS.escape ? CSS.escape(el.id) : el.id);
|
|
299
|
+
try { if (document.querySelectorAll(css).length === 1) return css; } catch (err) { /* fall through */ }
|
|
300
|
+
}
|
|
301
|
+
const parts = [];
|
|
302
|
+
let node = el;
|
|
303
|
+
while (node && node.nodeType === 1 && parts.length < 6) {
|
|
304
|
+
if (node === document.documentElement) { parts.unshift('html'); break; }
|
|
305
|
+
let part = node.tagName.toLowerCase();
|
|
306
|
+
const parent = node.parentElement;
|
|
307
|
+
if (parent) {
|
|
308
|
+
const siblings = Array.prototype.filter.call(parent.children, (child) => child.tagName === node.tagName);
|
|
309
|
+
if (siblings.length > 1) {
|
|
310
|
+
const index = Array.prototype.indexOf.call(siblings, node) + 1;
|
|
311
|
+
part = part + ':nth-of-type(' + index + ')';
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
parts.unshift(part);
|
|
315
|
+
const candidate = parts.join(' > ');
|
|
316
|
+
try { if (document.querySelectorAll(candidate).length === 1) return candidate; } catch (err) { /* keep walking */ }
|
|
317
|
+
node = node.parentElement;
|
|
318
|
+
}
|
|
319
|
+
return parts.join(' > ');
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function describe(el) {
|
|
323
|
+
const rect = el.getBoundingClientRect();
|
|
324
|
+
let visible = !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length);
|
|
325
|
+
try {
|
|
326
|
+
const style = getComputedStyle(el);
|
|
327
|
+
if (style.visibility === 'hidden' || style.display === 'none') visible = false;
|
|
328
|
+
} catch (err) { /* ignore */ }
|
|
329
|
+
const attributes = {};
|
|
330
|
+
const attrCount = Math.min(el.attributes.length, 30);
|
|
331
|
+
for (let i = 0; i < attrCount; i += 1) {
|
|
332
|
+
const attr = el.attributes[i];
|
|
333
|
+
attributes[attr.name] = String(attr.value).slice(0, 200);
|
|
334
|
+
}
|
|
335
|
+
const classes = Array.prototype.slice.call(el.classList).slice(0, 10);
|
|
336
|
+
const text = (el.textContent || '').trim().replace(/\s+/g, ' ').slice(0, 160);
|
|
337
|
+
return {
|
|
338
|
+
selector: uniqueSelector(el),
|
|
339
|
+
tag: el.tagName.toLowerCase(),
|
|
340
|
+
id: el.id || undefined,
|
|
341
|
+
classes: classes,
|
|
342
|
+
role: el.getAttribute('role') || undefined,
|
|
343
|
+
text: text || undefined,
|
|
344
|
+
attributes: attributes,
|
|
345
|
+
visible: visible,
|
|
346
|
+
box: { x: Math.round(rect.x), y: Math.round(rect.y), width: Math.round(rect.width), height: Math.round(rect.height) },
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const result = { matchCount: 0, elementCount: 0, elements: [], parseError: undefined };
|
|
351
|
+
let source = [];
|
|
352
|
+
if (input.selector) {
|
|
353
|
+
try {
|
|
354
|
+
source = Array.prototype.slice.call(document.querySelectorAll(input.selector));
|
|
355
|
+
} catch (err) {
|
|
356
|
+
result.parseError = err && err.message ? err.message : String(err);
|
|
357
|
+
}
|
|
358
|
+
} else {
|
|
359
|
+
source = Array.prototype.slice.call(document.querySelectorAll('*'));
|
|
360
|
+
}
|
|
361
|
+
if (result.parseError) return result;
|
|
362
|
+
|
|
363
|
+
result.matchCount = source.length;
|
|
364
|
+
result.elementCount = source.length;
|
|
365
|
+
const limit = input.mode === 'validate' ? 5 : 100;
|
|
366
|
+
result.elements = source.slice(0, limit).map(describe);
|
|
367
|
+
|
|
368
|
+
if (input.mode === 'inspect') {
|
|
369
|
+
result.title = document.title;
|
|
370
|
+
result.finalUrl = location.href;
|
|
371
|
+
result.viewport = { width: window.innerWidth, height: window.innerHeight };
|
|
372
|
+
if (input.includeHtml) {
|
|
373
|
+
const html = document.documentElement.outerHTML;
|
|
374
|
+
result.html = html.slice(0, input.maxHtml);
|
|
375
|
+
result.htmlTruncated = html.length > input.maxHtml;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return result;
|
|
379
|
+
}, {
|
|
380
|
+
mode: cfg.mode,
|
|
381
|
+
selector: cfg.selector,
|
|
382
|
+
includeHtml: !!cfg.includeHtml,
|
|
383
|
+
maxHtml: cfg.maxHtmlChars || 20000,
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
data.consoleMessages = consoleMessages;
|
|
387
|
+
data.durationMs = Date.now() - cfg.startedAt;
|
|
388
|
+
send({ ok: true, data: data });
|
|
389
|
+
await browser.close();
|
|
390
|
+
process.exit(0);
|
|
391
|
+
} catch (err) {
|
|
392
|
+
const message = err && err.message ? err.message : String(err);
|
|
393
|
+
const classified = classifyError(message);
|
|
394
|
+
send({ ok: false, kind: classified.kind, error: message, hint: classified.hint });
|
|
395
|
+
try { if (browser) await browser.close(); } catch (closeErr) { /* ignore */ }
|
|
396
|
+
process.exit(1);
|
|
397
|
+
}
|
|
398
|
+
})().catch((err) => {
|
|
399
|
+
send({ ok: false, kind: 'UNKNOWN', error: err && err.message ? err.message : String(err) });
|
|
400
|
+
process.exit(1);
|
|
401
|
+
});
|
|
402
|
+
`;
|
|
403
|
+
/**
|
|
404
|
+
* Write the probe script to a temp file and run it with the project's
|
|
405
|
+
* Playwright, killing the whole tree on timeout or client disconnect.
|
|
406
|
+
*/
|
|
407
|
+
export async function runBrowserScript(config, options) {
|
|
408
|
+
const scriptPath = tempFilePath('pw-mcp-probe', '.cjs');
|
|
409
|
+
await writeFile(scriptPath, PROBE_SCRIPT, 'utf8');
|
|
410
|
+
try {
|
|
411
|
+
const outcome = await runProcess(process.execPath, [scriptPath, JSON.stringify(config)], {
|
|
412
|
+
cwd: config.projectRoot,
|
|
413
|
+
env: { ...process.env, FORCE_COLOR: '0' },
|
|
414
|
+
timeoutMs: options.timeoutMs ?? 45_000,
|
|
415
|
+
signal: options.signal,
|
|
416
|
+
});
|
|
417
|
+
if (outcome.spawnError) {
|
|
418
|
+
return {
|
|
419
|
+
ok: false,
|
|
420
|
+
kind: 'SPAWN_FAILED',
|
|
421
|
+
error: outcome.spawnError.message,
|
|
422
|
+
hint: 'Could not start a Node child process to inspect the page.',
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
if (outcome.aborted) {
|
|
426
|
+
return { ok: false, kind: 'CLIENT_DISCONNECT', error: 'Inspection aborted (client disconnected).' };
|
|
427
|
+
}
|
|
428
|
+
const json = extractJsonFromText(outcome.stdout);
|
|
429
|
+
if (json !== null) {
|
|
430
|
+
try {
|
|
431
|
+
const parsed = JSON.parse(json);
|
|
432
|
+
if (parsed.ok)
|
|
433
|
+
return { ok: true, data: parsed.data };
|
|
434
|
+
return {
|
|
435
|
+
ok: false,
|
|
436
|
+
kind: parsed.kind ?? 'UNKNOWN',
|
|
437
|
+
error: parsed.error ?? 'Inspection failed.',
|
|
438
|
+
hint: parsed.hint,
|
|
439
|
+
stderrTail: tailText(outcome.stderr),
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
catch {
|
|
443
|
+
/* fall through to generic handling */
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
if (outcome.timedOut) {
|
|
447
|
+
return {
|
|
448
|
+
ok: false,
|
|
449
|
+
kind: 'TIMEOUT',
|
|
450
|
+
error: `Page inspection exceeded ${options.timeoutMs ?? 45_000}ms and was killed.`,
|
|
451
|
+
hint: 'The page may be blocking on network requests. Raise timeoutMs or use waitUntil: "domcontentloaded".',
|
|
452
|
+
stderrTail: tailText(outcome.stderr),
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
return {
|
|
456
|
+
ok: false,
|
|
457
|
+
kind: 'UNKNOWN',
|
|
458
|
+
error: 'The inspection process produced no parseable output.',
|
|
459
|
+
hint: 'Check that the page loads and that Playwright is installed correctly.',
|
|
460
|
+
stderrTail: tailText(outcome.stderr),
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
finally {
|
|
464
|
+
await rm(scriptPath, { force: true }).catch(() => undefined);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
function tailText(text, limit = 2_000) {
|
|
468
|
+
const trimmed = text.trim();
|
|
469
|
+
return trimmed.length > limit ? trimmed.slice(-limit) : trimmed;
|
|
470
|
+
}
|
|
471
|
+
/** Accept only http(s) URLs for page-facing tools. */
|
|
472
|
+
export function assertHttpUrl(url) {
|
|
473
|
+
const trimmed = url.trim();
|
|
474
|
+
let parsed;
|
|
475
|
+
try {
|
|
476
|
+
parsed = new URL(trimmed);
|
|
477
|
+
}
|
|
478
|
+
catch {
|
|
479
|
+
throw new PlaywrightMcpError(`"${url}" is not a valid URL`, 'INVALID_PATH', {
|
|
480
|
+
hint: 'Use a full URL, e.g. http://localhost:3000/login',
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
484
|
+
throw new PlaywrightMcpError(`Unsupported URL scheme "${parsed.protocol}"`, 'INVALID_PATH', {
|
|
485
|
+
hint: 'Only http:// and https:// URLs are supported.',
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
return parsed.toString();
|
|
489
|
+
}
|
|
490
|
+
//# sourceMappingURL=shared.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../../src/tools/shared.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
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 type { ToolContext, ToolResponse } from '../types/index.js';
|
|
8
|
+
declare const validateSelectorInput: z.ZodObject<{
|
|
9
|
+
url: z.ZodString;
|
|
10
|
+
selector: z.ZodString;
|
|
11
|
+
projectRoot: z.ZodOptional<z.ZodString>;
|
|
12
|
+
timeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
13
|
+
}, z.core.$strip>;
|
|
14
|
+
export type ValidateSelectorInput = z.infer<typeof validateSelectorInput>;
|
|
15
|
+
export declare const validateSelectorSchema: z.ZodObject<{
|
|
16
|
+
url: z.ZodString;
|
|
17
|
+
selector: z.ZodString;
|
|
18
|
+
projectRoot: z.ZodOptional<z.ZodString>;
|
|
19
|
+
timeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
20
|
+
}, z.core.$strip>;
|
|
21
|
+
export declare const validateSelectorTool: {
|
|
22
|
+
name: string;
|
|
23
|
+
description: string;
|
|
24
|
+
inputSchema: z.ZodObject<{
|
|
25
|
+
url: z.ZodString;
|
|
26
|
+
selector: z.ZodString;
|
|
27
|
+
projectRoot: z.ZodOptional<z.ZodString>;
|
|
28
|
+
timeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
29
|
+
}, z.core.$strip>;
|
|
30
|
+
handler: (args: ValidateSelectorInput, ctx: ToolContext) => Promise<ToolResponse>;
|
|
31
|
+
};
|
|
32
|
+
export {};
|