mirror of
https://github.com/microsoft/playwright-mcp.git
synced 2026-03-31 15:23:12 +00:00
Compare commits
8 Commits
112cbb8574
...
v0.0.69
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3b0d127df | ||
|
|
c7285818ed | ||
|
|
3e8fc820a6 | ||
|
|
9521308275 | ||
|
|
eed21856dc | ||
|
|
b2df777103 | ||
|
|
4c6d66d04e | ||
|
|
a6baddb044 |
21
.devcontainer/devcontainer.json
Normal file
21
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/devcontainers/spec/main/schemas/devContainer.schema.json",
|
||||
"name": "Playwright",
|
||||
"image": "mcr.microsoft.com/playwright:v1.58.2-noble",
|
||||
"privileged": true,
|
||||
"init": true,
|
||||
"remoteUser": "pwuser",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/desktop-lite:1": {},
|
||||
"ghcr.io/devcontainers/features/github-cli:1": {},
|
||||
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}
|
||||
},
|
||||
"forwardPorts": [
|
||||
6080
|
||||
],
|
||||
"portsAttributes": {
|
||||
"6080": {
|
||||
"label": "noVNC"
|
||||
}
|
||||
}
|
||||
}
|
||||
2
.github/workflows/publish.yml
vendored
2
.github/workflows/publish.yml
vendored
@@ -135,6 +135,8 @@ jobs:
|
||||
- name: Build extension
|
||||
working-directory: ./packages/extension
|
||||
run: npm run build
|
||||
env:
|
||||
SET_EXTENSION_PUBLIC_KEY_IN_MANIFEST: 1
|
||||
- name: Get extension version
|
||||
id: get-version
|
||||
working-directory: ./packages/extension
|
||||
|
||||
31
CLAUDE.md
Normal file
31
CLAUDE.md
Normal file
@@ -0,0 +1,31 @@
|
||||
## Commit Convention
|
||||
|
||||
Semantic commit messages: `label(scope): description`
|
||||
|
||||
Labels: `fix`, `feat`, `chore`, `docs`, `test`, `devops`
|
||||
|
||||
```bash
|
||||
git checkout -b fix-39562
|
||||
# ... make changes ...
|
||||
git add <changed-files>
|
||||
git commit -m "$(cat <<'EOF'
|
||||
fix(proxy): handle SOCKS proxy authentication
|
||||
|
||||
Fixes: https://github.com/microsoft/playwright/issues/39562
|
||||
EOF
|
||||
)"
|
||||
git push origin fix-39562
|
||||
gh pr create --repo microsoft/playwright --head username:fix-39562 \
|
||||
--title "fix(proxy): handle SOCKS proxy authentication" \
|
||||
--body "$(cat <<'EOF'
|
||||
## Summary
|
||||
- <describe the change very! briefly>
|
||||
|
||||
Fixes https://github.com/microsoft/playwright/issues/39562
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
Never add Co-Authored-By agents in commit message.
|
||||
Branch naming for issue fixes: `fix-<issue-number>`
|
||||
|
||||
@@ -53,7 +53,6 @@ FROM base
|
||||
ARG PLAYWRIGHT_BROWSERS_PATH
|
||||
ARG USERNAME=node
|
||||
ENV NODE_ENV=production
|
||||
ENV PLAYWRIGHT_MCP_OUTPUT_DIR=/tmp/playwright-output
|
||||
|
||||
# Set the correct ownership for the runtime user on production `node_modules`
|
||||
RUN chown -R ${USERNAME}:${USERNAME} node_modules
|
||||
@@ -63,5 +62,8 @@ USER ${USERNAME}
|
||||
COPY --from=browser --chown=${USERNAME}:${USERNAME} ${PLAYWRIGHT_BROWSERS_PATH} ${PLAYWRIGHT_BROWSERS_PATH}
|
||||
COPY --chown=${USERNAME}:${USERNAME} packages/playwright-mcp/cli.js packages/playwright-mcp/package.json ./
|
||||
|
||||
# Current working directory must be writable as MCP may need to create default output dir in it.
|
||||
WORKDIR /home/${USERNAME}
|
||||
|
||||
# Run in headless and only with chromium (other browsers need more dependencies not included in this image)
|
||||
ENTRYPOINT ["node", "cli.js", "--headless", "--browser", "chromium", "--no-sandbox"]
|
||||
ENTRYPOINT ["node", "/app/cli.js", "--headless", "--browser", "chromium", "--no-sandbox"]
|
||||
|
||||
113
README.md
113
README.md
@@ -240,6 +240,8 @@ Go to `Advanced settings` -> `Extensions` -> `Add custom extension`. Name to you
|
||||
<details>
|
||||
<summary>Kiro</summary>
|
||||
|
||||
[](https://kiro.dev/launch/mcp/add?name=playwright&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22%40playwright%2Fmcp%40latest%22%5D%7D)
|
||||
|
||||
Follow the MCP Servers [documentation](https://kiro.dev/docs/mcp/). For example in `.kiro/settings/mcp.json`:
|
||||
|
||||
```json
|
||||
@@ -386,11 +388,9 @@ Playwright MCP server supports following arguments. They can be provided in the
|
||||
| --proxy-server <proxy> | specify proxy server, for example "http://myproxy:3128" or "socks5://myproxy:8080"<br>*env* `PLAYWRIGHT_MCP_PROXY_SERVER` |
|
||||
| --sandbox | enable the sandbox for all process types that are normally not sandboxed.<br>*env* `PLAYWRIGHT_MCP_SANDBOX` |
|
||||
| --save-session | Whether to save the Playwright MCP session into the output directory.<br>*env* `PLAYWRIGHT_MCP_SAVE_SESSION` |
|
||||
| --save-trace | Whether to save the Playwright Trace of the session into the output directory.<br>*env* `PLAYWRIGHT_MCP_SAVE_TRACE` |
|
||||
| --save-video <size> | Whether to save the video of the session into the output directory. For example "--save-video=800x600"<br>*env* `PLAYWRIGHT_MCP_SAVE_VIDEO` |
|
||||
| --secrets <path> | path to a file containing secrets in the dotenv format<br>*env* `PLAYWRIGHT_MCP_SECRETS` |
|
||||
| --shared-browser-context | reuse the same browser context between all connected HTTP clients.<br>*env* `PLAYWRIGHT_MCP_SHARED_BROWSER_CONTEXT` |
|
||||
| --snapshot-mode <mode> | when taking snapshots for responses, specifies the mode to use. Can be "incremental", "full", or "none". Default is incremental.<br>*env* `PLAYWRIGHT_MCP_SNAPSHOT_MODE` |
|
||||
| --snapshot-mode <mode> | when taking snapshots for responses, specifies the mode to use. Can be "full" or "none". Default is "full".<br>*env* `PLAYWRIGHT_MCP_SNAPSHOT_MODE` |
|
||||
| --storage-state <path> | path to the storage state file for isolated sessions.<br>*env* `PLAYWRIGHT_MCP_STORAGE_STATE` |
|
||||
| --test-id-attribute <attribute> | specify the attribute to use for test ids, defaults to "data-testid"<br>*env* `PLAYWRIGHT_MCP_TEST_ID_ATTRIBUTE` |
|
||||
| --timeout-action <timeout> | specify action timeout in milliseconds, defaults to 5000ms<br>*env* `PLAYWRIGHT_MCP_TIMEOUT_ACTION` |
|
||||
@@ -598,28 +598,15 @@ npx @playwright/mcp@latest --config path/to/config.json
|
||||
*/
|
||||
saveSession?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to save the Playwright trace of the session into the output directory.
|
||||
*/
|
||||
saveTrace?: boolean;
|
||||
|
||||
/**
|
||||
* If specified, saves the Playwright video of the session into the output directory.
|
||||
*/
|
||||
saveVideo?: {
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reuse the same browser context between all connected HTTP clients.
|
||||
*/
|
||||
sharedBrowserContext?: boolean;
|
||||
|
||||
/**
|
||||
* Secrets are used to prevent LLM from getting sensitive data while
|
||||
* automating scenarios such as authentication.
|
||||
* Prefer the browser.contextOptions.storageState over secrets file as a more secure alternative.
|
||||
* Secrets are used to replace matching plain text in the tool responses to prevent the LLM
|
||||
* from accidentally getting sensitive data. It is a convenience and not a security feature,
|
||||
* make sure to always examine information coming in and from the tool on the client.
|
||||
*/
|
||||
secrets?: Record<string, string>;
|
||||
|
||||
@@ -628,11 +615,6 @@ npx @playwright/mcp@latest --config path/to/config.json
|
||||
*/
|
||||
outputDir?: string;
|
||||
|
||||
/**
|
||||
* Whether to save snapshots, console messages, network logs and other session logs to a file or to the standard output. Defaults to "stdout".
|
||||
*/
|
||||
outputMode?: 'file' | 'stdout';
|
||||
|
||||
console?: {
|
||||
/**
|
||||
* The level of console messages to return. Each level includes the messages of more severe levels. Defaults to "info".
|
||||
@@ -675,6 +657,11 @@ npx @playwright/mcp@latest --config path/to/config.json
|
||||
* Configures default navigation timeout: https://playwright.dev/docs/api/class-page#page-set-default-navigation-timeout. Defaults to 60000ms.
|
||||
*/
|
||||
navigation?: number;
|
||||
|
||||
/**
|
||||
* Configures default expect timeout: https://playwright.dev/docs/test-timeouts#expect-timeout. Defaults to 5000ms.
|
||||
*/
|
||||
expect?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -686,12 +673,14 @@ npx @playwright/mcp@latest --config path/to/config.json
|
||||
/**
|
||||
* When taking snapshots for responses, specifies the mode to use.
|
||||
*/
|
||||
mode?: 'incremental' | 'full' | 'none';
|
||||
mode?: 'full' | 'none';
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether to allow file uploads from anywhere on the file system.
|
||||
* By default (false), file uploads are restricted to paths within the MCP roots only.
|
||||
* allowUnrestrictedFileAccess acts as a guardrail to prevent the LLM from accidentally
|
||||
* wandering outside its intended workspace. It is a convenience defense to catch unintended
|
||||
* file access, not a secure boundary; a deliberate attempt to reach other directories can be
|
||||
* easily worked around, so always rely on client-level permissions for true security.
|
||||
*/
|
||||
allowUnrestrictedFileAccess?: boolean;
|
||||
|
||||
@@ -804,6 +793,7 @@ http.createServer(async (req, res) => {
|
||||
- Parameters:
|
||||
- `element` (string, optional): Human-readable element description used to obtain permission to interact with the element
|
||||
- `ref` (string): Exact target element reference from the page snapshot
|
||||
- `selector` (string, optional): CSS or role selector for the target element, when "ref" is not available
|
||||
- `doubleClick` (boolean, optional): Whether to perform a double click instead of a single click
|
||||
- `button` (string, optional): Button to click, defaults to left
|
||||
- `modifiers` (array, optional): Modifier keys to press
|
||||
@@ -824,6 +814,7 @@ http.createServer(async (req, res) => {
|
||||
- Description: Returns all console messages
|
||||
- Parameters:
|
||||
- `level` (string): Level of the console messages to return. Each level includes the messages of more severe levels. Defaults to "info".
|
||||
- `all` (boolean, optional): Return all console messages since the beginning of the session, not just since the last navigation. Defaults to false.
|
||||
- `filename` (string, optional): Filename to save the console messages to. If not provided, messages are returned as text.
|
||||
- Read-only: **true**
|
||||
|
||||
@@ -835,8 +826,10 @@ http.createServer(async (req, res) => {
|
||||
- Parameters:
|
||||
- `startElement` (string): Human-readable source element description used to obtain the permission to interact with the element
|
||||
- `startRef` (string): Exact source element reference from the page snapshot
|
||||
- `startSelector` (string, optional): CSS or role selector for the source element, when ref is not available
|
||||
- `endElement` (string): Human-readable target element description used to obtain the permission to interact with the element
|
||||
- `endRef` (string): Exact target element reference from the page snapshot
|
||||
- `endSelector` (string, optional): CSS or role selector for the target element, when ref is not available
|
||||
- Read-only: **false**
|
||||
|
||||
<!-- NOTE: This has been generated via update-readme.js -->
|
||||
@@ -848,6 +841,8 @@ http.createServer(async (req, res) => {
|
||||
- `function` (string): () => { /* code */ } or (element) => { /* code */ } when element is provided
|
||||
- `element` (string, optional): Human-readable element description used to obtain permission to interact with the element
|
||||
- `ref` (string, optional): Exact target element reference from the page snapshot
|
||||
- `selector` (string, optional): CSS or role selector for the target element, when "ref" is not available.
|
||||
- `filename` (string, optional): Filename to save the result to. If not provided, result is returned as text.
|
||||
- Read-only: **false**
|
||||
|
||||
<!-- NOTE: This has been generated via update-readme.js -->
|
||||
@@ -886,6 +881,7 @@ http.createServer(async (req, res) => {
|
||||
- Parameters:
|
||||
- `element` (string, optional): Human-readable element description used to obtain permission to interact with the element
|
||||
- `ref` (string): Exact target element reference from the page snapshot
|
||||
- `selector` (string, optional): CSS or role selector for the target element, when "ref" is not available
|
||||
- Read-only: **false**
|
||||
|
||||
<!-- NOTE: This has been generated via update-readme.js -->
|
||||
@@ -911,7 +907,10 @@ http.createServer(async (req, res) => {
|
||||
- Title: List network requests
|
||||
- Description: Returns all network requests since loading the page
|
||||
- Parameters:
|
||||
- `includeStatic` (boolean): Whether to include successful static resources like images, fonts, scripts, etc. Defaults to false.
|
||||
- `static` (boolean): Whether to include successful static resources like images, fonts, scripts, etc. Defaults to false.
|
||||
- `requestBody` (boolean): Whether to include request body. Defaults to false.
|
||||
- `requestHeaders` (boolean): Whether to include request headers. Defaults to false.
|
||||
- `filter` (string, optional): Only return requests whose URL matches this regexp (e.g. "/api/.*user").
|
||||
- `filename` (string, optional): Filename to save the network requests to. If not provided, requests are returned as text.
|
||||
- Read-only: **true**
|
||||
|
||||
@@ -940,7 +939,8 @@ http.createServer(async (req, res) => {
|
||||
- Title: Run Playwright code
|
||||
- Description: Run Playwright code snippet
|
||||
- Parameters:
|
||||
- `code` (string): A JavaScript function containing Playwright code to execute. It will be invoked with a single argument, page, which you can use for any page interaction. For example: `async (page) => { await page.getByRole('button', { name: 'Submit' }).click(); return await page.title(); }`
|
||||
- `code` (string, optional): A JavaScript function containing Playwright code to execute. It will be invoked with a single argument, page, which you can use for any page interaction. For example: `async (page) => { await page.getByRole('button', { name: 'Submit' }).click(); return await page.title(); }`
|
||||
- `filename` (string, optional): Load code from the specified file. If both code and filename are provided, code will be ignored.
|
||||
- Read-only: **false**
|
||||
|
||||
<!-- NOTE: This has been generated via update-readme.js -->
|
||||
@@ -951,6 +951,7 @@ http.createServer(async (req, res) => {
|
||||
- Parameters:
|
||||
- `element` (string, optional): Human-readable element description used to obtain permission to interact with the element
|
||||
- `ref` (string): Exact target element reference from the page snapshot
|
||||
- `selector` (string, optional): CSS or role selector for the target element, when "ref" is not available
|
||||
- `values` (array): Array of values to select in the dropdown. This can be a single value or multiple values.
|
||||
- Read-only: **false**
|
||||
|
||||
@@ -961,6 +962,8 @@ http.createServer(async (req, res) => {
|
||||
- Description: Capture accessibility snapshot of the current page, this is better than screenshot
|
||||
- Parameters:
|
||||
- `filename` (string, optional): Save snapshot to markdown file instead of returning it in the response.
|
||||
- `selector` (string, optional): Element selector of the root element to capture a partial snapshot instead of the whole page
|
||||
- `depth` (number, optional): Limit the depth of the snapshot tree
|
||||
- Read-only: **true**
|
||||
|
||||
<!-- NOTE: This has been generated via update-readme.js -->
|
||||
@@ -973,6 +976,7 @@ http.createServer(async (req, res) => {
|
||||
- `filename` (string, optional): File name to save the screenshot to. Defaults to `page-{timestamp}.{png|jpeg}` if not specified. Prefer relative file names to stay within the output directory.
|
||||
- `element` (string, optional): Human-readable element description used to obtain permission to screenshot the element. If not provided, the screenshot will be taken of viewport. If element is provided, ref must be provided too.
|
||||
- `ref` (string, optional): Exact target element reference from the page snapshot. If not provided, the screenshot will be taken of viewport. If ref is provided, element must be provided too.
|
||||
- `selector` (string, optional): CSS or role selector for the target element, when "ref" is not available.
|
||||
- `fullPage` (boolean, optional): When true, takes a screenshot of the full scrollable page, instead of the currently visible viewport. Cannot be used with element screenshots.
|
||||
- Read-only: **true**
|
||||
|
||||
@@ -984,6 +988,7 @@ http.createServer(async (req, res) => {
|
||||
- Parameters:
|
||||
- `element` (string, optional): Human-readable element description used to obtain permission to interact with the element
|
||||
- `ref` (string): Exact target element reference from the page snapshot
|
||||
- `selector` (string, optional): CSS or role selector for the target element, when "ref" is not available
|
||||
- `text` (string): Text to type into the element
|
||||
- `submit` (boolean, optional): Whether to submit entered text (press Enter after)
|
||||
- `slowly` (boolean, optional): Whether to type one character at a time. Useful for triggering key handlers in the page. By default entire text is filled in at once.
|
||||
@@ -1020,14 +1025,6 @@ http.createServer(async (req, res) => {
|
||||
<details>
|
||||
<summary><b>Browser installation</b></summary>
|
||||
|
||||
<!-- NOTE: This has been generated via update-readme.js -->
|
||||
|
||||
- **browser_install**
|
||||
- Title: Install the browser specified in the config
|
||||
- Description: Install the browser specified in the config. Call this if you get an error about the browser not being installed.
|
||||
- Parameters: None
|
||||
- Read-only: **false**
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
@@ -1048,6 +1045,15 @@ http.createServer(async (req, res) => {
|
||||
|
||||
<!-- NOTE: This has been generated via update-readme.js -->
|
||||
|
||||
- **browser_network_state_set**
|
||||
- Title: Set network state
|
||||
- Description: Sets the browser network state to online or offline. When offline, all network requests will fail.
|
||||
- Parameters:
|
||||
- `state` (string): Set to "offline" to simulate offline mode, "online" to restore network connectivity
|
||||
- Read-only: **false**
|
||||
|
||||
<!-- NOTE: This has been generated via update-readme.js -->
|
||||
|
||||
- **browser_route**
|
||||
- Title: Mock network requests
|
||||
- Description: Set up a route to mock network requests matching a URL pattern
|
||||
@@ -1247,6 +1253,16 @@ http.createServer(async (req, res) => {
|
||||
|
||||
<!-- NOTE: This has been generated via update-readme.js -->
|
||||
|
||||
- **browser_resume**
|
||||
- Title: Resume paused script execution
|
||||
- Description: Resume script execution after it was paused. When called with step set to true, execution will pause again before the next action.
|
||||
- Parameters:
|
||||
- `step` (boolean, optional): When true, execution will pause again before the next action, allowing step-by-step debugging.
|
||||
- `location` (string, optional): Pause execution at a specific <file>:<line>, e.g. "example.spec.ts:42".
|
||||
- Read-only: **false**
|
||||
|
||||
<!-- NOTE: This has been generated via update-readme.js -->
|
||||
|
||||
- **browser_start_tracing**
|
||||
- Title: Start tracing
|
||||
- Description: Start trace recording
|
||||
@@ -1259,6 +1275,7 @@ http.createServer(async (req, res) => {
|
||||
- Title: Start video
|
||||
- Description: Start video recording
|
||||
- Parameters:
|
||||
- `filename` (string, optional): Filename to save the video.
|
||||
- `size` (object, optional): Video size
|
||||
- Read-only: **true**
|
||||
|
||||
@@ -1275,8 +1292,18 @@ http.createServer(async (req, res) => {
|
||||
- **browser_stop_video**
|
||||
- Title: Stop video
|
||||
- Description: Stop video recording
|
||||
- Parameters: None
|
||||
- Read-only: **true**
|
||||
|
||||
<!-- NOTE: This has been generated via update-readme.js -->
|
||||
|
||||
- **browser_video_chapter**
|
||||
- Title: Video chapter
|
||||
- Description: Add a chapter marker to the video recording. Shows a full-screen chapter card with blurred backdrop.
|
||||
- Parameters:
|
||||
- `filename` (string, optional): Filename to save the video
|
||||
- `title` (string): Chapter title
|
||||
- `description` (string, optional): Chapter description
|
||||
- `duration` (number, optional): Duration in milliseconds to show the chapter card
|
||||
- Read-only: **true**
|
||||
|
||||
</details>
|
||||
@@ -1288,10 +1315,13 @@ http.createServer(async (req, res) => {
|
||||
|
||||
- **browser_mouse_click_xy**
|
||||
- Title: Click
|
||||
- Description: Click left mouse button at a given position
|
||||
- Description: Click mouse button at a given position
|
||||
- Parameters:
|
||||
- `x` (number): X coordinate
|
||||
- `y` (number): Y coordinate
|
||||
- `button` (string, optional): Button to click, defaults to left
|
||||
- `clickCount` (number, optional): Number of clicks, defaults to 1
|
||||
- `delay` (number, optional): Time to wait between mouse down and mouse up in milliseconds, defaults to 0
|
||||
- Read-only: **false**
|
||||
|
||||
<!-- NOTE: This has been generated via update-readme.js -->
|
||||
@@ -1371,6 +1401,7 @@ http.createServer(async (req, res) => {
|
||||
- Parameters:
|
||||
- `element` (string, optional): Human-readable element description used to obtain permission to interact with the element
|
||||
- `ref` (string): Exact target element reference from the page snapshot
|
||||
- `selector` (string, optional): CSS or role selector for the target element, when "ref" is not available
|
||||
- Read-only: **true**
|
||||
|
||||
<!-- NOTE: This has been generated via update-readme.js -->
|
||||
@@ -1391,6 +1422,7 @@ http.createServer(async (req, res) => {
|
||||
- Parameters:
|
||||
- `element` (string): Human-readable list description
|
||||
- `ref` (string): Exact target element reference that points to the list
|
||||
- `selector` (string, optional): CSS or role selector for the target list, when "ref" is not available.
|
||||
- `items` (array): Items to verify
|
||||
- Read-only: **false**
|
||||
|
||||
@@ -1411,7 +1443,8 @@ http.createServer(async (req, res) => {
|
||||
- Parameters:
|
||||
- `type` (string): Type of the element
|
||||
- `element` (string): Human-readable element description
|
||||
- `ref` (string): Exact target element reference that points to the element
|
||||
- `ref` (string): Exact target element reference from the page snapshot
|
||||
- `selector` (string, optional): CSS or role selector for the target element, when "ref" is not available
|
||||
- `value` (string): Value to verify. For checkbox, use "true" or "false".
|
||||
- Read-only: **false**
|
||||
|
||||
|
||||
64
package-lock.json
generated
64
package-lock.json
generated
@@ -1,19 +1,19 @@
|
||||
{
|
||||
"name": "playwright-mcp-internal",
|
||||
"version": "0.0.68",
|
||||
"version": "0.0.69",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "playwright-mcp-internal",
|
||||
"version": "0.0.68",
|
||||
"version": "0.0.69",
|
||||
"license": "Apache-2.0",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.25.2",
|
||||
"@playwright/test": "1.59.0-alpha-1771104257000",
|
||||
"@playwright/test": "1.59.0-alpha-1774912654000",
|
||||
"@types/node": "^24.3.0"
|
||||
}
|
||||
},
|
||||
@@ -854,13 +854,13 @@
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.59.0-alpha-1771104257000",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.0-alpha-1771104257000.tgz",
|
||||
"integrity": "sha512-0zUPgLuSbyO2xtA+FdEWejFpA5tYU1dINMj2D6KGbB7dgxW8V/4bOrpYS38hizSrzpdSiuRcIK7UgiNFxEeK3A==",
|
||||
"version": "1.59.0-alpha-1774912654000",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.0-alpha-1774912654000.tgz",
|
||||
"integrity": "sha512-/OjIU6mfPP7UisfqKLnKqjG8gDq4gwoek55z6VqaWnGgZnRdJwCNBao5Azymk5qmp7hzxu3rXdW0sFmsNXLEfQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright": "1.59.0-alpha-1771104257000"
|
||||
"playwright": "1.59.0-alpha-1774912654000"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
@@ -2585,11 +2585,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/path-to-regexp": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
|
||||
"integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
|
||||
"version": "8.4.0",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.0.tgz",
|
||||
"integrity": "sha512-PuseHIvAnz3bjrM2rGJtSgo1zjgxapTLZ7x2pjhzWwlp4SJQgK3f3iZIQwkpEnBaKz6seKBADpM4B4ySkuYypg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
@@ -2603,11 +2602,10 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
|
||||
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
@@ -2626,12 +2624,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.59.0-alpha-1771104257000",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.0-alpha-1771104257000.tgz",
|
||||
"integrity": "sha512-6SCMMMJaDRsSqiKVLmb2nhtLES7iTYawTWWrQK6UdIGNzXi8lka4sLKRec3L4DnTWwddAvCuRn8035dhNiHzbg==",
|
||||
"version": "1.59.0-alpha-1774912654000",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.0-alpha-1774912654000.tgz",
|
||||
"integrity": "sha512-98v8NQ0ZaNTdOhwo24sq7Y79wuh+Q6TShG6DKgyQIUr4cBV5gDRIcRjTVZz5LhwYAwCOXCanvY5er47mowV8ww==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.59.0-alpha-1771104257000"
|
||||
"playwright-core": "1.59.0-alpha-1774912654000"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
@@ -2648,9 +2646,9 @@
|
||||
"link": true
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.59.0-alpha-1771104257000",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.0-alpha-1771104257000.tgz",
|
||||
"integrity": "sha512-YiXup3pnpQUCBMSIW5zx8CErwRx4K6O5Kojkw2BzJui8MazoMUDU6E3xGsb1kzFviEAE09LFQ+y1a0RhIJQ5SA==",
|
||||
"version": "1.59.0-alpha-1774912654000",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.0-alpha-1774912654000.tgz",
|
||||
"integrity": "sha512-A4zBS+GASROUFrHTMeC0ySI1+f/Wjbm0OJIAUFu4q+0Si3tfLg742dGwq1AezmMBovINt6Fi7fOTjtGZCQQCEw==",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
@@ -3102,11 +3100,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tinyglobby/node_modules/picomatch": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -3356,11 +3353,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/picomatch": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -3420,7 +3416,7 @@
|
||||
},
|
||||
"packages/extension": {
|
||||
"name": "@playwright/mcp-extension",
|
||||
"version": "0.0.68",
|
||||
"version": "0.0.69",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@types/chrome": "^0.0.315",
|
||||
@@ -3445,11 +3441,11 @@
|
||||
},
|
||||
"packages/playwright-mcp": {
|
||||
"name": "@playwright/mcp",
|
||||
"version": "0.0.68",
|
||||
"version": "0.0.69",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright": "1.59.0-alpha-1771104257000",
|
||||
"playwright-core": "1.59.0-alpha-1771104257000"
|
||||
"playwright": "1.59.0-alpha-1774912654000",
|
||||
"playwright-core": "1.59.0-alpha-1774912654000"
|
||||
},
|
||||
"bin": {
|
||||
"playwright-mcp": "cli.js"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "playwright-mcp-internal",
|
||||
"version": "0.0.68",
|
||||
"version": "0.0.69",
|
||||
"private": true,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -26,7 +26,7 @@
|
||||
],
|
||||
"devDependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.25.2",
|
||||
"@playwright/test": "1.59.0-alpha-1771104257000",
|
||||
"@playwright/test": "1.59.0-alpha-1774912654000",
|
||||
"@types/node": "^24.3.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "Playwright MCP Bridge",
|
||||
"version": "0.0.68",
|
||||
"version": "0.0.69",
|
||||
"description": "Share browser tabs with Playwright MCP server",
|
||||
"permissions": [
|
||||
"debugger",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@playwright/mcp-extension",
|
||||
"version": "0.0.68",
|
||||
"version": "0.0.69",
|
||||
"description": "Playwright MCP Browser Extension",
|
||||
"private": true,
|
||||
"repository": {
|
||||
|
||||
@@ -123,6 +123,14 @@ const test = base.extend<TestFixtures>({
|
||||
},
|
||||
});
|
||||
|
||||
function cliEnv() {
|
||||
return {
|
||||
PLAYWRIGHT_SERVER_REGISTRY: test.info().outputPath('registry'),
|
||||
PLAYWRIGHT_DAEMON_SESSION_DIR: test.info().outputPath('daemon'),
|
||||
PLAYWRIGHT_SOCKETS_DIR: path.join(test.info().project.outputDir, 'ds', String(test.info().parallelIndex)),
|
||||
};
|
||||
}
|
||||
|
||||
async function runCli(
|
||||
args: string[],
|
||||
options: { mcpBrowser?: string, testInfo: any },
|
||||
@@ -133,7 +141,7 @@ async function runCli(
|
||||
const testInfo = options.testInfo;
|
||||
|
||||
// Path to the terminal CLI
|
||||
const cliPath = path.join(__dirname, '../../../node_modules/playwright/lib/cli/client/program.js');
|
||||
const cliPath = path.join(__dirname, '../../../node_modules/playwright-core/lib/tools/cli-client/cli.js');
|
||||
|
||||
return new Promise<CliResult>((resolve, reject) => {
|
||||
let stdout = '';
|
||||
@@ -143,9 +151,7 @@ async function runCli(
|
||||
cwd: testInfo.outputPath(),
|
||||
env: {
|
||||
...process.env,
|
||||
PLAYWRIGHT_DAEMON_INSTALL_DIR: testInfo.outputPath(),
|
||||
PLAYWRIGHT_DAEMON_SESSION_DIR: testInfo.outputPath('daemon'),
|
||||
PLAYWRIGHT_DAEMON_SOCKETS_DIR: path.join(testInfo.project.outputDir, 'daemon-sockets'),
|
||||
...cliEnv(),
|
||||
PLAYWRIGHT_MCP_BROWSER: options.mcpBrowser,
|
||||
PLAYWRIGHT_MCP_HEADLESS: 'false',
|
||||
},
|
||||
@@ -213,8 +219,7 @@ test(`navigate with extension`, async ({ browserWithExtension, startClient, serv
|
||||
});
|
||||
|
||||
const selectorPage = await confirmationPagePromise;
|
||||
// For browser_navigate command, the UI shows Allow/Reject buttons instead of tab selector
|
||||
await selectorPage.getByRole('button', { name: 'Allow' }).click();
|
||||
await selectorPage.locator('.tab-item', { hasText: 'Playwright MCP extension' }).getByRole('button', { name: 'Connect' }).click();
|
||||
|
||||
expect(await navigateResponse).toHaveResponse({
|
||||
snapshot: expect.stringContaining(`- generic [active] [ref=e1]: Hello, world!`),
|
||||
@@ -295,8 +300,7 @@ testWithOldExtensionVersion(`works with old extension version`, async ({ browser
|
||||
});
|
||||
|
||||
const selectorPage = await confirmationPagePromise;
|
||||
// For browser_navigate command, the UI shows Allow/Reject buttons instead of tab selector
|
||||
await selectorPage.getByRole('button', { name: 'Allow' }).click();
|
||||
await selectorPage.locator('.tab-item', { hasText: 'Playwright MCP extension' }).getByRole('button', { name: 'Connect' }).click();
|
||||
|
||||
expect(await navigateResponse).toHaveResponse({
|
||||
snapshot: expect.stringContaining(`- generic [active] [ref=e1]: Hello, world!`),
|
||||
|
||||
@@ -19,6 +19,10 @@ import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { viteStaticCopy } from 'vite-plugin-static-copy';
|
||||
|
||||
// Public key matching the Chrome Web Store listing — used to fix the extension ID across installs.
|
||||
// Set SET_EXTENSION_PUBLIC_KEY_IN_MANIFEST=1 in release builds to inject it into the manifest.
|
||||
const extensionPublicKey = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwRsUUO4mmbCi4JpmrIoIw31iVW9+xUJRZ6nSzya17PQkaUPDxe1IpgM+vpd/xB6mJWlJSyE1Lj95c0sbomGfVY1M0zUeKbaRVcAb+/a6m59gNR+ubFlmTX0nK9/8fE2FpRB9D+4N5jyeIPQuASW/0oswI2/ijK7hH5NTRX8gWc/ROMSgUj7rKhTAgBrICt/NsStgDPsxRTPPJnhJ/ViJtM1P5KsSYswE987DPoFnpmkFpq8g1ae0eYbQfXy55ieaacC4QWyJPj3daU2kMfBQw7MXnnk0H/WDxouMOIHnd8MlQxpEMqAihj7KpuONH+MUhuj9HEQo4df6bSaIuQ0b4QIDAQAB';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
@@ -31,7 +35,14 @@ export default defineConfig({
|
||||
},
|
||||
{
|
||||
src: '../../manifest.json',
|
||||
dest: '.'
|
||||
dest: '.',
|
||||
...(!!process.env.SET_EXTENSION_PUBLIC_KEY_IN_MANIFEST ? {
|
||||
transform: (content: string) => {
|
||||
const manifest = JSON.parse(content);
|
||||
manifest.key = extensionPublicKey;
|
||||
return JSON.stringify(manifest, null, 2);
|
||||
}
|
||||
} : {})
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
@@ -16,9 +16,17 @@
|
||||
*/
|
||||
|
||||
const { program } = require('playwright-core/lib/utilsBundle');
|
||||
const { decorateMCPCommand } = require('playwright/lib/mcp/program');
|
||||
const { decorateMCPCommand } = require('playwright-core/lib/tools/mcp/program');
|
||||
|
||||
if (process.argv.includes('install-browser')) {
|
||||
const argv = process.argv.map(arg => arg === 'install-browser' ? 'install' : arg);
|
||||
const { program: mainProgram } = require('playwright-core/lib/cli/program');
|
||||
mainProgram.parse(argv);
|
||||
return;
|
||||
}
|
||||
|
||||
const packageJSON = require('./package.json');
|
||||
const p = program.version('Version ' + packageJSON.version).name('Playwright MCP');
|
||||
decorateMCPCommand(p, packageJSON.version)
|
||||
|
||||
void program.parseAsync(process.argv);
|
||||
|
||||
39
packages/playwright-mcp/config.d.ts
vendored
39
packages/playwright-mcp/config.d.ts
vendored
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type * as playwright from 'playwright';
|
||||
import type * as playwright from '../../..';
|
||||
|
||||
export type ToolCapability =
|
||||
'config' |
|
||||
@@ -137,28 +137,15 @@ export type Config = {
|
||||
*/
|
||||
saveSession?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to save the Playwright trace of the session into the output directory.
|
||||
*/
|
||||
saveTrace?: boolean;
|
||||
|
||||
/**
|
||||
* If specified, saves the Playwright video of the session into the output directory.
|
||||
*/
|
||||
saveVideo?: {
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reuse the same browser context between all connected HTTP clients.
|
||||
*/
|
||||
sharedBrowserContext?: boolean;
|
||||
|
||||
/**
|
||||
* Secrets are used to prevent LLM from getting sensitive data while
|
||||
* automating scenarios such as authentication.
|
||||
* Prefer the browser.contextOptions.storageState over secrets file as a more secure alternative.
|
||||
* Secrets are used to replace matching plain text in the tool responses to prevent the LLM
|
||||
* from accidentally getting sensitive data. It is a convenience and not a security feature,
|
||||
* make sure to always examine information coming in and from the tool on the client.
|
||||
*/
|
||||
secrets?: Record<string, string>;
|
||||
|
||||
@@ -167,11 +154,6 @@ export type Config = {
|
||||
*/
|
||||
outputDir?: string;
|
||||
|
||||
/**
|
||||
* Whether to save snapshots, console messages, network logs and other session logs to a file or to the standard output. Defaults to "stdout".
|
||||
*/
|
||||
outputMode?: 'file' | 'stdout';
|
||||
|
||||
console?: {
|
||||
/**
|
||||
* The level of console messages to return. Each level includes the messages of more severe levels. Defaults to "info".
|
||||
@@ -214,6 +196,11 @@ export type Config = {
|
||||
* Configures default navigation timeout: https://playwright.dev/docs/api/class-page#page-set-default-navigation-timeout. Defaults to 60000ms.
|
||||
*/
|
||||
navigation?: number;
|
||||
|
||||
/**
|
||||
* Configures default expect timeout: https://playwright.dev/docs/test-timeouts#expect-timeout. Defaults to 5000ms.
|
||||
*/
|
||||
expect?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -225,12 +212,14 @@ export type Config = {
|
||||
/**
|
||||
* When taking snapshots for responses, specifies the mode to use.
|
||||
*/
|
||||
mode?: 'incremental' | 'full' | 'none';
|
||||
mode?: 'full' | 'none';
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether to allow file uploads from anywhere on the file system.
|
||||
* By default (false), file uploads are restricted to paths within the MCP roots only.
|
||||
* allowUnrestrictedFileAccess acts as a guardrail to prevent the LLM from accidentally
|
||||
* wandering outside its intended workspace. It is a convenience defense to catch unintended
|
||||
* file access, not a secure boundary; a deliberate attempt to reach other directories can be
|
||||
* easily worked around, so always rely on client-level permissions for true security.
|
||||
*/
|
||||
allowUnrestrictedFileAccess?: boolean;
|
||||
|
||||
|
||||
@@ -15,5 +15,5 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const { createConnection } = require('playwright/lib/mcp/index');
|
||||
const { createConnection } = require('playwright-core/lib/tools/exports');
|
||||
module.exports = { createConnection };
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@playwright/mcp",
|
||||
"version": "0.0.68",
|
||||
"version": "0.0.69",
|
||||
"description": "Playwright Tools for MCP",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -33,8 +33,8 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"playwright": "1.59.0-alpha-1771104257000",
|
||||
"playwright-core": "1.59.0-alpha-1771104257000"
|
||||
"playwright": "1.59.0-alpha-1774912654000",
|
||||
"playwright-core": "1.59.0-alpha-1774912654000"
|
||||
},
|
||||
"bin": {
|
||||
"playwright-mcp": "cli.js"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# Where is the source?
|
||||
|
||||
Playwright MCP source code is located in the [Playwright monorepo](https://github.com/microsoft/playwright/blob/main/packages/playwright/src/mcp). Please refer to the contributor's guide in [CONTRIBUTING.md](../CONTRIBUTING.md) for more details.
|
||||
Playwright MCP source code is located in the [Playwright monorepo](https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/tools/mcp). Please refer to the contributor's guide in [CONTRIBUTING.md](../CONTRIBUTING.md) for more details.
|
||||
|
||||
@@ -30,7 +30,6 @@ test('test snapshot tool list', async ({ client }) => {
|
||||
'browser_select_option',
|
||||
'browser_type',
|
||||
'browser_close',
|
||||
'browser_install',
|
||||
'browser_navigate_back',
|
||||
'browser_navigate',
|
||||
'browser_network_requests',
|
||||
|
||||
25
packages/playwright-mcp/tests/cli.spec.ts
Normal file
25
packages/playwright-mcp/tests/cli.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import child_process from 'child_process';
|
||||
import path from 'path';
|
||||
import { test, expect } from './fixtures';
|
||||
|
||||
const cliPath = path.resolve(__dirname, '..', 'cli.js');
|
||||
|
||||
test('install-browser --help', async () => {
|
||||
const output = child_process.execSync(`node ${cliPath} install-browser --help`, { encoding: 'utf-8' });
|
||||
expect(output).toContain('install');
|
||||
});
|
||||
@@ -74,10 +74,10 @@ export const test = baseTest.extend<TestFixtures & TestOptions, WorkerFixtures>(
|
||||
},
|
||||
|
||||
startClient: async ({ mcpHeadless, mcpBrowser, mcpMode, mcpArgs }, use, testInfo) => {
|
||||
const configDir = path.dirname(test.info().config.configFile!);
|
||||
const clients: Client[] = [];
|
||||
|
||||
await use(async options => {
|
||||
const cwd = testInfo.outputPath();
|
||||
const args: string[] = mcpArgs ?? [];
|
||||
if (process.env.CI && process.platform === 'linux')
|
||||
args.push('--no-sandbox');
|
||||
@@ -90,7 +90,7 @@ export const test = baseTest.extend<TestFixtures & TestOptions, WorkerFixtures>(
|
||||
if (options?.config) {
|
||||
const configFile = testInfo.outputPath('config.json');
|
||||
await fs.promises.writeFile(configFile, JSON.stringify(options.config, null, 2));
|
||||
args.push(`--config=${path.relative(configDir, configFile)}`);
|
||||
args.push(`--config=${path.relative(cwd, configFile)}`);
|
||||
}
|
||||
|
||||
const client = new Client({ name: options?.clientName ?? 'test', version: '1.0.0' }, options?.roots ? { capabilities: { roots: {} } } : undefined);
|
||||
@@ -103,7 +103,7 @@ export const test = baseTest.extend<TestFixtures & TestOptions, WorkerFixtures>(
|
||||
};
|
||||
});
|
||||
}
|
||||
const { transport, stderr } = await createTransport(args, mcpMode, testInfo.outputPath('ms-playwright'), options?.extensionToken);
|
||||
const { transport, stderr } = await createTransport(args, cwd, mcpMode, testInfo.outputPath('ms-playwright'), options?.extensionToken);
|
||||
let stderrBuffer = '';
|
||||
stderr?.on('data', data => {
|
||||
if (process.env.PWMCP_DEBUG)
|
||||
@@ -182,12 +182,14 @@ export const test = baseTest.extend<TestFixtures & TestOptions, WorkerFixtures>(
|
||||
},
|
||||
});
|
||||
|
||||
async function createTransport(args: string[], mcpMode: TestOptions['mcpMode'], profilesDir: string, extensionToken?: string): Promise<{
|
||||
async function createTransport(args: string[], cwd: string, mcpMode: TestOptions['mcpMode'], profilesDir: string, extensionToken?: string): Promise<{
|
||||
transport: Transport,
|
||||
stderr: Stream | null,
|
||||
}> {
|
||||
if (mcpMode === 'docker') {
|
||||
const dockerArgs = ['run', '--rm', '-i', '--network=host', '-v', `${test.info().project.outputDir}:/app/test-results`];
|
||||
const relCwd = path.relative(test.info().project.outputDir, cwd);
|
||||
const dockerCwd = path.posix.join('/app/test-results', relCwd.split(path.sep).join('/'));
|
||||
const dockerArgs = ['run', '--rm', '-i', '--network=host', '-v', `${test.info().project.outputDir}:/app/test-results`, '-w', dockerCwd];
|
||||
const transport = new StdioClientTransport({
|
||||
command: 'docker',
|
||||
args: [...dockerArgs, 'playwright-mcp-dev:latest', ...args],
|
||||
@@ -201,7 +203,7 @@ async function createTransport(args: string[], mcpMode: TestOptions['mcpMode'],
|
||||
const transport = new StdioClientTransport({
|
||||
command: 'node',
|
||||
args: [path.join(__dirname, '../cli.js'), ...args],
|
||||
cwd: path.dirname(test.info().config.configFile!),
|
||||
cwd,
|
||||
stderr: 'pipe',
|
||||
env: {
|
||||
...process.env,
|
||||
@@ -222,7 +224,7 @@ type Response = Awaited<ReturnType<Client['callTool']>>;
|
||||
|
||||
export const expect = baseExpect.extend({
|
||||
toHaveResponse(response: Response, object: any) {
|
||||
const parsed = parseResponse(response);
|
||||
const parsed = parseResponse(response, test.info().outputPath());
|
||||
const isNot = this.isNot;
|
||||
try {
|
||||
if (isNot)
|
||||
@@ -246,7 +248,7 @@ export function formatOutput(output: string): string[] {
|
||||
return output.split('\n').map(line => line.replace(/^pw:mcp:test /, '').replace(/user data dir.*/, 'user data dir').trim()).filter(Boolean);
|
||||
}
|
||||
|
||||
function parseResponse(response: any) {
|
||||
function parseResponse(response: any, cwd: string) {
|
||||
const text = response.content[0].text;
|
||||
const sections = parseSections(text);
|
||||
|
||||
@@ -255,7 +257,7 @@ function parseResponse(response: any) {
|
||||
const code = sections.get('Ran Playwright code');
|
||||
const tabs = sections.get('Open tabs');
|
||||
const pageState = sections.get('Page state');
|
||||
const snapshot = sections.get('Snapshot');
|
||||
const snapshotSection = sections.get('Snapshot');
|
||||
const consoleMessages = sections.get('New console messages');
|
||||
const modalState = sections.get('Modal state');
|
||||
const downloads = sections.get('Downloads');
|
||||
@@ -263,6 +265,19 @@ function parseResponse(response: any) {
|
||||
const isError = response.isError;
|
||||
const attachments = response.content.slice(1);
|
||||
|
||||
let snapshot: string | undefined;
|
||||
if (snapshotSection) {
|
||||
const match = snapshotSection.match(/\[Snapshot\]\(([^)]+)\)/);
|
||||
if (match) {
|
||||
try {
|
||||
snapshot = fs.readFileSync(path.resolve(cwd, match[1]), 'utf-8');
|
||||
} catch {
|
||||
}
|
||||
} else {
|
||||
snapshot = snapshotSection.replace(/^```yaml\n?/, '').replace(/\n?```$/, '');
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
error,
|
||||
result,
|
||||
|
||||
@@ -20,7 +20,7 @@ const fs = require('fs')
|
||||
const path = require('path')
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
const { browserTools } = require('playwright/lib/mcp/browser/tools');
|
||||
const { browserTools } = require('playwright-core/lib/tools/exports');
|
||||
|
||||
const capabilities = /** @type {Record<string, string>} */ ({
|
||||
'core-navigation': 'Core automation',
|
||||
|
||||
2
roll.js
2
roll.js
@@ -3,7 +3,7 @@ const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
function copyConfig() {
|
||||
const src = path.join(__dirname, '..', 'playwright', 'packages', 'playwright', 'src', 'mcp', 'config.d.ts');
|
||||
const src = path.join(__dirname, '..', 'playwright', 'packages', 'playwright-core', 'src', 'tools', 'mcp', 'config.d.ts');
|
||||
const dst = path.join(__dirname, 'packages', 'playwright-mcp', 'config.d.ts');
|
||||
let content = fs.readFileSync(src, 'utf-8');
|
||||
content = content.replace(
|
||||
|
||||
Reference in New Issue
Block a user