playwright 1.56.0-alpha-2025-08-21 → 1.56.0-alpha-2025-08-23
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 +1 -1
- package/lib/mcp/browser/backend.js +11 -6
- package/lib/mcp/browser/tools.js +15 -25
- package/lib/mcp/sdk/mdb.js +52 -33
- package/lib/mcp/sdk/server.js +1 -1
- package/lib/mcp/test/backend.js +7 -3
- package/lib/mcp/test/context.js +24 -2
- package/lib/mcp/test/runTests.js +2 -1
- package/lib/program.js +22 -0
- package/lib/runner/testRunner.js +49 -3
- package/lib/runner/testServer.js +8 -40
- package/package.json +2 -2
- package/lib/mcp/test/program.js +0 -42
package/lib/index.js
CHANGED
|
@@ -222,7 +222,7 @@ const playwrightFixtures = {
|
|
|
222
222
|
if ((0, import_utils.debugMode)() === "inspector")
|
|
223
223
|
testInfo._setDebugMode();
|
|
224
224
|
playwright._defaultContextOptions = _combinedContextOptions;
|
|
225
|
-
playwright._defaultContextTimeout = process.env.
|
|
225
|
+
playwright._defaultContextTimeout = process.env.PLAYWRIGHT_DEBUGGER_MCP ? 5e3 : actionTimeout || 0;
|
|
226
226
|
playwright._defaultContextNavigationTimeout = navigationTimeout || 0;
|
|
227
227
|
await use();
|
|
228
228
|
playwright._defaultContextOptions = void 0;
|
|
@@ -38,11 +38,12 @@ var import_tools = require("./tools");
|
|
|
38
38
|
var import_exports = require("../sdk/exports");
|
|
39
39
|
var import_mdb = require("../sdk/mdb");
|
|
40
40
|
var import_util = require("../../util");
|
|
41
|
+
const tools = [import_tools.snapshot, import_tools.pickLocator, import_tools.evaluate];
|
|
41
42
|
class BrowserBackend {
|
|
42
43
|
constructor(page) {
|
|
43
44
|
this.name = "Playwright";
|
|
44
45
|
this.version = "0.0.1";
|
|
45
|
-
this._tools =
|
|
46
|
+
this._tools = tools;
|
|
46
47
|
this._page = page;
|
|
47
48
|
}
|
|
48
49
|
async initialize() {
|
|
@@ -72,14 +73,18 @@ const doneToolSchema = (0, import_exports.defineToolSchema)({
|
|
|
72
73
|
type: "destructive"
|
|
73
74
|
});
|
|
74
75
|
async function runBrowserBackendOnError(page, message) {
|
|
75
|
-
if (!process.env.
|
|
76
|
+
if (!process.env.PLAYWRIGHT_DEBUGGER_MCP)
|
|
76
77
|
return;
|
|
77
|
-
const
|
|
78
|
-
|
|
78
|
+
const snapshot2 = await page._snapshotForAI();
|
|
79
|
+
const introMessage = `### Paused on error:
|
|
79
80
|
${(0, import_util.stripAnsiEscapes)(message())}
|
|
80
81
|
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
### Current page snapshot:
|
|
83
|
+
${snapshot2}
|
|
84
|
+
|
|
85
|
+
### Task
|
|
86
|
+
Try recovering from the error prior to continuing, use following tools to recover: ${tools.map((tool) => tool.schema.name).join(", ")}`;
|
|
87
|
+
await (0, import_mdb.runOnPauseBackendLoop)(process.env.PLAYWRIGHT_DEBUGGER_MCP, new BrowserBackend(page), introMessage);
|
|
83
88
|
}
|
|
84
89
|
// Annotate the CommonJS export names for ESM import in node:
|
|
85
90
|
0 && (module.exports = {
|
package/lib/mcp/browser/tools.js
CHANGED
|
@@ -39,9 +39,9 @@ var import_tool = require("./tool.js");
|
|
|
39
39
|
var mcp = __toESM(require("../sdk/bundle"));
|
|
40
40
|
const snapshot = (0, import_tool.defineTool)({
|
|
41
41
|
schema: {
|
|
42
|
-
name: "
|
|
43
|
-
title: "
|
|
44
|
-
description: "Capture
|
|
42
|
+
name: "playwright_test_browser_snapshot",
|
|
43
|
+
title: "Capture page snapshot",
|
|
44
|
+
description: "Capture page snapshot for debugging",
|
|
45
45
|
inputSchema: mcp.z.object({}),
|
|
46
46
|
type: "readOnly"
|
|
47
47
|
},
|
|
@@ -63,23 +63,21 @@ const elementSchema = mcp.z.object({
|
|
|
63
63
|
});
|
|
64
64
|
const pickLocator = (0, import_tool.defineTool)({
|
|
65
65
|
schema: {
|
|
66
|
-
name: "
|
|
67
|
-
title: "
|
|
68
|
-
description: "
|
|
66
|
+
name: "playwright_test_generate_locator",
|
|
67
|
+
title: "Create locator for element",
|
|
68
|
+
description: "Generate locator for the given element to use in tests",
|
|
69
69
|
inputSchema: elementSchema,
|
|
70
70
|
type: "readOnly"
|
|
71
71
|
},
|
|
72
72
|
handle: async (page, params) => {
|
|
73
73
|
const locator = await refLocator(page, params);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
]
|
|
82
|
-
};
|
|
74
|
+
try {
|
|
75
|
+
const { resolvedSelector } = await locator._resolveSelector();
|
|
76
|
+
const locatorString = (0, import_utils.asLocator)("javascript", resolvedSelector);
|
|
77
|
+
return { content: [{ type: "text", text: locatorString }] };
|
|
78
|
+
} catch (e) {
|
|
79
|
+
throw new Error(`Ref not found, likely because element was removed. Use ${snapshot.schema.name} to see what elements are currently on the page.`);
|
|
80
|
+
}
|
|
83
81
|
}
|
|
84
82
|
});
|
|
85
83
|
const evaluateSchema = mcp.z.object({
|
|
@@ -89,8 +87,8 @@ const evaluateSchema = mcp.z.object({
|
|
|
89
87
|
});
|
|
90
88
|
const evaluate = (0, import_tool.defineTool)({
|
|
91
89
|
schema: {
|
|
92
|
-
name: "
|
|
93
|
-
title: "Evaluate
|
|
90
|
+
name: "playwright_test_evaluate_on_pause",
|
|
91
|
+
title: "Evaluate in page",
|
|
94
92
|
description: "Evaluate JavaScript expression on page or element",
|
|
95
93
|
inputSchema: evaluateSchema,
|
|
96
94
|
type: "destructive"
|
|
@@ -115,14 +113,6 @@ async function refLocator(page, elementRef) {
|
|
|
115
113
|
throw new Error(`Ref ${elementRef.ref} not found in the current page snapshot. Try capturing new snapshot.`);
|
|
116
114
|
return page.locator(`aria-ref=${elementRef.ref}`).describe(elementRef.element);
|
|
117
115
|
}
|
|
118
|
-
async function generateLocator(locator) {
|
|
119
|
-
try {
|
|
120
|
-
const { resolvedSelector } = await locator._resolveSelector();
|
|
121
|
-
return (0, import_utils.asLocator)("javascript", resolvedSelector);
|
|
122
|
-
} catch (e) {
|
|
123
|
-
throw new Error("Ref not found, likely because element was removed. Use browser_snapshot to see what elements are currently on the page.");
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
116
|
// Annotate the CommonJS export names for ESM import in node:
|
|
127
117
|
0 && (module.exports = {
|
|
128
118
|
elementSchema,
|
package/lib/mcp/sdk/mdb.js
CHANGED
|
@@ -29,8 +29,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
29
29
|
var mdb_exports = {};
|
|
30
30
|
__export(mdb_exports, {
|
|
31
31
|
MDBBackend: () => MDBBackend,
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
runMainBackend: () => runMainBackend,
|
|
33
|
+
runOnPauseBackendLoop: () => runOnPauseBackendLoop
|
|
34
34
|
});
|
|
35
35
|
module.exports = __toCommonJS(mdb_exports);
|
|
36
36
|
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
|
@@ -39,15 +39,23 @@ var import_bundle = require("./bundle.js");
|
|
|
39
39
|
var import_bundle2 = require("./bundle.js");
|
|
40
40
|
var mcpBundle = __toESM(require("./bundle.js"));
|
|
41
41
|
var import_tool = require("./tool.js");
|
|
42
|
+
var mcpServer = __toESM(require("./server.js"));
|
|
42
43
|
var mcpHttp = __toESM(require("./http.js"));
|
|
43
|
-
var
|
|
44
|
+
var import_server = require("./server.js");
|
|
45
|
+
const mdbDebug = (0, import_utilsBundle.debug)("pw:mcp:mdb");
|
|
44
46
|
const errorsDebug = (0, import_utilsBundle.debug)("pw:mcp:errors");
|
|
45
47
|
class MDBBackend {
|
|
46
|
-
constructor() {
|
|
48
|
+
constructor(topLevelBackend) {
|
|
47
49
|
this._stack = [];
|
|
50
|
+
this._initialized = false;
|
|
51
|
+
this._topLevelBackend = topLevelBackend;
|
|
48
52
|
}
|
|
49
53
|
async initialize(server) {
|
|
50
|
-
this.
|
|
54
|
+
if (this._initialized)
|
|
55
|
+
return;
|
|
56
|
+
this._initialized = true;
|
|
57
|
+
const transport = await (0, import_server.wrapInProcess)(this._topLevelBackend);
|
|
58
|
+
await this._pushClient(transport);
|
|
51
59
|
}
|
|
52
60
|
async listTools() {
|
|
53
61
|
const response = await this._client().listTools();
|
|
@@ -56,9 +64,11 @@ class MDBBackend {
|
|
|
56
64
|
async callTool(name, args) {
|
|
57
65
|
if (name === pushToolsSchema.name)
|
|
58
66
|
return await this._pushTools(pushToolsSchema.inputSchema.parse(args || {}));
|
|
59
|
-
|
|
67
|
+
const interruptPromise = new import_utils.ManualPromise();
|
|
68
|
+
this._interruptPromise = interruptPromise;
|
|
60
69
|
let [entry] = this._stack;
|
|
61
70
|
while (entry && !entry.toolNames.includes(name)) {
|
|
71
|
+
mdbDebug("popping client from stack for ", name);
|
|
62
72
|
this._stack.shift();
|
|
63
73
|
await entry.client.close();
|
|
64
74
|
entry = this._stack[0];
|
|
@@ -68,16 +78,22 @@ class MDBBackend {
|
|
|
68
78
|
this._client().callTool({
|
|
69
79
|
name,
|
|
70
80
|
arguments: args
|
|
71
|
-
}).then((
|
|
72
|
-
resultPromise.resolve(
|
|
81
|
+
}).then((result2) => {
|
|
82
|
+
resultPromise.resolve(result2);
|
|
73
83
|
}).catch((e) => {
|
|
84
|
+
mdbDebug("error in client call", e);
|
|
74
85
|
if (this._stack.length < 2)
|
|
75
86
|
throw e;
|
|
76
87
|
this._stack.shift();
|
|
77
88
|
const prevEntry = this._stack[0];
|
|
78
|
-
void prevEntry.resultPromise.then((
|
|
89
|
+
void prevEntry.resultPromise.then((result2) => resultPromise.resolve(result2));
|
|
79
90
|
});
|
|
80
|
-
|
|
91
|
+
const result = await Promise.race([interruptPromise, resultPromise]);
|
|
92
|
+
if (interruptPromise.isDone())
|
|
93
|
+
mdbDebug("client call intercepted", result);
|
|
94
|
+
else
|
|
95
|
+
mdbDebug("client call result", result);
|
|
96
|
+
return result;
|
|
81
97
|
}
|
|
82
98
|
_client() {
|
|
83
99
|
const [entry] = this._stack;
|
|
@@ -86,22 +102,28 @@ class MDBBackend {
|
|
|
86
102
|
return entry.client;
|
|
87
103
|
}
|
|
88
104
|
async _pushTools(params) {
|
|
105
|
+
mdbDebug("pushing tools to the stack", params.mcpUrl);
|
|
106
|
+
const transport = new import_bundle2.StreamableHTTPClientTransport(new URL(params.mcpUrl));
|
|
107
|
+
await this._pushClient(transport, params.introMessage);
|
|
108
|
+
return { content: [{ type: "text", text: "Tools pushed" }] };
|
|
109
|
+
}
|
|
110
|
+
async _pushClient(transport, introMessage) {
|
|
111
|
+
mdbDebug("pushing client to the stack");
|
|
89
112
|
const client = new mcpBundle.Client({ name: "Internal client", version: "0.0.0" });
|
|
90
113
|
client.setRequestHandler(import_bundle.PingRequestSchema, () => ({}));
|
|
91
|
-
const transport = new import_bundle2.StreamableHTTPClientTransport(new URL(params.mcpUrl));
|
|
92
114
|
await client.connect(transport);
|
|
115
|
+
mdbDebug("connected to the new client");
|
|
116
|
+
const { tools } = await client.listTools();
|
|
117
|
+
this._stack.unshift({ client, toolNames: tools.map((tool) => tool.name), resultPromise: void 0 });
|
|
118
|
+
mdbDebug("new tools added to the stack:", tools.map((tool) => tool.name));
|
|
119
|
+
mdbDebug("interrupting current call:", !!this._interruptPromise);
|
|
93
120
|
this._interruptPromise?.resolve({
|
|
94
121
|
content: [{
|
|
95
122
|
type: "text",
|
|
96
|
-
text:
|
|
123
|
+
text: introMessage || ""
|
|
97
124
|
}]
|
|
98
125
|
});
|
|
99
126
|
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
127
|
return { content: [{ type: "text", text: "Tools pushed" }] };
|
|
106
128
|
}
|
|
107
129
|
}
|
|
@@ -115,20 +137,17 @@ const pushToolsSchema = (0, import_tool.defineToolSchema)({
|
|
|
115
137
|
}),
|
|
116
138
|
type: "readOnly"
|
|
117
139
|
});
|
|
118
|
-
async function
|
|
119
|
-
const mdbBackend = new MDBBackend();
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
nameInConfig: "playwright-mdb",
|
|
123
|
-
version: "0.0.0",
|
|
140
|
+
async function runMainBackend(backendFactory, options) {
|
|
141
|
+
const mdbBackend = new MDBBackend(backendFactory.create());
|
|
142
|
+
const factory = {
|
|
143
|
+
...backendFactory,
|
|
124
144
|
create: () => mdbBackend
|
|
125
145
|
};
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
return mdbUrl;
|
|
146
|
+
const url = await startAsHttp(factory, { port: options?.port || 0 });
|
|
147
|
+
process.env.PLAYWRIGHT_DEBUGGER_MCP = url;
|
|
148
|
+
if (options?.port !== void 0)
|
|
149
|
+
return url;
|
|
150
|
+
await mcpServer.connect(factory, new mcpBundle.StdioServerTransport(), false);
|
|
132
151
|
}
|
|
133
152
|
async function runOnPauseBackendLoop(mdbUrl, backend, introMessage) {
|
|
134
153
|
const wrappedBackend = new OnceTimeServerBackendWrapper(backend);
|
|
@@ -179,8 +198,8 @@ class OnceTimeServerBackendWrapper {
|
|
|
179
198
|
async callTool(name, args) {
|
|
180
199
|
return this._backend.callTool(name, args);
|
|
181
200
|
}
|
|
182
|
-
serverClosed() {
|
|
183
|
-
this._backend.serverClosed?.();
|
|
201
|
+
serverClosed(server) {
|
|
202
|
+
this._backend.serverClosed?.(server);
|
|
184
203
|
this._selfDestructPromise.resolve();
|
|
185
204
|
}
|
|
186
205
|
async waitForClosed() {
|
|
@@ -190,6 +209,6 @@ class OnceTimeServerBackendWrapper {
|
|
|
190
209
|
// Annotate the CommonJS export names for ESM import in node:
|
|
191
210
|
0 && (module.exports = {
|
|
192
211
|
MDBBackend,
|
|
193
|
-
|
|
194
|
-
|
|
212
|
+
runMainBackend,
|
|
213
|
+
runOnPauseBackendLoop
|
|
195
214
|
});
|
package/lib/mcp/sdk/server.js
CHANGED
|
@@ -95,7 +95,7 @@ function createServer(name, version, backend, runHeartbeat) {
|
|
|
95
95
|
errorsDebug(e);
|
|
96
96
|
}
|
|
97
97
|
});
|
|
98
|
-
addServerListener(server, "close", () => backend.serverClosed?.());
|
|
98
|
+
addServerListener(server, "close", () => backend.serverClosed?.(server));
|
|
99
99
|
return server;
|
|
100
100
|
}
|
|
101
101
|
const startHeartbeat = (server) => {
|
package/lib/mcp/test/backend.js
CHANGED
|
@@ -37,11 +37,11 @@ var import_listTests = require("./listTests");
|
|
|
37
37
|
var import_runTests = require("./runTests");
|
|
38
38
|
var import_tools = require("../browser/tools");
|
|
39
39
|
class TestServerBackend {
|
|
40
|
-
constructor(resolvedLocation) {
|
|
40
|
+
constructor(resolvedLocation, options) {
|
|
41
41
|
this.name = "Playwright";
|
|
42
42
|
this.version = "0.0.1";
|
|
43
43
|
this._tools = [import_listTests.listTests, import_runTests.runTests];
|
|
44
|
-
this._context = new import_context.Context(resolvedLocation);
|
|
44
|
+
this._context = new import_context.Context(resolvedLocation, options);
|
|
45
45
|
}
|
|
46
46
|
async listTools() {
|
|
47
47
|
return [
|
|
@@ -56,7 +56,11 @@ class TestServerBackend {
|
|
|
56
56
|
if (!tool)
|
|
57
57
|
throw new Error(`Tool not found: ${name}. Available tools: ${this._tools.map((tool2) => tool2.schema.name).join(", ")}`);
|
|
58
58
|
const parsedArguments = tool.schema.inputSchema.parse(args || {});
|
|
59
|
-
|
|
59
|
+
const result = await tool.handle(this._context, parsedArguments);
|
|
60
|
+
const stdio = this._context.takeStdio();
|
|
61
|
+
if (stdio.trim())
|
|
62
|
+
result.content.push({ type: "text", text: stdio });
|
|
63
|
+
return result;
|
|
60
64
|
}
|
|
61
65
|
serverClosed() {
|
|
62
66
|
void this._context.close();
|
package/lib/mcp/test/context.js
CHANGED
|
@@ -23,20 +23,42 @@ __export(context_exports, {
|
|
|
23
23
|
module.exports = __toCommonJS(context_exports);
|
|
24
24
|
var import_testRunner = require("../../runner/testRunner");
|
|
25
25
|
class Context {
|
|
26
|
-
constructor(configLocation) {
|
|
26
|
+
constructor(configLocation, options) {
|
|
27
|
+
this._stdio = [];
|
|
27
28
|
this.configLocation = configLocation;
|
|
29
|
+
this.options = options;
|
|
28
30
|
}
|
|
29
31
|
async createTestRunner() {
|
|
30
32
|
if (this._testRunner)
|
|
31
33
|
await this._testRunner.stopTests();
|
|
32
34
|
const testRunner = new import_testRunner.TestRunner(this.configLocation, {});
|
|
33
|
-
await testRunner.initialize({
|
|
35
|
+
await testRunner.initialize({
|
|
36
|
+
sendStdioEvents: true,
|
|
37
|
+
muteConsole: this.options?.muteConsole
|
|
38
|
+
});
|
|
39
|
+
testRunner.on(import_testRunner.TestRunnerEvent.StdioChunk, (chunk, stdio) => {
|
|
40
|
+
this._stdio.push({ chunk, stdio });
|
|
41
|
+
});
|
|
42
|
+
this._testRunner = testRunner;
|
|
43
|
+
testRunner.on(import_testRunner.TestRunnerEvent.TestFilesChanged, (testFiles) => {
|
|
44
|
+
this._testRunner?.emit(import_testRunner.TestRunnerEvent.TestFilesChanged, testFiles);
|
|
45
|
+
});
|
|
34
46
|
this._testRunner = testRunner;
|
|
35
47
|
return testRunner;
|
|
36
48
|
}
|
|
49
|
+
takeStdio() {
|
|
50
|
+
const text = this._stdio.map((entry) => chunkToPayload(entry.stdio, entry.chunk)).join("\n");
|
|
51
|
+
this._stdio = [];
|
|
52
|
+
return text;
|
|
53
|
+
}
|
|
37
54
|
async close() {
|
|
38
55
|
}
|
|
39
56
|
}
|
|
57
|
+
function chunkToPayload(type, chunk) {
|
|
58
|
+
if (chunk instanceof Uint8Array)
|
|
59
|
+
return "<binary>";
|
|
60
|
+
return `[${type}] ${chunk}`;
|
|
61
|
+
}
|
|
40
62
|
// Annotate the CommonJS export names for ESM import in node:
|
|
41
63
|
0 && (module.exports = {
|
|
42
64
|
Context
|
package/lib/mcp/test/runTests.js
CHANGED
|
@@ -65,7 +65,8 @@ const runTests = (0, import_tool.defineTool)({
|
|
|
65
65
|
const result = await testRunner.runTests(reporter, {
|
|
66
66
|
testIds: params.tests?.map((test) => test.id),
|
|
67
67
|
// For automatic recovery
|
|
68
|
-
timeout: 0
|
|
68
|
+
timeout: 0,
|
|
69
|
+
workers: 1
|
|
69
70
|
});
|
|
70
71
|
const text = stream.content();
|
|
71
72
|
return {
|
package/lib/program.js
CHANGED
|
@@ -46,6 +46,8 @@ var testServer = __toESM(require("./runner/testServer"));
|
|
|
46
46
|
var import_watchMode = require("./runner/watchMode");
|
|
47
47
|
var import_testRunner = require("./runner/testRunner");
|
|
48
48
|
var import_reporters = require("./runner/reporters");
|
|
49
|
+
var import_backend = require("./mcp/test/backend");
|
|
50
|
+
var import_mdb = require("./mcp/sdk/mdb");
|
|
49
51
|
function addTestCommand(program3) {
|
|
50
52
|
const command = program3.command("test [test-filter...]");
|
|
51
53
|
command.description("run tests with Playwright Test");
|
|
@@ -139,6 +141,25 @@ Arguments [dir]:
|
|
|
139
141
|
Examples:
|
|
140
142
|
$ npx playwright merge-reports playwright-report`);
|
|
141
143
|
}
|
|
144
|
+
function addMCPServerCommand(program3) {
|
|
145
|
+
const command = program3.command("run-mcp-server", { hidden: true });
|
|
146
|
+
command.description("Interact with the test runner over MCP");
|
|
147
|
+
command.option("-c, --config <file>", `Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}"`);
|
|
148
|
+
command.option("--host <host>", "host to bind server to. Default is localhost. Use 0.0.0.0 to bind to all interfaces.");
|
|
149
|
+
command.option("--port <port>", "port to listen on for SSE transport.");
|
|
150
|
+
command.action(async (options) => {
|
|
151
|
+
const resolvedLocation = (0, import_configLoader.resolveConfigLocation)(options.config);
|
|
152
|
+
const backendFactory = {
|
|
153
|
+
name: "Playwright Test Runner",
|
|
154
|
+
nameInConfig: "playwright-test-runner",
|
|
155
|
+
version: "0.0.0",
|
|
156
|
+
create: () => new import_backend.TestServerBackend(resolvedLocation, { muteConsole: options.port === void 0 })
|
|
157
|
+
};
|
|
158
|
+
const mdbUrl = await (0, import_mdb.runMainBackend)(backendFactory, { port: options.port === void 0 ? void 0 : +options.port });
|
|
159
|
+
if (mdbUrl)
|
|
160
|
+
console.error("MCP Listening on: ", mdbUrl);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
142
163
|
async function runTests(args, opts) {
|
|
143
164
|
await (0, import_utils.startProfiling)();
|
|
144
165
|
const cliOverrides = overridesFromOptions(opts);
|
|
@@ -336,6 +357,7 @@ addTestCommand(import_program.program);
|
|
|
336
357
|
addShowReportCommand(import_program.program);
|
|
337
358
|
addMergeReportsCommand(import_program.program);
|
|
338
359
|
addClearCacheCommand(import_program.program);
|
|
360
|
+
addMCPServerCommand(import_program.program);
|
|
339
361
|
addDevServerCommand(import_program.program);
|
|
340
362
|
addTestServerCommand(import_program.program);
|
|
341
363
|
// Annotate the CommonJS export names for ESM import in node:
|
package/lib/runner/testRunner.js
CHANGED
|
@@ -36,6 +36,8 @@ module.exports = __toCommonJS(testRunner_exports);
|
|
|
36
36
|
var import_events = __toESM(require("events"));
|
|
37
37
|
var import_fs = __toESM(require("fs"));
|
|
38
38
|
var import_path = __toESM(require("path"));
|
|
39
|
+
var import_util = __toESM(require("util"));
|
|
40
|
+
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
|
39
41
|
var import_server = require("playwright-core/lib/server");
|
|
40
42
|
var import_utils = require("playwright-core/lib/utils");
|
|
41
43
|
var import_configLoader = require("../common/configLoader");
|
|
@@ -46,13 +48,17 @@ var import_webServerPlugin = require("../plugins/webServerPlugin");
|
|
|
46
48
|
var import_base = require("../reporters/base");
|
|
47
49
|
var import_internalReporter = require("../reporters/internalReporter");
|
|
48
50
|
var import_compilationCache = require("../transform/compilationCache");
|
|
49
|
-
var
|
|
51
|
+
var import_util2 = require("../util");
|
|
50
52
|
var import_reporters = require("./reporters");
|
|
51
53
|
var import_tasks = require("./tasks");
|
|
52
54
|
var import_lastRun = require("./lastRun");
|
|
53
55
|
const TestRunnerEvent = {
|
|
54
|
-
TestFilesChanged: "testFilesChanged"
|
|
56
|
+
TestFilesChanged: "testFilesChanged",
|
|
57
|
+
StdioChunk: "stdioChunk"
|
|
55
58
|
};
|
|
59
|
+
const originalDebugLog = import_utilsBundle.debug.log;
|
|
60
|
+
const originalStdoutWrite = process.stdout.write;
|
|
61
|
+
const originalStderrWrite = process.stderr.write;
|
|
56
62
|
class TestRunner extends import_events.default {
|
|
57
63
|
constructor(configLocation, configCLIOverrides) {
|
|
58
64
|
super();
|
|
@@ -73,6 +79,10 @@ class TestRunner extends import_events.default {
|
|
|
73
79
|
async initialize(params) {
|
|
74
80
|
this._watchTestDirs = !!params.watchTestDirs;
|
|
75
81
|
this._populateDependenciesOnList = !!params.populateDependenciesOnList;
|
|
82
|
+
this._setInterceptStdio({
|
|
83
|
+
sendStdioEvents: !!params.sendStdioEvents,
|
|
84
|
+
muteConsole: !!params.muteConsole
|
|
85
|
+
});
|
|
76
86
|
}
|
|
77
87
|
resizeTerminal(params) {
|
|
78
88
|
process.stdout.columns = params.cols;
|
|
@@ -307,6 +317,10 @@ class TestRunner extends import_events.default {
|
|
|
307
317
|
async closeGracefully() {
|
|
308
318
|
(0, import_utils.gracefullyProcessExitDoNotHang)(0);
|
|
309
319
|
}
|
|
320
|
+
async stop() {
|
|
321
|
+
this._setInterceptStdio({ sendStdioEvents: false, muteConsole: false });
|
|
322
|
+
await this.runGlobalTeardown();
|
|
323
|
+
}
|
|
310
324
|
async _loadConfig(overrides) {
|
|
311
325
|
try {
|
|
312
326
|
const config = await (0, import_configLoader.loadConfig)(this.configLocation, overrides);
|
|
@@ -319,7 +333,7 @@ class TestRunner extends import_events.default {
|
|
|
319
333
|
}
|
|
320
334
|
return { config };
|
|
321
335
|
} catch (e) {
|
|
322
|
-
return { config: null, error: (0,
|
|
336
|
+
return { config: null, error: (0, import_util2.serializeError)(e) };
|
|
323
337
|
}
|
|
324
338
|
}
|
|
325
339
|
async _loadConfigOrReportError(reporter, overrides) {
|
|
@@ -332,6 +346,38 @@ class TestRunner extends import_events.default {
|
|
|
332
346
|
await reporter.onExit();
|
|
333
347
|
return null;
|
|
334
348
|
}
|
|
349
|
+
_setInterceptStdio(options) {
|
|
350
|
+
if (process.env.PWTEST_DEBUG)
|
|
351
|
+
return;
|
|
352
|
+
if (options.sendStdioEvents || options.muteConsole) {
|
|
353
|
+
if (import_utilsBundle.debug.log === originalDebugLog) {
|
|
354
|
+
import_utilsBundle.debug.log = (...args) => {
|
|
355
|
+
const string = import_util.default.format(...args) + "\n";
|
|
356
|
+
return originalStderrWrite.apply(process.stderr, [string]);
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
const stdoutWrite = (chunk) => {
|
|
360
|
+
if (options.sendStdioEvents)
|
|
361
|
+
this.emit(TestRunnerEvent.StdioChunk, chunk, "stdout");
|
|
362
|
+
if (!options.muteConsole)
|
|
363
|
+
originalStdoutWrite.apply(process.stdout, [chunk]);
|
|
364
|
+
return true;
|
|
365
|
+
};
|
|
366
|
+
const stderrWrite = (chunk) => {
|
|
367
|
+
if (options.sendStdioEvents)
|
|
368
|
+
this.emit(TestRunnerEvent.StdioChunk, chunk, "stderr");
|
|
369
|
+
if (!options.muteConsole)
|
|
370
|
+
originalStderrWrite.apply(process.stderr, [chunk]);
|
|
371
|
+
return true;
|
|
372
|
+
};
|
|
373
|
+
process.stdout.write = stdoutWrite;
|
|
374
|
+
process.stderr.write = stderrWrite;
|
|
375
|
+
} else {
|
|
376
|
+
import_utilsBundle.debug.log = originalDebugLog;
|
|
377
|
+
process.stdout.write = originalStdoutWrite;
|
|
378
|
+
process.stderr.write = originalStderrWrite;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
335
381
|
}
|
|
336
382
|
function printInternalError(e) {
|
|
337
383
|
console.error("Internal error:", e);
|
package/lib/runner/testServer.js
CHANGED
|
@@ -28,13 +28,11 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
28
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
29
|
var testServer_exports = {};
|
|
30
30
|
__export(testServer_exports, {
|
|
31
|
-
TestRunnerEvent: () => TestRunnerEvent,
|
|
32
31
|
TestServerDispatcher: () => TestServerDispatcher,
|
|
33
32
|
runTestServer: () => runTestServer,
|
|
34
33
|
runUIMode: () => runUIMode
|
|
35
34
|
});
|
|
36
35
|
module.exports = __toCommonJS(testServer_exports);
|
|
37
|
-
var import_util = __toESM(require("util"));
|
|
38
36
|
var import_server = require("playwright-core/lib/server");
|
|
39
37
|
var import_utils = require("playwright-core/lib/utils");
|
|
40
38
|
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
|
@@ -43,9 +41,6 @@ var import_list = __toESM(require("../reporters/list"));
|
|
|
43
41
|
var import_reporters = require("./reporters");
|
|
44
42
|
var import_sigIntWatcher = require("./sigIntWatcher");
|
|
45
43
|
var import_testRunner = require("./testRunner");
|
|
46
|
-
const originalDebugLog = import_utilsBundle.debug.log;
|
|
47
|
-
const originalStdoutWrite = process.stdout.write;
|
|
48
|
-
const originalStderrWrite = process.stderr.write;
|
|
49
44
|
class TestServer {
|
|
50
45
|
constructor(configLocation, configCLIOverrides) {
|
|
51
46
|
this._configLocation = configLocation;
|
|
@@ -56,13 +51,9 @@ class TestServer {
|
|
|
56
51
|
return await (0, import_server.startTraceViewerServer)({ ...options, transport: this._dispatcher.transport });
|
|
57
52
|
}
|
|
58
53
|
async stop() {
|
|
59
|
-
await this._dispatcher?.
|
|
60
|
-
await this._dispatcher?.runGlobalTeardown();
|
|
54
|
+
await this._dispatcher?.stop();
|
|
61
55
|
}
|
|
62
56
|
}
|
|
63
|
-
const TestRunnerEvent = {
|
|
64
|
-
TestFilesChanged: "testFilesChanged"
|
|
65
|
-
};
|
|
66
57
|
class TestServerDispatcher {
|
|
67
58
|
constructor(configLocation, configCLIOverrides) {
|
|
68
59
|
this._serializer = require.resolve("./uiModeReporter");
|
|
@@ -78,7 +69,8 @@ class TestServerDispatcher {
|
|
|
78
69
|
}
|
|
79
70
|
};
|
|
80
71
|
this._dispatchEvent = (method, params) => this.transport.sendEvent?.(method, params);
|
|
81
|
-
this._testRunner.on(TestRunnerEvent.TestFilesChanged, (testFiles) => this._dispatchEvent("testFilesChanged", { testFiles }));
|
|
72
|
+
this._testRunner.on(import_testRunner.TestRunnerEvent.TestFilesChanged, (testFiles) => this._dispatchEvent("testFilesChanged", { testFiles }));
|
|
73
|
+
this._testRunner.on(import_testRunner.TestRunnerEvent.StdioChunk, (chunk, stdio) => this._dispatchEvent("stdio", chunkToPayload(stdio, chunk)));
|
|
82
74
|
}
|
|
83
75
|
async _wireReporter(messageSink) {
|
|
84
76
|
return await (0, import_reporters.createReporterForTestServer)(this._serializer, messageSink);
|
|
@@ -93,10 +85,10 @@ class TestServerDispatcher {
|
|
|
93
85
|
async initialize(params) {
|
|
94
86
|
this._serializer = params.serializer || require.resolve("./uiModeReporter");
|
|
95
87
|
this._closeOnDisconnect = !!params.closeOnDisconnect;
|
|
96
|
-
await this._setInterceptStdio(!!params.interceptStdio);
|
|
97
88
|
await this._testRunner.initialize({
|
|
98
|
-
|
|
99
|
-
|
|
89
|
+
...params,
|
|
90
|
+
sendStdioEvents: !!params.interceptStdio,
|
|
91
|
+
muteConsole: !!params.interceptStdio
|
|
100
92
|
});
|
|
101
93
|
}
|
|
102
94
|
async ping() {
|
|
@@ -167,31 +159,8 @@ class TestServerDispatcher {
|
|
|
167
159
|
async stopTests() {
|
|
168
160
|
await this._testRunner.stopTests();
|
|
169
161
|
}
|
|
170
|
-
async
|
|
171
|
-
|
|
172
|
-
return;
|
|
173
|
-
if (intercept) {
|
|
174
|
-
if (import_utilsBundle.debug.log === originalDebugLog) {
|
|
175
|
-
import_utilsBundle.debug.log = (...args) => {
|
|
176
|
-
const string = import_util.default.format(...args) + "\n";
|
|
177
|
-
return originalStderrWrite.apply(process.stderr, [string]);
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
const stdoutWrite = (chunk) => {
|
|
181
|
-
this._dispatchEvent("stdio", chunkToPayload("stdout", chunk));
|
|
182
|
-
return true;
|
|
183
|
-
};
|
|
184
|
-
const stderrWrite = (chunk) => {
|
|
185
|
-
this._dispatchEvent("stdio", chunkToPayload("stderr", chunk));
|
|
186
|
-
return true;
|
|
187
|
-
};
|
|
188
|
-
process.stdout.write = stdoutWrite;
|
|
189
|
-
process.stderr.write = stderrWrite;
|
|
190
|
-
} else {
|
|
191
|
-
import_utilsBundle.debug.log = originalDebugLog;
|
|
192
|
-
process.stdout.write = originalStdoutWrite;
|
|
193
|
-
process.stderr.write = originalStderrWrite;
|
|
194
|
-
}
|
|
162
|
+
async stop() {
|
|
163
|
+
await this._testRunner.stop();
|
|
195
164
|
}
|
|
196
165
|
async closeGracefully() {
|
|
197
166
|
await this._testRunner.closeGracefully();
|
|
@@ -257,7 +226,6 @@ function chunkToPayload(type, chunk) {
|
|
|
257
226
|
}
|
|
258
227
|
// Annotate the CommonJS export names for ESM import in node:
|
|
259
228
|
0 && (module.exports = {
|
|
260
|
-
TestRunnerEvent,
|
|
261
229
|
TestServerDispatcher,
|
|
262
230
|
runTestServer,
|
|
263
231
|
runUIMode
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "playwright",
|
|
3
|
-
"version": "1.56.0-alpha-2025-08-
|
|
3
|
+
"version": "1.56.0-alpha-2025-08-23",
|
|
4
4
|
"description": "A high-level API to automate web browsers",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
},
|
|
61
61
|
"license": "Apache-2.0",
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"playwright-core": "1.56.0-alpha-2025-08-
|
|
63
|
+
"playwright-core": "1.56.0-alpha-2025-08-23"
|
|
64
64
|
},
|
|
65
65
|
"optionalDependencies": {
|
|
66
66
|
"fsevents": "2.3.2"
|
package/lib/mcp/test/program.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
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 __copyProps = (to, from, except, desc) => {
|
|
9
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
-
for (let key of __getOwnPropNames(from))
|
|
11
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
12
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
13
|
-
}
|
|
14
|
-
return to;
|
|
15
|
-
};
|
|
16
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
17
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
18
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
19
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
20
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
21
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
22
|
-
mod
|
|
23
|
-
));
|
|
24
|
-
var import_path = __toESM(require("path"));
|
|
25
|
-
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
|
26
|
-
var import_configLoader = require("../../common/configLoader");
|
|
27
|
-
var import_backend = require("./backend.js");
|
|
28
|
-
var import_mdb = require("../sdk/mdb");
|
|
29
|
-
import_utilsBundle.program.version("Version 0.0.1").name("playwright-test-mcp").option("--config <file>", 'Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}"').option("--host <host>", "host to bind server to. Default is localhost. Use 0.0.0.0 to bind to all interfaces.").option("--port <port>", "port to listen on for SSE transport.").action(async (options) => {
|
|
30
|
-
const resolvedLocation = (0, import_configLoader.resolveConfigLocation)(options.config);
|
|
31
|
-
console.error("Test config: ", import_path.default.relative(process.cwd(), resolvedLocation.resolvedConfigFile ?? resolvedLocation.configDir));
|
|
32
|
-
const backendFactory = {
|
|
33
|
-
name: "Playwright Test",
|
|
34
|
-
nameInConfig: "playwright-test-mcp",
|
|
35
|
-
version: "0.0.0",
|
|
36
|
-
create: () => new import_backend.TestServerBackend(resolvedLocation)
|
|
37
|
-
};
|
|
38
|
-
const mdbUrl = await (0, import_mdb.runToolsBackend)(backendFactory, { port: 9224 });
|
|
39
|
-
process.env.PLAYWRIGHT_TEST_DEBUGGER_MCP = mdbUrl;
|
|
40
|
-
console.error("MCP Listening on: ", mdbUrl);
|
|
41
|
-
});
|
|
42
|
-
void import_utilsBundle.program.parseAsync(process.argv);
|