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
package/dist/http.js ADDED
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Streamable HTTP bridge for the MCP server.
3
+ *
4
+ * `createMcpHandler` (MCP SDK) is fetch-shaped: it answers the 2025
5
+ * streamable-HTTP transport *statelessly* — a fresh server per request, which
6
+ * is exactly what a serverless function wants — and the modern envelope
7
+ * transport on the same URL. This module adds the thin adapter for runtimes
8
+ * that hand you Node's `IncomingMessage`/`ServerResponse` pair (Vercel
9
+ * functions, a plain `node:http` server), so the very same code path can be
10
+ * verified locally before it is deployed.
11
+ *
12
+ * The stdio transport in server.ts is untouched; this is an additional way to
13
+ * reach the same eight tools.
14
+ */
15
+ import { createMcpHandler } from '@modelcontextprotocol/server';
16
+ import { createServer } from './server.js';
17
+ import { logger } from './utils/logger.js';
18
+ /** Mirrors the SDK's default POST body bound, so oversized bodies die early. */
19
+ export const MAX_BODY_BYTES = 4 * 1024 * 1024;
20
+ /** Build the fetch-shaped MCP handler backed by a fresh server per request. */
21
+ export function createMcpHttpHandler() {
22
+ return createMcpHandler(() => createServer(), {
23
+ legacy: 'stateless',
24
+ onerror: (error) => logger.error('mcp http handler error', { error }),
25
+ });
26
+ }
27
+ /** A failure that maps onto an HTTP status before any response is written. */
28
+ class RequestError extends Error {
29
+ status;
30
+ constructor(status, message) {
31
+ super(message);
32
+ this.status = status;
33
+ }
34
+ }
35
+ function firstHeader(value) {
36
+ if (Array.isArray(value))
37
+ return value[0];
38
+ return value;
39
+ }
40
+ function toHeaders(raw) {
41
+ const headers = new Headers();
42
+ for (const [key, value] of Object.entries(raw)) {
43
+ if (value === undefined)
44
+ continue;
45
+ if (Array.isArray(value)) {
46
+ for (const item of value)
47
+ headers.append(key, item);
48
+ }
49
+ else {
50
+ headers.set(key, value);
51
+ }
52
+ }
53
+ return headers;
54
+ }
55
+ async function readBody(req) {
56
+ const chunks = [];
57
+ let size = 0;
58
+ for await (const chunk of req) {
59
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
60
+ size += buf.byteLength;
61
+ if (size > MAX_BODY_BYTES) {
62
+ req.destroy();
63
+ throw new RequestError(413, 'request body too large');
64
+ }
65
+ chunks.push(buf);
66
+ }
67
+ return Buffer.concat(chunks, size);
68
+ }
69
+ function buildRequest(req, method, body) {
70
+ const protocol = (firstHeader(req.headers['x-forwarded-proto']) ?? 'http').split(',')[0].trim();
71
+ const host = firstHeader(req.headers.host) ?? 'localhost';
72
+ const init = { method, headers: toHeaders(req.headers) };
73
+ if (body && body.byteLength > 0)
74
+ init.body = body;
75
+ return new Request(`${protocol}://${host}${req.url ?? '/'}`, init);
76
+ }
77
+ /** Resolve when the socket drains or closes, so a dead client cannot hang us. */
78
+ function waitDrain(res) {
79
+ return new Promise((resolve) => {
80
+ const done = () => {
81
+ res.off('drain', done);
82
+ res.off('close', done);
83
+ resolve();
84
+ };
85
+ res.once('drain', done);
86
+ res.once('close', done);
87
+ });
88
+ }
89
+ /**
90
+ * Serve one Node request through the MCP handler.
91
+ *
92
+ * Always settles: a transport failure becomes a JSON error response, and a
93
+ * mid-stream failure closes the socket so the client stops waiting.
94
+ */
95
+ export async function handleNodeRequest(handler, req, res) {
96
+ let response;
97
+ try {
98
+ const method = (req.method ?? 'GET').toUpperCase();
99
+ const body = method === 'GET' || method === 'HEAD' ? undefined : await readBody(req);
100
+ response = await handler.fetch(buildRequest(req, method, body));
101
+ }
102
+ catch (err) {
103
+ const status = err instanceof RequestError ? err.status : 500;
104
+ if (status !== 413 && !(err instanceof RequestError)) {
105
+ logger.error('mcp http request failed', { error: err });
106
+ }
107
+ if (!res.headersSent && !res.writableEnded) {
108
+ res.statusCode = status;
109
+ res.setHeader('content-type', 'application/json');
110
+ res.end(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }));
111
+ }
112
+ else if (!res.writableEnded) {
113
+ res.destroy();
114
+ }
115
+ return;
116
+ }
117
+ res.statusCode = response.status;
118
+ response.headers.forEach((value, key) => {
119
+ // Let Node choose chunked framing for streamed bodies.
120
+ if (key.toLowerCase() === 'content-length' && response.body)
121
+ return;
122
+ res.setHeader(key, value);
123
+ });
124
+ const body = response.body;
125
+ if (!body) {
126
+ res.end();
127
+ return;
128
+ }
129
+ const reader = body.getReader();
130
+ try {
131
+ for (;;) {
132
+ if (res.destroyed || res.writableEnded) {
133
+ await reader.cancel().catch(() => undefined);
134
+ break;
135
+ }
136
+ const { done, value } = await reader.read();
137
+ if (done)
138
+ break;
139
+ if (value && value.byteLength > 0 && !res.write(Buffer.from(value))) {
140
+ await waitDrain(res);
141
+ }
142
+ }
143
+ }
144
+ catch (err) {
145
+ logger.warn('mcp http response stream failed', { error: err });
146
+ if (!res.writableEnded)
147
+ res.destroy();
148
+ return;
149
+ }
150
+ finally {
151
+ await reader.cancel().catch(() => undefined);
152
+ }
153
+ if (!res.writableEnded)
154
+ res.end();
155
+ }
156
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAEhE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAE3C,gFAAgF;AAChF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AAE9C,+EAA+E;AAC/E,MAAM,UAAU,oBAAoB;IAClC,OAAO,gBAAgB,CAAC,GAAG,EAAE,CAAC,YAAY,EAAE,EAAE;QAC5C,MAAM,EAAE,WAAW;QACnB,OAAO,EAAE,CAAC,KAAY,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,wBAAwB,EAAE,EAAE,KAAK,EAAE,CAAC;KAC7E,CAAC,CAAC;AACL,CAAC;AAED,8EAA8E;AAC9E,MAAM,YAAa,SAAQ,KAAK;IACrB,MAAM,CAAS;IAExB,YAAY,MAAc,EAAE,OAAe;QACzC,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAED,SAAS,WAAW,CAAC,KAAoC;IACvD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC;IAC1C,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,SAAS,CAAC,GAAwB;IACzC,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;IAC9B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/C,IAAI,KAAK,KAAK,SAAS;YAAE,SAAS;QAClC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,KAAK,MAAM,IAAI,IAAI,KAAK;gBAAE,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QACtD,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,KAAK,UAAU,QAAQ,CAAC,GAAoB;IAC1C,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,EAAE,CAAC;QAC9B,MAAM,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAmB,CAAC,CAAC;QAC9E,IAAI,IAAI,GAAG,CAAC,UAAU,CAAC;QACvB,IAAI,IAAI,GAAG,cAAc,EAAE,CAAC;YAC1B,GAAG,CAAC,OAAO,EAAE,CAAC;YACd,MAAM,IAAI,YAAY,CAAC,GAAG,EAAE,wBAAwB,CAAC,CAAC;QACxD,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACnB,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;AACrC,CAAC;AAED,SAAS,YAAY,CAAC,GAAoB,EAAE,MAAc,EAAE,IAAwB;IAClF,MAAM,QAAQ,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAChG,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,WAAW,CAAC;IAC1D,MAAM,IAAI,GAAgB,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;IACtE,IAAI,IAAI,IAAI,IAAI,CAAC,UAAU,GAAG,CAAC;QAAE,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IAClD,OAAO,IAAI,OAAO,CAAC,GAAG,QAAQ,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;AACrE,CAAC;AAED,iFAAiF;AACjF,SAAS,SAAS,CAAC,GAAmB;IACpC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,IAAI,GAAG,GAAS,EAAE;YACtB,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YACvB,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YACvB,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC;QACF,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACxB,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,OAAuB,EACvB,GAAoB,EACpB,GAAmB;IAEnB,IAAI,QAAkB,CAAC;IACvB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,CAAC,GAAG,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;QACnD,MAAM,IAAI,GAAG,MAAM,KAAK,KAAK,IAAI,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAC;QACrF,QAAQ,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;IAClE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,MAAM,GAAG,GAAG,YAAY,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC;QAC9D,IAAI,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,GAAG,YAAY,YAAY,CAAC,EAAE,CAAC;YACrD,MAAM,CAAC,KAAK,CAAC,yBAAyB,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;QAC1D,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,WAAW,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;YAC3C,GAAG,CAAC,UAAU,GAAG,MAAM,CAAC;YACxB,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAC;YAClD,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QACvF,CAAC;aAAM,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;YAC9B,GAAG,CAAC,OAAO,EAAE,CAAC;QAChB,CAAC;QACD,OAAO;IACT,CAAC;IAED,GAAG,CAAC,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC;IACjC,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QACtC,uDAAuD;QACvD,IAAI,GAAG,CAAC,WAAW,EAAE,KAAK,gBAAgB,IAAI,QAAQ,CAAC,IAAI;YAAE,OAAO;QACpE,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC;IAC3B,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,GAAG,CAAC,GAAG,EAAE,CAAC;QACV,OAAO;IACT,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;IAChC,IAAI,CAAC;QACH,SAAS,CAAC;YACR,IAAI,GAAG,CAAC,SAAS,IAAI,GAAG,CAAC,aAAa,EAAE,CAAC;gBACvC,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;gBAC7C,MAAM;YACR,CAAC;YACD,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;YAC5C,IAAI,IAAI;gBAAE,MAAM;YAChB,IAAI,KAAK,IAAI,KAAK,CAAC,UAAU,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;gBACpE,MAAM,SAAS,CAAC,GAAG,CAAC,CAAC;YACvB,CAAC;QACH,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,CAAC,IAAI,CAAC,iCAAiC,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;QAC/D,IAAI,CAAC,GAAG,CAAC,aAAa;YAAE,GAAG,CAAC,OAAO,EAAE,CAAC;QACtC,OAAO;IACT,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAC/C,CAAC;IAED,IAAI,CAAC,GAAG,CAAC,aAAa;QAAE,GAAG,CAAC,GAAG,EAAE,CAAC;AACpC,CAAC"}
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * playwright-e2e-mcp entry point.
4
+ *
5
+ * When executed (npx playwright-e2e-mcp / the `bin` shim) it connects
6
+ * the MCP server over stdio. When imported it only exposes the API, so
7
+ * tests and tooling can construct a server without side effects.
8
+ */
9
+ import { logger } from './utils/logger.js';
10
+ export { createServer, startServer, SERVER_NAME, SERVER_VERSION } from './server.js';
11
+ export { createMcpHttpHandler, handleNodeRequest, MAX_BODY_BYTES } from './http.js';
12
+ export { logger };
package/dist/index.js ADDED
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * playwright-e2e-mcp entry point.
4
+ *
5
+ * When executed (npx playwright-e2e-mcp / the `bin` shim) it connects
6
+ * the MCP server over stdio. When imported it only exposes the API, so
7
+ * tests and tooling can construct a server without side effects.
8
+ */
9
+ import { realpathSync } from 'node:fs';
10
+ import { pathToFileURL } from 'node:url';
11
+ import { logger, createLogger } from './utils/logger.js';
12
+ import { SERVER_VERSION } from './server.js';
13
+ function isMainModule() {
14
+ const entry = process.argv[1];
15
+ if (!entry)
16
+ return false;
17
+ try {
18
+ return import.meta.url === pathToFileURL(realpathSync(entry)).href;
19
+ }
20
+ catch {
21
+ return false;
22
+ }
23
+ }
24
+ async function main() {
25
+ if (process.argv.includes('--version') || process.argv.includes('-v')) {
26
+ process.stdout.write(`${SERVER_VERSION}\n`);
27
+ return;
28
+ }
29
+ if (process.argv.includes('--help') || process.argv.includes('-h')) {
30
+ process.stdout.write([
31
+ 'playwright-e2e-mcp — MCP server for running and debugging Playwright tests',
32
+ '',
33
+ 'Usage: playwright-e2e-mcp [--version] [--help]',
34
+ '',
35
+ 'Connects over stdio (MCP). Configuration via environment:',
36
+ ' PW_MCP_PROJECT_ROOT default project root (default: cwd)',
37
+ ' LOG_LEVEL debug | info | warn | error | silent (default: info)',
38
+ ' LOG_FORMAT text | json (default: text)',
39
+ '',
40
+ ].join('\n'));
41
+ return;
42
+ }
43
+ const { startServer } = await import('./server.js');
44
+ await startServer();
45
+ }
46
+ if (isMainModule()) {
47
+ main().catch((err) => {
48
+ const log = createLogger({ bindings: { name: 'playwright-e2e-mcp' } });
49
+ log.error('server failed to start', { error: err });
50
+ process.exit(1);
51
+ });
52
+ }
53
+ export { createServer, startServer, SERVER_NAME, SERVER_VERSION } from './server.js';
54
+ export { createMcpHttpHandler, handleNodeRequest, MAX_BODY_BYTES } from './http.js';
55
+ export { logger };
56
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;GAMG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7C,SAAS,YAAY;IACnB,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC9B,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IACzB,IAAI,CAAC;QACH,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,aAAa,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACtE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,cAAc,IAAI,CAAC,CAAC;QAC5C,OAAO;IACT,CAAC;IACD,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACnE,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB;YACE,4EAA4E;YAC5E,EAAE;YACF,gDAAgD;YAChD,EAAE;YACF,2DAA2D;YAC3D,6DAA6D;YAC7D,8EAA8E;YAC9E,qDAAqD;YACrD,EAAE;SACH,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAC;QACF,OAAO;IACT,CAAC;IAED,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;IACpD,MAAM,WAAW,EAAE,CAAC;AACtB,CAAC;AAED,IAAI,YAAY,EAAE,EAAE,CAAC;IACnB,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;QAC5B,MAAM,GAAG,GAAG,YAAY,CAAC,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,oBAAoB,EAAE,EAAE,CAAC,CAAC;QACvE,GAAG,CAAC,KAAK,CAAC,wBAAwB,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;QACpD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACrF,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACpF,OAAO,EAAE,MAAM,EAAE,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * MCP server setup: creates the McpServer instance, registers the eight
3
+ * tools (with spec tool annotations), and wires each invocation to a ToolContext (per-tool logger,
4
+ * abort signal, shared store, default project root).
5
+ *
6
+ * Logs go to stderr only — stdout belongs to the MCP protocol.
7
+ */
8
+ import { McpServer } from '@modelcontextprotocol/server';
9
+ import type { ToolStore } from './types/index.js';
10
+ export declare const SERVER_NAME = "playwright-e2e-mcp";
11
+ export declare const SERVER_VERSION: string;
12
+ export interface CreateServerOptions {
13
+ /** Default project root for tool calls (defaults to cwd / env var). */
14
+ projectRoot?: string;
15
+ /** Share a store across servers (tests). */
16
+ store?: ToolStore;
17
+ }
18
+ export declare function resolveDefaultProjectRoot(): string;
19
+ export declare function createServer(options?: CreateServerOptions): McpServer;
20
+ /** Close the server, kill orphaned children, and exit. */
21
+ export declare function shutdown(server: McpServer, code: number, reason: string): Promise<void>;
22
+ /** Connect the server over stdio and install shutdown handlers. */
23
+ export declare function startServer(): Promise<void>;
package/dist/server.js ADDED
@@ -0,0 +1,225 @@
1
+ /**
2
+ * MCP server setup: creates the McpServer instance, registers the eight
3
+ * tools (with spec tool annotations), and wires each invocation to a ToolContext (per-tool logger,
4
+ * abort signal, shared store, default project root).
5
+ *
6
+ * Logs go to stderr only — stdout belongs to the MCP protocol.
7
+ */
8
+ import { createRequire } from 'node:module';
9
+ import { statSync } from 'node:fs';
10
+ import { StdioServerTransport } from '@modelcontextprotocol/server/stdio';
11
+ import { McpServer } from '@modelcontextprotocol/server';
12
+ import { toPlaywrightMcpError } from './types/index.js';
13
+ import { logger } from './utils/logger.js';
14
+ import { killActiveChildren } from './utils/playwright-runner.js';
15
+ import { createToolStore, toolError } from './tools/shared.js';
16
+ import { runTestTool } from './tools/run-test.js';
17
+ import { getFailureTool } from './tools/get-failure.js';
18
+ import { inspectPageTool } from './tools/inspect-page.js';
19
+ import { listTestsTool } from './tools/list-tests.js';
20
+ import { validateSelectorTool } from './tools/validate-selector.js';
21
+ import { generateE2ETestTool } from './tools/generate-e2e-test.js';
22
+ import { compareVisualStateTool } from './tools/compare-visual-state.js';
23
+ import { diagnoseFlakyTool } from './tools/diagnose-flaky.js';
24
+ const require = createRequire(import.meta.url);
25
+ function readVersion() {
26
+ try {
27
+ const pkg = require('../package.json');
28
+ return pkg.version ?? '0.0.0';
29
+ }
30
+ catch {
31
+ return '0.0.0';
32
+ }
33
+ }
34
+ export const SERVER_NAME = 'playwright-e2e-mcp';
35
+ export const SERVER_VERSION = readVersion();
36
+ const TOOLS = [
37
+ {
38
+ ...runTestTool,
39
+ title: 'Run E2E tests',
40
+ // Spawns Playwright, hits live apps, writes test-results/ artifacts.
41
+ annotations: {
42
+ readOnlyHint: false,
43
+ destructiveHint: false,
44
+ idempotentHint: true,
45
+ openWorldHint: true,
46
+ },
47
+ handler: runTestTool.handler,
48
+ },
49
+ {
50
+ ...getFailureTool,
51
+ title: 'Analyze a test failure',
52
+ // Pure reads: JSON report, trace.zip, generated failure summaries.
53
+ annotations: {
54
+ readOnlyHint: true,
55
+ idempotentHint: true,
56
+ openWorldHint: false,
57
+ },
58
+ handler: getFailureTool.handler,
59
+ },
60
+ {
61
+ ...inspectPageTool,
62
+ title: 'Inspect live page DOM',
63
+ // Navigates and evaluates read-only DOM queries; never clicks or types.
64
+ annotations: {
65
+ readOnlyHint: true,
66
+ idempotentHint: true,
67
+ openWorldHint: true,
68
+ },
69
+ handler: inspectPageTool.handler,
70
+ },
71
+ {
72
+ ...listTestsTool,
73
+ title: 'List available tests',
74
+ // Parses test files and config without modifying them.
75
+ annotations: {
76
+ readOnlyHint: true,
77
+ idempotentHint: true,
78
+ openWorldHint: false,
79
+ },
80
+ handler: listTestsTool.handler,
81
+ },
82
+ {
83
+ ...validateSelectorTool,
84
+ title: 'Validate CSS selector',
85
+ // Opens a page and queries the selector; no state-changing actions.
86
+ annotations: {
87
+ readOnlyHint: true,
88
+ idempotentHint: true,
89
+ openWorldHint: true,
90
+ },
91
+ handler: validateSelectorTool.handler,
92
+ },
93
+ {
94
+ ...generateE2ETestTool,
95
+ title: 'Generate an E2E test',
96
+ // Writes a new spec under tests/generated/ (never edits existing files).
97
+ annotations: {
98
+ readOnlyHint: false,
99
+ destructiveHint: false,
100
+ idempotentHint: false,
101
+ openWorldHint: false,
102
+ },
103
+ handler: generateE2ETestTool.handler,
104
+ },
105
+ {
106
+ ...compareVisualStateTool,
107
+ title: 'Compare visual state',
108
+ // Navigates the app; baseline action overwrites only the stored baseline.
109
+ annotations: {
110
+ readOnlyHint: false,
111
+ destructiveHint: false,
112
+ idempotentHint: true,
113
+ openWorldHint: true,
114
+ },
115
+ handler: compareVisualStateTool.handler,
116
+ },
117
+ {
118
+ ...diagnoseFlakyTool,
119
+ title: 'Diagnose flaky test',
120
+ // Spawns Playwright repeatedly; writes test-results/ artifacts only.
121
+ annotations: {
122
+ readOnlyHint: false,
123
+ destructiveHint: false,
124
+ idempotentHint: true,
125
+ openWorldHint: true,
126
+ },
127
+ handler: diagnoseFlakyTool.handler,
128
+ },
129
+ ];
130
+ export function resolveDefaultProjectRoot() {
131
+ const fromEnv = process.env.PW_MCP_PROJECT_ROOT;
132
+ // A host that collects PW_MCP_PROJECT_ROOT via MCPB user_config but does
133
+ // not substitute `${user_config.project_root}` would pass the placeholder
134
+ // through verbatim; that can never be a real path, so fall back to cwd.
135
+ if (fromEnv && fromEnv.includes('${')) {
136
+ logger.warn('PW_MCP_PROJECT_ROOT contains an unsubstituted ${...} placeholder; using cwd', {
137
+ value: fromEnv,
138
+ });
139
+ return process.cwd();
140
+ }
141
+ const root = fromEnv && fromEnv.trim() !== '' ? fromEnv : process.cwd();
142
+ if (fromEnv) {
143
+ try {
144
+ statSync(root);
145
+ }
146
+ catch {
147
+ logger.warn('PW_MCP_PROJECT_ROOT does not exist; tool calls will fail until it does', { root });
148
+ }
149
+ }
150
+ return root;
151
+ }
152
+ export function createServer(options = {}) {
153
+ const projectRoot = options.projectRoot ?? resolveDefaultProjectRoot();
154
+ const store = options.store ?? createToolStore();
155
+ const server = new McpServer({ name: SERVER_NAME, version: SERVER_VERSION });
156
+ for (const tool of TOOLS) {
157
+ server.registerTool(tool.name, {
158
+ title: tool.title,
159
+ description: tool.description,
160
+ inputSchema: tool.inputSchema,
161
+ annotations: tool.annotations,
162
+ }, async (args, reqCtx) => {
163
+ const ctx = {
164
+ logger: logger.child({ tool: tool.name }),
165
+ // v2: the request abort signal lives under ctx.mcpReq.
166
+ signal: reqCtx.mcpReq.signal,
167
+ store,
168
+ projectRoot,
169
+ };
170
+ try {
171
+ return await tool.handler(args, ctx);
172
+ }
173
+ catch (err) {
174
+ // Backstop: tools already convert their own errors via guard().
175
+ const error = toPlaywrightMcpError(err);
176
+ ctx.logger.error('tool threw unexpectedly', { error, kind: error.kind });
177
+ return toolError(error.kind, error.message, error.hint, error.details);
178
+ }
179
+ });
180
+ }
181
+ logger.debug('server created', { version: SERVER_VERSION, projectRoot, tools: TOOLS.length });
182
+ return server;
183
+ }
184
+ let shuttingDown = false;
185
+ /** Close the server, kill orphaned children, and exit. */
186
+ export async function shutdown(server, code, reason) {
187
+ if (shuttingDown)
188
+ return;
189
+ shuttingDown = true;
190
+ const killed = killActiveChildren();
191
+ logger.info('shutting down', { reason, killedProcesses: killed });
192
+ try {
193
+ await server.close();
194
+ }
195
+ catch (err) {
196
+ logger.warn('error while closing server', { error: err });
197
+ }
198
+ process.exit(code);
199
+ }
200
+ /** Connect the server over stdio and install shutdown handlers. */
201
+ export async function startServer() {
202
+ const server = createServer();
203
+ const transport = new StdioServerTransport();
204
+ process.on('SIGINT', () => void shutdown(server, 0, 'SIGINT'));
205
+ process.on('SIGTERM', () => void shutdown(server, 0, 'SIGTERM'));
206
+ // The client went away: stdin ends, and we must not leave browsers or
207
+ // test workers running.
208
+ process.stdin.on('end', () => void shutdown(server, 0, 'stdin ended (client disconnected)'));
209
+ process.stdin.on('close', () => void shutdown(server, 0, 'stdin closed (client disconnected)'));
210
+ process.on('uncaughtException', (err) => {
211
+ logger.error('uncaught exception', { error: err });
212
+ void shutdown(server, 1, 'uncaughtException');
213
+ });
214
+ process.on('unhandledRejection', (reason) => {
215
+ logger.error('unhandled rejection', { error: reason });
216
+ void shutdown(server, 1, 'unhandledRejection');
217
+ });
218
+ await server.connect(transport);
219
+ logger.info('connected over stdio', {
220
+ version: SERVER_VERSION,
221
+ projectRoot: resolveDefaultProjectRoot(),
222
+ tools: TOOLS.map((tool) => tool.name),
223
+ });
224
+ }
225
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,77 @@
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 type { ToolContext, ToolResponse } from '../types/index.js';
11
+ declare const compareInput: z.ZodObject<{
12
+ url: z.ZodString;
13
+ name: z.ZodString;
14
+ projectRoot: z.ZodOptional<z.ZodString>;
15
+ selector: z.ZodOptional<z.ZodString>;
16
+ fullPage: z.ZodOptional<z.ZodBoolean>;
17
+ waitUntil: z.ZodOptional<z.ZodEnum<{
18
+ load: "load";
19
+ domcontentloaded: "domcontentloaded";
20
+ networkidle: "networkidle";
21
+ }>>;
22
+ waitFor: z.ZodOptional<z.ZodString>;
23
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
24
+ action: z.ZodOptional<z.ZodEnum<{
25
+ compare: "compare";
26
+ baseline: "baseline";
27
+ }>>;
28
+ tolerance: z.ZodOptional<z.ZodNumber>;
29
+ pixelThreshold: z.ZodOptional<z.ZodNumber>;
30
+ }, z.core.$strip>;
31
+ export type CompareVisualStateInput = z.infer<typeof compareInput>;
32
+ export declare const compareVisualStateSchema: z.ZodObject<{
33
+ url: z.ZodString;
34
+ name: z.ZodString;
35
+ projectRoot: z.ZodOptional<z.ZodString>;
36
+ selector: z.ZodOptional<z.ZodString>;
37
+ fullPage: z.ZodOptional<z.ZodBoolean>;
38
+ waitUntil: z.ZodOptional<z.ZodEnum<{
39
+ load: "load";
40
+ domcontentloaded: "domcontentloaded";
41
+ networkidle: "networkidle";
42
+ }>>;
43
+ waitFor: z.ZodOptional<z.ZodString>;
44
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
45
+ action: z.ZodOptional<z.ZodEnum<{
46
+ compare: "compare";
47
+ baseline: "baseline";
48
+ }>>;
49
+ tolerance: z.ZodOptional<z.ZodNumber>;
50
+ pixelThreshold: z.ZodOptional<z.ZodNumber>;
51
+ }, z.core.$strip>;
52
+ export declare const compareVisualStateTool: {
53
+ name: string;
54
+ description: string;
55
+ inputSchema: z.ZodObject<{
56
+ url: z.ZodString;
57
+ name: z.ZodString;
58
+ projectRoot: z.ZodOptional<z.ZodString>;
59
+ selector: z.ZodOptional<z.ZodString>;
60
+ fullPage: z.ZodOptional<z.ZodBoolean>;
61
+ waitUntil: z.ZodOptional<z.ZodEnum<{
62
+ load: "load";
63
+ domcontentloaded: "domcontentloaded";
64
+ networkidle: "networkidle";
65
+ }>>;
66
+ waitFor: z.ZodOptional<z.ZodString>;
67
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
68
+ action: z.ZodOptional<z.ZodEnum<{
69
+ compare: "compare";
70
+ baseline: "baseline";
71
+ }>>;
72
+ tolerance: z.ZodOptional<z.ZodNumber>;
73
+ pixelThreshold: z.ZodOptional<z.ZodNumber>;
74
+ }, z.core.$strip>;
75
+ handler: (args: CompareVisualStateInput, ctx: ToolContext) => Promise<ToolResponse>;
76
+ };
77
+ export {};