playwright 1.56.0-alpha-1758061937000 → 1.56.0-alpha-2025-09-18

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.
@@ -29,10 +29,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
29
29
  var generateAgents_exports = {};
30
30
  __export(generateAgents_exports, {
31
31
  initClaudeCodeRepo: () => initClaudeCodeRepo,
32
- initOpencodeRepo: () => initOpencodeRepo
32
+ initOpencodeRepo: () => initOpencodeRepo,
33
+ initVSCodeRepo: () => initVSCodeRepo
33
34
  });
34
35
  module.exports = __toCommonJS(generateAgents_exports);
35
- var import_crypto = __toESM(require("crypto"));
36
36
  var import_fs = __toESM(require("fs"));
37
37
  var import_path = __toESM(require("path"));
38
38
  var import_utilsBundle = require("playwright-core/lib/utilsBundle");
@@ -88,22 +88,28 @@ const claudeToolMap = /* @__PURE__ */ new Map([
88
88
  ["ls", ["Glob"]],
89
89
  ["grep", ["Grep"]],
90
90
  ["read", ["Read"]],
91
- ["edit", ["Edit", "MultiEdit", "NotebookEdit"]],
91
+ ["edit", ["Edit", "MultiEdit"]],
92
92
  ["write", ["Write"]]
93
93
  ]);
