playwright 1.57.0-alpha-2025-11-10 → 1.57.0-alpha-2025-11-12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/agents/generateAgents.js +22 -21
- package/lib/agents/playwright-test-planner.agent.md +2 -47
- package/lib/mcp/browser/config.js +4 -0
- package/lib/mcp/browser/context.js +1 -0
- package/lib/mcp/browser/tab.js +12 -3
- package/lib/mcp/browser/tools/runCode.js +75 -0
- package/lib/mcp/browser/tools.js +2 -0
- package/lib/mcp/program.js +1 -1
- package/lib/mcp/test/plannerTools.js +100 -2
- package/lib/mcp/test/testBackend.js +2 -0
- package/lib/mcp/test/testContext.js +6 -1
- package/lib/mcp/test/testTools.js +1 -1
- package/lib/worker/testTracing.js +1 -0
- package/package.json +2 -2
|
@@ -207,24 +207,11 @@ class CopilotGenerator {
|
|
|
207
207
|
await deleteFile(`.github/chatmodes/ \u{1F3AD} planner.chatmode.md`, "legacy planner chatmode");
|
|
208
208
|
await deleteFile(`.github/chatmodes/\u{1F3AD} generator.chatmode.md`, "legacy generator chatmode");
|
|
209
209
|
await deleteFile(`.github/chatmodes/\u{1F3AD} healer.chatmode.md`, "legacy healer chatmode");
|
|
210
|
+
await deleteFile(`.github/agents/ \u{1F3AD} planner.agent.md`, "legacy planner agent");
|
|
211
|
+
await deleteFile(`.github/agents/\u{1F3AD} generator.agent.md`, "legacy generator agent");
|
|
212
|
+
await deleteFile(`.github/agents/\u{1F3AD} healer.agent.md`, "legacy healer agent");
|
|
210
213
|
await VSCodeGenerator.appendToMCPJson();
|
|
211
|
-
const
|
|
212
|
-
const mcpConfig = {
|
|
213
|
-
"mcpServers": {
|
|
214
|
-
"playwright-test": {
|
|
215
|
-
"type": "stdio",
|
|
216
|
-
"command": "npx",
|
|
217
|
-
"args": [
|
|
218
|
-
`--prefix=/home/runner/work/${cwdFolder}/${cwdFolder}`,
|
|
219
|
-
"playwright",
|
|
220
|
-
"run-test-mcp-server",
|
|
221
|
-
"--headless",
|
|
222
|
-
`--config=/home/runner/work/${cwdFolder}/${cwdFolder}`
|
|
223
|
-
],
|
|
224
|
-
"tools": ["*"]
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
};
|
|
214
|
+
const mcpConfig = { mcpServers: CopilotGenerator.mcpServers };
|
|
228
215
|
if (!import_fs.default.existsSync(".github/copilot-setup-steps.yml")) {
|
|
229
216
|
const yaml2 = import_fs.default.readFileSync(import_path.default.join(__dirname, "copilot-setup-steps.yml"), "utf-8");
|
|
230
217
|
await writeFile(".github/workflows/copilot-setup-steps.yml", yaml2, "\u{1F527}", "GitHub Copilot setup steps");
|
|
@@ -241,10 +228,11 @@ class CopilotGenerator {
|
|
|
241
228
|
const examples = agent.examples.length ? ` Examples: ${agent.examples.map((example) => `<example>${example}</example>`).join("")}` : "";
|
|
242
229
|
const lines = [];
|
|
243
230
|
const header = {
|
|
244
|
-
name: agent.header.name,
|
|
245
|
-
description: agent.header.description + examples,
|
|
246
|
-
tools: agent.header.tools,
|
|
247
|
-
model: "Claude Sonnet 4"
|
|
231
|
+
"name": agent.header.name,
|
|
232
|
+
"description": agent.header.description + examples,
|
|
233
|
+
"tools": agent.header.tools,
|
|
234
|
+
"model": "Claude Sonnet 4",
|
|
235
|
+
"mcp-servers": CopilotGenerator.mcpServers
|
|
248
236
|
};
|
|
249
237
|
lines.push(`---`);
|
|
250
238
|
lines.push(import_utilsBundle.yaml.stringify(header) + `---`);
|
|
@@ -253,6 +241,19 @@ class CopilotGenerator {
|
|
|
253
241
|
lines.push("");
|
|
254
242
|
return lines.join("\n");
|
|
255
243
|
}
|
|
244
|
+
static {
|
|
245
|
+
this.mcpServers = {
|
|
246
|
+
"playwright-test": {
|
|
247
|
+
"type": "stdio",
|
|
248
|
+
"command": "npx",
|
|
249
|
+
"args": [
|
|
250
|
+
"playwright",
|
|
251
|
+
"run-test-mcp-server"
|
|
252
|
+
],
|
|
253
|
+
"tools": ["*"]
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
}
|
|
256
257
|
}
|
|
257
258
|
class VSCodeGenerator {
|
|
258
259
|
static async init(config, projectName) {
|
|
@@ -5,7 +5,6 @@ model: sonnet
|
|
|
5
5
|
color: green
|
|
6
6
|
tools:
|
|
7
7
|
- search
|
|
8
|
-
- edit
|
|
9
8
|
- playwright-test/browser_click
|
|
10
9
|
- playwright-test/browser_close
|
|
11
10
|
- playwright-test/browser_console_messages
|
|
@@ -24,6 +23,7 @@ tools:
|
|
|
24
23
|
- playwright-test/browser_type
|
|
25
24
|
- playwright-test/browser_wait_for
|
|
26
25
|
- playwright-test/planner_setup_page
|
|
26
|
+
- playwright-test/planner_save_plan
|
|
27
27
|
---
|
|
28
28
|
|
|
29
29
|
You are an expert web test planner with extensive experience in quality assurance, user experience testing, and test
|
|
@@ -61,52 +61,7 @@ You will:
|
|
|
61
61
|
|
|
62
62
|
5. **Create Documentation**
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
- Executive summary of the tested page/application
|
|
66
|
-
- Individual scenarios as separate sections
|
|
67
|
-
- Each scenario formatted with numbered steps
|
|
68
|
-
- Each test case with proposed file name for implementation
|
|
69
|
-
- Clear expected results for verification
|
|
70
|
-
|
|
71
|
-
<example-spec>
|
|
72
|
-
# TodoMVC Application - Comprehensive Test Plan
|
|
73
|
-
|
|
74
|
-
## Application Overview
|
|
75
|
-
|
|
76
|
-
The TodoMVC application is a React-based todo list manager that provides core task management functionality. The
|
|
77
|
-
application features:
|
|
78
|
-
|
|
79
|
-
- **Task Management**: Add, edit, complete, and delete individual todos
|
|
80
|
-
- **Bulk Operations**: Mark all todos as complete/incomplete and clear all completed todos
|
|
81
|
-
- **Filtering**: View todos by All, Active, or Completed status
|
|
82
|
-
- **URL Routing**: Support for direct navigation to filtered views via URLs
|
|
83
|
-
- **Counter Display**: Real-time count of active (incomplete) todos
|
|
84
|
-
- **Persistence**: State maintained during session (browser refresh behavior not tested)
|
|
85
|
-
|
|
86
|
-
## Test Scenarios
|
|
87
|
-
|
|
88
|
-
### 1. Adding New Todos
|
|
89
|
-
|
|
90
|
-
**Seed:** `tests/seed.spec.ts`
|
|
91
|
-
|
|
92
|
-
#### 1.1 Add Valid Todo
|
|
93
|
-
|
|
94
|
-
**File** `tests/adding-new-todos/add-valid-todo.spec.ts`
|
|
95
|
-
|
|
96
|
-
**Steps:**
|
|
97
|
-
1. Click in the "What needs to be done?" input field
|
|
98
|
-
2. Type "Buy groceries"
|
|
99
|
-
3. Press Enter key
|
|
100
|
-
|
|
101
|
-
**Expected Results:**
|
|
102
|
-
- Todo appears in the list with unchecked checkbox
|
|
103
|
-
- Counter shows "1 item left"
|
|
104
|
-
- Input field is cleared and ready for next entry
|
|
105
|
-
- Todo list controls become visible (Mark all as complete checkbox)
|
|
106
|
-
|
|
107
|
-
#### 1.2
|
|
108
|
-
...
|
|
109
|
-
</example-spec>
|
|
64
|
+
Submit your test plan using `planner_save_plan` tool.
|
|
110
65
|
|
|
111
66
|
**Quality Standards**:
|
|
112
67
|
- Write steps that are specific enough for any tester to follow
|
|
@@ -164,6 +164,7 @@ function configFromCLIOptions(cliOptions) {
|
|
|
164
164
|
contextOptions,
|
|
165
165
|
cdpEndpoint: cliOptions.cdpEndpoint,
|
|
166
166
|
cdpHeaders: cliOptions.cdpHeader,
|
|
167
|
+
initPage: cliOptions.initPage,
|
|
167
168
|
initScript: cliOptions.initScript
|
|
168
169
|
},
|
|
169
170
|
server: {
|
|
@@ -208,6 +209,9 @@ function configFromEnv() {
|
|
|
208
209
|
options.headless = envToBoolean(process.env.PLAYWRIGHT_MCP_HEADLESS);
|
|
209
210
|
options.host = envToString(process.env.PLAYWRIGHT_MCP_HOST);
|
|
210
211
|
options.ignoreHttpsErrors = envToBoolean(process.env.PLAYWRIGHT_MCP_IGNORE_HTTPS_ERRORS);
|
|
212
|
+
const initPage = envToString(process.env.PLAYWRIGHT_MCP_INIT_PAGE);
|
|
213
|
+
if (initPage)
|
|
214
|
+
options.initPage = [initPage];
|
|
211
215
|
const initScript = envToString(process.env.PLAYWRIGHT_MCP_INIT_SCRIPT);
|
|
212
216
|
if (initScript)
|
|
213
217
|
options.initScript = [initScript];
|
|
@@ -75,6 +75,7 @@ class Context {
|
|
|
75
75
|
const { browserContext } = await this._ensureBrowserContext();
|
|
76
76
|
const page = await browserContext.newPage();
|
|
77
77
|
this._currentTab = this._tabs.find((t) => t.page === page);
|
|
78
|
+
await this._currentTab.initializedPromise;
|
|
78
79
|
return this._currentTab;
|
|
79
80
|
}
|
|
80
81
|
async selectTab(index) {
|
package/lib/mcp/browser/tab.js
CHANGED
|
@@ -29,6 +29,7 @@ var import_utils2 = require("./tools/utils");
|
|
|
29
29
|
var import_log = require("../log");
|
|
30
30
|
var import_dialogs = require("./tools/dialogs");
|
|
31
31
|
var import_files = require("./tools/files");
|
|
32
|
+
var import_transform = require("../../transform/transform");
|
|
32
33
|
const TabEvents = {
|
|
33
34
|
modalState: "modalState"
|
|
34
35
|
};
|
|
@@ -64,7 +65,7 @@ class Tab extends import_events.EventEmitter {
|
|
|
64
65
|
page.setDefaultNavigationTimeout(this.context.config.timeouts.navigation);
|
|
65
66
|
page.setDefaultTimeout(this.context.config.timeouts.action);
|
|
66
67
|
page[tabSymbol] = this;
|
|
67
|
-
this.
|
|
68
|
+
this.initializedPromise = this._initialize();
|
|
68
69
|
}
|
|
69
70
|
static forPage(page) {
|
|
70
71
|
return page[tabSymbol];
|
|
@@ -85,6 +86,14 @@ class Tab extends import_events.EventEmitter {
|
|
|
85
86
|
const requests = await this.page.requests().catch(() => []);
|
|
86
87
|
for (const request of requests)
|
|
87
88
|
this._requests.add(request);
|
|
89
|
+
for (const initPage of this.context.config.browser.initPage || []) {
|
|
90
|
+
try {
|
|
91
|
+
const { default: func } = await (0, import_transform.requireOrImport)(initPage);
|
|
92
|
+
await func({ page: this.page });
|
|
93
|
+
} catch (e) {
|
|
94
|
+
(0, import_log.logUnhandledError)(e);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
88
97
|
}
|
|
89
98
|
modalStates() {
|
|
90
99
|
return this._modalStates;
|
|
@@ -166,11 +175,11 @@ class Tab extends import_events.EventEmitter {
|
|
|
166
175
|
await this.waitForLoadState("load", { timeout: 5e3 });
|
|
167
176
|
}
|
|
168
177
|
async consoleMessages(type) {
|
|
169
|
-
await this.
|
|
178
|
+
await this.initializedPromise;
|
|
170
179
|
return this._consoleMessages.filter((message) => type ? message.type === type : true);
|
|
171
180
|
}
|
|
172
181
|
async requests() {
|
|
173
|
-
await this.
|
|
182
|
+
await this.initializedPromise;
|
|
174
183
|
return this._requests;
|
|
175
184
|
}
|
|
176
185
|
async captureSnapshot() {
|
|
@@ -0,0 +1,75 @@
|
|
|
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 runCode_exports = {};
|
|
30
|
+
__export(runCode_exports, {
|
|
31
|
+
default: () => runCode_default
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(runCode_exports);
|
|
34
|
+
var import_vm = __toESM(require("vm"));
|
|
35
|
+
var import_utils = require("playwright-core/lib/utils");
|
|
36
|
+
var import_bundle = require("../../sdk/bundle");
|
|
37
|
+
var import_tool = require("./tool");
|
|
38
|
+
const codeSchema = import_bundle.z.object({
|
|
39
|
+
code: import_bundle.z.string().describe(`Playwright code snippet to run. The snippet should access the \`page\` object to interact with the page. Can make multiple statements. For example: \`await page.getByRole('button', { name: 'Submit' }).click();\``)
|
|
40
|
+
});
|
|
41
|
+
const runCode = (0, import_tool.defineTabTool)({
|
|
42
|
+
capability: "core",
|
|
43
|
+
schema: {
|
|
44
|
+
name: "browser_run_code",
|
|
45
|
+
title: "Run Playwright code",
|
|
46
|
+
description: "Run Playwright code snippet",
|
|
47
|
+
inputSchema: codeSchema,
|
|
48
|
+
type: "action"
|
|
49
|
+
},
|
|
50
|
+
handle: async (tab, params, response) => {
|
|
51
|
+
response.setIncludeSnapshot();
|
|
52
|
+
response.addCode(params.code);
|
|
53
|
+
const __end__ = new import_utils.ManualPromise();
|
|
54
|
+
const context = {
|
|
55
|
+
page: tab.page,
|
|
56
|
+
__end__
|
|
57
|
+
};
|
|
58
|
+
import_vm.default.createContext(context);
|
|
59
|
+
await tab.waitForCompletion(async () => {
|
|
60
|
+
const snippet = `(async () => {
|
|
61
|
+
try {
|
|
62
|
+
${params.code};
|
|
63
|
+
__end__.resolve();
|
|
64
|
+
} catch (e) {
|
|
65
|
+
__end__.reject(e);
|
|
66
|
+
}
|
|
67
|
+
})()`;
|
|
68
|
+
import_vm.default.runInContext(snippet, context);
|
|
69
|
+
await __end__;
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
var runCode_default = [
|
|
74
|
+
runCode
|
|
75
|
+
];
|
package/lib/mcp/browser/tools.js
CHANGED
|
@@ -44,6 +44,7 @@ var import_mouse = __toESM(require("./tools/mouse"));
|
|
|
44
44
|
var import_navigate = __toESM(require("./tools/navigate"));
|
|
45
45
|
var import_network = __toESM(require("./tools/network"));
|
|
46
46
|
var import_pdf = __toESM(require("./tools/pdf"));
|
|
47
|
+
var import_runCode = __toESM(require("./tools/runCode"));
|
|
47
48
|
var import_snapshot = __toESM(require("./tools/snapshot"));
|
|
48
49
|
var import_screenshot = __toESM(require("./tools/screenshot"));
|
|
49
50
|
var import_tabs = __toESM(require("./tools/tabs"));
|
|
@@ -63,6 +64,7 @@ const browserTools = [
|
|
|
63
64
|
...import_network.default,
|
|
64
65
|
...import_mouse.default,
|
|
65
66
|
...import_pdf.default,
|
|
67
|
+
...import_runCode.default,
|
|
66
68
|
...import_screenshot.default,
|
|
67
69
|
...import_snapshot.default,
|
|
68
70
|
...import_tabs.default,
|
package/lib/mcp/program.js
CHANGED
|
@@ -42,7 +42,7 @@ var import_proxyBackend = require("./sdk/proxyBackend");
|
|
|
42
42
|
var import_browserServerBackend = require("./browser/browserServerBackend");
|
|
43
43
|
var import_extensionContextFactory = require("./extension/extensionContextFactory");
|
|
44
44
|
function decorateCommand(command, version) {
|
|
45
|
-
command.option("--allowed-hosts <hosts...>", "comma-separated list of hosts this server is allowed to serve from. Defaults to the host the server is bound to. Pass '*' to disable the host check.", import_config.commaSeparatedList).option("--allowed-origins <origins>", "semicolon-separated list of origins to allow the browser to request. Default is to allow all.", import_config.semicolonSeparatedList).option("--blocked-origins <origins>", "semicolon-separated list of origins to block the browser from requesting. Blocklist is evaluated before allowlist. If used without the allowlist, requests not matching the blocklist are still allowed.", import_config.semicolonSeparatedList).option("--block-service-workers", "block service workers").option("--browser <browser>", "browser or chrome channel to use, possible values: chrome, firefox, webkit, msedge.").option("--caps <caps>", "comma-separated list of additional capabilities to enable, possible values: vision, pdf.", import_config.commaSeparatedList).option("--cdp-endpoint <endpoint>", "CDP endpoint to connect to.").option("--cdp-header <headers...>", "CDP headers to send with the connect request, multiple can be specified.", import_config.headerParser).option("--config <path>", "path to the configuration file.").option("--device <device>", 'device to emulate, for example: "iPhone 15"').option("--executable-path <path>", "path to the browser executable.").option("--extension", 'Connect to a running browser instance (Edge/Chrome only). Requires the "Playwright MCP Bridge" browser extension to be installed.').option("--grant-permissions <permissions...>", 'List of permissions to grant to the browser context, for example "geolocation", "clipboard-read", "clipboard-write".', import_config.commaSeparatedList).option("--headless", "run browser in headless mode, headed by default").option("--host <host>", "host to bind server to. Default is localhost. Use 0.0.0.0 to bind to all interfaces.").option("--ignore-https-errors", "ignore https errors").option("--init-script <path...>", "path to JavaScript file to add as an initialization script. The script will be evaluated in every page before any of the page's scripts. Can be specified multiple times.").option("--isolated", "keep the browser profile in memory, do not save it to disk.").option("--image-responses <mode>", 'whether to send image responses to the client. Can be "allow" or "omit", Defaults to "allow".').option("--no-sandbox", "disable the sandbox for all process types that are normally sandboxed.").option("--output-dir <path>", "path to the directory for output files.").option("--port <port>", "port to listen on for SSE transport.").option("--proxy-bypass <bypass>", 'comma-separated domains to bypass proxy, for example ".com,chromium.org,.domain.com"').option("--proxy-server <proxy>", 'specify proxy server, for example "http://myproxy:3128" or "socks5://myproxy:8080"').option("--save-session", "Whether to save the Playwright MCP session into the output directory.").option("--save-trace", "Whether to save the Playwright Trace of the session into the output directory.").option("--save-video <size>", 'Whether to save the video of the session into the output directory. For example "--save-video=800x600"', import_config.resolutionParser.bind(null, "--save-video")).option("--secrets <path>", "path to a file containing secrets in the dotenv format", import_config.dotenvFileLoader).option("--shared-browser-context", "reuse the same browser context between all connected HTTP clients.").option("--storage-state <path>", "path to the storage state file for isolated sessions.").option("--test-id-attribute <attribute>", 'specify the attribute to use for test ids, defaults to "data-testid"').option("--timeout-action <timeout>", "specify action timeout in milliseconds, defaults to 5000ms", import_config.numberParser).option("--timeout-navigation <timeout>", "specify navigation timeout in milliseconds, defaults to 60000ms", import_config.numberParser).option("--user-agent <ua string>", "specify user agent string").option("--user-data-dir <path>", "path to the user data directory. If not specified, a temporary directory will be created.").option("--viewport-size <size>", 'specify browser viewport size in pixels, for example "1280x720"', import_config.resolutionParser.bind(null, "--viewport-size")).addOption(new import_utilsBundle.ProgramOption("--connect-tool", "Allow to switch between different browser connection methods.").hideHelp()).addOption(new import_utilsBundle.ProgramOption("--vision", "Legacy option, use --caps=vision instead").hideHelp()).action(async (options) => {
|
|
45
|
+
command.option("--allowed-hosts <hosts...>", "comma-separated list of hosts this server is allowed to serve from. Defaults to the host the server is bound to. Pass '*' to disable the host check.", import_config.commaSeparatedList).option("--allowed-origins <origins>", "semicolon-separated list of origins to allow the browser to request. Default is to allow all.", import_config.semicolonSeparatedList).option("--blocked-origins <origins>", "semicolon-separated list of origins to block the browser from requesting. Blocklist is evaluated before allowlist. If used without the allowlist, requests not matching the blocklist are still allowed.", import_config.semicolonSeparatedList).option("--block-service-workers", "block service workers").option("--browser <browser>", "browser or chrome channel to use, possible values: chrome, firefox, webkit, msedge.").option("--caps <caps>", "comma-separated list of additional capabilities to enable, possible values: vision, pdf.", import_config.commaSeparatedList).option("--cdp-endpoint <endpoint>", "CDP endpoint to connect to.").option("--cdp-header <headers...>", "CDP headers to send with the connect request, multiple can be specified.", import_config.headerParser).option("--config <path>", "path to the configuration file.").option("--device <device>", 'device to emulate, for example: "iPhone 15"').option("--executable-path <path>", "path to the browser executable.").option("--extension", 'Connect to a running browser instance (Edge/Chrome only). Requires the "Playwright MCP Bridge" browser extension to be installed.').option("--grant-permissions <permissions...>", 'List of permissions to grant to the browser context, for example "geolocation", "clipboard-read", "clipboard-write".', import_config.commaSeparatedList).option("--headless", "run browser in headless mode, headed by default").option("--host <host>", "host to bind server to. Default is localhost. Use 0.0.0.0 to bind to all interfaces.").option("--ignore-https-errors", "ignore https errors").option("--init-page <path...>", "path to TypeScript file to evaluate on Playwright page object").option("--init-script <path...>", "path to JavaScript file to add as an initialization script. The script will be evaluated in every page before any of the page's scripts. Can be specified multiple times.").option("--isolated", "keep the browser profile in memory, do not save it to disk.").option("--image-responses <mode>", 'whether to send image responses to the client. Can be "allow" or "omit", Defaults to "allow".').option("--no-sandbox", "disable the sandbox for all process types that are normally sandboxed.").option("--output-dir <path>", "path to the directory for output files.").option("--port <port>", "port to listen on for SSE transport.").option("--proxy-bypass <bypass>", 'comma-separated domains to bypass proxy, for example ".com,chromium.org,.domain.com"').option("--proxy-server <proxy>", 'specify proxy server, for example "http://myproxy:3128" or "socks5://myproxy:8080"').option("--save-session", "Whether to save the Playwright MCP session into the output directory.").option("--save-trace", "Whether to save the Playwright Trace of the session into the output directory.").option("--save-video <size>", 'Whether to save the video of the session into the output directory. For example "--save-video=800x600"', import_config.resolutionParser.bind(null, "--save-video")).option("--secrets <path>", "path to a file containing secrets in the dotenv format", import_config.dotenvFileLoader).option("--shared-browser-context", "reuse the same browser context between all connected HTTP clients.").option("--storage-state <path>", "path to the storage state file for isolated sessions.").option("--test-id-attribute <attribute>", 'specify the attribute to use for test ids, defaults to "data-testid"').option("--timeout-action <timeout>", "specify action timeout in milliseconds, defaults to 5000ms", import_config.numberParser).option("--timeout-navigation <timeout>", "specify navigation timeout in milliseconds, defaults to 60000ms", import_config.numberParser).option("--user-agent <ua string>", "specify user agent string").option("--user-data-dir <path>", "path to the user data directory. If not specified, a temporary directory will be created.").option("--viewport-size <size>", 'specify browser viewport size in pixels, for example "1280x720"', import_config.resolutionParser.bind(null, "--viewport-size")).addOption(new import_utilsBundle.ProgramOption("--connect-tool", "Allow to switch between different browser connection methods.").hideHelp()).addOption(new import_utilsBundle.ProgramOption("--vision", "Legacy option, use --caps=vision instead").hideHelp()).action(async (options) => {
|
|
46
46
|
(0, import_watchdog.setupExitWatchdog)();
|
|
47
47
|
if (options.vision) {
|
|
48
48
|
console.error("The --vision option is deprecated, use --caps=vision instead");
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,12 +17,24 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
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
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
var plannerTools_exports = {};
|
|
20
30
|
__export(plannerTools_exports, {
|
|
21
|
-
|
|
31
|
+
saveTestPlan: () => saveTestPlan,
|
|
32
|
+
setupPage: () => setupPage,
|
|
33
|
+
submitTestPlan: () => submitTestPlan
|
|
22
34
|
});
|
|
23
35
|
module.exports = __toCommonJS(plannerTools_exports);
|
|
36
|
+
var import_fs = __toESM(require("fs"));
|
|
37
|
+
var import_path = __toESM(require("path"));
|
|
24
38
|
var import_bundle = require("../sdk/bundle");
|
|
25
39
|
var import_testTool = require("./testTool");
|
|
26
40
|
const setupPage = (0, import_testTool.defineTestTool)({
|
|
@@ -40,7 +54,91 @@ const setupPage = (0, import_testTool.defineTestTool)({
|
|
|
40
54
|
return { content: [] };
|
|
41
55
|
}
|
|
42
56
|
});
|
|
57
|
+
const planSchema = import_bundle.z.object({
|
|
58
|
+
overview: import_bundle.z.string().describe("A brief overview of the application to be tested"),
|
|
59
|
+
suites: import_bundle.z.array(import_bundle.z.object({
|
|
60
|
+
name: import_bundle.z.string().describe("The name of the suite"),
|
|
61
|
+
seedFile: import_bundle.z.string().describe("A seed file that was used to setup the page for testing."),
|
|
62
|
+
tests: import_bundle.z.array(import_bundle.z.object({
|
|
63
|
+
name: import_bundle.z.string().describe("The name of the test"),
|
|
64
|
+
file: import_bundle.z.string().describe('The file the test should be saved to, for example: "tests/<suite-name>/<test-name>.spec.ts".'),
|
|
65
|
+
steps: import_bundle.z.array(import_bundle.z.string().describe(`The steps to be executed to perform the test. For example: 'Click on the "Submit" button'`)),
|
|
66
|
+
expectedResults: import_bundle.z.array(import_bundle.z.string().describe("The expected results of the steps for test to verify."))
|
|
67
|
+
}))
|
|
68
|
+
}))
|
|
69
|
+
});
|
|
70
|
+
const submitTestPlan = (0, import_testTool.defineTestTool)({
|
|
71
|
+
schema: {
|
|
72
|
+
name: "planner_submit_plan",
|
|
73
|
+
title: "Submit test plan",
|
|
74
|
+
description: "Submit the test plan to the test planner",
|
|
75
|
+
inputSchema: planSchema,
|
|
76
|
+
type: "readOnly"
|
|
77
|
+
},
|
|
78
|
+
handle: async (context, params) => {
|
|
79
|
+
return {
|
|
80
|
+
content: [{
|
|
81
|
+
type: "text",
|
|
82
|
+
text: JSON.stringify(params, null, 2)
|
|
83
|
+
}]
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
const saveTestPlan = (0, import_testTool.defineTestTool)({
|
|
88
|
+
schema: {
|
|
89
|
+
name: "planner_save_plan",
|
|
90
|
+
title: "Save test plan as markdown file",
|
|
91
|
+
description: "Save the test plan as a markdown file",
|
|
92
|
+
inputSchema: planSchema.extend({
|
|
93
|
+
name: import_bundle.z.string().describe('The name of the test plan, for example: "Test Plan".'),
|
|
94
|
+
fileName: import_bundle.z.string().describe('The file to save the test plan to, for example: "spec/test.plan.md". Relative to the workspace root.')
|
|
95
|
+
}),
|
|
96
|
+
type: "readOnly"
|
|
97
|
+
},
|
|
98
|
+
handle: async (context, params) => {
|
|
99
|
+
const lines = [];
|
|
100
|
+
lines.push(`# ${params.name}`);
|
|
101
|
+
lines.push(``);
|
|
102
|
+
lines.push(`## Application Overview`);
|
|
103
|
+
lines.push(``);
|
|
104
|
+
lines.push(params.overview);
|
|
105
|
+
lines.push(``);
|
|
106
|
+
lines.push(`## Test Scenarios`);
|
|
107
|
+
for (let i = 0; i < params.suites.length; i++) {
|
|
108
|
+
lines.push(``);
|
|
109
|
+
const suite = params.suites[i];
|
|
110
|
+
lines.push(`### ${i + 1}. ${suite.name}`);
|
|
111
|
+
lines.push(``);
|
|
112
|
+
lines.push(`**Seed:** \`${suite.seedFile}\``);
|
|
113
|
+
for (let j = 0; j < suite.tests.length; j++) {
|
|
114
|
+
lines.push(``);
|
|
115
|
+
const test = suite.tests[j];
|
|
116
|
+
lines.push(`#### ${i + 1}.${j + 1}. ${test.name}`);
|
|
117
|
+
lines.push(``);
|
|
118
|
+
lines.push(`**File:** \`${test.file}\``);
|
|
119
|
+
lines.push(``);
|
|
120
|
+
lines.push(`**Steps:**`);
|
|
121
|
+
for (let k = 0; k < test.steps.length; k++)
|
|
122
|
+
lines.push(` ${k + 1}. ${test.steps[k]}`);
|
|
123
|
+
lines.push(``);
|
|
124
|
+
lines.push(`**Expected Results:**`);
|
|
125
|
+
for (const result of test.expectedResults)
|
|
126
|
+
lines.push(` - ${result}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
lines.push(``);
|
|
130
|
+
await import_fs.default.promises.writeFile(import_path.default.resolve(context.rootPath, params.fileName), lines.join("\n"));
|
|
131
|
+
return {
|
|
132
|
+
content: [{
|
|
133
|
+
type: "text",
|
|
134
|
+
text: `Test plan saved to ${params.fileName}`
|
|
135
|
+
}]
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
});
|
|
43
139
|
// Annotate the CommonJS export names for ESM import in node:
|
|
44
140
|
0 && (module.exports = {
|
|
45
|
-
|
|
141
|
+
saveTestPlan,
|
|
142
|
+
setupPage,
|
|
143
|
+
submitTestPlan
|
|
46
144
|
});
|
|
@@ -44,7 +44,9 @@ class TestServerBackend {
|
|
|
44
44
|
this.name = "Playwright";
|
|
45
45
|
this.version = "0.0.1";
|
|
46
46
|
this._tools = [
|
|
47
|
+
plannerTools.saveTestPlan,
|
|
47
48
|
plannerTools.setupPage,
|
|
49
|
+
plannerTools.submitTestPlan,
|
|
48
50
|
generatorTools.setupPage,
|
|
49
51
|
generatorTools.generatorReadLog,
|
|
50
52
|
generatorTools.generatorWriteTest,
|
|
@@ -34,6 +34,7 @@ __export(testContext_exports, {
|
|
|
34
34
|
});
|
|
35
35
|
module.exports = __toCommonJS(testContext_exports);
|
|
36
36
|
var import_fs = __toESM(require("fs"));
|
|
37
|
+
var import_os = __toESM(require("os"));
|
|
37
38
|
var import_path = __toESM(require("path"));
|
|
38
39
|
var import_utils = require("playwright-core/lib/utils");
|
|
39
40
|
var import_base = require("../../reporters/base");
|
|
@@ -74,6 +75,10 @@ class TestContext {
|
|
|
74
75
|
constructor(pushClient, options) {
|
|
75
76
|
this._pushClient = pushClient;
|
|
76
77
|
this.options = options;
|
|
78
|
+
if (options?.headless !== void 0)
|
|
79
|
+
this.computedHeaded = !options.headless;
|
|
80
|
+
else
|
|
81
|
+
this.computedHeaded = !process.env.CI && !(import_os.default.platform() === "linux" && !process.env.DISPLAY);
|
|
77
82
|
}
|
|
78
83
|
initialize(rootPath, configLocation) {
|
|
79
84
|
this.configLocation = configLocation;
|
|
@@ -144,7 +149,7 @@ class TestContext {
|
|
|
144
149
|
async runSeedTest(seedFile, projectName, progress) {
|
|
145
150
|
await this.runWithGlobalSetup(async (testRunner, reporter) => {
|
|
146
151
|
const result = await testRunner.runTests(reporter, {
|
|
147
|
-
headed:
|
|
152
|
+
headed: this.computedHeaded,
|
|
148
153
|
locations: ["/" + (0, import_utils.escapeRegExp)(seedFile) + "/"],
|
|
149
154
|
projects: [projectName],
|
|
150
155
|
timeout: 0,
|
|
@@ -91,7 +91,7 @@ const debugTest = (0, import_testTool.defineTestTool)({
|
|
|
91
91
|
handle: async (context, params, progress) => {
|
|
92
92
|
await context.runWithGlobalSetup(async (testRunner, reporter) => {
|
|
93
93
|
await testRunner.runTests(reporter, {
|
|
94
|
-
headed:
|
|
94
|
+
headed: context.computedHeaded,
|
|
95
95
|
testIds: [params.test.id],
|
|
96
96
|
// For automatic recovery
|
|
97
97
|
timeout: 0,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "playwright",
|
|
3
|
-
"version": "1.57.0-alpha-2025-11-
|
|
3
|
+
"version": "1.57.0-alpha-2025-11-12",
|
|
4
4
|
"description": "A high-level API to automate web browsers",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
},
|
|
65
65
|
"license": "Apache-2.0",
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"playwright-core": "1.57.0-alpha-2025-11-
|
|
67
|
+
"playwright-core": "1.57.0-alpha-2025-11-12"
|
|
68
68
|
},
|
|
69
69
|
"optionalDependencies": {
|
|
70
70
|
"fsevents": "2.3.2"
|