playwright 1.56.0-alpha-2025-09-17 → 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 +25 -32
- 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,7 +38,6 @@ 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");
|
|
44
43
|
var import_server2 = require("../sdk/server");
|
|
@@ -162,32 +161,32 @@ class PersistentContextFactory {
|
|
|
162
161
|
(0, import_log.testDebug)("lock user data dir", userDataDir);
|
|
163
162
|
const browserType = playwright[this.config.browser.browserName];
|
|
164
163
|
for (let i = 0; i < 5; i++) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
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
|
+
}
|
|
190
188
|
}
|
|
189
|
+
throw new Error(`Browser is already in use for ${userDataDir}, use --isolated to run multiple instances of the same browser`);
|
|
191
190
|
}
|
|
192
191
|
async _closeBrowserContext(browserContext, userDataDir) {
|
|
193
192
|
(0, import_log.testDebug)("close browser context (persistent)");
|
|
@@ -207,12 +206,6 @@ class PersistentContextFactory {
|
|
|
207
206
|
return result;
|
|
208
207
|
}
|
|
209
208
|
}
|
|
210
|
-
async function alreadyRunning(config, browserType, userDataDir) {
|
|
211
|
-
const execPath = config.browser.launchOptions.executablePath ?? (0, import_processUtils.getBrowserExecPath)(config.browser.launchOptions.channel ?? browserType.name());
|
|
212
|
-
if (!execPath)
|
|
213
|
-
return false;
|
|
214
|
-
return !!(0, import_processUtils.findBrowserProcess)(execPath, userDataDir);
|
|
215
|
-
}
|
|
216
209
|
async function injectCdpPort(browserConfig) {
|
|
217
210
|
if (browserConfig.browserName === "chromium")
|
|
218
211
|
browserConfig.launchOptions.cdpPort = await findFreePort();
|
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-2025-09-
|
|
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-2025-09-
|
|
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
|
-
});
|