playwright 1.56.0-alpha-2025-09-08 → 1.56.0-alpha-1757456950000

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # 🎭 Playwright
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) <!-- GEN:chromium-version-badge -->[![Chromium version](https://img.shields.io/badge/chromium-140.0.7339.41-blue.svg?logo=google-chrome)](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[![Firefox version](https://img.shields.io/badge/firefox-141.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[![WebKit version](https://img.shields.io/badge/webkit-26.0-blue.svg?logo=safari)](https://webkit.org/)<!-- GEN:stop --> [![Join Discord](https://img.shields.io/badge/join-discord-informational)](https://aka.ms/playwright/discord)
3
+ [![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) <!-- GEN:chromium-version-badge -->[![Chromium version](https://img.shields.io/badge/chromium-141.0.7390.7-blue.svg?logo=google-chrome)](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[![Firefox version](https://img.shields.io/badge/firefox-141.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[![WebKit version](https://img.shields.io/badge/webkit-26.0-blue.svg?logo=safari)](https://webkit.org/)<!-- GEN:stop --> [![Join Discord](https://img.shields.io/badge/join-discord-informational)](https://aka.ms/playwright/discord)
4
4
 
5
5
  ## [Documentation](https://playwright.dev) | [API reference](https://playwright.dev/docs/api/class-playwright)
6
6
 
@@ -8,7 +8,7 @@ Playwright is a framework for Web Testing and Automation. It allows testing [Chr
8
8
 
9
9
  | | Linux | macOS | Windows |
10
10
  | :--- | :---: | :---: | :---: |
11
- | Chromium <!-- GEN:chromium-version -->140.0.7339.41<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
11
+ | Chromium <!-- GEN:chromium-version -->141.0.7390.7<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
12
12
  | WebKit <!-- GEN:webkit-version -->26.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
13
13
  | Firefox <!-- GEN:firefox-version -->141.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
14
14
 
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.PLAYWRIGHT_DEBUGGER_ENABLED ? 5e3 : actionTimeout || 0;
225
+ playwright._defaultContextTimeout = testInfo._pauseOnError() ? 5e3 : actionTimeout || 0;
226
226
  playwright._defaultContextNavigationTimeout = navigationTimeout || 0;
227
227
  await use();
228
228
  playwright._defaultContextOptions = void 0;
@@ -262,6 +262,9 @@ const playwrightFixtures = {
262
262
  tracingGroupSteps.push(step);
263
263
  },
264
264
  onApiCallRecovery: (data, error, channelOwner, recoveryHandlers) => {
265
+ const testInfo2 = (0, import_globals.currentTestInfo)();
266
+ if (!testInfo2 || !testInfo2._pauseOnError())
267
+ return;
265
268
  const step = data.userData;
266
269
  if (!step)
267
270
  return;
@@ -39,8 +39,9 @@ var import_browserContextFactory = require("./browser/browserContextFactory");
39
39
  var import_proxyBackend = require("./sdk/proxyBackend");
40
40
  var import_browserServerBackend = require("./browser/browserServerBackend");
41
41
  var import_extensionContextFactory = require("./extension/extensionContextFactory");
42
+ var import_host = require("./vscode/host");
42
43
  function decorateCommand(command, version) {
43
- command.option("--allowed-origins <origins>", "semicolon-separated list of origins to allow the browser to request. Default is to allow all.", import_config.semicolonSeparatedList).option("--blocked-origins <origins>", "semicolon-separated list of origins to block the browser from requesting. Blocklist is evaluated before allowlist. If used without the allowlist, requests not matching the blocklist are still allowed.", import_config.semicolonSeparatedList).option("--block-service-workers", "block service workers").option("--browser <browser>", "browser or chrome channel to use, possible values: chrome, firefox, webkit, msedge.").option("--caps <caps>", "comma-separated list of additional capabilities to enable, possible values: vision, pdf.", import_config.commaSeparatedList).option("--cdp-endpoint <endpoint>", "CDP endpoint to connect to.").option("--cdp-header <headers...>", "CDP headers to send with the connect request, multiple can be specified.", import_config.headerParser).option("--config <path>", "path to the configuration file.").option("--device <device>", 'device to emulate, for example: "iPhone 15"').option("--executable-path <path>", "path to the browser executable.").option("--extension", 'Connect to a running browser instance (Edge/Chrome only). Requires the "Playwright MCP Bridge" browser extension to be installed.').option("--headless", "run browser in headless mode, headed by default").option("--host <host>", "host to bind server to. Default is localhost. Use 0.0.0.0 to bind to all interfaces.").option("--ignore-https-errors", "ignore https errors").option("--isolated", "keep the browser profile in memory, do not save it to disk.").option("--image-responses <mode>", 'whether to send image responses to the client. Can be "allow" or "omit", Defaults to "allow".').option("--no-sandbox", "disable the sandbox for all process types that are normally sandboxed.").option("--output-dir <path>", "path to the directory for output files.").option("--port <port>", "port to listen on for SSE transport.").option("--proxy-bypass <bypass>", 'comma-separated domains to bypass proxy, for example ".com,chromium.org,.domain.com"').option("--proxy-server <proxy>", 'specify proxy server, for example "http://myproxy:3128" or "socks5://myproxy:8080"').option("--save-session", "Whether to save the Playwright MCP session into the output directory.").option("--save-trace", "Whether to save the Playwright Trace of the session into the output directory.").option("--secrets <path>", "path to a file containing secrets in the dotenv format", import_config.dotenvFileLoader).option("--storage-state <path>", "path to the storage state file for isolated sessions.").option("--timeout-action <timeout>", "specify action timeout in milliseconds, defaults to 5000ms", import_config.numberParser).option("--timeout-navigation <timeout>", "specify navigation timeout in milliseconds, defaults to 60000ms", import_config.numberParser).option("--user-agent <ua string>", "specify user agent string").option("--user-data-dir <path>", "path to the user data directory. If not specified, a temporary directory will be created.").option("--viewport-size <size>", 'specify browser viewport size in pixels, for example "1280, 720"').addOption(new import_utilsBundle.ProgramOption("--connect-tool", "Allow to switch between different browser connection methods.").hideHelp()).addOption(new import_utilsBundle.ProgramOption("--vision", "Legacy option, use --caps=vision instead").hideHelp()).action(async (options) => {
44
+ command.option("--allowed-origins <origins>", "semicolon-separated list of origins to allow the browser to request. Default is to allow all.", import_config.semicolonSeparatedList).option("--blocked-origins <origins>", "semicolon-separated list of origins to block the browser from requesting. Blocklist is evaluated before allowlist. If used without the allowlist, requests not matching the blocklist are still allowed.", import_config.semicolonSeparatedList).option("--block-service-workers", "block service workers").option("--browser <browser>", "browser or chrome channel to use, possible values: chrome, firefox, webkit, msedge.").option("--caps <caps>", "comma-separated list of additional capabilities to enable, possible values: vision, pdf.", import_config.commaSeparatedList).option("--cdp-endpoint <endpoint>", "CDP endpoint to connect to.").option("--cdp-header <headers...>", "CDP headers to send with the connect request, multiple can be specified.", import_config.headerParser).option("--config <path>", "path to the configuration file.").option("--device <device>", 'device to emulate, for example: "iPhone 15"').option("--executable-path <path>", "path to the browser executable.").option("--extension", 'Connect to a running browser instance (Edge/Chrome only). Requires the "Playwright MCP Bridge" browser extension to be installed.').option("--headless", "run browser in headless mode, headed by default").option("--host <host>", "host to bind server to. Default is localhost. Use 0.0.0.0 to bind to all interfaces.").option("--ignore-https-errors", "ignore https errors").option("--isolated", "keep the browser profile in memory, do not save it to disk.").option("--image-responses <mode>", 'whether to send image responses to the client. Can be "allow" or "omit", Defaults to "allow".').option("--no-sandbox", "disable the sandbox for all process types that are normally sandboxed.").option("--output-dir <path>", "path to the directory for output files.").option("--port <port>", "port to listen on for SSE transport.").option("--proxy-bypass <bypass>", 'comma-separated domains to bypass proxy, for example ".com,chromium.org,.domain.com"').option("--proxy-server <proxy>", 'specify proxy server, for example "http://myproxy:3128" or "socks5://myproxy:8080"').option("--save-session", "Whether to save the Playwright MCP session into the output directory.").option("--save-trace", "Whether to save the Playwright Trace of the session into the output directory.").option("--secrets <path>", "path to a file containing secrets in the dotenv format", import_config.dotenvFileLoader).option("--storage-state <path>", "path to the storage state file for isolated sessions.").option("--timeout-action <timeout>", "specify action timeout in milliseconds, defaults to 5000ms", import_config.numberParser).option("--timeout-navigation <timeout>", "specify navigation timeout in milliseconds, defaults to 60000ms", import_config.numberParser).option("--user-agent <ua string>", "specify user agent string").option("--user-data-dir <path>", "path to the user data directory. If not specified, a temporary directory will be created.").option("--viewport-size <size>", 'specify browser viewport size in pixels, for example "1280, 720"').addOption(new import_utilsBundle.ProgramOption("--connect-tool", "Allow to switch between different browser connection methods.").hideHelp()).addOption(new import_utilsBundle.ProgramOption("--vscode", "VS Code tools.").hideHelp()).addOption(new import_utilsBundle.ProgramOption("--vision", "Legacy option, use --caps=vision instead").hideHelp()).action(async (options) => {
44
45
  setupExitWatchdog();
45
46
  if (options.vision) {
46
47
  console.error("The --vision option is deprecated, use --caps=vision instead");
@@ -59,6 +60,10 @@ function decorateCommand(command, version) {
59
60
  await mcpServer.start(serverBackendFactory, config.server);
60
61
  return;
61
62
  }
63
+ if (options.vscode) {
64
+ await (0, import_host.runVSCodeTools)(config);
65
+ return;
66
+ }
62
67
  if (options.connectTool) {
63
68
  const providers = [
64
69
  {
@@ -150,7 +150,7 @@ async function runMainBackend(backendFactory, options) {
150
150
  return url;
151
151
  await mcpServer.connect(factory, new mcpBundle.StdioServerTransport(), false);
152
152
  }
153
- async function runOnPauseBackendLoop(mdbUrl, backend, introMessage) {
153
+ async function runOnPauseBackendLoop(backend, introMessage) {
154
154
  const wrappedBackend = new OnceTimeServerBackendWrapper(backend);
155
155
  const factory = {
156
156
  name: "on-pause-backend",
@@ -163,7 +163,7 @@ async function runOnPauseBackendLoop(mdbUrl, backend, introMessage) {
163
163
  const url = mcpHttp.httpAddressToString(httpServer.address());
164
164
  const client = new mcpBundle.Client({ name: "Internal client", version: "0.0.0" });
165
165
  client.setRequestHandler(mcpBundle.PingRequestSchema, () => ({}));
166
- const transport = new mcpBundle.StreamableHTTPClientTransport(new URL(mdbUrl));
166
+ const transport = new mcpBundle.StreamableHTTPClientTransport(new URL(process.env.PLAYWRIGHT_DEBUGGER_MCP));
167
167
  await client.connect(transport);
168
168
  const pushToolsResult = await client.callTool({
169
169
  name: pushToolsSchema.name,
@@ -34,6 +34,7 @@ __export(browserBackend_exports, {
34
34
  module.exports = __toCommonJS(browserBackend_exports);
35
35
  var mcp = __toESM(require("../sdk/exports"));
36
36
  var mcpBundle = __toESM(require("../sdk/bundle"));
37
+ var import_globals = require("../../common/globals");
37
38
  var import_browserTools = require("./browserTools");
38
39
  var import_util = require("../../util");
39
40
  const tools = [import_browserTools.snapshot, import_browserTools.pickLocator, import_browserTools.evaluate];
@@ -71,7 +72,8 @@ const doneToolSchema = mcp.defineToolSchema({
71
72
  type: "destructive"
72
73
  });
73
74
  async function runBrowserBackendOnError(page, message) {
74
- if (!process.env.PLAYWRIGHT_DEBUGGER_ENABLED)
75
+ const testInfo = (0, import_globals.currentTestInfo)();
76
+ if (!testInfo || !testInfo._pauseOnError())
75
77
  return;
76
78
  const snapshot2 = await page._snapshotForAI();
77
79
  const introMessage = `### Paused on error:
@@ -82,7 +84,7 @@ ${snapshot2}
82
84
 
83
85
  ### Task
84
86
  Try recovering from the error prior to continuing, use following tools to recover: ${tools.map((tool) => tool.schema.name).join(", ")}`;
85
- await mcp.runOnPauseBackendLoop(process.env.PLAYWRIGHT_MDB_URL, new BrowserBackend(page), introMessage);
87
+ await mcp.runOnPauseBackendLoop(new BrowserBackend(page), introMessage);
86
88
  }
87
89
  // Annotate the CommonJS export names for ESM import in node:
88
90
  0 && (module.exports = {
@@ -64,7 +64,7 @@ const runTests = (0, import_testTool.defineTestTool)({
64
64
  title: "Run tests",
65
65
  description: "Run tests",
66
66
  inputSchema: import_bundle.z.object({
67
- locations: import_bundle.z.array(import_bundle.z.string()).describe('Folder, file or location to run: "test/e2e" or "test/e2e/file.spec.ts" or "test/e2e/file.spec.ts:20"'),
67
+ locations: import_bundle.z.array(import_bundle.z.string()).optional().describe('Folder, file or location to run: "test/e2e" or "test/e2e/file.spec.ts" or "test/e2e/file.spec.ts:20"'),
68
68
  projects: import_bundle.z.array(import_bundle.z.string()).optional().describe('Projects to run, projects from playwright.config.ts, by default runs all projects. Running with "chromium" is a good start')
69
69
  }),
70
70
  type: "readOnly"
@@ -112,15 +112,13 @@ const debugTest = (0, import_testTool.defineTestTool)({
112
112
  const configDir = context.configLocation.configDir;
113
113
  const reporter = new import_list.default({ configDir, screen });
114
114
  const testRunner = await context.createTestRunner();
115
- process.env.PLAYWRIGHT_DEBUGGER_ENABLED = "1";
116
115
  const result = await testRunner.runTests(reporter, {
117
- headed: true,
116
+ headed: !context.options?.headless,
118
117
  testIds: [params.test.id],
119
118
  // For automatic recovery
120
119
  timeout: 0,
121
- workers: 1
122
- }).finally(() => {
123
- process.env.PLAYWRIGHT_DEBUGGER_ENABLED = void 0;
120
+ workers: 1,
121
+ pauseOnError: true
124
122
  });
125
123
  const text = stream.content();
126
124
  return {
@@ -0,0 +1,189 @@
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 host_exports = {};
30
+ __export(host_exports, {
31
+ runVSCodeTools: () => runVSCodeTools
32
+ });
33
+ module.exports = __toCommonJS(host_exports);
34
+ var import_path = __toESM(require("path"));
35
+ var mcpBundle = __toESM(require("../sdk/bundle"));
36
+ var mcpServer = __toESM(require("../sdk/server"));
37
+ var import_log = require("../log");
38
+ var import_browserServerBackend = require("../browser/browserServerBackend");
39
+ var import_browserContextFactory = require("../browser/browserContextFactory");
40
+ const packageJSON = require("../../../package.json");
41
+ const { z, zodToJsonSchema } = mcpBundle;
42
+ const contextSwitchOptions = z.object({
43
+ connectionString: z.string().optional().describe("The connection string to use to connect to the browser"),
44
+ lib: z.string().optional().describe("The library to use for the connection"),
45
+ debugController: z.boolean().optional().describe("Enable the debug controller")
46
+ });
47
+ class VSCodeProxyBackend {
48
+ constructor(_config, _defaultTransportFactory) {
49
+ this._config = _config;
50
+ this._defaultTransportFactory = _defaultTransportFactory;
51
+ this.name = "Playwright MCP Client Switcher";
52
+ this.version = packageJSON.version;
53
+ this._roots = [];
54
+ this._contextSwitchTool = this._defineContextSwitchTool();
55
+ }
56
+ async initialize(server, clientVersion, roots) {
57
+ this._clientVersion = clientVersion;
58
+ this._roots = roots;
59
+ const transport = await this._defaultTransportFactory(this);
60
+ await this._setCurrentClient(transport);
61
+ }
62
+ async listTools() {
63
+ const response = await this._currentClient.listTools();
64
+ return [
65
+ ...response.tools,
66
+ this._contextSwitchTool
67
+ ];
68
+ }
69
+ async callTool(name, args) {
70
+ if (name === this._contextSwitchTool.name)
71
+ return this._callContextSwitchTool(args);
72
+ return await this._currentClient.callTool({
73
+ name,
74
+ arguments: args
75
+ });
76
+ }
77
+ serverClosed(server) {
78
+ void this._currentClient?.close().catch(import_log.logUnhandledError);
79
+ }
80
+ onContext(context) {
81
+ this._context = context;
82
+ context.on("close", () => {
83
+ this._context = void 0;
84
+ });
85
+ }
86
+ async _getDebugControllerURL() {
87
+ if (!this._context)
88
+ return;
89
+ const browser = this._context.browser();
90
+ if (!browser || !browser._launchServer)
91
+ return;
92
+ if (this._browser !== browser)
93
+ this._browserServer = void 0;
94
+ if (!this._browserServer)
95
+ this._browserServer = await browser._launchServer({ _debugController: true });
96
+ const url = new URL(this._browserServer.wsEndpoint());
97
+ url.searchParams.set("debug-controller", "1");
98
+ return url.toString();
99
+ }
100
+ async _callContextSwitchTool(params) {
101
+ if (params.debugController) {
102
+ const url = await this._getDebugControllerURL();
103
+ const lines = [`### Result`];
104
+ if (url) {
105
+ lines.push(`URL: ${url}`);
106
+ lines.push(`Version: ${packageJSON.version}`);
107
+ } else {
108
+ lines.push(`No open browsers.`);
109
+ }
110
+ return { content: [{ type: "text", text: lines.join("\n") }] };
111
+ }
112
+ if (!params.connectionString || !params.lib) {
113
+ const transport = await this._defaultTransportFactory(this);
114
+ await this._setCurrentClient(transport);
115
+ return {
116
+ content: [{ type: "text", text: "### Result\nSuccessfully disconnected.\n" }]
117
+ };
118
+ }
119
+ await this._setCurrentClient(
120
+ new mcpBundle.StdioClientTransport({
121
+ command: process.execPath,
122
+ cwd: process.cwd(),
123
+ args: [
124
+ import_path.default.join(__dirname, "main.js"),
125
+ JSON.stringify(this._config),
126
+ params.connectionString,
127
+ params.lib
128
+ ]
129
+ })
130
+ );
131
+ return {
132
+ content: [{ type: "text", text: "### Result\nSuccessfully connected.\n" }]
133
+ };
134
+ }
135
+ _defineContextSwitchTool() {
136
+ return {
137
+ name: "browser_connect",
138
+ description: "Do not call, this tool is used in the integration with the Playwright VS Code Extension and meant for programmatic usage only.",
139
+ inputSchema: zodToJsonSchema(contextSwitchOptions, { strictUnions: true }),
140
+ annotations: {
141
+ title: "Connect to a browser running in VS Code.",
142
+ readOnlyHint: true,
143
+ openWorldHint: false
144
+ }
145
+ };
146
+ }
147
+ async _setCurrentClient(transport) {
148
+ await this._currentClient?.close();
149
+ this._currentClient = void 0;
150
+ const client = new mcpBundle.Client(this._clientVersion);
151
+ client.registerCapabilities({
152
+ roots: {
153
+ listRoots: true
154
+ }
155
+ });
156
+ client.setRequestHandler(mcpBundle.ListRootsRequestSchema, () => ({ roots: this._roots }));
157
+ client.setRequestHandler(mcpBundle.PingRequestSchema, () => ({}));
158
+ await client.connect(transport);
159
+ this._currentClient = client;
160
+ }
161
+ }
162
+ async function runVSCodeTools(config) {
163
+ const serverBackendFactory = {
164
+ name: "Playwright w/ vscode",
165
+ nameInConfig: "playwright-vscode",
166
+ version: packageJSON.version,
167
+ create: () => new VSCodeProxyBackend(
168
+ config,
169
+ (delegate) => mcpServer.wrapInProcess(
170
+ new import_browserServerBackend.BrowserServerBackend(
171
+ config,
172
+ {
173
+ async createContext(clientInfo, abortSignal, toolName) {
174
+ const context = await (0, import_browserContextFactory.contextFactory)(config).createContext(clientInfo, abortSignal, toolName);
175
+ delegate.onContext(context.browserContext);
176
+ return context;
177
+ }
178
+ }
179
+ )
180
+ )
181
+ )
182
+ };
183
+ await mcpServer.start(serverBackendFactory, config.server);
184
+ return;
185
+ }
186
+ // Annotate the CommonJS export names for ESM import in node:
187
+ 0 && (module.exports = {
188
+ runVSCodeTools
189
+ });
@@ -0,0 +1,77 @@
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 mcpBundle = __toESM(require("../sdk/bundle"));
25
+ var mcpServer = __toESM(require("../sdk/server"));
26
+ var import_browserServerBackend = require("../browser/browserServerBackend");
27
+ class VSCodeBrowserContextFactory {
28
+ constructor(_config, _playwright, _connectionString) {
29
+ this._config = _config;
30
+ this._playwright = _playwright;
31
+ this._connectionString = _connectionString;
32
+ this.name = "vscode";
33
+ this.description = "Connect to a browser running in the Playwright VS Code extension";
34
+ }
35
+ async createContext(clientInfo, abortSignal) {
36
+ let launchOptions = this._config.browser.launchOptions;
37
+ if (this._config.browser.userDataDir) {
38
+ launchOptions = {
39
+ ...launchOptions,
40
+ ...this._config.browser.contextOptions,
41
+ userDataDir: this._config.browser.userDataDir
42
+ };
43
+ }
44
+ const connectionString = new URL(this._connectionString);
45
+ connectionString.searchParams.set("launch-options", JSON.stringify(launchOptions));
46
+ const browserType = this._playwright.chromium;
47
+ const browser = await browserType.connect(connectionString.toString());
48
+ const context = browser.contexts()[0] ?? await browser.newContext(this._config.browser.contextOptions);
49
+ return {
50
+ browserContext: context,
51
+ close: async () => {
52
+ await browser.close();
53
+ }
54
+ };
55
+ }
56
+ }
57
+ async function main(config, connectionString, lib) {
58
+ const playwright = await import(lib).then((mod) => mod.default ?? mod);
59
+ const factory = new VSCodeBrowserContextFactory(config, playwright, connectionString);
60
+ await mcpServer.connect(
61
+ {
62
+ name: "Playwright MCP",
63
+ nameInConfig: "playwright-vscode",
64
+ create: () => new import_browserServerBackend.BrowserServerBackend(config, factory),
65
+ version: "unused"
66
+ },
67
+ new mcpBundle.StdioServerTransport(),
68
+ false
69
+ );
70
+ }
71
+ void (async () => {
72
+ await main(
73
+ JSON.parse(process.argv[2]),
74
+ process.argv[3],
75
+ process.argv[4]
76
+ );
77
+ })();
package/lib/program.js CHANGED
@@ -151,6 +151,7 @@ function addBrowserMCPServerCommand(program3) {
151
151
  function addTestMCPServerCommand(program3) {
152
152
  const command = program3.command("run-test-mcp-server", { hidden: true });
153
153
  command.description("Interact with the test runner over MCP");
154
+ command.option("--headless", "run browser in headless mode, headed by default");
154
155
  command.option("-c, --config <file>", `Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}"`);
155
156
  command.option("--host <host>", "host to bind server to. Default is localhost. Use 0.0.0.0 to bind to all interfaces.");
156
157
  command.option("--port <port>", "port to listen on for SSE transport.");
@@ -160,7 +161,7 @@ function addTestMCPServerCommand(program3) {
160
161
  name: "Playwright Test Runner",
161
162
  nameInConfig: "playwright-test-runner",
162
163
  version: packageJSON.version,
163
- create: () => new import_testBackend.TestServerBackend(resolvedLocation, { muteConsole: options.port === void 0 })
164
+ create: () => new import_testBackend.TestServerBackend(resolvedLocation, { muteConsole: options.port === void 0, headless: options.headless })
164
165
  };
165
166
  const mdbUrl = await (0, import_exports.runMainBackend)(backendFactory, { port: options.port === void 0 ? void 0 : +options.port });
166
167
  if (mdbUrl)
@@ -158,7 +158,7 @@ class Dispatcher {
158
158
  _createWorker(testGroup, parallelIndex, loaderData) {
159
159
  const projectConfig = this._config.projects.find((p) => p.id === testGroup.projectId);
160
160
  const outputDir = projectConfig.project.outputDir;
161
- const worker = new import_workerHost.WorkerHost(testGroup, parallelIndex, loaderData, this._extraEnvByProjectId.get(testGroup.projectId) || {}, outputDir);
161
+ const worker = new import_workerHost.WorkerHost(testGroup, parallelIndex, loaderData, this._extraEnvByProjectId.get(testGroup.projectId) || {}, outputDir, this._failureTracker.pauseOnError());
162
162
  const handleOutput = (params) => {
163
163
  const chunk = chunkFromParams(params);
164
164
  if (worker.didFail()) {
@@ -363,7 +363,7 @@ class JobDispatcher {
363
363
  }
364
364
  }
365
365
  _onDone(params) {
366
- if (!this._remainingByTestId.size && !this._failedTests.size && !params.fatalErrors.length && !params.skipTestsDueToSetupFailure.length && !params.fatalUnknownTestIds && !params.unexpectedExitError) {
366
+ if (!this._remainingByTestId.size && !this._failedTests.size && !params.fatalErrors.length && !params.skipTestsDueToSetupFailure.length && !params.fatalUnknownTestIds && !params.unexpectedExitError && !params.stoppedDueToUnhandledErrorInTestFail) {
367
367
  this._finished({ didFail: false });
368
368
  return;
369
369
  }
@@ -22,10 +22,11 @@ __export(failureTracker_exports, {
22
22
  });
23
23
  module.exports = __toCommonJS(failureTracker_exports);
24
24
  class FailureTracker {
25
- constructor(_config) {
25
+ constructor(_config, options) {
26
26
  this._config = _config;
27
27
  this._failureCount = 0;
28
28
  this._hasWorkerErrors = false;
29
+ this._pauseOnError = options?.pauseOnError ?? false;
29
30
  }
30
31
  onRootSuite(rootSuite) {
31
32
  this._rootSuite = rootSuite;
@@ -37,6 +38,9 @@ class FailureTracker {
37
38
  onWorkerError() {
38
39
  this._hasWorkerErrors = true;
39
40
  }
41
+ pauseOnError() {
42
+ return this._pauseOnError;
43
+ }
40
44
  hasReachedMaxFailures() {
41
45
  return this.maxFailures() > 0 && this._failureCount >= this.maxFailures();
42
46
  }
@@ -60,14 +60,14 @@ var import_compilationCache = require("../transform/compilationCache");
60
60
  var import_util2 = require("../util");
61
61
  const readDirAsync = (0, import_util.promisify)(import_fs.default.readdir);
62
62
  class TestRun {
63
- constructor(config, reporter) {
63
+ constructor(config, reporter, options) {
64
64
  this.rootSuite = void 0;
65
65
  this.phases = [];
66
66
  this.projectFiles = /* @__PURE__ */ new Map();
67
67
  this.projectSuites = /* @__PURE__ */ new Map();
68
68
  this.config = config;
69
69
  this.reporter = reporter;
70
- this.failureTracker = new import_failureTracker.FailureTracker(config);
70
+ this.failureTracker = new import_failureTracker.FailureTracker(config, options);
71
71
  }
72
72
  }
73
73
  async function runTasks(testRun, tasks, globalTimeout, cancelPromise) {
@@ -270,7 +270,7 @@ class TestRunner extends import_events.default {
270
270
  (0, import_tasks.createLoadTask)("out-of-process", { filterOnly: true, failOnLoadErrors: false, doNotRunDepsOutsideProjectFilter: true }),
271
271
  ...(0, import_tasks.createRunTestsTasks)(config)
272
272
  ];
273
- const testRun = new import_tasks.TestRun(config, reporter);
273
+ const testRun = new import_tasks.TestRun(config, reporter, { pauseOnError: params.pauseOnError });
274
274
  const run = (0, import_tasks.runTasks)(testRun, tasks, 0, stop).then(async (status) => {
275
275
  this._testRun = void 0;
276
276
  return status;
@@ -39,7 +39,7 @@ var import_ipc = require("../common/ipc");
39
39
  var import_folders = require("../isomorphic/folders");
40
40
  let lastWorkerIndex = 0;
41
41
  class WorkerHost extends import_processHost.ProcessHost {
42
- constructor(testGroup, parallelIndex, config, extraEnv, outputDir) {
42
+ constructor(testGroup, parallelIndex, config, extraEnv, outputDir, pauseOnError) {
43
43
  const workerIndex = lastWorkerIndex++;
44
44
  super(require.resolve("../worker/workerMain.js"), `worker-${workerIndex}`, {
45
45
  ...extraEnv,
@@ -56,7 +56,8 @@ class WorkerHost extends import_processHost.ProcessHost {
56
56
  repeatEachIndex: testGroup.repeatEachIndex,
57
57
  projectId: testGroup.projectId,
58
58
  config,
59
- artifactsDir: import_path.default.join(outputDir, (0, import_folders.artifactsFolderName)(workerIndex))
59
+ artifactsDir: import_path.default.join(outputDir, (0, import_folders.artifactsFolderName)(workerIndex)),
60
+ pauseOnError
60
61
  };
61
62
  }
62
63
  async start() {
@@ -67,6 +67,7 @@ class TestInfoImpl {
67
67
  this._startWallTime = Date.now();
68
68
  this._requireFile = test?._requireFile ?? "";
69
69
  this._uniqueSymbol = Symbol("testInfoUniqueSymbol");
70
+ this._workerParams = workerParams;
70
71
  this.repeatEachIndex = workerParams.repeatEachIndex;
71
72
  this.retry = retry;
72
73
  this.workerIndex = workerParams.workerIndex;
@@ -443,6 +444,9 @@ ${(0, import_utils.stringifyStackFrames)(step.boxedStack).join("\n")}`;
443
444
  setTimeout(timeout) {
444
445
  this._timeoutManager.setTimeout(timeout);
445
446
  }
447
+ _pauseOnError() {
448
+ return this._workerParams.pauseOnError;
449
+ }
446
450
  }
447
451
  class TestStepInfoImpl {
448
452
  constructor(testInfo, stepId, title, parentStep) {
@@ -43,6 +43,9 @@ class WorkerMain extends import_process.ProcessRunner {
43
43
  this._fatalErrors = [];
44
44
  // The stage of the full cleanup. Once "finished", we can safely stop running anything.
45
45
  this._didRunFullCleanup = false;
46
+ // Whether the worker was stopped due to an unhandled error in a test marked with test.fail().
47
+ // This should force dispatcher to use a new worker instead.
48
+ this._stoppedDueToUnhandledErrorInTestFail = false;
46
49
  // Whether the worker was requested to stop.
47
50
  this._isStopped = false;
48
51
  // This promise resolves once the single "run test group" call finishes.
@@ -154,8 +157,10 @@ class WorkerMain extends import_process.ProcessRunner {
154
157
  }
155
158
  const isExpectError = error instanceof Error && !!error.matcherResult;
156
159
  const shouldContinueInThisWorker = this._currentTest.expectedStatus === "failed" && isExpectError;
157
- if (!shouldContinueInThisWorker)
160
+ if (!shouldContinueInThisWorker) {
161
+ this._stoppedDueToUnhandledErrorInTestFail = true;
158
162
  void this._stop();
163
+ }
159
164
  }
160
165
  async _loadIfNeeded() {
161
166
  if (this._config)
@@ -204,7 +209,8 @@ class WorkerMain extends import_process.ProcessRunner {
204
209
  const donePayload = {
205
210
  fatalErrors: this._fatalErrors,
206
211
  skipTestsDueToSetupFailure: [],
207
- fatalUnknownTestIds
212
+ fatalUnknownTestIds,
213
+ stoppedDueToUnhandledErrorInTestFail: this._stoppedDueToUnhandledErrorInTestFail
208
214
  };
209
215
  for (const test of this._skipRemainingTestsInSuite?.allTests() || []) {
210
216
  if (entries.has(test.id))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "playwright",
3
- "version": "1.56.0-alpha-2025-09-08",
3
+ "version": "1.56.0-alpha-1757456950000",
4
4
  "description": "A high-level API to automate web browsers",
5
5
  "repository": {
6
6
  "type": "git",
@@ -64,7 +64,7 @@
64
64
  },
65
65
  "license": "Apache-2.0",
66
66
  "dependencies": {
67
- "playwright-core": "1.56.0-alpha-2025-09-08"
67
+ "playwright-core": "1.56.0-alpha-1757456950000"
68
68
  },
69
69
  "optionalDependencies": {
70
70
  "fsevents": "2.3.2"