playwright 1.55.0 → 1.56.0-alpha-2025-08-21
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/lib/index.js +45 -4
- package/lib/isomorphic/testServerConnection.js +0 -7
- package/lib/matchers/expect.js +7 -19
- package/lib/matchers/toBeTruthy.js +2 -0
- package/lib/matchers/toEqual.js +2 -0
- package/lib/matchers/toMatchText.js +3 -0
- package/lib/mcp/browser/backend.js +88 -0
- package/lib/mcp/browser/tool.js +30 -0
- package/lib/mcp/browser/tools.js +132 -0
- package/lib/mcp/{bundle.js → sdk/bundle.js} +5 -2
- package/lib/mcp/sdk/call.js +49 -0
- package/lib/mcp/{exports.js → sdk/exports.js} +12 -10
- package/lib/mcp/{transport.js → sdk/http.js} +51 -68
- package/lib/mcp/sdk/mdb.js +195 -0
- package/lib/mcp/{proxyBackend.js → sdk/proxyBackend.js} +8 -10
- package/lib/mcp/{server.js → sdk/server.js} +43 -14
- package/lib/mcp/{tool.js → sdk/tool.js} +6 -1
- package/lib/mcp/test/backend.js +68 -0
- package/lib/mcp/test/context.js +43 -0
- package/lib/mcp/test/listTests.js +88 -0
- package/lib/mcp/test/program.js +42 -0
- package/lib/mcp/test/runTests.js +82 -0
- package/lib/mcp/test/streams.js +41 -0
- package/lib/mcp/test/tool.js +30 -0
- package/lib/mcpBundleImpl.js +17 -13
- package/lib/runner/dispatcher.js +1 -22
- package/lib/runner/failureTracker.js +0 -14
- package/lib/runner/testRunner.js +2 -25
- package/lib/runner/testServer.js +2 -8
- package/lib/runner/watchMode.js +1 -53
- package/lib/runner/workerHost.js +2 -6
- package/lib/worker/testInfo.js +1 -24
- package/lib/worker/workerMain.js +0 -5
- package/package.json +3 -3
- /package/lib/mcp/{inProcessTransport.js → sdk/inProcessTransport.js} +0 -0
|
@@ -26,29 +26,57 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
26
26
|
mod
|
|
27
27
|
));
|
|
28
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
-
var
|
|
30
|
-
__export(
|
|
31
|
-
|
|
29
|
+
var http_exports = {};
|
|
30
|
+
__export(http_exports, {
|
|
31
|
+
httpAddressToString: () => httpAddressToString,
|
|
32
|
+
installHttpTransport: () => installHttpTransport,
|
|
33
|
+
startHttpServer: () => startHttpServer
|
|
32
34
|
});
|
|
33
|
-
module.exports = __toCommonJS(
|
|
35
|
+
module.exports = __toCommonJS(http_exports);
|
|
34
36
|
var import_assert = __toESM(require("assert"));
|
|
35
37
|
var import_http = __toESM(require("http"));
|
|
36
38
|
var import_crypto = __toESM(require("crypto"));
|
|
37
39
|
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
|
38
|
-
var
|
|
39
|
-
var
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
var mcp = __toESM(require("./bundle"));
|
|
41
|
+
var import_server = require("./server");
|
|
42
|
+
const testDebug = (0, import_utilsBundle.debug)("pw:mcp:test");
|
|
43
|
+
async function startHttpServer(config, abortSignal) {
|
|
44
|
+
const { host, port } = config;
|
|
45
|
+
const httpServer = import_http.default.createServer();
|
|
46
|
+
await new Promise((resolve, reject) => {
|
|
47
|
+
httpServer.on("error", reject);
|
|
48
|
+
abortSignal?.addEventListener("abort", () => {
|
|
49
|
+
httpServer.close();
|
|
50
|
+
reject(new Error("Aborted"));
|
|
51
|
+
});
|
|
52
|
+
httpServer.listen(port, host, () => {
|
|
53
|
+
resolve();
|
|
54
|
+
httpServer.removeListener("error", reject);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
return httpServer;
|
|
58
|
+
}
|
|
59
|
+
function httpAddressToString(address) {
|
|
60
|
+
(0, import_assert.default)(address, "Could not bind server socket");
|
|
61
|
+
if (typeof address === "string")
|
|
62
|
+
return address;
|
|
63
|
+
const resolvedPort = address.port;
|
|
64
|
+
let resolvedHost = address.family === "IPv4" ? address.address : `[${address.address}]`;
|
|
65
|
+
if (resolvedHost === "0.0.0.0" || resolvedHost === "[::]")
|
|
66
|
+
resolvedHost = "localhost";
|
|
67
|
+
return `http://${resolvedHost}:${resolvedPort}`;
|
|
47
68
|
}
|
|
48
|
-
async function
|
|
49
|
-
|
|
69
|
+
async function installHttpTransport(httpServer, serverBackendFactory) {
|
|
70
|
+
const sseSessions = /* @__PURE__ */ new Map();
|
|
71
|
+
const streamableSessions = /* @__PURE__ */ new Map();
|
|
72
|
+
httpServer.on("request", async (req, res) => {
|
|
73
|
+
const url = new URL(`http://localhost${req.url}`);
|
|
74
|
+
if (url.pathname.startsWith("/sse"))
|
|
75
|
+
await handleSSE(serverBackendFactory, req, res, url, sseSessions);
|
|
76
|
+
else
|
|
77
|
+
await handleStreamable(serverBackendFactory, req, res, streamableSessions);
|
|
78
|
+
});
|
|
50
79
|
}
|
|
51
|
-
const testDebug = (0, import_utilsBundle.debug)("pw:mcp:test");
|
|
52
80
|
async function handleSSE(serverBackendFactory, req, res, url, sessions) {
|
|
53
81
|
if (req.method === "POST") {
|
|
54
82
|
const sessionId = url.searchParams.get("sessionId");
|
|
@@ -63,10 +91,10 @@ async function handleSSE(serverBackendFactory, req, res, url, sessions) {
|
|
|
63
91
|
}
|
|
64
92
|
return await transport.handlePostMessage(req, res);
|
|
65
93
|
} else if (req.method === "GET") {
|
|
66
|
-
const transport = new
|
|
94
|
+
const transport = new mcp.SSEServerTransport("/sse", res);
|
|
67
95
|
sessions.set(transport.sessionId, transport);
|
|
68
96
|
testDebug(`create SSE session: ${transport.sessionId}`);
|
|
69
|
-
await
|
|
97
|
+
await (0, import_server.connect)(serverBackendFactory, transport, false);
|
|
70
98
|
res.on("close", () => {
|
|
71
99
|
testDebug(`delete SSE session: ${transport.sessionId}`);
|
|
72
100
|
sessions.delete(transport.sessionId);
|
|
@@ -88,11 +116,11 @@ async function handleStreamable(serverBackendFactory, req, res, sessions) {
|
|
|
88
116
|
return await transport.handleRequest(req, res);
|
|
89
117
|
}
|
|
90
118
|
if (req.method === "POST") {
|
|
91
|
-
const transport = new
|
|
119
|
+
const transport = new mcp.StreamableHTTPServerTransport({
|
|
92
120
|
sessionIdGenerator: () => import_crypto.default.randomUUID(),
|
|
93
121
|
onsessioninitialized: async (sessionId2) => {
|
|
94
122
|
testDebug(`create http session: ${transport.sessionId}`);
|
|
95
|
-
await
|
|
123
|
+
await (0, import_server.connect)(serverBackendFactory, transport, true);
|
|
96
124
|
sessions.set(sessionId2, transport);
|
|
97
125
|
}
|
|
98
126
|
});
|
|
@@ -108,54 +136,9 @@ async function handleStreamable(serverBackendFactory, req, res, sessions) {
|
|
|
108
136
|
res.statusCode = 400;
|
|
109
137
|
res.end("Invalid request");
|
|
110
138
|
}
|
|
111
|
-
function startHttpTransport(httpServer, serverBackendFactory) {
|
|
112
|
-
const sseSessions = /* @__PURE__ */ new Map();
|
|
113
|
-
const streamableSessions = /* @__PURE__ */ new Map();
|
|
114
|
-
httpServer.on("request", async (req, res) => {
|
|
115
|
-
const url2 = new URL(`http://localhost${req.url}`);
|
|
116
|
-
if (url2.pathname.startsWith("/sse"))
|
|
117
|
-
await handleSSE(serverBackendFactory, req, res, url2, sseSessions);
|
|
118
|
-
else
|
|
119
|
-
await handleStreamable(serverBackendFactory, req, res, streamableSessions);
|
|
120
|
-
});
|
|
121
|
-
const url = httpAddressToString(httpServer.address());
|
|
122
|
-
const message = [
|
|
123
|
-
`Listening on ${url}`,
|
|
124
|
-
"Put this in your client config:",
|
|
125
|
-
JSON.stringify({
|
|
126
|
-
"mcpServers": {
|
|
127
|
-
"playwright": {
|
|
128
|
-
"url": `${url}/mcp`
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}, void 0, 2),
|
|
132
|
-
"For legacy SSE transport support, you can use the /sse endpoint instead."
|
|
133
|
-
].join("\n");
|
|
134
|
-
console.error(message);
|
|
135
|
-
}
|
|
136
|
-
async function startHttpServer(config) {
|
|
137
|
-
const { host, port } = config;
|
|
138
|
-
const httpServer = import_http.default.createServer();
|
|
139
|
-
await new Promise((resolve, reject) => {
|
|
140
|
-
httpServer.on("error", reject);
|
|
141
|
-
httpServer.listen(port, host, () => {
|
|
142
|
-
resolve();
|
|
143
|
-
httpServer.removeListener("error", reject);
|
|
144
|
-
});
|
|
145
|
-
});
|
|
146
|
-
return httpServer;
|
|
147
|
-
}
|
|
148
|
-
function httpAddressToString(address) {
|
|
149
|
-
(0, import_assert.default)(address, "Could not bind server socket");
|
|
150
|
-
if (typeof address === "string")
|
|
151
|
-
return address;
|
|
152
|
-
const resolvedPort = address.port;
|
|
153
|
-
let resolvedHost = address.family === "IPv4" ? address.address : `[${address.address}]`;
|
|
154
|
-
if (resolvedHost === "0.0.0.0" || resolvedHost === "[::]")
|
|
155
|
-
resolvedHost = "localhost";
|
|
156
|
-
return `http://${resolvedHost}:${resolvedPort}`;
|
|
157
|
-
}
|
|
158
139
|
// Annotate the CommonJS export names for ESM import in node:
|
|
159
140
|
0 && (module.exports = {
|
|
160
|
-
|
|
141
|
+
httpAddressToString,
|
|
142
|
+
installHttpTransport,
|
|
143
|
+
startHttpServer
|
|
161
144
|
});
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var mdb_exports = {};
|
|
30
|
+
__export(mdb_exports, {
|
|
31
|
+
MDBBackend: () => MDBBackend,
|
|
32
|
+
runOnPauseBackendLoop: () => runOnPauseBackendLoop,
|
|
33
|
+
runToolsBackend: () => runToolsBackend
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(mdb_exports);
|
|
36
|
+
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
|
37
|
+
var import_utils = require("playwright-core/lib/utils");
|
|
38
|
+
var import_bundle = require("./bundle.js");
|
|
39
|
+
var import_bundle2 = require("./bundle.js");
|
|
40
|
+
var mcpBundle = __toESM(require("./bundle.js"));
|
|
41
|
+
var import_tool = require("./tool.js");
|
|
42
|
+
var mcpHttp = __toESM(require("./http.js"));
|
|
43
|
+
var import_call = require("./call.js");
|
|
44
|
+
const errorsDebug = (0, import_utilsBundle.debug)("pw:mcp:errors");
|
|
45
|
+
class MDBBackend {
|
|
46
|
+
constructor() {
|
|
47
|
+
this._stack = [];
|
|
48
|
+
}
|
|
49
|
+
async initialize(server) {
|
|
50
|
+
this._server = server;
|
|
51
|
+
}
|
|
52
|
+
async listTools() {
|
|
53
|
+
const response = await this._client().listTools();
|
|
54
|
+
return response.tools;
|
|
55
|
+
}
|
|
56
|
+
async callTool(name, args) {
|
|
57
|
+
if (name === pushToolsSchema.name)
|
|
58
|
+
return await this._pushTools(pushToolsSchema.inputSchema.parse(args || {}));
|
|
59
|
+
this._interruptPromise = new import_utils.ManualPromise();
|
|
60
|
+
let [entry] = this._stack;
|
|
61
|
+
while (entry && !entry.toolNames.includes(name)) {
|
|
62
|
+
this._stack.shift();
|
|
63
|
+
await entry.client.close();
|
|
64
|
+
entry = this._stack[0];
|
|
65
|
+
}
|
|
66
|
+
const resultPromise = new import_utils.ManualPromise();
|
|
67
|
+
entry.resultPromise = resultPromise;
|
|
68
|
+
this._client().callTool({
|
|
69
|
+
name,
|
|
70
|
+
arguments: args
|
|
71
|
+
}).then((result) => {
|
|
72
|
+
resultPromise.resolve(result);
|
|
73
|
+
}).catch((e) => {
|
|
74
|
+
if (this._stack.length < 2)
|
|
75
|
+
throw e;
|
|
76
|
+
this._stack.shift();
|
|
77
|
+
const prevEntry = this._stack[0];
|
|
78
|
+
void prevEntry.resultPromise.then((result) => resultPromise.resolve(result));
|
|
79
|
+
});
|
|
80
|
+
return await Promise.race([this._interruptPromise, resultPromise]);
|
|
81
|
+
}
|
|
82
|
+
_client() {
|
|
83
|
+
const [entry] = this._stack;
|
|
84
|
+
if (!entry)
|
|
85
|
+
throw new Error("No debugging backend available");
|
|
86
|
+
return entry.client;
|
|
87
|
+
}
|
|
88
|
+
async _pushTools(params) {
|
|
89
|
+
const client = new mcpBundle.Client({ name: "Internal client", version: "0.0.0" });
|
|
90
|
+
client.setRequestHandler(import_bundle.PingRequestSchema, () => ({}));
|
|
91
|
+
const transport = new import_bundle2.StreamableHTTPClientTransport(new URL(params.mcpUrl));
|
|
92
|
+
await client.connect(transport);
|
|
93
|
+
this._interruptPromise?.resolve({
|
|
94
|
+
content: [{
|
|
95
|
+
type: "text",
|
|
96
|
+
text: params.introMessage || ""
|
|
97
|
+
}]
|
|
98
|
+
});
|
|
99
|
+
this._interruptPromise = void 0;
|
|
100
|
+
const { tools } = await client.listTools();
|
|
101
|
+
this._stack.unshift({ client, toolNames: tools.map((tool) => tool.name), resultPromise: void 0 });
|
|
102
|
+
await this._server.notification({
|
|
103
|
+
method: "notifications/tools/list_changed"
|
|
104
|
+
});
|
|
105
|
+
return { content: [{ type: "text", text: "Tools pushed" }] };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const pushToolsSchema = (0, import_tool.defineToolSchema)({
|
|
109
|
+
name: "mdb_push_tools",
|
|
110
|
+
title: "Push MCP tools to the tools stack",
|
|
111
|
+
description: "Push MCP tools to the tools stack",
|
|
112
|
+
inputSchema: import_bundle.z.object({
|
|
113
|
+
mcpUrl: import_bundle.z.string(),
|
|
114
|
+
introMessage: import_bundle.z.string().optional()
|
|
115
|
+
}),
|
|
116
|
+
type: "readOnly"
|
|
117
|
+
});
|
|
118
|
+
async function runToolsBackend(backendFactory, options) {
|
|
119
|
+
const mdbBackend = new MDBBackend();
|
|
120
|
+
const mdbBackendFactory = {
|
|
121
|
+
name: "Playwright MDB",
|
|
122
|
+
nameInConfig: "playwright-mdb",
|
|
123
|
+
version: "0.0.0",
|
|
124
|
+
create: () => mdbBackend
|
|
125
|
+
};
|
|
126
|
+
const mdbUrl = await startAsHttp(mdbBackendFactory, options);
|
|
127
|
+
const backendUrl = await startAsHttp(backendFactory, { port: 0 });
|
|
128
|
+
const result = await (0, import_call.callTool)(mdbUrl, pushToolsSchema.name, { mcpUrl: backendUrl });
|
|
129
|
+
if (result.isError)
|
|
130
|
+
errorsDebug("Failed to push tools", result.content);
|
|
131
|
+
return mdbUrl;
|
|
132
|
+
}
|
|
133
|
+
async function runOnPauseBackendLoop(mdbUrl, backend, introMessage) {
|
|
134
|
+
const wrappedBackend = new OnceTimeServerBackendWrapper(backend);
|
|
135
|
+
const factory = {
|
|
136
|
+
name: "on-pause-backend",
|
|
137
|
+
nameInConfig: "on-pause-backend",
|
|
138
|
+
version: "0.0.0",
|
|
139
|
+
create: () => wrappedBackend
|
|
140
|
+
};
|
|
141
|
+
const httpServer = await mcpHttp.startHttpServer({ port: 0 });
|
|
142
|
+
await mcpHttp.installHttpTransport(httpServer, factory);
|
|
143
|
+
const url = mcpHttp.httpAddressToString(httpServer.address());
|
|
144
|
+
const client = new mcpBundle.Client({ name: "Internal client", version: "0.0.0" });
|
|
145
|
+
client.setRequestHandler(import_bundle.PingRequestSchema, () => ({}));
|
|
146
|
+
const transport = new import_bundle2.StreamableHTTPClientTransport(new URL(mdbUrl));
|
|
147
|
+
await client.connect(transport);
|
|
148
|
+
const pushToolsResult = await client.callTool({
|
|
149
|
+
name: pushToolsSchema.name,
|
|
150
|
+
arguments: {
|
|
151
|
+
mcpUrl: url,
|
|
152
|
+
introMessage
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
if (pushToolsResult.isError)
|
|
156
|
+
errorsDebug("Failed to push tools", pushToolsResult.content);
|
|
157
|
+
await transport.terminateSession();
|
|
158
|
+
await client.close();
|
|
159
|
+
await wrappedBackend.waitForClosed();
|
|
160
|
+
httpServer.close();
|
|
161
|
+
}
|
|
162
|
+
async function startAsHttp(backendFactory, options) {
|
|
163
|
+
const httpServer = await mcpHttp.startHttpServer(options);
|
|
164
|
+
await mcpHttp.installHttpTransport(httpServer, backendFactory);
|
|
165
|
+
return mcpHttp.httpAddressToString(httpServer.address());
|
|
166
|
+
}
|
|
167
|
+
class OnceTimeServerBackendWrapper {
|
|
168
|
+
constructor(backend) {
|
|
169
|
+
this._selfDestructPromise = new import_utils.ManualPromise();
|
|
170
|
+
this._backend = backend;
|
|
171
|
+
this._backend.requestSelfDestruct = () => this._selfDestructPromise.resolve();
|
|
172
|
+
}
|
|
173
|
+
async initialize(server, clientVersion, roots) {
|
|
174
|
+
await this._backend.initialize?.(server, clientVersion, roots);
|
|
175
|
+
}
|
|
176
|
+
async listTools() {
|
|
177
|
+
return this._backend.listTools();
|
|
178
|
+
}
|
|
179
|
+
async callTool(name, args) {
|
|
180
|
+
return this._backend.callTool(name, args);
|
|
181
|
+
}
|
|
182
|
+
serverClosed() {
|
|
183
|
+
this._backend.serverClosed?.();
|
|
184
|
+
this._selfDestructPromise.resolve();
|
|
185
|
+
}
|
|
186
|
+
async waitForClosed() {
|
|
187
|
+
await this._selfDestructPromise;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
191
|
+
0 && (module.exports = {
|
|
192
|
+
MDBBackend,
|
|
193
|
+
runOnPauseBackendLoop,
|
|
194
|
+
runToolsBackend
|
|
195
|
+
});
|
|
@@ -32,17 +32,15 @@ __export(proxyBackend_exports, {
|
|
|
32
32
|
});
|
|
33
33
|
module.exports = __toCommonJS(proxyBackend_exports);
|
|
34
34
|
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
|
35
|
-
var
|
|
35
|
+
var mcp = __toESM(require("./bundle"));
|
|
36
36
|
const errorsDebug = (0, import_utilsBundle.debug)("pw:mcp:errors");
|
|
37
37
|
class ProxyBackend {
|
|
38
|
-
constructor(
|
|
38
|
+
constructor(mcpProviders) {
|
|
39
39
|
this._roots = [];
|
|
40
|
-
this.name = name;
|
|
41
|
-
this.version = version;
|
|
42
40
|
this._mcpProviders = mcpProviders;
|
|
43
41
|
this._contextSwitchTool = this._defineContextSwitchTool();
|
|
44
42
|
}
|
|
45
|
-
async initialize(clientVersion, roots) {
|
|
43
|
+
async initialize(server, clientVersion, roots) {
|
|
46
44
|
this._roots = roots;
|
|
47
45
|
await this._setCurrentClient(this._mcpProviders[0]);
|
|
48
46
|
}
|
|
@@ -91,8 +89,8 @@ Error: ${error}
|
|
|
91
89
|
"Connect to a browser using one of the available methods:",
|
|
92
90
|
...this._mcpProviders.map((factory) => `- "${factory.name}": ${factory.description}`)
|
|
93
91
|
].join("\n"),
|
|
94
|
-
inputSchema:
|
|
95
|
-
name:
|
|
92
|
+
inputSchema: mcp.zodToJsonSchema(mcp.z.object({
|
|
93
|
+
name: mcp.z.enum(this._mcpProviders.map((factory) => factory.name)).default(this._mcpProviders[0].name).describe("The method to use to connect to the browser")
|
|
96
94
|
}), { strictUnions: true }),
|
|
97
95
|
annotations: {
|
|
98
96
|
title: "Connect to a browser context",
|
|
@@ -104,14 +102,14 @@ Error: ${error}
|
|
|
104
102
|
async _setCurrentClient(factory) {
|
|
105
103
|
await this._currentClient?.close();
|
|
106
104
|
this._currentClient = void 0;
|
|
107
|
-
const client = new
|
|
105
|
+
const client = new mcp.Client({ name: "Playwright MCP Proxy", version: "0.0.0" });
|
|
108
106
|
client.registerCapabilities({
|
|
109
107
|
roots: {
|
|
110
108
|
listRoots: true
|
|
111
109
|
}
|
|
112
110
|
});
|
|
113
|
-
client.setRequestHandler(
|
|
114
|
-
client.setRequestHandler(
|
|
111
|
+
client.setRequestHandler(mcp.ListRootsRequestSchema, () => ({ roots: this._roots }));
|
|
112
|
+
client.setRequestHandler(mcp.PingRequestSchema, () => ({}));
|
|
115
113
|
const transport = await factory.connect();
|
|
116
114
|
await client.connect(transport);
|
|
117
115
|
this._currentClient = client;
|
|
@@ -29,35 +29,42 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
29
29
|
var server_exports = {};
|
|
30
30
|
__export(server_exports, {
|
|
31
31
|
connect: () => connect,
|
|
32
|
-
createServer: () => createServer
|
|
32
|
+
createServer: () => createServer,
|
|
33
|
+
start: () => start,
|
|
34
|
+
wrapInProcess: () => wrapInProcess
|
|
33
35
|
});
|
|
34
36
|
module.exports = __toCommonJS(server_exports);
|
|
35
37
|
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
|
36
|
-
var
|
|
38
|
+
var mcp = __toESM(require("./bundle"));
|
|
39
|
+
var import_inProcessTransport = require("./inProcessTransport");
|
|
40
|
+
var import_http = require("./http");
|
|
37
41
|
const serverDebug = (0, import_utilsBundle.debug)("pw:mcp:server");
|
|
38
42
|
const errorsDebug = (0, import_utilsBundle.debug)("pw:mcp:errors");
|
|
39
|
-
async function connect(
|
|
40
|
-
const
|
|
41
|
-
const server = createServer(backend, runHeartbeat);
|
|
43
|
+
async function connect(factory, transport, runHeartbeat) {
|
|
44
|
+
const server = createServer(factory.name, factory.version, factory.create(), runHeartbeat);
|
|
42
45
|
await server.connect(transport);
|
|
43
46
|
}
|
|
44
|
-
function
|
|
45
|
-
|
|
47
|
+
async function wrapInProcess(backend) {
|
|
48
|
+
const server = createServer("Internal", "0.0.0", backend, false);
|
|
49
|
+
return new import_inProcessTransport.InProcessTransport(server);
|
|
50
|
+
}
|
|
51
|
+
function createServer(name, version, backend, runHeartbeat) {
|
|
52
|
+
let initializedPromiseResolve = () => {
|
|
46
53
|
};
|
|
47
|
-
const initializedPromise = new Promise((resolve) =>
|
|
48
|
-
const server = new
|
|
54
|
+
const initializedPromise = new Promise((resolve) => initializedPromiseResolve = resolve);
|
|
55
|
+
const server = new mcp.Server({ name, version }, {
|
|
49
56
|
capabilities: {
|
|
50
57
|
tools: {}
|
|
51
58
|
}
|
|
52
59
|
});
|
|
53
|
-
server.setRequestHandler(
|
|
60
|
+
server.setRequestHandler(mcp.ListToolsRequestSchema, async () => {
|
|
54
61
|
serverDebug("listTools");
|
|
55
62
|
await initializedPromise;
|
|
56
63
|
const tools = await backend.listTools();
|
|
57
64
|
return { tools };
|
|
58
65
|
});
|
|
59
66
|
let heartbeatRunning = false;
|
|
60
|
-
server.setRequestHandler(
|
|
67
|
+
server.setRequestHandler(mcp.CallToolRequestSchema, async (request) => {
|
|
61
68
|
serverDebug("callTool", request);
|
|
62
69
|
await initializedPromise;
|
|
63
70
|
if (runHeartbeat && !heartbeatRunning) {
|
|
@@ -82,8 +89,8 @@ function createServer(backend, runHeartbeat) {
|
|
|
82
89
|
clientRoots = roots;
|
|
83
90
|
}
|
|
84
91
|
const clientVersion = server.getClientVersion() ?? { name: "unknown", version: "unknown" };
|
|
85
|
-
await backend.initialize?.(clientVersion, clientRoots);
|
|
86
|
-
|
|
92
|
+
await backend.initialize?.(server, clientVersion, clientRoots);
|
|
93
|
+
initializedPromiseResolve();
|
|
87
94
|
} catch (e) {
|
|
88
95
|
errorsDebug(e);
|
|
89
96
|
}
|
|
@@ -111,8 +118,30 @@ function addServerListener(server, event, listener) {
|
|
|
111
118
|
listener();
|
|
112
119
|
};
|
|
113
120
|
}
|
|
121
|
+
async function start(serverBackendFactory, options) {
|
|
122
|
+
if (options.port === void 0) {
|
|
123
|
+
await connect(serverBackendFactory, new mcp.StdioServerTransport(), false);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const httpServer = await (0, import_http.startHttpServer)(options);
|
|
127
|
+
await (0, import_http.installHttpTransport)(httpServer, serverBackendFactory);
|
|
128
|
+
const url = (0, import_http.httpAddressToString)(httpServer.address());
|
|
129
|
+
const mcpConfig = { mcpServers: {} };
|
|
130
|
+
mcpConfig.mcpServers[serverBackendFactory.nameInConfig] = {
|
|
131
|
+
url: `${url}/mcp`
|
|
132
|
+
};
|
|
133
|
+
const message = [
|
|
134
|
+
`Listening on ${url}`,
|
|
135
|
+
"Put this in your client config:",
|
|
136
|
+
JSON.stringify(mcpConfig, void 0, 2),
|
|
137
|
+
"For legacy SSE transport support, you can use the /sse endpoint instead."
|
|
138
|
+
].join("\n");
|
|
139
|
+
console.error(message);
|
|
140
|
+
}
|
|
114
141
|
// Annotate the CommonJS export names for ESM import in node:
|
|
115
142
|
0 && (module.exports = {
|
|
116
143
|
connect,
|
|
117
|
-
createServer
|
|
144
|
+
createServer,
|
|
145
|
+
start,
|
|
146
|
+
wrapInProcess
|
|
118
147
|
});
|
|
@@ -18,10 +18,11 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
var tool_exports = {};
|
|
20
20
|
__export(tool_exports, {
|
|
21
|
+
defineToolSchema: () => defineToolSchema,
|
|
21
22
|
toMcpTool: () => toMcpTool
|
|
22
23
|
});
|
|
23
24
|
module.exports = __toCommonJS(tool_exports);
|
|
24
|
-
var import_bundle = require("./bundle");
|
|
25
|
+
var import_bundle = require("./bundle.js");
|
|
25
26
|
function toMcpTool(tool) {
|
|
26
27
|
return {
|
|
27
28
|
name: tool.name,
|
|
@@ -35,7 +36,11 @@ function toMcpTool(tool) {
|
|
|
35
36
|
}
|
|
36
37
|
};
|
|
37
38
|
}
|
|
39
|
+
function defineToolSchema(tool) {
|
|
40
|
+
return tool;
|
|
41
|
+
}
|
|
38
42
|
// Annotate the CommonJS export names for ESM import in node:
|
|
39
43
|
0 && (module.exports = {
|
|
44
|
+
defineToolSchema,
|
|
40
45
|
toMcpTool
|
|
41
46
|
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var backend_exports = {};
|
|
30
|
+
__export(backend_exports, {
|
|
31
|
+
TestServerBackend: () => TestServerBackend
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(backend_exports);
|
|
34
|
+
var mcp = __toESM(require("../sdk/exports.js"));
|
|
35
|
+
var import_context = require("./context");
|
|
36
|
+
var import_listTests = require("./listTests");
|
|
37
|
+
var import_runTests = require("./runTests");
|
|
38
|
+
var import_tools = require("../browser/tools");
|
|
39
|
+
class TestServerBackend {
|
|
40
|
+
constructor(resolvedLocation) {
|
|
41
|
+
this.name = "Playwright";
|
|
42
|
+
this.version = "0.0.1";
|
|
43
|
+
this._tools = [import_listTests.listTests, import_runTests.runTests];
|
|
44
|
+
this._context = new import_context.Context(resolvedLocation);
|
|
45
|
+
}
|
|
46
|
+
async listTools() {
|
|
47
|
+
return [
|
|
48
|
+
...this._tools.map((tool) => mcp.toMcpTool(tool.schema)),
|
|
49
|
+
mcp.toMcpTool(import_tools.snapshot.schema),
|
|
50
|
+
mcp.toMcpTool(import_tools.pickLocator.schema),
|
|
51
|
+
mcp.toMcpTool(import_tools.evaluate.schema)
|
|
52
|
+
];
|
|
53
|
+
}
|
|
54
|
+
async callTool(name, args) {
|
|
55
|
+
const tool = this._tools.find((tool2) => tool2.schema.name === name);
|
|
56
|
+
if (!tool)
|
|
57
|
+
throw new Error(`Tool not found: ${name}. Available tools: ${this._tools.map((tool2) => tool2.schema.name).join(", ")}`);
|
|
58
|
+
const parsedArguments = tool.schema.inputSchema.parse(args || {});
|
|
59
|
+
return await tool.handle(this._context, parsedArguments);
|
|
60
|
+
}
|
|
61
|
+
serverClosed() {
|
|
62
|
+
void this._context.close();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
66
|
+
0 && (module.exports = {
|
|
67
|
+
TestServerBackend
|
|
68
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var context_exports = {};
|
|
20
|
+
__export(context_exports, {
|
|
21
|
+
Context: () => Context
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(context_exports);
|
|
24
|
+
var import_testRunner = require("../../runner/testRunner");
|
|
25
|
+
class Context {
|
|
26
|
+
constructor(configLocation) {
|
|
27
|
+
this.configLocation = configLocation;
|
|
28
|
+
}
|
|
29
|
+
async createTestRunner() {
|
|
30
|
+
if (this._testRunner)
|
|
31
|
+
await this._testRunner.stopTests();
|
|
32
|
+
const testRunner = new import_testRunner.TestRunner(this.configLocation, {});
|
|
33
|
+
await testRunner.initialize({});
|
|
34
|
+
this._testRunner = testRunner;
|
|
35
|
+
return testRunner;
|
|
36
|
+
}
|
|
37
|
+
async close() {
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
41
|
+
0 && (module.exports = {
|
|
42
|
+
Context
|
|
43
|
+
});
|