94
+ const commonMcpServers = {
95
+ playwrightTest: {
96
+ type: "local",
97
+ command: "npx",
98
+ args: ["playwright", "run-test-mcp-server"]
99
+ }
100
+ };
94
101
  function saveAsClaudeCode(agent) {
95
- function asClaudeTool(hash2, tool) {
102
+ function asClaudeTool(tool) {
96
103
  const [first, second] = tool.split("/");
97
104
  if (!second)
98
105
  return (claudeToolMap.get(first) || [first]).join(", ");
99
- return `mcp__${first}-${hash2}__${second}`;
106
+ return `mcp__${first}__${second}`;
100
107
  }
101
- const hash = shortHash(agent.header.name);
102
108
  const lines = [];
103
109
  lines.push(`---`);
104
110
  lines.push(`name: ${agent.header.name}`);
105
111
  lines.push(`description: ${agent.header.description}. Examples: ${agent.examples.map((example) => `<example>${example}</example>`).join("")}`);
106
- lines.push(`tools: ${agent.header.tools.map((tool) => asClaudeTool(hash, tool)).join(", ")}`);
112
+ lines.push(`tools: ${agent.header.tools.map((tool) => asClaudeTool(tool)).join(", ")}`);
107
113
  lines.push(`model: ${agent.header.model}`);
108
114
  lines.push(`color: ${agent.header.color}`);
109
115
  lines.push(`---`);
@@ -119,13 +125,13 @@ const opencodeToolMap = /* @__PURE__ */ new Map([
119
125
  ["write", ["write"]]
120
126
  ]);
121
127
  function saveAsOpencodeJson(agents) {
122
- function asOpencodeTool(tools, hash, tool) {
128
+ function asOpencodeTool(tools, tool) {
123
129
  const [first, second] = tool.split("/");
124
130
  if (!second) {
125
131
  for (const tool2 of opencodeToolMap.get(first) || [first])
126
132
  tools[tool2] = true;
127
133
  } else {
128
- tools[`${first}-${hash}*${second}`] = true;
134
+ tools[`${first}*${second}`] = true;
129
135
  }
130
136
  }
131
137
  const result = {};
@@ -136,7 +142,6 @@ function saveAsOpencodeJson(agents) {
136
142
  };
137
143
  result["agent"] = {};
138
144
  for (const agent of agents) {
139
- const hash = shortHash(agent.header.name);
140
145
  const tools = {};
141
146
  result["agent"][agent.header.name] = {
142
147
  description: agent.header.description,
@@ -145,20 +150,16 @@ function saveAsOpencodeJson(agents) {
145
150
  tools
146
151
  };
147
152
  for (const tool of agent.header.tools)
148
- asOpencodeTool(tools, hash, tool);
149
- for (const [name, mcp] of Object.entries(agent.header["mcp-servers"])) {
150
- result["mcp"][name + "-" + hash] = {
151
- type: mcp.type,
152
- command: [mcp.command, ...mcp.args],
153
- enabled: true
154
- };
155
- }
153
+ asOpencodeTool(tools, tool);
156
154
  }
155
+ const server = commonMcpServers.playwrightTest;
156
+ result["mcp"]["playwright-test"] = {
157
+ type: server.type,
158
+ command: [server.command, ...server.args],
159
+ enabled: true
160
+ };
157
161
  return JSON.stringify(result, null, 2);
158
162
  }
159
- function shortHash(str) {
160
- return import_crypto.default.createHash("sha256").update(str).digest("hex").slice(0, 4);
161
- }
162
163
  async function loadAgents() {
163
164
  const files = await import_fs.default.promises.readdir(__dirname);
164
165
  return Promise.all(files.filter((file) => file.endsWith(".md")).map((file) => AgentParser.parseFile(import_path.default.join(__dirname, file))));
@@ -172,18 +173,62 @@ async function initClaudeCodeRepo() {
172
173
  await import_fs.default.promises.mkdir(".claude/agents", { recursive: true });
173
174
  for (const agent of agents)
174
175
  await writeFile(`.claude/agents/${agent.header.name}.md`, saveAsClaudeCode(agent));
175
- const mcpServers = {};
176
- for (const agent of agents) {
177
- const hash = shortHash(agent.header.name);
178
- for (const [name, mcp] of Object.entries(agent.header["mcp-servers"])) {
179
- const entry = {
180
- command: mcp.command,
181
- args: mcp.args
182
- };
183
- mcpServers[name + "-" + hash] = entry;
176
+ await writeFile(".mcp.json", JSON.stringify({
177
+ mcpServers: {
178
+ "playwright-test": {
179
+ command: commonMcpServers.playwrightTest.command,
180
+ args: commonMcpServers.playwrightTest.args
181
+ }
184
182
  }
183
+ }, null, 2));
184
+ }
185
+ const vscodeToolMap = /* @__PURE__ */ new Map([
186
+ ["ls", ["listDirectory", "fileSearch"]],
187
+ ["grep", ["textSearch"]],
188
+ ["read", ["readFile"]],
189
+ ["edit", ["editFiles"]],
190
+ ["write", ["createFile", "createDirectory"]]
191
+ ]);
192
+ function saveAsVSCodeChatmode(agent) {
193
+ function asVscodeTool(tool) {
194
+ const [first, second] = tool.split("/");
195
+ if (second)
196
+ return second;
197
+ return vscodeToolMap.get(first) || first;
185
198
  }
186
- await writeFile(".mcp.json", JSON.stringify({ mcpServers }, null, 2));
199
+ const tools = agent.header.tools.map(asVscodeTool).flat().map((tool) => `'${tool}'`).join(", ");
200
+ const lines = [];
201
+ lines.push(`---`);
202
+ lines.push(`description: ${agent.header.description}. Examples: ${agent.examples.map((example) => `<example>${example}</example>`).join("")}`);
203
+ lines.push(`tools: [${tools}]`);
204
+ lines.push(`---`);
205
+ lines.push("");
206
+ lines.push(agent.instructions);
207
+ return lines.join("\n");
208
+ }
209
+ async function initVSCodeRepo() {
210
+ const agents = await loadAgents();
211
+ await import_fs.default.promises.mkdir(".github/chatmodes", { recursive: true });
212
+ for (const agent of agents)
213
+ await writeFile(`.github/chatmodes/${agent.header.name}.chatmode.md`, saveAsVSCodeChatmode(agent));
214
+ await import_fs.default.promises.mkdir(".vscode", { recursive: true });
215
+ const mcpJsonPath = ".vscode/mcp.json";
216
+ let mcpJson = {
217
+ servers: {},
218
+ inputs: []
219
+ };
220
+ try {
221
+ mcpJson = JSON.parse(import_fs.default.readFileSync(mcpJsonPath, "utf8"));
222
+ } catch {
223
+ }
224
+ if (!mcpJson.servers)
225
+ mcpJson.servers = {};
226
+ mcpJson.servers["playwright-test"] = {
227
+ type: "stdio",
228
+ command: commonMcpServers.playwrightTest.command,
229
+ args: commonMcpServers.playwrightTest.args
230
+ };
231
+ await writeFile(mcpJsonPath, JSON.stringify(mcpJson, null, 2));
187
232
  }
188
233
  async function initOpencodeRepo() {
189
234
  const agents = await loadAgents();
@@ -199,5 +244,6 @@ async function initOpencodeRepo() {
199
244
  // Annotate the CommonJS export names for ESM import in node:
200
245
  0 && (module.exports = {
201
246
  initClaudeCodeRepo,
202
- initOpencodeRepo
247
+ initOpencodeRepo,
248
+ initVSCodeRepo
203
249
  });
@@ -8,28 +8,23 @@ tools:
8
8
  - grep
9
9
  - read
10
10
  - write
11
- - playwright/browser_click
12
- - playwright/browser_drag
13
- - playwright/browser_evaluate
14
- - playwright/browser_file_upload
15
- - playwright/browser_handle_dialog
16
- - playwright/browser_hover
17
- - playwright/browser_navigate
18
- - playwright/browser_press_key
19
- - playwright/browser_select_option
20
- - playwright/browser_snapshot
21
- - playwright/browser_type
22
- - playwright/browser_verify_element_visible
23
- - playwright/browser_verify_list_visible
24
- - playwright/browser_verify_text_visible
25
- - playwright/browser_verify_value
26
- - playwright/browser_wait_for
27
- - playwright/test_setup_page
28
- mcp-servers:
29
- playwright:
30
- type: 'local'
31
- command: 'npx'
32
- args: ['playwright', 'run-test-mcp-server']
11
+ - playwright-test/browser_click
12
+ - playwright-test/browser_drag
13
+ - playwright-test/browser_evaluate
14
+ - playwright-test/browser_file_upload
15
+ - playwright-test/browser_handle_dialog
16
+ - playwright-test/browser_hover
17
+ - playwright-test/browser_navigate
18
+ - playwright-test/browser_press_key
19
+ - playwright-test/browser_select_option
20
+ - playwright-test/browser_snapshot
21
+ - playwright-test/browser_type
22
+ - playwright-test/browser_verify_element_visible
23
+ - playwright-test/browser_verify_list_visible
24
+ - playwright-test/browser_verify_text_visible
25
+ - playwright-test/browser_verify_value
26
+ - playwright-test/browser_wait_for
27
+ - playwright-test/test_setup_page
33
28
  ---
34
29
 
35
30
  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.
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: playwright-test-fixer
2
+ name: playwright-test-healer
3
3
  description: Use this agent when you need to debug and fix failing Playwright tests
4
4
  color: red
5
5
  model: sonnet
@@ -9,21 +9,15 @@ tools:
9
9
  - read
10
10
  - write
11
11
  - edit
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
19
- mcp-servers:
20
- playwright:
21
- type: 'local'
22
- command: 'npx'
23
- args: ['playwright', 'run-test-mcp-server']
12
+ - playwright-test/browser_evaluate
13
+ - playwright-test/browser_generate_locator
14
+ - playwright-test/browser_snapshot
15
+ - playwright-test/test_debug
16
+ - playwright-test/test_list
17
+ - playwright-test/test_run
24
18
  ---
25
19
 
26
- You are the Playwright Test Fixer, an expert test automation engineer specializing in debugging and
20
+ You are the Playwright Test Healer, an expert test automation engineer specializing in debugging and
27
21
  resolving Playwright test failures. Your mission is to systematically identify, diagnose, and fix
28
22
  broken Playwright tests using a methodical approach.
29
23
 
@@ -43,7 +37,7 @@ Your workflow:
43
37
  - Updating selectors to match current application state
44
38
  - Fixing assertions and expected values
45
39
  - Improving test reliability and maintainability
46
- - For inherently dynamic data, utilize regular expressions to produce resilient locators
40
+ - For inherently dynamic data, utilize regular expressions to produce resilient locators
47
41
  6. **Verification**: Restart the test after each fix to validate the changes
48
42
  7. **Iteration**: Repeat the investigation and fixing process until the test passes cleanly
49
43
 
@@ -62,19 +56,19 @@ Key principles:
62
56
  <example>
63
57
  Context: A developer has a failing Playwright test that needs to be debugged and fixed.
64
58
  user: 'The login test is failing, can you fix it?'
65
- assistant: 'I'll use the playwright-test-fixer agent to debug and fix the failing login test.'
59
+ assistant: 'I'll use the playwright-test-healer agent to debug and fix the failing login test.'
66
60
  <commentary>
67
61
  The user has identified a specific failing test that needs debugging and fixing, which is exactly what the
68
- playwright-test-fixer agent is designed for.
62
+ playwright-test-healer agent is designed for.
69
63
  </commentary>
70
64
  </example>
71
65
 
72
66
  <example>
73
67
  Context: After running a test suite, several tests are reported as failing.
74
68
  user: 'Test user-registration.spec.ts is broken after the recent changes'
75
- assistant: 'Let me use the playwright-test-fixer agent to investigate and fix the user-registration test.'
69
+ assistant: 'Let me use the playwright-test-healer agent to investigate and fix the user-registration test.'
76
70
  <commentary>
77
71
  A specific test file is failing and needs debugging, which requires the systematic approach of the
78
- playwright-test-fixer agent.
72
+ playwright-test-healer agent.
79
73
  </commentary>
80
74
  </example>
@@ -8,36 +8,31 @@ tools:
8
8
  - grep
9
9
  - read
10
10
  - write
11
- - playwright/browser_click
12
- - playwright/browser_close
13
- - playwright/browser_console_messages
14
- - playwright/browser_drag
15
- - playwright/browser_evaluate
16
- - playwright/browser_file_upload
17
- - playwright/browser_handle_dialog
18
- - playwright/browser_hover
19
- - playwright/browser_navigate
20
- - playwright/browser_navigate_back
21
- - playwright/browser_network_requests
22
- - playwright/browser_press_key
23
- - playwright/browser_select_option
24
- - playwright/browser_snapshot
25
- - playwright/browser_take_screenshot
26
- - playwright/browser_type
27
- - playwright/browser_wait_for
28
- - playwright/test_setup_page
29
- mcp-servers:
30
- playwright:
31
- type: 'local'
32
- command: 'npx'
33
- args: ['playwright', 'run-test-mcp-server']
11
+ - playwright-test/browser_click
12
+ - playwright-test/browser_close
13
+ - playwright-test/browser_console_messages
14
+ - playwright-test/browser_drag
15
+ - playwright-test/browser_evaluate
16
+ - playwright-test/browser_file_upload
17
+ - playwright-test/browser_handle_dialog
18
+ - playwright-test/browser_hover
19
+ - playwright-test/browser_navigate
20
+ - playwright-test/browser_navigate_back
21
+ - playwright-test/browser_network_requests
22
+ - playwright-test/browser_press_key
23
+ - playwright-test/browser_select_option
24
+ - playwright-test/browser_snapshot
25
+ - playwright-test/browser_take_screenshot
26
+ - playwright-test/browser_type
27
+ - playwright-test/browser_wait_for
28
+ - playwright-test/test_setup_page
34
29
  ---
35
30
 
36
31
  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.
37
32
 
38
33
  When given a target web page or application, you will:
39
34
 
40
- 1. **Navigate and Explore**:
35
+ 1. **Navigate and Explore**:
41
36
  - Invoke the `test_setup_page` tool once to set up page before using any other tools
42
37
  - Explore the aria snapshot, use browser_* tools to navigate and discover interface.
43
38
  - Thoroughly explore the interface, identifying all interactive elements, forms, navigation paths, and functionality
@@ -38,9 +38,9 @@ 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");
42
41
  var import_log = require("../log");
43
42
  var import_config = require("./config");
43
+ var import_server2 = require("../sdk/server");
44
44
  function contextFactory(config) {
45
45
  if (config.browser.remoteEndpoint)
46
46
  return new RemoteContextFactory(config);
@@ -99,7 +99,7 @@ class IsolatedContextFactory extends BaseContextFactory {
99
99
  async _doObtainBrowser(clientInfo) {
100
100
  await injectCdpPort(this.config.browser);
101
101
  const browserType = playwright[this.config.browser.browserName];
102
- const tracesDir = await (0, import_config.outputFile)(this.config, clientInfo.rootPath, `traces`);
102
+ const tracesDir = await (0, import_config.outputFile)(this.config, clientInfo, `traces`);
103
103
  if (this.config.saveTrace)
104
104
  await startTraceServer(this.config, tracesDir);
105
105
  return browserType.launch({
@@ -153,40 +153,40 @@ class PersistentContextFactory {
153
153
  async createContext(clientInfo) {
154
154
  await injectCdpPort(this.config.browser);
155
155
  (0, import_log.testDebug)("create browser context (persistent)");
156
- const userDataDir = this.config.browser.userDataDir ?? await this._createUserDataDir(clientInfo.rootPath);
157
- const tracesDir = await (0, import_config.outputFile)(this.config, clientInfo.rootPath, `traces`);
156
+ const userDataDir = this.config.browser.userDataDir ?? await this._createUserDataDir(clientInfo);
157
+ const tracesDir = await (0, import_config.outputFile)(this.config, clientInfo, `traces`);
158
158
  if (this.config.saveTrace)
159
159
  await startTraceServer(this.config, tracesDir);
160
160
  this._userDataDirs.add(userDataDir);
161
161
  (0, import_log.testDebug)("lock user data dir", userDataDir);
162
162
  const browserType = playwright[this.config.browser.browserName];
163
163
  for (let i = 0; i < 5; i++) {
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;
164
+ const launchOptions = {
165
+ tracesDir,
166
+ ...this.config.browser.launchOptions,
167
+ ...this.config.browser.contextOptions,
168
+ handleSIGINT: false,
169
+ handleSIGTERM: false,
170
+ ignoreDefaultArgs: [
171
+ "--disable-extensions"
172
+ ],
173
+ assistantMode: true
174
+ };
175
+ try {
176
+ const browserContext = await browserType.launchPersistentContext(userDataDir, launchOptions);
177
+ const close = () => this._closeBrowserContext(browserContext, userDataDir);
178
+ return { browserContext, close };
179
+ } catch (error) {
180
+ if (error.message.includes("Executable doesn't exist"))
181
+ throw new Error(`Browser specified in your config is not installed. Either install it (likely) or change the config.`);
182
+ if (error.message.includes("ProcessSingleton") || error.message.includes("Invalid URL")) {
183
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
184
+ continue;
185
+ }
186
+ throw error;
187
+ }
189
188
  }
189
+ throw new Error(`Browser is already in use for ${userDataDir}, use --isolated to run multiple instances of the same browser`);
190
190
  }
191
191
  async _closeBrowserContext(browserContext, userDataDir) {
192
192
  (0, import_log.testDebug)("close browser context (persistent)");
@@ -196,21 +196,16 @@ class PersistentContextFactory {
196
196
  this._userDataDirs.delete(userDataDir);
197
197
  (0, import_log.testDebug)("close browser context complete (persistent)");
198
198
  }
199
- async _createUserDataDir(rootPath) {
199
+ async _createUserDataDir(clientInfo) {
200
200
  const dir = process.env.PWMCP_PROFILES_DIR_FOR_TEST ?? import_registry.registryDirectory;
201
201
  const browserToken = this.config.browser.launchOptions?.channel ?? this.config.browser?.browserName;
202
+ const rootPath = (0, import_server2.firstRootPath)(clientInfo);
202
203
  const rootPathToken = rootPath ? `-${createHash(rootPath)}` : "";
203
204
  const result = import_path.default.join(dir, `mcp-${browserToken}${rootPathToken}`);
204
205
  await import_fs.default.promises.mkdir(result, { recursive: true });
205
206
  return result;
206
207
  }
207
208
  }
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
- }
214
209
  async function injectCdpPort(browserConfig) {
215
210
  if (browserConfig.browserName === "chromium")
216
211
  browserConfig.launchOptions.cdpPort = await findFreePort();
@@ -21,7 +21,6 @@ __export(browserServerBackend_exports, {
21
21
  BrowserServerBackend: () => BrowserServerBackend
22
22
  });
23
23
  module.exports = __toCommonJS(browserServerBackend_exports);
24
- var import_url = require("url");
25
24
  var import_context = require("./context");
26
25
  var import_log = require("../log");
27
26
  var import_response = require("./response");
@@ -34,19 +33,13 @@ class BrowserServerBackend {
34
33
  this._browserContextFactory = factory;
35
34
  this._tools = (0, import_tools.filteredTools)(config);
36
35
  }
37
- async initialize(server, clientVersion, roots) {
38
- let rootPath;
39
- if (roots.length > 0) {
40
- const firstRootUri = roots[0]?.uri;
41
- const url = firstRootUri ? new URL(firstRootUri) : void 0;
42
- rootPath = url ? (0, import_url.fileURLToPath)(url) : void 0;
43
- }
44
- this._sessionLog = this._config.saveSession ? await import_sessionLog.SessionLog.create(this._config, rootPath) : void 0;
36
+ async initialize(server, clientInfo) {
37
+ this._sessionLog = this._config.saveSession ? await import_sessionLog.SessionLog.create(this._config, clientInfo) : void 0;
45
38
  this._context = new import_context.Context({
46
39
  config: this._config,
47
40
  browserContextFactory: this._browserContextFactory,
48
41
  sessionLog: this._sessionLog,
49
- clientInfo: { ...clientVersion, rootPath }
42
+ clientInfo
50
43
  });
51
44
  }
52
45
  async listTools() {
@@ -45,6 +45,7 @@ var import_os = __toESM(require("os"));
45
45
  var import_path = __toESM(require("path"));
46
46
  var import_playwright_core = require("playwright-core");
47
47
  var import_utilsBundle = require("playwright-core/lib/utilsBundle");
48
+ var import_server = require("../sdk/server");
48
49
  const defaultConfig = {
49
50
  browser: {
50
51
  browserName: "chromium",
@@ -215,8 +216,9 @@ async function loadConfig(configFile) {
215
216
  throw new Error(`Failed to load config file: ${configFile}, ${error}`);
216
217
  }
217
218
  }
218
- async function outputFile(config, rootPath, name) {
219
- const outputDir = config.outputDir ?? (rootPath ? import_path.default.join(rootPath, ".playwright-mcp") : void 0) ?? import_path.default.join(import_os.default.tmpdir(), "playwright-mcp-output", sanitizeForFilePath((/* @__PURE__ */ new Date()).toISOString()));
219
+ async function outputFile(config, clientInfo, name) {
220
+ const rootPath = (0, import_server.firstRootPath)(clientInfo);
221
+ const outputDir = config.outputDir ?? (rootPath ? import_path.default.join(rootPath, ".playwright-mcp") : void 0) ?? import_path.default.join(process.env.PW_TMPDIR_FOR_TEST ?? import_os.default.tmpdir(), "playwright-mcp-output", String(clientInfo.timestamp));
220
222
  await import_fs.default.promises.mkdir(outputDir, { recursive: true });
221
223
  const fileName = sanitizeForFilePath(name);
222
224
  return import_path.default.join(outputDir, fileName);
@@ -96,7 +96,7 @@ class Context {
96
96
  return url;
97
97
  }
98
98
  async outputFile(name) {
99
- return (0, import_config.outputFile)(this.config, this._clientInfo.rootPath, name);
99
+ return (0, import_config.outputFile)(this.config, this._clientInfo, name);
100
100
  }
101
101
  _onPageCreated(page) {
102
102
  const tab = new import_tab.Tab(this, page, (tab2) => this._onPageClosed(tab2));
@@ -43,8 +43,8 @@ class SessionLog {
43
43
  this._folder = sessionFolder;
44
44
  this._file = import_path.default.join(this._folder, "session.md");
45
45
  }
46
- static async create(config, rootPath) {
47
- const sessionFolder = await (0, import_config.outputFile)(config, rootPath, `session-${Date.now()}`);
46
+ static async create(config, clientInfo) {
47
+ const sessionFolder = await (0, import_config.outputFile)(config, clientInfo, `session-${Date.now()}`);
48
48
  await import_fs.default.promises.mkdir(sessionFolder, { recursive: true });
49
49
  console.error(`Session: ${sessionFolder}`);
50
50
  return new SessionLog(sessionFolder);
@@ -48,9 +48,9 @@ class MDBBackend {
48
48
  this._stack = [];
49
49
  this._topLevelBackend = topLevelBackend;
50
50
  }
51
- async initialize(server, clientVersion, roots) {
52
- if (!this._roots)
53
- this._roots = roots;
51
+ async initialize(server, clientInfo) {
52
+ if (!this._clientInfo)
53
+ this._clientInfo = clientInfo;
54
54
  }
55
55
  async listTools() {
56
56
  const client = await this._client();
@@ -103,8 +103,8 @@ class MDBBackend {
103
103
  }
104
104
  async _pushClient(transport, introMessage) {
105
105
  mdbDebug("pushing client to the stack");
106
- const client = new mcpBundle.Client({ name: "Internal client", version: "0.0.0" }, { capabilities: { roots: {} } });
107
- client.setRequestHandler(mcpBundle.ListRootsRequestSchema, () => ({ roots: this._roots || [] }));
106
+ const client = new mcpBundle.Client({ name: "Pushing client", version: "0.0.0" }, { capabilities: { roots: {} } });
107
+ client.setRequestHandler(mcpBundle.ListRootsRequestSchema, () => ({ roots: this._clientInfo?.roots || [] }));
108
108
  client.setRequestHandler(mcpBundle.PingRequestSchema, () => ({}));
109
109
  await client.connect(transport);
110
110
  mdbDebug("connected to the new client");
@@ -155,7 +155,7 @@ async function runOnPauseBackendLoop(backend, introMessage) {
155
155
  const httpServer = await mcpHttp.startHttpServer({ port: 0 });
156
156
  await mcpHttp.installHttpTransport(httpServer, factory);
157
157
  const url = mcpHttp.httpAddressToString(httpServer.address());
158
- const client = new mcpBundle.Client({ name: "Internal client", version: "0.0.0" });
158
+ const client = new mcpBundle.Client({ name: "On-pause client", version: "0.0.0" });
159
159
  client.setRequestHandler(mcpBundle.PingRequestSchema, () => ({}));
160
160
  const transport = new mcpBundle.StreamableHTTPClientTransport(new URL(process.env.PLAYWRIGHT_DEBUGGER_MCP));
161
161
  await client.connect(transport);
@@ -183,8 +183,8 @@ class ServerBackendWithCloseListener {
183
183
  this._serverClosedPromise = new import_utils.ManualPromise();
184
184
  this._backend = backend;
185
185
  }
186
- async initialize(server, clientVersion, roots) {
187
- await this._backend.initialize?.(server, clientVersion, roots);
186
+ async initialize(server, clientInfo) {
187
+ await this._backend.initialize?.(server, clientInfo);
188
188
  }
189
189
  async listTools() {
190
190
  return this._backend.listTools();
@@ -37,12 +37,11 @@ const errorsDebug = (0, import_utilsBundle.debug)("pw:mcp:errors");
37
37
  const { z, zodToJsonSchema } = mcpBundle;
38
38
  class ProxyBackend {
39
39
  constructor(mcpProviders) {
40
- this._roots = [];
41
40
  this._mcpProviders = mcpProviders;
42
41
  this._contextSwitchTool = this._defineContextSwitchTool();
43
42
  }
44
- async initialize(server, clientVersion, roots) {
45
- this._roots = roots;
43
+ async initialize(server, clientInfo) {
44
+ this._clientInfo = clientInfo;
46
45
  }
47
46
  async listTools() {
48
47
  const currentClient = await this._ensureCurrentClient();
@@ -115,7 +114,7 @@ Error: ${error}
115
114
  listRoots: true
116
115
  }
117
116
  });
118
- client.setRequestHandler(mcpBundle.ListRootsRequestSchema, () => ({ roots: this._roots }));
117
+ client.setRequestHandler(mcpBundle.ListRootsRequestSchema, () => ({ roots: this._clientInfo?.roots || [] }));
119
118
  client.setRequestHandler(mcpBundle.PingRequestSchema, () => ({}));
120
119
  const transport = await factory.connect();
121
120
  await client.connect(transport);
@@ -30,10 +30,12 @@ var server_exports = {};
30
30
  __export(server_exports, {
31
31
  connect: () => connect,
32
32
  createServer: () => createServer,
33
+ firstRootPath: () => firstRootPath,
33
34
  start: () => start,
34
35
  wrapInProcess: () => wrapInProcess
35
36
  });
36
37
  module.exports = __toCommonJS(server_exports);
38
+ var import_url = require("url");
37
39
  var import_utilsBundle = require("playwright-core/lib/utilsBundle");
38
40
  var mcpBundle = __toESM(require("./bundle"));
39
41
  var import_http = require("./http");
@@ -83,8 +85,13 @@ const initializeServer = async (server, backend, runHeartbeat) => {
83
85
  const { roots } = await server.listRoots();
84
86
  clientRoots = roots;
85
87
  }
86
- const clientVersion = server.getClientVersion() ?? { name: "unknown", version: "unknown" };
87
- await backend.initialize?.(server, clientVersion, clientRoots);
88
+ const clientInfo = {
89
+ name: server.getClientVersion()?.name ?? "unknown",
90
+ version: server.getClientVersion()?.version ?? "unknown",
91
+ roots: clientRoots,
92
+ timestamp: Date.now()
93
+ };
94
+ await backend.initialize?.(server, clientInfo);
88
95
  if (runHeartbeat)
89
96
  startHeartbeat(server);
90
97
  };
@@ -128,10 +135,18 @@ async function start(serverBackendFactory, options) {
128
135
  ].join("\n");
129
136
  console.error(message);
130
137
  }
138
+ function firstRootPath(clientInfo) {
139
+ if (clientInfo.roots.length === 0)
140
+ return void 0;
141
+ const firstRootUri = clientInfo.roots[0]?.uri;
142
+ const url = firstRootUri ? new URL(firstRootUri) : void 0;
143
+ return url ? (0, import_url.fileURLToPath)(url) : void 0;
144
+ }
131
145
  // Annotate the CommonJS export names for ESM import in node:
132
146
  0 && (module.exports = {
133
147
  connect,
134
148
  createServer,
149
+ firstRootPath,
135
150
  start,
136
151
  wrapInProcess
137
152
  });
@@ -31,7 +31,6 @@ __export(testBackend_exports, {
31
31
  TestServerBackend: () => TestServerBackend
32
32
  });
33
33
  module.exports = __toCommonJS(testBackend_exports);
34
- var import_url = require("url");
35
34
  var mcp = __toESM(require("../sdk/exports"));
36
35
  var import_testContext = require("./testContext");
37
36
  var import_testTools = require("./testTools.js");
@@ -45,19 +44,15 @@ class TestServerBackend {
45
44
  this._context = new import_testContext.TestContext(options);
46
45
  this._configOption = configOption;
47
46
  }
48
- async initialize(server, clientVersion, roots) {
47
+ async initialize(server, clientInfo) {
49
48
  if (this._configOption) {
50
49
  this._context.setConfigLocation((0, import_configLoader.resolveConfigLocation)(this._configOption));
51
50
  return;
52
51
  }
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
- }
52
+ const rootPath = mcp.firstRootPath(clientInfo);
53
+ if (rootPath) {
54
+ this._context.setConfigLocation((0, import_configLoader.resolveConfigLocation)(rootPath));
55
+ return;
61
56
  }
62
57
  throw new Error("No config option or MCP root path provided");
63
58
  }
@@ -50,12 +50,10 @@ class VSCodeProxyBackend {
50
50
  this._defaultTransportFactory = _defaultTransportFactory;
51
51
  this.name = "Playwright MCP Client Switcher";
52
52
  this.version = packageJSON.version;
53
- this._roots = [];
54
53
  this._contextSwitchTool = this._defineContextSwitchTool();
55
54
  }
56
- async initialize(server, clientVersion, roots) {
57
- this._clientVersion = clientVersion;
58
- this._roots = roots;
55
+ async initialize(server, clientInfo) {
56
+ this._clientInfo = clientInfo;
59
57
  const transport = await this._defaultTransportFactory(this);
60
58
  await this._setCurrentClient(transport);
61
59
  }
@@ -147,13 +145,13 @@ class VSCodeProxyBackend {
147
145
  async _setCurrentClient(transport) {
148
146
  await this._currentClient?.close();
149
147
  this._currentClient = void 0;
150
- const client = new mcpBundle.Client(this._clientVersion);
148
+ const client = new mcpBundle.Client({ name: this._clientInfo.name, version: this._clientInfo.version });
151
149
  client.registerCapabilities({
152
150
  roots: {
153
151
  listRoots: true
154
152
  }
155
153
  });
156
- client.setRequestHandler(mcpBundle.ListRootsRequestSchema, () => ({ roots: this._roots }));
154
+ client.setRequestHandler(mcpBundle.ListRootsRequestSchema, () => ({ roots: this._clientInfo.roots }));
157
155
  client.setRequestHandler(mcpBundle.PingRequestSchema, () => ({}));
158
156
  await client.connect(transport);
159
157
  this._currentClient = client;
package/lib/program.js CHANGED
@@ -175,9 +175,12 @@ function addInitAgentsCommand(program3) {
175
175
  command.description("Initialize repository agents for the Claude Code");
176
176
  command.option("--claude", "Initialize repository agents for the Claude Code");
177
177
  command.option("--opencode", "Initialize repository agents for the Opencode");
178
+ command.option("--vscode", "Initialize repository agents for the VS Code Copilot");
178
179
  command.action(async (opts) => {
179
180
  if (opts.opencode)
180
181
  await (0, import_generateAgents.initOpencodeRepo)();
182
+ else if (opts.vscode)
183
+ await (0, import_generateAgents.initVSCodeRepo)();
181
184
  else
182
185
  await (0, import_generateAgents.initClaudeCodeRepo)();
183
186
  });
@@ -113,7 +113,17 @@ class HtmlReporter {
113
113
  else if (process.env.PLAYWRIGHT_HTML_NO_SNIPPETS)
114
114
  noSnippets = true;
115
115
  noSnippets = noSnippets || this._options.noSnippets;
116
- const builder = new HtmlBuilder(this.config, this._outputFolder, this._attachmentsBaseURL, process.env.PLAYWRIGHT_HTML_TITLE || this._options.title, noSnippets);
116
+ let noCopyPrompt;
117
+ if (process.env.PLAYWRIGHT_HTML_NO_COPY_PROMPT === "false" || process.env.PLAYWRIGHT_HTML_NO_COPY_PROMPT === "0")
118
+ noCopyPrompt = false;
119
+ else if (process.env.PLAYWRIGHT_HTML_NO_COPY_PROMPT)
120
+ noCopyPrompt = true;
121
+ noCopyPrompt = noCopyPrompt || this._options.noCopyPrompt;
122
+ const builder = new HtmlBuilder(this.config, this._outputFolder, this._attachmentsBaseURL, {
123
+ title: process.env.PLAYWRIGHT_HTML_TITLE || this._options.title,
124
+ noSnippets,
125
+ noCopyPrompt
126
+ });
117
127
  this._buildResult = await builder.build(this.config.metadata, projectSuites, result, this._topLevelErrors);
118
128
  }
119
129
  async onExit() {
@@ -197,16 +207,15 @@ function startHtmlReportServer(folder) {
197
207
  return server;
198
208
  }
199
209
  class HtmlBuilder {
200
- constructor(config, outputDir, attachmentsBaseURL, title, noSnippets = false) {
210
+ constructor(config, outputDir, attachmentsBaseURL, options) {
201
211
  this._stepsInFile = new import_utils.MultiMap();
202
212
  this._hasTraces = false;
203
213
  this._config = config;
204
214
  this._reportFolder = outputDir;
205
- this._noSnippets = noSnippets;
215
+ this._options = options;
206
216
  import_fs.default.mkdirSync(this._reportFolder, { recursive: true });
207
217
  this._dataZipFile = new import_zipBundle.yazl.ZipFile();
208
218
  this._attachmentsBaseURL = attachmentsBaseURL;
209
- this._title = title;
210
219
  }
211
220
  async build(metadata, projectSuites, result, topLevelErrors) {
212
221
  const data = /* @__PURE__ */ new Map();
@@ -231,7 +240,7 @@ class HtmlBuilder {
231
240
  }
232
241
  }
233
242
  }
234
- if (!this._noSnippets)
243
+ if (!this._options.noSnippets)
235
244
  createSnippets(this._stepsInFile);
236
245
  let ok = true;
237
246
  for (const [fileId, { testFile, testFileSummary }] of data) {
@@ -260,13 +269,13 @@ class HtmlBuilder {
260
269
  }
261
270
  const htmlReport = {
262
271
  metadata,
263
- title: this._title,
264
272
  startTime: result.startTime.getTime(),
265
273
  duration: result.duration,
266
274
  files: [...data.values()].map((e) => e.testFileSummary),
267
275
  projectNames: projectSuites.map((r) => r.project().name),
268
276
  stats: { ...[...data.values()].reduce((a, e) => addStats(a, e.testFileSummary.stats), emptyStats()) },
269
- errors: topLevelErrors.map((error) => (0, import_base.formatError)(import_base.internalScreen, error).message)
277
+ errors: topLevelErrors.map((error) => (0, import_base.formatError)(import_base.internalScreen, error).message),
278
+ options: this._options
270
279
  };
271
280
  htmlReport.files.sort((f1, f2) => {
272
281
  const w1 = f1.stats.unexpected * 1e3 + f1.stats.flaky;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "playwright",
3
- "version": "1.56.0-alpha-1758061937000",
3
+ "version": "1.56.0-alpha-2025-09-18",
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-1758061937000"
67
+ "playwright-core": "1.56.0-alpha-2025-09-18"
68
68
  },
69
69
  "optionalDependencies": {
70
70
  "fsevents": "2.3.2"
package/types/test.d.ts CHANGED
@@ -22,7 +22,16 @@ export type BlobReporterOptions = { outputDir?: string, fileName?: string };
22
22
  export type ListReporterOptions = { printSteps?: boolean };
23
23
  export type JUnitReporterOptions = { outputFile?: string, stripANSIControlSequences?: boolean, includeProjectInTestName?: boolean };
24
24
  export type JsonReporterOptions = { outputFile?: string };
25
- export type HtmlReporterOptions = { outputFolder?: string, open?: 'always' | 'never' | 'on-failure', host?: string, port?: number, attachmentsBaseURL?: string, title?: string, noSnippets?: boolean };
25
+ export type HtmlReporterOptions = {
26
+ outputFolder?: string;
27
+ open?: 'always' | 'never' | 'on-failure';
28
+ host?: string;
29
+ port?: number;
30
+ attachmentsBaseURL?: string;
31
+ title?: string;
32
+ noSnippets?: boolean;
33
+ noCopyPrompt?: boolean;
34
+ };
26
35
 
27
36
  export type ReporterDescription = Readonly<
28
37
  ['blob'] | ['blob', BlobReporterOptions] |
@@ -1,102 +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 __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
- });