playwright 1.56.0-alpha-2025-09-11 → 1.56.0-alpha-2025-09-12

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-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-142.0.1-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.16-blue.svg?logo=google-chrome)](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[![Firefox version](https://img.shields.io/badge/firefox-142.0.1-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 -->141.0.7390.7<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
11
+ | Chromium <!-- GEN:chromium-version -->141.0.7390.16<!-- 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 -->142.0.1<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
14
14
 
@@ -9,18 +9,18 @@ tools:
9
9
  - read
10
10
  - write
11
11
  - edit
12
- - playwright-test/browser_evaluate
13
- - playwright-test/browser_generate_locator
14
- - playwright-test/browser_snapshot
15
- - playwright-test/playwright_test_debug_test
16
- - playwright-test/playwright_test_list_tests
17
- - playwright-test/playwright_test_run_tests
12
+ - playwright/browser_evaluate
13
+ - playwright/browser_generate_locator
14
+ - playwright/browser_snapshot
15
+ - playwright/test_debug
16
+ - playwright/test_list
17
+ - playwright/test_run
18
+ - playwright/test_setup_page
18
19
  mcp-servers:
19
- playwright-test:
20
+ playwright:
20
21
  type: 'local'
21
22
  command: 'npx'
22
23
  args: ['playwright', 'run-test-mcp-server']
23
- tools: ['*']
24
24
  ---
25
25
 
26
26
  You are the Playwright Test Fixer, an expert test automation engineer specializing in debugging and
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  name: playwright-test-generator
3
3
  description: Use this agent when you need to create automated browser tests using Playwright
4
- color: blue
5
4
  model: sonnet
5
+ color: blue
6
6
  tools:
7
7
  - ls
8
8
  - grep
@@ -24,16 +24,12 @@ tools:
24
24
  - playwright/browser_verify_text_visible
25
25
  - playwright/browser_verify_value
26
26
  - playwright/browser_wait_for
27
+ - playwright/test_setup_page
27
28
  mcp-servers:
28
29
  playwright:
29
30
  type: 'local'
30
31
  command: 'npx'
31
- args:
32
- - 'playwright'
33
- - 'run-mcp-server'
34
- - '--isolated'
35
- - '--viewport-size=1280,720'
36
- - '--caps=testing'
32
+ args: ['playwright', 'run-test-mcp-server']
37
33
  ---
38
34
 
39
35
  You are a Playwright Test Generator, an expert in browser automation and end-to-end testing. Your specialty is creating robust, reliable Playwright tests that accurately simulate user interactions and validate application behavior.
@@ -42,7 +38,9 @@ Your process is methodical and thorough:
42
38
 
43
39
  1. **Scenario Analysis**: Carefully analyze the test scenario provided, identifying all user actions, expected outcomes, and validation points. Break down complex flows into discrete, testable steps.
44
40
 
45
- 2. **Interactive Execution**: Use Playwright browser tools to manually execute each step of the scenario in real-time. This allows you to:
41
+ 2. **Interactive Execution**:
42
+ - For each test, start with the `test_setup_page` tool to set up page for the scenario
43
+ - Use Playwright tools to manually execute each step of the scenario in real-time
46
44
  - Verify that each action works as expected
47
45
  - Identify the correct locators and interaction patterns
48
46
  - Observe actual application behavior and responses
@@ -25,22 +25,22 @@ tools:
25
25
  - playwright/browser_take_screenshot
26
26
  - playwright/browser_type
27
27
  - playwright/browser_wait_for
28
+ - playwright/test_setup_page
28
29
  mcp-servers:
29
30
  playwright:
30
31
  type: 'local'
31
32
  command: 'npx'
32
- args:
33
- - 'playwright'
34
- - 'run-mcp-server'
35
- - '--isolated'
36
- - '--viewport-size=1280,720'
33
+ args: ['playwright', 'run-test-mcp-server']
37
34
  ---
38
35
 
39
36
  You are an expert web test planner with extensive experience in quality assurance, user experience testing, and test scenario design. Your expertise includes functional testing, usability testing, edge case identification, and comprehensive test coverage planning.
40
37
 
41
38
  When given a target web page or application, you will:
42
39
 
43
- 1. **Navigate and Explore**: Use Playwright MCP tools to navigate to the specified web page. Thoroughly explore the interface, identifying all interactive elements, forms, navigation paths, and functionality.
40
+ 1. **Navigate and Explore**:
41
+ - Invoke the `test_setup_page` tool once to set up page before using any other tools
42
+ - Explore the aria snapshot, use browser_* tools to navigate and discover interface.
43
+ - Thoroughly explore the interface, identifying all interactive elements, forms, navigation paths, and functionality
44
44
 
45
45
  2. **Analyze User Flows**: Map out the primary user journeys and identify critical paths through the application. Consider different user types and their typical behaviors.
46
46
 
package/lib/index.js CHANGED
@@ -399,6 +399,7 @@ const playwrightFixtures = {
399
399
  if (!_reuseContext) {
400
400
  const { context: context2, close } = await _contextFactory();
401
401
  await use(context2);
402
+ await (0, import_browserBackend.runBrowserBackendAtEnd)(context2);
402
403
  await close();
403
404
  return;
404
405
  }
@@ -38,6 +38,7 @@ var import_path = __toESM(require("path"));
38
38
  var playwright = __toESM(require("playwright-core"));
39
39
  var import_registry = require("playwright-core/lib/server/registry/index");
40
40
  var import_server = require("playwright-core/lib/server");
41
+ var import_processUtils = require("./processUtils");
41
42
  var import_log = require("../log");
42
43
  var import_config = require("./config");
43
44
  function contextFactory(config) {
@@ -160,32 +161,32 @@ class PersistentContextFactory {
160
161
  (0, import_log.testDebug)("lock user data dir", userDataDir);
161
162
  const browserType = playwright[this.config.browser.browserName];
162
163
  for (let i = 0; i < 5; i++) {
163
- const launchOptions = {
164
- tracesDir,
165
- ...this.config.browser.launchOptions,
166
- ...this.config.browser.contextOptions,
167
- handleSIGINT: false,
168
- handleSIGTERM: false,
169
- ignoreDefaultArgs: [
170
- "--disable-extensions"
171
- ],
172
- assistantMode: true
173
- };
174
- try {
175
- const browserContext = await browserType.launchPersistentContext(userDataDir, launchOptions);
176
- const close = () => this._closeBrowserContext(browserContext, userDataDir);
177
- return { browserContext, close };
178
- } catch (error) {
179
- if (error.message.includes("Executable doesn't exist"))
180
- throw new Error(`Browser specified in your config is not installed. Either install it (likely) or change the config.`);
181
- if (error.message.includes("ProcessSingleton") || error.message.includes("Invalid URL")) {
182
- await new Promise((resolve) => setTimeout(resolve, 1e3));
183
- continue;
184
- }
185
- throw error;
186
- }
164
+ if (!await alreadyRunning(this.config, browserType, userDataDir))
165
+ break;
166
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
167
+ }
168
+ const launchOptions = {
169
+ tracesDir,
170
+ ...this.config.browser.launchOptions,
171
+ ...this.config.browser.contextOptions,
172
+ handleSIGINT: false,
173
+ handleSIGTERM: false,
174
+ ignoreDefaultArgs: [
175
+ "--disable-extensions"
176
+ ],
177
+ assistantMode: true
178
+ };
179
+ try {
180
+ const browserContext = await browserType.launchPersistentContext(userDataDir, launchOptions);
181
+ const close = () => this._closeBrowserContext(browserContext, userDataDir);
182
+ return { browserContext, close };
183
+ } catch (error) {
184
+ if (error.message.includes("Executable doesn't exist"))
185
+ throw new Error(`Browser specified in your config is not installed. Either install it (likely) or change the config.`);
186
+ if (error.message.includes("ProcessSingleton") || error.message.includes("Invalid URL"))
187
+ throw new Error(`Browser is already in use for ${userDataDir}, use --isolated to run multiple instances of the same browser`);
188
+ throw error;
187
189
  }
188
- throw new Error(`Browser is already in use for ${userDataDir}, use --isolated to run multiple instances of the same browser`);
189
190
  }
190
191
  async _closeBrowserContext(browserContext, userDataDir) {
191
192
  (0, import_log.testDebug)("close browser context (persistent)");
@@ -204,6 +205,12 @@ class PersistentContextFactory {
204
205
  return result;
205
206
  }
206
207
  }
208
+ async function alreadyRunning(config, browserType, userDataDir) {
209
+ const execPath = config.browser.launchOptions.executablePath ?? (0, import_processUtils.getBrowserExecPath)(config.browser.launchOptions.channel ?? browserType.name());
210
+ if (!execPath)
211
+ return false;
212
+ return !!(0, import_processUtils.findBrowserProcess)(execPath, userDataDir);
213
+ }
207
214
  async function injectCdpPort(browserConfig) {
208
215
  if (browserConfig.browserName === "chromium")
209
216
  browserConfig.launchOptions.cdpPort = await findFreePort();
@@ -0,0 +1,102 @@
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 processUtils_exports = {};
30
+ __export(processUtils_exports, {
31
+ findBrowserProcess: () => findBrowserProcess,
32
+ getBrowserExecPath: () => getBrowserExecPath
33
+ });
34
+ module.exports = __toCommonJS(processUtils_exports);
35
+ var import_child_process = __toESM(require("child_process"));
36
+ var import_fs = __toESM(require("fs"));
37
+ var import_registry = require("playwright-core/lib/server/registry/index");
38
+ function getBrowserExecPath(channelOrName) {
39
+ return import_registry.registry.findExecutable(channelOrName)?.executablePath("javascript");
40
+ }
41
+ function findBrowserProcess(execPath, arg) {
42
+ const predicate = (line) => line.includes(execPath) && line.includes(arg) && !line.includes("--type");
43
+ try {
44
+ switch (process.platform) {
45
+ case "darwin":
46
+ return findProcessMacos(predicate);
47
+ case "linux":
48
+ return findProcessLinux(predicate);
49
+ case "win32":
50
+ return findProcessWindows(execPath, arg, predicate);
51
+ default:
52
+ return void 0;
53
+ }
54
+ } catch {
55
+ return void 0;
56
+ }
57
+ }
58
+ function findProcessLinux(predicate) {
59
+ const procDirs = import_fs.default.readdirSync("/proc").filter((name) => /^\d+$/.test(name));
60
+ for (const pid of procDirs) {
61
+ try {
62
+ const cmdlineBuffer = import_fs.default.readFileSync(`/proc/${pid}/cmdline`);
63
+ const cmdline = cmdlineBuffer.toString().replace(/\0/g, " ").trim();
64
+ if (predicate(cmdline))
65
+ return `${pid} ${cmdline}`;
66
+ } catch {
67
+ continue;
68
+ }
69
+ }
70
+ return void 0;
71
+ }
72
+ function findProcessMacos(predicate) {
73
+ const result = import_child_process.default.spawnSync("/bin/ps", ["-axo", "pid=,command="]);
74
+ if (result.status !== 0 || !result.stdout)
75
+ return void 0;
76
+ return findMatchingLine(result.stdout.toString(), predicate);
77
+ }
78
+ function findProcessWindows(execPath, arg, predicate) {
79
+ const psEscape = (path) => `'${path.replaceAll("'", "''")}'`;
80
+ const filter = `$_.ExecutablePath -eq ${psEscape(execPath)} -and $_.CommandLine.Contains(${psEscape(arg)}) -and $_.CommandLine -notmatch '--type'`;
81
+ const ps = import_child_process.default.spawnSync(
82
+ "powershell.exe",
83
+ [
84
+ "-NoProfile",
85
+ "-Command",
86
+ `Get-CimInstance Win32_Process | Where-Object { ${filter} } | Select-Object -Property ProcessId,CommandLine | ForEach-Object { "$($_.ProcessId) $($_.CommandLine)" }`
87
+ ],
88
+ { encoding: "utf8" }
89
+ );
90
+ if (ps.status !== 0 || !ps.stdout)
91
+ return void 0;
92
+ return findMatchingLine(ps.stdout.toString(), predicate);
93
+ }
94
+ function findMatchingLine(psOutput, predicate) {
95
+ const lines = psOutput.split("\n").map((l) => l.trim()).filter(Boolean);
96
+ return lines.find(predicate);
97
+ }
98
+ // Annotate the CommonJS export names for ESM import in node:
99
+ 0 && (module.exports = {
100
+ findBrowserProcess,
101
+ getBrowserExecPath
102
+ });
@@ -0,0 +1,42 @@
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 watchdog_exports = {};
20
+ __export(watchdog_exports, {
21
+ setupExitWatchdog: () => setupExitWatchdog
22
+ });
23
+ module.exports = __toCommonJS(watchdog_exports);
24
+ var import_context = require("./context");
25
+ function setupExitWatchdog() {
26
+ let isExiting = false;
27
+ const handleExit = async () => {
28
+ if (isExiting)
29
+ return;
30
+ isExiting = true;
31
+ setTimeout(() => process.exit(0), 15e3);
32
+ await import_context.Context.disposeAll();
33
+ process.exit(0);
34
+ };
35
+ process.stdin.on("close", handleExit);
36
+ process.on("SIGINT", handleExit);
37
+ process.on("SIGTERM", handleExit);
38
+ }
39
+ // Annotate the CommonJS export names for ESM import in node:
40
+ 0 && (module.exports = {
41
+ setupExitWatchdog
42
+ });
@@ -34,7 +34,7 @@ module.exports = __toCommonJS(program_exports);
34
34
  var import_utilsBundle = require("playwright-core/lib/utilsBundle");
35
35
  var mcpServer = __toESM(require("./sdk/server"));
36
36
  var import_config = require("./browser/config");
37
- var import_context = require("./browser/context");
37
+ var import_watchdog = require("./browser/watchdog");
38
38
  var import_browserContextFactory = require("./browser/browserContextFactory");
39
39
  var import_proxyBackend = require("./sdk/proxyBackend");
40
40
  var import_browserServerBackend = require("./browser/browserServerBackend");
@@ -42,7 +42,7 @@ var import_extensionContextFactory = require("./extension/extensionContextFactor
42
42
  var import_host = require("./vscode/host");
43
43
  function decorateCommand(command, version) {
44
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) => {
45
- setupExitWatchdog();
45
+ (0, import_watchdog.setupExitWatchdog)();
46
46
  if (options.vision) {
47
47
  console.error("The --vision option is deprecated, use --caps=vision instead");
48
48
  options.caps = "vision";
@@ -95,20 +95,6 @@ function decorateCommand(command, version) {
95
95
  await mcpServer.start(factory, config.server);
96
96
  });
97
97
  }
98
- function setupExitWatchdog() {
99
- let isExiting = false;
100
- const handleExit = async () => {
101
- if (isExiting)
102
- return;
103
- isExiting = true;
104
- setTimeout(() => process.exit(0), 15e3);
105
- await import_context.Context.disposeAll();
106
- process.exit(0);
107
- };
108
- process.stdin.on("close", handleExit);
109
- process.on("SIGINT", handleExit);
110
- process.on("SIGTERM", handleExit);
111
- }
112
98
  // Annotate the CommonJS export names for ESM import in node:
113
99
  0 && (module.exports = {
114
100
  decorateCommand
@@ -46,21 +46,19 @@ const z = mcpBundle.z;
46
46
  class MDBBackend {
47
47
  constructor(topLevelBackend) {
48
48
  this._stack = [];
49
- this._initialized = false;
50
49
  this._topLevelBackend = topLevelBackend;
51
50
  }
52
- async initialize(server) {
53
- if (this._initialized)
54
- return;
55
- this._initialized = true;
56
- const transport = await (0, import_server.wrapInProcess)(this._topLevelBackend);
57
- await this._pushClient(transport);
51
+ async initialize(server, clientVersion, roots) {
52
+ if (!this._roots)
53
+ this._roots = roots;
58
54
  }
59
55
  async listTools() {
60
- const response = await this._client().listTools();
56
+ const client = await this._client();
57
+ const response = await client.listTools();
61
58
  return response.tools;
62
59
  }
63
60
  async callTool(name, args) {
61
+ await this._client();
64
62
  if (name === pushToolsSchema.name)
65
63
  return await this._pushTools(pushToolsSchema.inputSchema.parse(args || {}));
66
64
  const interruptPromise = new import_utils.ManualPromise();
@@ -69,26 +67,20 @@ class MDBBackend {
69
67
  while (entry && !entry.toolNames.includes(name)) {
70
68
  mdbDebug("popping client from stack for ", name);
71
69
  this._stack.shift();
72
- await entry.client.close();
70
+ await entry.client.close().catch(errorsDebug);
73
71
  entry = this._stack[0];
74
72
  }
75
73
  if (!entry)
76
74
  throw new Error(`Tool ${name} not found in the tool stack`);
75
+ const client = await this._client();
77
76
  const resultPromise = new import_utils.ManualPromise();
78
77
  entry.resultPromise = resultPromise;
79
- this._client().callTool({
78
+ client.callTool({
80
79
  name,
81
80
  arguments: args
82
81
  }).then((result2) => {
83
82
  resultPromise.resolve(result2);
84
- }).catch((e) => {
85
- mdbDebug("error in client call", e);
86
- if (this._stack.length < 2)
87
- throw e;
88
- this._stack.shift();
89
- const prevEntry = this._stack[0];
90
- void prevEntry.resultPromise.then((result2) => resultPromise.resolve(result2));
91
- });
83
+ }).catch((e) => resultPromise.reject(e));
92
84
  const result = await Promise.race([interruptPromise, resultPromise]);
93
85
  if (interruptPromise.isDone())
94
86
  mdbDebug("client call intercepted", result);
@@ -96,11 +88,12 @@ class MDBBackend {
96
88
  mdbDebug("client call result", result);
97
89
  return result;
98
90
  }
99
- _client() {
100
- const [entry] = this._stack;
101
- if (!entry)
102
- throw new Error("No debugging backend available");
103
- return entry.client;
91
+ async _client() {
92
+ if (!this._stack.length) {
93
+ const transport = await (0, import_server.wrapInProcess)(this._topLevelBackend);
94
+ await this._pushClient(transport);
95
+ }
96
+ return this._stack[0].client;
104
97
  }
105
98
  async _pushTools(params) {
106
99
  mdbDebug("pushing tools to the stack", params.mcpUrl);
@@ -110,7 +103,8 @@ class MDBBackend {
110
103
  }
111
104
  async _pushClient(transport, introMessage) {
112
105
  mdbDebug("pushing client to the stack");
113
- const client = new mcpBundle.Client({ name: "Internal client", version: "0.0.0" });
106
+ const client = new mcpBundle.Client({ name: "Internal client", version: "0.0.0" }, { capabilities: { roots: {} } });
107
+ client.setRequestHandler(mcpBundle.ListRootsRequestSchema, () => ({ roots: this._roots || [] }));
114
108
  client.setRequestHandler(mcpBundle.PingRequestSchema, () => ({}));
115
109
  await client.connect(transport);
116
110
  mdbDebug("connected to the new client");
@@ -151,7 +145,7 @@ async function runMainBackend(backendFactory, options) {
151
145
  await mcpServer.connect(factory, new mcpBundle.StdioServerTransport(), false);
152
146
  }
153
147
  async function runOnPauseBackendLoop(backend, introMessage) {
154
- const wrappedBackend = new OnceTimeServerBackendWrapper(backend);
148
+ const wrappedBackend = new ServerBackendWithCloseListener(backend);
155
149
  const factory = {
156
150
  name: "on-pause-backend",
157
151
  nameInConfig: "on-pause-backend",
@@ -184,11 +178,10 @@ async function startAsHttp(backendFactory, options) {
184
178
  await mcpHttp.installHttpTransport(httpServer, backendFactory);
185
179
  return mcpHttp.httpAddressToString(httpServer.address());
186
180
  }
187
- class OnceTimeServerBackendWrapper {
181
+ class ServerBackendWithCloseListener {
188
182
  constructor(backend) {
189
- this._selfDestructPromise = new import_utils.ManualPromise();
183
+ this._serverClosedPromise = new import_utils.ManualPromise();
190
184
  this._backend = backend;
191
- this._backend.requestSelfDestruct = () => this._selfDestructPromise.resolve();
192
185
  }
193
186
  async initialize(server, clientVersion, roots) {
194
187
  await this._backend.initialize?.(server, clientVersion, roots);
@@ -201,10 +194,10 @@ class OnceTimeServerBackendWrapper {
201
194
  }
202
195
  serverClosed(server) {
203
196
  this._backend.serverClosed?.(server);
204
- this._selfDestructPromise.resolve();
197
+ this._serverClosedPromise.resolve();
205
198
  }
206
199
  async waitForClosed() {
207
- await this._selfDestructPromise;
200
+ await this._serverClosedPromise;
208
201
  }
209
202
  }
210
203
  // Annotate the CommonJS export names for ESM import in node:
@@ -43,10 +43,10 @@ class ProxyBackend {
43
43
  }
44
44
  async initialize(server, clientVersion, roots) {
45
45
  this._roots = roots;
46
- await this._setCurrentClient(this._mcpProviders[0]);
47
46
  }
48
47
  async listTools() {
49
- const response = await this._currentClient.listTools();
48
+ const currentClient = await this._ensureCurrentClient();
49
+ const response = await currentClient.listTools();
50
50
  if (this._mcpProviders.length === 1)
51
51
  return response.tools;
52
52
  return [
@@ -57,7 +57,8 @@ class ProxyBackend {
57
57
  async callTool(name, args) {
58
58
  if (name === this._contextSwitchTool.name)
59
59
  return this._callContextSwitchTool(args);
60
- return await this._currentClient.callTool({
60
+ const currentClient = await this._ensureCurrentClient();
61
+ return await currentClient.callTool({
61
62
  name,
62
63
  arguments: args
63
64
  });
@@ -100,6 +101,11 @@ Error: ${error}
100
101
  }
101
102
  };
102
103
  }
104
+ async _ensureCurrentClient() {
105
+ if (this._currentClient)
106
+ return this._currentClient;
107
+ return await this._setCurrentClient(this._mcpProviders[0]);
108
+ }
103
109
  async _setCurrentClient(factory) {
104
110
  await this._currentClient?.close();
105
111
  this._currentClient = void 0;
@@ -114,6 +120,7 @@ Error: ${error}
114
120
  const transport = await factory.connect();
115
121
  await client.connect(transport);
116
122
  this._currentClient = client;
123
+ return client;
117
124
  }
118
125
  }
119
126
  // Annotate the CommonJS export names for ESM import in node:
@@ -39,7 +39,6 @@ var mcpBundle = __toESM(require("./bundle"));
39
39
  var import_http = require("./http");
40
40
  var import_inProcessTransport = require("./inProcessTransport");
41
41
  const serverDebug = (0, import_utilsBundle.debug)("pw:mcp:server");
42
- const errorsDebug = (0, import_utilsBundle.debug)("pw:mcp:errors");
43
42
  async function connect(factory, transport, runHeartbeat) {
44
43
  const server = createServer(factory.name, factory.version, factory.create(), runHeartbeat);
45
44
  await server.connect(transport);
@@ -49,9 +48,6 @@ async function wrapInProcess(backend) {
49
48
  return new import_inProcessTransport.InProcessTransport(server);
50
49
  }
51
50
  function createServer(name, version, backend, runHeartbeat) {
52
- let initializedPromiseResolve = () => {
53
- };
54
- const initializedPromise = new Promise((resolve) => initializedPromiseResolve = resolve);
55
51
  const server = new mcpBundle.Server({ name, version }, {
56
52
  capabilities: {
57
53
  tools: {}
@@ -59,19 +55,16 @@ function createServer(name, version, backend, runHeartbeat) {
59
55
  });
60
56
  server.setRequestHandler(mcpBundle.ListToolsRequestSchema, async () => {
61
57
  serverDebug("listTools");
62
- await initializedPromise;
63
58
  const tools = await backend.listTools();
64
59
  return { tools };
65
60
  });
66
- let heartbeatRunning = false;
61
+ let initializePromise;
67
62
  server.setRequestHandler(mcpBundle.CallToolRequestSchema, async (request) => {
68
63
  serverDebug("callTool", request);
69
- await initializedPromise;
70
- if (runHeartbeat && !heartbeatRunning) {
71
- heartbeatRunning = true;
72
- startHeartbeat(server);
73
- }
74
64
  try {
65
+ if (!initializePromise)
66
+ initializePromise = initializeServer(server, backend, runHeartbeat);
67
+ await initializePromise;
75
68
  return await backend.callTool(request.params.name, request.params.arguments || {});
76
69
  } catch (error) {
77
70
  return {
@@ -80,30 +73,21 @@ function createServer(name, version, backend, runHeartbeat) {
80
73
  };
81
74
  }
82
75
  });
83
- addServerListener(server, "initialized", async () => {
84
- try {
85
- const capabilities = server.getClientCapabilities();
86
- let clientRoots = [];
87
- if (capabilities?.roots) {
88
- for (let i = 0; i < 2; i++) {
89
- try {
90
- const { roots } = await server.listRoots(void 0, { timeout: 2e3 });
91
- clientRoots = roots;
92
- } catch (e) {
93
- continue;
94
- }
95
- }
96
- }
97
- const clientVersion = server.getClientVersion() ?? { name: "unknown", version: "unknown" };
98
- await backend.initialize?.(server, clientVersion, clientRoots);
99
- initializedPromiseResolve();
100
- } catch (e) {
101
- errorsDebug(e);
102
- }
103
- });
104
76
  addServerListener(server, "close", () => backend.serverClosed?.(server));
105
77
  return server;
106
78
  }
79
+ const initializeServer = async (server, backend, runHeartbeat) => {
80
+ const capabilities = server.getClientCapabilities();
81
+ let clientRoots = [];
82
+ if (capabilities?.roots) {
83
+ const { roots } = await server.listRoots();
84
+ clientRoots = roots;
85
+ }
86
+ const clientVersion = server.getClientVersion() ?? { name: "unknown", version: "unknown" };
87
+ await backend.initialize?.(server, clientVersion, clientRoots);
88
+ if (runHeartbeat)
89
+ startHeartbeat(server);
90
+ };
107
91
  const startHeartbeat = (server) => {
108
92
  const beat = () => {
109
93
  Promise.race([
@@ -28,6 +28,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
29
  var browserBackend_exports = {};
30
30
  __export(browserBackend_exports, {
31
+ runBrowserBackendAtEnd: () => runBrowserBackendAtEnd,
31
32
  runBrowserBackendOnError: () => runBrowserBackendOnError
32
33
  });
33
34
  module.exports = __toCommonJS(browserBackend_exports);
@@ -40,15 +41,6 @@ async function runBrowserBackendOnError(page, message) {
40
41
  const testInfo = (0, import_globals.currentTestInfo)();
41
42
  if (!testInfo || !testInfo._pauseOnError())
42
43
  return;
43
- const browserContextFactory = {
44
- createContext: async (clientInfo, abortSignal, toolName) => {
45
- return {
46
- browserContext: page.context(),
47
- close: async () => {
48
- }
49
- };
50
- }
51
- };
52
44
  const config = {
53
45
  ...import_config.defaultConfig,
54
46
  capabilities: ["testing"]
@@ -62,9 +54,39 @@ ${snapshot}
62
54
 
63
55
  ### Task
64
56
  Try recovering from the error prior to continuing`;
65
- await mcp.runOnPauseBackendLoop(new import_browserServerBackend.BrowserServerBackend(config, browserContextFactory), introMessage);
57
+ await mcp.runOnPauseBackendLoop(new import_browserServerBackend.BrowserServerBackend(config, identityFactory(page.context())), introMessage);
58
+ }
59
+ async function runBrowserBackendAtEnd(context) {
60
+ const testInfo = (0, import_globals.currentTestInfo)();
61
+ if (!testInfo || !testInfo._pauseAtEnd())
62
+ return;
63
+ const page = context.pages()[0];
64
+ if (!page)
65
+ return;
66
+ const snapshot = await page._snapshotForAI();
67
+ const introMessage = `### Paused at end of test. ready for interaction
68
+
69
+ ### Current page snapshot:
70
+ ${snapshot}`;
71
+ const config = {
72
+ ...import_config.defaultConfig,
73
+ capabilities: ["testing"]
74
+ };
75
+ await mcp.runOnPauseBackendLoop(new import_browserServerBackend.BrowserServerBackend(config, identityFactory(context)), introMessage);
76
+ }
77
+ function identityFactory(browserContext) {
78
+ return {
79
+ createContext: async (clientInfo, abortSignal, toolName) => {
80
+ return {
81
+ browserContext,
82
+ close: async () => {
83
+ }
84
+ };
85
+ }
86
+ };
66
87
  }
67
88
  // Annotate the CommonJS export names for ESM import in node:
68
89
  0 && (module.exports = {
90
+ runBrowserBackendAtEnd,
69
91
  runBrowserBackendOnError
70
92
  });
@@ -31,16 +31,35 @@ __export(testBackend_exports, {
31
31
  TestServerBackend: () => TestServerBackend
32
32
  });
33
33
  module.exports = __toCommonJS(testBackend_exports);
34
+ var import_url = require("url");
34
35
  var mcp = __toESM(require("../sdk/exports"));
35
36
  var import_testContext = require("./testContext");
36
37
  var import_testTools = require("./testTools.js");
37
38
  var import_tools = require("../browser/tools");
39
+ var import_configLoader = require("../../common/configLoader");
38
40
  class TestServerBackend {
39
- constructor(resolvedLocation, options) {
41
+ constructor(configOption, options) {
40
42
  this.name = "Playwright";
41
43
  this.version = "0.0.1";
42
- this._tools = [import_testTools.listTests, import_testTools.runTests, import_testTools.debugTest];
43
- this._context = new import_testContext.TestContext(resolvedLocation, options);
44
+ this._tools = [import_testTools.listTests, import_testTools.runTests, import_testTools.debugTest, import_testTools.setupPage];
45
+ this._context = new import_testContext.TestContext(options);
46
+ this._configOption = configOption;
47
+ }
48
+ async initialize(server, clientVersion, roots) {
49
+ if (this._configOption) {
50
+ this._context.setConfigLocation((0, import_configLoader.resolveConfigLocation)(this._configOption));
51
+ return;
52
+ }
53
+ if (roots.length > 0) {
54
+ const firstRootUri = roots[0]?.uri;
55
+ const url = firstRootUri ? new URL(firstRootUri) : void 0;
56
+ const folder = url ? (0, import_url.fileURLToPath)(url) : void 0;
57
+ if (folder) {
58
+ this._context.setConfigLocation((0, import_configLoader.resolveConfigLocation)(folder));
59
+ return;
60
+ }
61
+ }
62
+ throw new Error("No config option or MCP root path provided");
44
63
  }
45
64
  async listTools() {
46
65
  return [
@@ -23,10 +23,12 @@ __export(testContext_exports, {
23
23
  module.exports = __toCommonJS(testContext_exports);
24
24
  var import_testRunner = require("../../runner/testRunner");
25
25
  class TestContext {
26
- constructor(configLocation, options) {
27
- this.configLocation = configLocation;
26
+ constructor(options) {
28
27
  this.options = options;
29
28
  }
29
+ setConfigLocation(configLocation) {
30
+ this.configLocation = configLocation;
31
+ }
30
32
  async createTestRunner() {
31
33
  if (this._testRunner)
32
34
  await this._testRunner.stopTests();
@@ -30,7 +30,8 @@ var testTools_exports = {};
30
30
  __export(testTools_exports, {
31
31
  debugTest: () => debugTest,
32
32
  listTests: () => listTests,
33
- runTests: () => runTests
33
+ runTests: () => runTests,
34
+ setupPage: () => setupPage
34
35
  });
35
36
  module.exports = __toCommonJS(testTools_exports);
36
37
  var import_utils = require("playwright-core/lib/utils");
@@ -42,7 +43,7 @@ var import_testTool = require("./testTool");
42
43
  var import_streams = require("./streams");
43
44
  const listTests = (0, import_testTool.defineTestTool)({
44
45
  schema: {
45
- name: "playwright_test_list_tests",
46
+ name: "test_list",
46
47
  title: "List tests",
47
48
  description: "List tests",
48
49
  inputSchema: import_bundle.z.object({}),
@@ -60,7 +61,7 @@ const listTests = (0, import_testTool.defineTestTool)({
60
61
  });
61
62
  const runTests = (0, import_testTool.defineTestTool)({
62
63
  schema: {
63
- name: "playwright_test_run_tests",
64
+ name: "test_run",
64
65
  title: "Run tests",
65
66
  description: "Run tests",
66
67
  inputSchema: import_bundle.z.object({
@@ -89,7 +90,7 @@ const runTests = (0, import_testTool.defineTestTool)({
89
90
  });
90
91
  const debugTest = (0, import_testTool.defineTestTool)({
91
92
  schema: {
92
- name: "playwright_test_debug_test",
93
+ name: "test_debug",
93
94
  title: "Debug single test",
94
95
  description: "Debug single test",
95
96
  inputSchema: import_bundle.z.object({
@@ -101,14 +102,7 @@ const debugTest = (0, import_testTool.defineTestTool)({
101
102
  type: "readOnly"
102
103
  },
103
104
  handle: async (context, params) => {
104
- const stream = new import_streams.StringWriteStream();
105
- const screen = {
106
- ...import_base.terminalScreen,
107
- isTTY: false,
108
- colors: import_utils.noColors,
109
- stdout: stream,
110
- stderr: stream
111
- };
105
+ const { screen, stream } = createScreen();
112
106
  const configDir = context.configLocation.configDir;
113
107
  const reporter = new import_list.default({ configDir, screen });
114
108
  const testRunner = await context.createTestRunner();
@@ -129,6 +123,35 @@ const debugTest = (0, import_testTool.defineTestTool)({
129
123
  };
130
124
  }
131
125
  });
126
+ const setupPage = (0, import_testTool.defineTestTool)({
127
+ schema: {
128
+ name: "test_setup_page",
129
+ title: "Setup page",
130
+ description: "Runs a blank test to setup the page for interaction",
131
+ inputSchema: import_bundle.z.object({
132
+ testLocation: import_bundle.z.string().describe('Location of the blank test to use for setup. For example: "test/e2e/file.spec.ts:20"')
133
+ }),
134
+ type: "readOnly"
135
+ },
136
+ handle: async (context, params) => {
137
+ const { screen, stream } = createScreen();
138
+ const configDir = context.configLocation.configDir;
139
+ const reporter = new import_list.default({ configDir, screen });
140
+ const testRunner = await context.createTestRunner();
141
+ const result = await testRunner.runTests(reporter, {
142
+ headed: !context.options?.headless,
143
+ locations: [params.testLocation],
144
+ timeout: 0,
145
+ workers: 1,
146
+ pauseAtEnd: true
147
+ });
148
+ const text = stream.content();
149
+ return {
150
+ content: [{ type: "text", text }],
151
+ isError: result.status !== "passed"
152
+ };
153
+ }
154
+ });
132
155
  function createScreen() {
133
156
  const stream = new import_streams.StringWriteStream();
134
157
  const screen = {
@@ -144,5 +167,6 @@ function createScreen() {
144
167
  0 && (module.exports = {
145
168
  debugTest,
146
169
  listTests,
147
- runTests
170
+ runTests,
171
+ setupPage
148
172
  });
package/lib/program.js CHANGED
@@ -49,6 +49,7 @@ var import_reporters = require("./runner/reporters");
49
49
  var import_exports = require("./mcp/sdk/exports");
50
50
  var import_testBackend = require("./mcp/test/testBackend");
51
51
  var import_program3 = require("./mcp/program");
52
+ var import_watchdog = require("./mcp/browser/watchdog");
52
53
  var import_generateAgents = require("./agents/generateAgents");
53
54
  const packageJSON = require("../package.json");
54
55
  function addTestCommand(program3) {
@@ -157,12 +158,12 @@ function addTestMCPServerCommand(program3) {
157
158
  command.option("--host <host>", "host to bind server to. Default is localhost. Use 0.0.0.0 to bind to all interfaces.");
158
159
  command.option("--port <port>", "port to listen on for SSE transport.");
159
160
  command.action(async (options) => {
160
- const resolvedLocation = (0, import_configLoader.resolveConfigLocation)(options.config);
161
+ (0, import_watchdog.setupExitWatchdog)();
161
162
  const backendFactory = {
162
163
  name: "Playwright Test Runner",
163
164
  nameInConfig: "playwright-test-runner",
164
165
  version: packageJSON.version,
165
- create: () => new import_testBackend.TestServerBackend(resolvedLocation, { muteConsole: options.port === void 0, headless: options.headless })
166
+ create: () => new import_testBackend.TestServerBackend(options.config, { muteConsole: options.port === void 0, headless: options.headless })
166
167
  };
167
168
  const mdbUrl = await (0, import_exports.runMainBackend)(backendFactory, { port: options.port === void 0 ? void 0 : +options.port });
168
169
  if (mdbUrl)
@@ -158,7 +158,14 @@ 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, this._failureTracker.pauseOnError());
161
+ const worker = new import_workerHost.WorkerHost(testGroup, {
162
+ parallelIndex,
163
+ config: loaderData,
164
+ extraEnv: this._extraEnvByProjectId.get(testGroup.projectId) || {},
165
+ outputDir,
166
+ pauseOnError: this._failureTracker.pauseOnError(),
167
+ pauseAtEnd: this._failureTracker.pauseAtEnd()
168
+ });
162
169
  const handleOutput = (params) => {
163
170
  const chunk = chunkFromParams(params);
164
171
  if (worker.didFail()) {
@@ -27,6 +27,7 @@ class FailureTracker {
27
27
  this._failureCount = 0;
28
28
  this._hasWorkerErrors = false;
29
29
  this._pauseOnError = options?.pauseOnError ?? false;
30
+ this._pauseAtEnd = options?.pauseAtEnd ?? false;
30
31
  }
31
32
  onRootSuite(rootSuite) {
32
33
  this._rootSuite = rootSuite;
@@ -41,6 +42,9 @@ class FailureTracker {
41
42
  pauseOnError() {
42
43
  return this._pauseOnError;
43
44
  }
45
+ pauseAtEnd() {
46
+ return this._pauseAtEnd;
47
+ }
44
48
  hasReachedMaxFailures() {
45
49
  return this.maxFailures() > 0 && this._failureCount >= this.maxFailures();
46
50
  }
@@ -71,6 +71,7 @@ class TestRunner extends import_events.default {
71
71
  });
72
72
  }
73
73
  async initialize(params) {
74
+ (0, import_utils.setPlaywrightTestProcessEnv)();
74
75
  this._watchTestDirs = !!params.watchTestDirs;
75
76
  this._populateDependenciesOnList = !!params.populateDependenciesOnList;
76
77
  }
@@ -270,7 +271,7 @@ class TestRunner extends import_events.default {
270
271
  (0, import_tasks.createLoadTask)("out-of-process", { filterOnly: true, failOnLoadErrors: false, doNotRunDepsOutsideProjectFilter: true }),
271
272
  ...(0, import_tasks.createRunTestsTasks)(config)
272
273
  ];
273
- const testRun = new import_tasks.TestRun(config, reporter, { pauseOnError: params.pauseOnError });
274
+ const testRun = new import_tasks.TestRun(config, reporter, { pauseOnError: params.pauseOnError, pauseAtEnd: params.pauseAtEnd });
274
275
  const run = (0, import_tasks.runTasks)(testRun, tasks, 0, stop).then(async (status) => {
275
276
  this._testRun = void 0;
276
277
  return status;
@@ -352,6 +353,7 @@ async function resolveCtDirs(config) {
352
353
  };
353
354
  }
354
355
  async function runAllTestsWithConfig(config) {
356
+ (0, import_utils.setPlaywrightTestProcessEnv)();
355
357
  const listOnly = config.cliListOnly;
356
358
  (0, import_gitCommitInfoPlugin.addGitCommitInfoPlugin)(config);
357
359
  (0, import_webServerPlugin.webServerPluginsForConfig)(config).forEach((p) => config.plugins.push({ factory: p }));
@@ -39,25 +39,26 @@ 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, pauseOnError) {
42
+ constructor(testGroup, options) {
43
43
  const workerIndex = lastWorkerIndex++;
44
44
  super(require.resolve("../worker/workerMain.js"), `worker-${workerIndex}`, {
45
- ...extraEnv,
45
+ ...options.extraEnv,
46
46
  FORCE_COLOR: "1",
47
47
  DEBUG_COLORS: process.env.DEBUG_COLORS === void 0 ? "1" : process.env.DEBUG_COLORS
48
48
  });
49
49
  this._didFail = false;
50
50
  this.workerIndex = workerIndex;
51
- this.parallelIndex = parallelIndex;
51
+ this.parallelIndex = options.parallelIndex;
52
52
  this._hash = testGroup.workerHash;
53
53
  this._params = {
54
54
  workerIndex: this.workerIndex,
55
- parallelIndex,
55
+ parallelIndex: options.parallelIndex,
56
56
  repeatEachIndex: testGroup.repeatEachIndex,
57
57
  projectId: testGroup.projectId,
58
- config,
59
- artifactsDir: import_path.default.join(outputDir, (0, import_folders.artifactsFolderName)(workerIndex)),
60
- pauseOnError
58
+ config: options.config,
59
+ artifactsDir: import_path.default.join(options.outputDir, (0, import_folders.artifactsFolderName)(workerIndex)),
60
+ pauseOnError: options.pauseOnError,
61
+ pauseAtEnd: options.pauseAtEnd
61
62
  };
62
63
  }
63
64
  async start() {
@@ -447,6 +447,9 @@ ${(0, import_utils.stringifyStackFrames)(step.boxedStack).join("\n")}`;
447
447
  _pauseOnError() {
448
448
  return this._workerParams.pauseOnError;
449
449
  }
450
+ _pauseAtEnd() {
451
+ return this._workerParams.pauseAtEnd;
452
+ }
450
453
  }
451
454
  class TestStepInfoImpl {
452
455
  constructor(testInfo, stepId, title, parentStep) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "playwright",
3
- "version": "1.56.0-alpha-2025-09-11",
3
+ "version": "1.56.0-alpha-2025-09-12",
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-11"
67
+ "playwright-core": "1.56.0-alpha-2025-09-12"
68
68
  },
69
69
  "optionalDependencies": {
70
70
  "fsevents": "2.3.2"