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.
- package/lib/agents/generateAgents.js +78 -32
- package/lib/agents/generator.md +17 -22
- package/lib/agents/{fixer.md → healer.md} +13 -19
- package/lib/agents/planner.md +19 -24
- package/lib/mcp/browser/browserContextFactory.js +31 -36
- package/lib/mcp/browser/browserServerBackend.js +3 -10
- package/lib/mcp/browser/config.js +4 -2
- package/lib/mcp/browser/context.js +1 -1
- package/lib/mcp/browser/sessionLog.js +2 -2
- package/lib/mcp/sdk/mdb.js +8 -8
- package/lib/mcp/sdk/proxyBackend.js +3 -4
- package/lib/mcp/sdk/server.js +17 -2
- package/lib/mcp/test/testBackend.js +5 -10
- package/lib/mcp/vscode/host.js +4 -6
- package/lib/program.js +3 -0
- package/lib/reporters/html.js +16 -7
- package/package.json +2 -2
- package/types/test.d.ts +10 -1
- package/lib/mcp/browser/processUtils.js +0 -102
|
@@ -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"
|
|
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(
|
|
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}
|
|
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(
|
|
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,
|
|
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}
|
|
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,
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
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
|
});
|
package/lib/agents/generator.md
CHANGED
|
@@ -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-
|
|
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
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
72
|
+
playwright-test-healer agent.
|
|
79
73
|
</commentary>
|
|
80
74
|
</example>
|
package/lib/agents/planner.md
CHANGED
|
@@ -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
|
|
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
|
|
157
|
-
const tracesDir = await (0, import_config.outputFile)(this.config, clientInfo
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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(
|
|
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,
|
|
38
|
-
|
|
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
|
|
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,
|
|
219
|
-
const
|
|
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
|
|
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,
|
|
47
|
-
const sessionFolder = await (0, import_config.outputFile)(config,
|
|
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);
|
package/lib/mcp/sdk/mdb.js
CHANGED
|
@@ -48,9 +48,9 @@ class MDBBackend {
|
|
|
48
48
|
this._stack = [];
|
|
49
49
|
this._topLevelBackend = topLevelBackend;
|
|
50
50
|
}
|
|
51
|
-
async initialize(server,
|
|
52
|
-
if (!this.
|
|
53
|
-
this.
|
|
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: "
|
|
107
|
-
client.setRequestHandler(mcpBundle.ListRootsRequestSchema, () => ({ roots: this.
|
|
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: "
|
|
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,
|
|
187
|
-
await this._backend.initialize?.(server,
|
|
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,
|
|
45
|
-
this.
|
|
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.
|
|
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);
|
package/lib/mcp/sdk/server.js
CHANGED
|
@@ -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
|
|
87
|
-
|
|
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,
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
}
|
package/lib/mcp/vscode/host.js
CHANGED
|
@@ -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,
|
|
57
|
-
this.
|
|
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.
|
|
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.
|
|
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
|
});
|
package/lib/reporters/html.js
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
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.
|
|
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.
|
|
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-
|
|
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-
|
|
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 = {
|
|
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
|
-
});
|