Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79111366a9 | ||
|
|
956b79a1ab | ||
|
|
b58ad48e0a | ||
|
|
41fba2bd71 | ||
|
|
cd2b589338 | ||
|
|
fbd62cd838 | ||
|
|
6aab683338 | ||
|
|
85c64bbe0f | ||
|
|
b213c187b0 | ||
|
|
412f6dc6fe | ||
|
|
4b1a6842b1 | ||
|
|
9cc61b4faf | ||
|
|
33b4c00923 |
24
.github/workflows/ci.yml
vendored
@@ -16,14 +16,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: '20'
|
node-version: '20'
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
- name: Install dependencies
|
- run: npm ci
|
||||||
run: npm ci
|
- run: npm run lint
|
||||||
- name: Run ESLint
|
|
||||||
run: npm run lint
|
|
||||||
- name: Ensure no changes
|
- name: Ensure no changes
|
||||||
run: git diff --exit-code
|
run: git diff --exit-code
|
||||||
|
|
||||||
test:
|
test_mcp:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -42,8 +40,9 @@ jobs:
|
|||||||
run: npx playwright install --with-deps
|
run: npx playwright install --with-deps
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: npm run test
|
run: npm run test
|
||||||
|
working-directory: ./packages/playwright-mcp
|
||||||
|
|
||||||
test_docker:
|
test_mcp_docker:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -71,14 +70,12 @@ jobs:
|
|||||||
# Used for the Docker tests to share the test-results folder with the container.
|
# Used for the Docker tests to share the test-results folder with the container.
|
||||||
umask 0000
|
umask 0000
|
||||||
npm run test -- --project=chromium-docker
|
npm run test -- --project=chromium-docker
|
||||||
|
working-directory: ./packages/playwright-mcp
|
||||||
env:
|
env:
|
||||||
MCP_IN_DOCKER: 1
|
MCP_IN_DOCKER: 1
|
||||||
|
|
||||||
test_extension:
|
test_extension:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
working-directory: ./extension
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Use Node.js 20
|
- name: Use Node.js 20
|
||||||
@@ -88,19 +85,17 @@ jobs:
|
|||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
- name: Playwright install
|
||||||
|
run: npx playwright install --with-deps
|
||||||
- name: Build extension
|
- name: Build extension
|
||||||
run: npm run build
|
run: npm run build
|
||||||
|
working-directory: ./packages/extension
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: extension
|
name: extension
|
||||||
path: ./extension/dist
|
path: ./extension/dist
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
- name: Install MCP server
|
|
||||||
run: |
|
|
||||||
cd ..
|
|
||||||
npm ci
|
|
||||||
npx playwright install chromium
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: |
|
run: |
|
||||||
if [[ "$(uname)" == "Linux" ]]; then
|
if [[ "$(uname)" == "Linux" ]]; then
|
||||||
@@ -109,3 +104,4 @@ jobs:
|
|||||||
npm run test
|
npm run test
|
||||||
fi
|
fi
|
||||||
shell: bash
|
shell: bash
|
||||||
|
working-directory: ./packages/extension
|
||||||
|
|||||||
23
.github/workflows/publish.yml
vendored
@@ -7,7 +7,7 @@ on:
|
|||||||
types: [published]
|
types: [published]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish-canary-npm:
|
publish-mcp-canary-npm:
|
||||||
if: github.event.schedule || github.event_name == 'workflow_dispatch'
|
if: github.event.schedule || github.event_name == 'workflow_dispatch'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
@@ -38,16 +38,19 @@ jobs:
|
|||||||
- name: Update package.json version
|
- name: Update package.json version
|
||||||
run: |
|
run: |
|
||||||
npm version ${{ steps.canary-version.outputs.version }} --no-git-tag-version
|
npm version ${{ steps.canary-version.outputs.version }} --no-git-tag-version
|
||||||
|
working-directory: ./packages/playwright-mcp
|
||||||
|
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: npx playwright install --with-deps
|
- run: npx playwright install --with-deps
|
||||||
- run: npm run lint
|
- run: npm run lint
|
||||||
- run: npm run ctest
|
- run: npm run ctest
|
||||||
|
working-directory: ./packages/playwright-mcp
|
||||||
|
|
||||||
- name: Publish to npm with next tag
|
- name: Publish to npm with next tag
|
||||||
run: npm publish --tag next
|
run: npm publish --tag next
|
||||||
|
working-directory: ./packages/playwright-mcp
|
||||||
|
|
||||||
publish-release-npm:
|
publish-mcp-release-npm:
|
||||||
if: github.event_name == 'release'
|
if: github.event_name == 'release'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
@@ -66,9 +69,11 @@ jobs:
|
|||||||
- run: npx playwright install --with-deps
|
- run: npx playwright install --with-deps
|
||||||
- run: npm run lint
|
- run: npm run lint
|
||||||
- run: npm run ctest
|
- run: npm run ctest
|
||||||
|
working-directory: ./packages/playwright-mcp
|
||||||
- run: npm publish
|
- run: npm publish
|
||||||
|
working-directory: ./packages/playwright-mcp
|
||||||
|
|
||||||
publish-release-docker:
|
publish-mcp-release-docker:
|
||||||
if: github.event_name == 'release'
|
if: github.event_name == 'release'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
@@ -93,8 +98,7 @@ jobs:
|
|||||||
id: build-push
|
id: build-push
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
file: ./Dockerfile
|
||||||
file: ./Dockerfile # Adjust path if your Dockerfile is elsewhere
|
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
@@ -127,17 +131,16 @@ jobs:
|
|||||||
node-version: 20
|
node-version: 20
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
- name: Install extension dependencies
|
- name: Install extension dependencies
|
||||||
working-directory: ./extension
|
|
||||||
run: npm ci
|
run: npm ci
|
||||||
- name: Build extension
|
- name: Build extension
|
||||||
working-directory: ./extension
|
working-directory: ./packages/extension
|
||||||
run: npm run build
|
run: npm run build
|
||||||
- name: Get extension version
|
- name: Get extension version
|
||||||
id: get-version
|
id: get-version
|
||||||
working-directory: ./extension
|
working-directory: ./packages/extension
|
||||||
run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
|
run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
|
||||||
- name: Package extension
|
- name: Package extension
|
||||||
working-directory: ./extension
|
working-directory: ./packages/extension
|
||||||
run: |
|
run: |
|
||||||
cd dist
|
cd dist
|
||||||
zip -r ../playwright-mcp-extension-${{ steps.get-version.outputs.version }}.zip .
|
zip -r ../playwright-mcp-extension-${{ steps.get-version.outputs.version }}.zip .
|
||||||
@@ -146,4 +149,4 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ github.token }}
|
GITHUB_TOKEN: ${{ github.token }}
|
||||||
run: |
|
run: |
|
||||||
gh release upload ${{github.event.release.tag_name}} ./extension/playwright-mcp-extension-${{ steps.get-version.outputs.version }}.zip
|
gh release upload ${{github.event.release.tag_name}} ./packages/extension/playwright-mcp-extension-${{ steps.get-version.outputs.version }}.zip
|
||||||
|
|||||||
2
.gitignore
vendored
@@ -1,5 +1,3 @@
|
|||||||
lib/
|
|
||||||
dist/
|
|
||||||
node_modules/
|
node_modules/
|
||||||
test-results/
|
test-results/
|
||||||
playwright-report/
|
playwright-report/
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
**/*
|
|
||||||
README.md
|
|
||||||
LICENSE
|
|
||||||
!cli.js
|
|
||||||
!index.*
|
|
||||||
!config.d.ts
|
|
||||||
@@ -16,6 +16,7 @@ WORKDIR /app
|
|||||||
RUN --mount=type=cache,target=/root/.npm,sharing=locked,id=npm-cache \
|
RUN --mount=type=cache,target=/root/.npm,sharing=locked,id=npm-cache \
|
||||||
--mount=type=bind,source=package.json,target=package.json \
|
--mount=type=bind,source=package.json,target=package.json \
|
||||||
--mount=type=bind,source=package-lock.json,target=package-lock.json \
|
--mount=type=bind,source=package-lock.json,target=package-lock.json \
|
||||||
|
--mount=type=bind,source=packages/playwright-mcp/package.json,target=packages/playwright-mcp/package.json \
|
||||||
npm ci --omit=dev && \
|
npm ci --omit=dev && \
|
||||||
# Install system dependencies for playwright
|
# Install system dependencies for playwright
|
||||||
npx -y playwright-core install-deps chromium
|
npx -y playwright-core install-deps chromium
|
||||||
@@ -28,10 +29,11 @@ FROM base AS builder
|
|||||||
RUN --mount=type=cache,target=/root/.npm,sharing=locked,id=npm-cache \
|
RUN --mount=type=cache,target=/root/.npm,sharing=locked,id=npm-cache \
|
||||||
--mount=type=bind,source=package.json,target=package.json \
|
--mount=type=bind,source=package.json,target=package.json \
|
||||||
--mount=type=bind,source=package-lock.json,target=package-lock.json \
|
--mount=type=bind,source=package-lock.json,target=package-lock.json \
|
||||||
|
--mount=type=bind,source=packages/playwright-mcp/package.json,target=packages/playwright-mcp/package.json \
|
||||||
npm ci
|
npm ci
|
||||||
|
|
||||||
# Copy the rest of the app
|
# Copy the rest of the app
|
||||||
COPY *.json *.js *.ts .
|
COPY packages/playwright-mcp/*.json packages/playwright-mcp/*.js packages/playwright-mcp/*.ts .
|
||||||
|
|
||||||
# ------------------------------
|
# ------------------------------
|
||||||
# Browser
|
# Browser
|
||||||
@@ -59,7 +61,7 @@ RUN chown -R ${USERNAME}:${USERNAME} node_modules
|
|||||||
USER ${USERNAME}
|
USER ${USERNAME}
|
||||||
|
|
||||||
COPY --from=browser --chown=${USERNAME}:${USERNAME} ${PLAYWRIGHT_BROWSERS_PATH} ${PLAYWRIGHT_BROWSERS_PATH}
|
COPY --from=browser --chown=${USERNAME}:${USERNAME} ${PLAYWRIGHT_BROWSERS_PATH} ${PLAYWRIGHT_BROWSERS_PATH}
|
||||||
COPY --chown=${USERNAME}:${USERNAME} cli.js package.json ./
|
COPY --chown=${USERNAME}:${USERNAME} packages/playwright-mcp/cli.js packages/playwright-mcp/package.json ./
|
||||||
|
|
||||||
# Run in headless and only with chromium (other browsers need more dependencies not included in this image)
|
# 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", "cli.js", "--headless", "--browser", "chromium", "--no-sandbox"]
|
||||||
|
|||||||
86
README.md
@@ -84,15 +84,11 @@ Follow the MCP install [guide](https://modelcontextprotocol.io/quickstart/user),
|
|||||||
<details>
|
<details>
|
||||||
<summary>Cline</summary>
|
<summary>Cline</summary>
|
||||||
|
|
||||||
Follow the instruction in the section [MCP Integration](https://docs.cline.bot/mcp/mcp-overview)
|
Follow the instruction in the section [Configuring MCP Servers](https://docs.cline.bot/mcp/configuring-mcp-servers)
|
||||||
|
|
||||||
Connection Types
|
**Example: Local Setup**
|
||||||
Local (stdio): Cline runs the server as a local process.
|
|
||||||
Remote (streamableHttp): hosted/cloud environments.
|
|
||||||
Legacy (sse): Only use if the host doesn't support the modern streamableHttp protocol.
|
|
||||||
|
|
||||||
Example: Local Setup
|
Add the following to your [`cline_mcp_settings.json`](https://docs.cline.bot/mcp/configuring-mcp-servers#editing-mcp-settings-files) file:
|
||||||
Add the following to your cline_mcp_settings.json file:
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -367,6 +363,10 @@ Playwright MCP server supports following arguments. They can be provided in the
|
|||||||
--cdp-endpoint <endpoint> CDP endpoint to connect to.
|
--cdp-endpoint <endpoint> CDP endpoint to connect to.
|
||||||
--cdp-header <headers...> CDP headers to send with the connect
|
--cdp-header <headers...> CDP headers to send with the connect
|
||||||
request, multiple can be specified.
|
request, multiple can be specified.
|
||||||
|
--codegen <lang> specify the language to use for code
|
||||||
|
generation, possible values:
|
||||||
|
"typescript", "none". Default is
|
||||||
|
"typescript".
|
||||||
--config <path> path to the configuration file.
|
--config <path> path to the configuration file.
|
||||||
--console-level <level> level of console messages to return:
|
--console-level <level> level of console messages to return:
|
||||||
"error", "warning", "info", "debug".
|
"error", "warning", "info", "debug".
|
||||||
@@ -404,6 +404,10 @@ Playwright MCP server supports following arguments. They can be provided in the
|
|||||||
--no-sandbox disable the sandbox for all process
|
--no-sandbox disable the sandbox for all process
|
||||||
types that are normally sandboxed.
|
types that are normally sandboxed.
|
||||||
--output-dir <path> path to the directory for output files.
|
--output-dir <path> path to the directory for output files.
|
||||||
|
--output-mode <mode> whether to save snapshots, console
|
||||||
|
messages, network logs to a file or to
|
||||||
|
the standard output. Can be "file" or
|
||||||
|
"stdout". Default is "stdout".
|
||||||
--port <port> port to listen on for SSE transport.
|
--port <port> port to listen on for SSE transport.
|
||||||
--proxy-bypass <bypass> comma-separated domains to bypass proxy,
|
--proxy-bypass <bypass> comma-separated domains to bypass proxy,
|
||||||
for example
|
for example
|
||||||
@@ -580,6 +584,11 @@ npx @playwright/mcp@latest --config path/to/config.json
|
|||||||
*/
|
*/
|
||||||
cdpHeaders?: Record<string, string>;
|
cdpHeaders?: Record<string, string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timeout in milliseconds for connecting to CDP endpoint. Defaults to 30000 (30 seconds). Pass 0 to disable timeout.
|
||||||
|
*/
|
||||||
|
cdpTimeout?: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remote endpoint to connect to an existing Playwright server.
|
* Remote endpoint to connect to an existing Playwright server.
|
||||||
*/
|
*/
|
||||||
@@ -658,6 +667,11 @@ npx @playwright/mcp@latest --config path/to/config.json
|
|||||||
*/
|
*/
|
||||||
outputDir?: string;
|
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?: {
|
console?: {
|
||||||
/**
|
/**
|
||||||
* The level of console messages to return. Each level includes the messages of more severe levels. Defaults to "info".
|
* The level of console messages to return. Each level includes the messages of more severe levels. Defaults to "info".
|
||||||
@@ -704,13 +718,18 @@ npx @playwright/mcp@latest --config path/to/config.json
|
|||||||
* When taking snapshots for responses, specifies the mode to use.
|
* When taking snapshots for responses, specifies the mode to use.
|
||||||
*/
|
*/
|
||||||
mode?: 'incremental' | 'full' | 'none';
|
mode?: 'incremental' | 'full' | 'none';
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to allow file uploads from anywhere on the file system.
|
* 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.
|
* By default (false), file uploads are restricted to paths within the MCP roots only.
|
||||||
*/
|
*/
|
||||||
allowUnrestrictedFileAccess?: boolean;
|
allowUnrestrictedFileAccess?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify the language to use for code generation.
|
||||||
|
*/
|
||||||
|
codegen?: 'typescript' | 'none';
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -810,7 +829,7 @@ http.createServer(async (req, res) => {
|
|||||||
- Title: Click
|
- Title: Click
|
||||||
- Description: Perform click on a web page
|
- Description: Perform click on a web page
|
||||||
- Parameters:
|
- Parameters:
|
||||||
- `element` (string): Human-readable element description used to obtain permission to interact with the element
|
- `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
|
- `ref` (string): Exact target element reference from the page snapshot
|
||||||
- `doubleClick` (boolean, optional): Whether to perform a double click instead of a single click
|
- `doubleClick` (boolean, optional): Whether to perform a double click instead of a single click
|
||||||
- `button` (string, optional): Button to click, defaults to left
|
- `button` (string, optional): Button to click, defaults to left
|
||||||
@@ -831,7 +850,8 @@ http.createServer(async (req, res) => {
|
|||||||
- Title: Get console messages
|
- Title: Get console messages
|
||||||
- Description: Returns all console messages
|
- Description: Returns all console messages
|
||||||
- Parameters:
|
- Parameters:
|
||||||
- `level` (string, optional): Level of the console messages to return. Each level includes the messages of more severe levels. Defaults to "info".
|
- `level` (string): Level of the console messages to return. Each level includes the messages of more severe levels. Defaults to "info".
|
||||||
|
- `filename` (string, optional): Filename to save the console messages to. If not provided, messages are returned as text.
|
||||||
- Read-only: **true**
|
- Read-only: **true**
|
||||||
|
|
||||||
<!-- NOTE: This has been generated via update-readme.js -->
|
<!-- NOTE: This has been generated via update-readme.js -->
|
||||||
@@ -891,7 +911,7 @@ http.createServer(async (req, res) => {
|
|||||||
- Title: Hover mouse
|
- Title: Hover mouse
|
||||||
- Description: Hover over element on page
|
- Description: Hover over element on page
|
||||||
- Parameters:
|
- Parameters:
|
||||||
- `element` (string): Human-readable element description used to obtain permission to interact with the element
|
- `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
|
- `ref` (string): Exact target element reference from the page snapshot
|
||||||
- Read-only: **false**
|
- Read-only: **false**
|
||||||
|
|
||||||
@@ -908,7 +928,7 @@ http.createServer(async (req, res) => {
|
|||||||
|
|
||||||
- **browser_navigate_back**
|
- **browser_navigate_back**
|
||||||
- Title: Go back
|
- Title: Go back
|
||||||
- Description: Go back to the previous page
|
- Description: Go back to the previous page in the history
|
||||||
- Parameters: None
|
- Parameters: None
|
||||||
- Read-only: **false**
|
- Read-only: **false**
|
||||||
|
|
||||||
@@ -918,7 +938,8 @@ http.createServer(async (req, res) => {
|
|||||||
- Title: List network requests
|
- Title: List network requests
|
||||||
- Description: Returns all network requests since loading the page
|
- Description: Returns all network requests since loading the page
|
||||||
- Parameters:
|
- Parameters:
|
||||||
- `includeStatic` (boolean, optional): Whether to include successful static resources like images, fonts, scripts, etc. Defaults to false.
|
- `includeStatic` (boolean): Whether to include successful static resources like images, fonts, scripts, etc. Defaults to false.
|
||||||
|
- `filename` (string, optional): Filename to save the network requests to. If not provided, requests are returned as text.
|
||||||
- Read-only: **true**
|
- Read-only: **true**
|
||||||
|
|
||||||
<!-- NOTE: This has been generated via update-readme.js -->
|
<!-- NOTE: This has been generated via update-readme.js -->
|
||||||
@@ -955,7 +976,7 @@ http.createServer(async (req, res) => {
|
|||||||
- Title: Select option
|
- Title: Select option
|
||||||
- Description: Select an option in a dropdown
|
- Description: Select an option in a dropdown
|
||||||
- Parameters:
|
- Parameters:
|
||||||
- `element` (string): Human-readable element description used to obtain permission to interact with the element
|
- `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
|
- `ref` (string): Exact target element reference from the page snapshot
|
||||||
- `values` (array): Array of values to select in the dropdown. This can be a single value or multiple values.
|
- `values` (array): Array of values to select in the dropdown. This can be a single value or multiple values.
|
||||||
- Read-only: **false**
|
- Read-only: **false**
|
||||||
@@ -975,7 +996,7 @@ http.createServer(async (req, res) => {
|
|||||||
- Title: Take a screenshot
|
- Title: Take a screenshot
|
||||||
- Description: Take a screenshot of the current page. You can't perform actions based on the screenshot, use browser_snapshot for actions.
|
- Description: Take a screenshot of the current page. You can't perform actions based on the screenshot, use browser_snapshot for actions.
|
||||||
- Parameters:
|
- Parameters:
|
||||||
- `type` (string, optional): Image format for the screenshot. Default is png.
|
- `type` (string): Image format for the screenshot. Default is png.
|
||||||
- `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.
|
- `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.
|
- `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.
|
- `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.
|
||||||
@@ -988,7 +1009,7 @@ http.createServer(async (req, res) => {
|
|||||||
- Title: Type text
|
- Title: Type text
|
||||||
- Description: Type text into editable element
|
- Description: Type text into editable element
|
||||||
- Parameters:
|
- Parameters:
|
||||||
- `element` (string): Human-readable element description used to obtain permission to interact with the element
|
- `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
|
- `ref` (string): Exact target element reference from the page snapshot
|
||||||
- `text` (string): Text to type into the element
|
- `text` (string): Text to type into the element
|
||||||
- `submit` (boolean, optional): Whether to submit entered text (press Enter after)
|
- `submit` (boolean, optional): Whether to submit entered text (press Enter after)
|
||||||
@@ -1045,18 +1066,25 @@ http.createServer(async (req, res) => {
|
|||||||
- Title: Click
|
- Title: Click
|
||||||
- Description: Click left mouse button at a given position
|
- Description: Click left mouse button at a given position
|
||||||
- Parameters:
|
- Parameters:
|
||||||
- `element` (string): Human-readable element description used to obtain permission to interact with the element
|
|
||||||
- `x` (number): X coordinate
|
- `x` (number): X coordinate
|
||||||
- `y` (number): Y coordinate
|
- `y` (number): Y coordinate
|
||||||
- Read-only: **false**
|
- Read-only: **false**
|
||||||
|
|
||||||
<!-- NOTE: This has been generated via update-readme.js -->
|
<!-- NOTE: This has been generated via update-readme.js -->
|
||||||
|
|
||||||
|
- **browser_mouse_down**
|
||||||
|
- Title: Press mouse down
|
||||||
|
- Description: Press mouse down
|
||||||
|
- Parameters:
|
||||||
|
- `button` (string, optional): Button to press, defaults to left
|
||||||
|
- Read-only: **false**
|
||||||
|
|
||||||
|
<!-- NOTE: This has been generated via update-readme.js -->
|
||||||
|
|
||||||
- **browser_mouse_drag_xy**
|
- **browser_mouse_drag_xy**
|
||||||
- Title: Drag mouse
|
- Title: Drag mouse
|
||||||
- Description: Drag left mouse button to a given position
|
- Description: Drag left mouse button to a given position
|
||||||
- Parameters:
|
- Parameters:
|
||||||
- `element` (string): Human-readable element description used to obtain permission to interact with the element
|
|
||||||
- `startX` (number): Start X coordinate
|
- `startX` (number): Start X coordinate
|
||||||
- `startY` (number): Start Y coordinate
|
- `startY` (number): Start Y coordinate
|
||||||
- `endX` (number): End X coordinate
|
- `endX` (number): End X coordinate
|
||||||
@@ -1069,11 +1097,29 @@ http.createServer(async (req, res) => {
|
|||||||
- Title: Move mouse
|
- Title: Move mouse
|
||||||
- Description: Move mouse to a given position
|
- Description: Move mouse to a given position
|
||||||
- Parameters:
|
- Parameters:
|
||||||
- `element` (string): Human-readable element description used to obtain permission to interact with the element
|
|
||||||
- `x` (number): X coordinate
|
- `x` (number): X coordinate
|
||||||
- `y` (number): Y coordinate
|
- `y` (number): Y coordinate
|
||||||
- Read-only: **false**
|
- Read-only: **false**
|
||||||
|
|
||||||
|
<!-- NOTE: This has been generated via update-readme.js -->
|
||||||
|
|
||||||
|
- **browser_mouse_up**
|
||||||
|
- Title: Press mouse up
|
||||||
|
- Description: Press mouse up
|
||||||
|
- Parameters:
|
||||||
|
- `button` (string, optional): Button to press, defaults to left
|
||||||
|
- Read-only: **false**
|
||||||
|
|
||||||
|
<!-- NOTE: This has been generated via update-readme.js -->
|
||||||
|
|
||||||
|
- **browser_mouse_wheel**
|
||||||
|
- Title: Scroll mouse wheel
|
||||||
|
- Description: Scroll mouse wheel
|
||||||
|
- Parameters:
|
||||||
|
- `deltaX` (number): X delta
|
||||||
|
- `deltaY` (number): Y delta
|
||||||
|
- Read-only: **false**
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
@@ -1099,7 +1145,7 @@ http.createServer(async (req, res) => {
|
|||||||
- Title: Create locator for element
|
- Title: Create locator for element
|
||||||
- Description: Generate locator for the given element to use in tests
|
- Description: Generate locator for the given element to use in tests
|
||||||
- Parameters:
|
- Parameters:
|
||||||
- `element` (string): Human-readable element description used to obtain permission to interact with the element
|
- `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
|
- `ref` (string): Exact target element reference from the page snapshot
|
||||||
- Read-only: **true**
|
- Read-only: **true**
|
||||||
|
|
||||||
|
|||||||
1885
extension/package-lock.json
generated
2148
package-lock.json
generated
43
package.json
@@ -1,51 +1,30 @@
|
|||||||
{
|
{
|
||||||
"name": "@playwright/mcp",
|
"name": "playwright-mcp-internal",
|
||||||
"version": "0.0.55",
|
"version": "0.0.58",
|
||||||
"description": "Playwright Tools for MCP",
|
"private": true,
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/microsoft/playwright-mcp.git"
|
"url": "git+https://github.com/microsoft/playwright-mcp.git"
|
||||||
},
|
},
|
||||||
"homepage": "https://playwright.dev",
|
"homepage": "https://playwright.dev",
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Microsoft Corporation"
|
"name": "Microsoft Corporation"
|
||||||
},
|
},
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "npm run update-readme",
|
|
||||||
"update-readme": "node update-readme.js",
|
|
||||||
"docker-build": "docker build --no-cache -t playwright-mcp-dev:latest .",
|
"docker-build": "docker build --no-cache -t playwright-mcp-dev:latest .",
|
||||||
"docker-rm": "docker rm playwright-mcp-dev",
|
"docker-rm": "docker rm playwright-mcp-dev",
|
||||||
"docker-run": "docker run -it -p 8080:8080 --name playwright-mcp-dev playwright-mcp-dev:latest",
|
"docker-run": "docker run -it -p 8080:8080 --name playwright-mcp-dev playwright-mcp-dev:latest",
|
||||||
"test": "playwright test",
|
"lint": "npm run lint --workspaces",
|
||||||
"ctest": "playwright test --project=chrome",
|
"test": "npm run test --workspaces",
|
||||||
"ftest": "playwright test --project=firefox",
|
"build": "npm run build --workspaces"
|
||||||
"wtest": "playwright test --project=webkit",
|
|
||||||
"dtest": "MCP_IN_DOCKER=1 playwright test --project=chromium-docker",
|
|
||||||
"npm-publish": "npm run clean && npm run test && npm publish",
|
|
||||||
"copy-config": "cp ../playwright/packages/playwright/src/mcp/config.d.ts . && perl -pi -e \"s|import type \\* as playwright from 'playwright-core';|import type * as playwright from 'playwright';|\" ./config.d.ts",
|
|
||||||
"roll": "npm run copy-config && npm run lint"
|
|
||||||
},
|
|
||||||
"exports": {
|
|
||||||
"./package.json": "./package.json",
|
|
||||||
".": {
|
|
||||||
"types": "./index.d.ts",
|
|
||||||
"default": "./index.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"playwright": "1.58.0-alpha-2026-01-07",
|
|
||||||
"playwright-core": "1.58.0-alpha-2026-01-07"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"mcp-server-playwright": "cli.js"
|
|
||||||
},
|
},
|
||||||
|
"workspaces": [
|
||||||
|
"packages/*"
|
||||||
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@modelcontextprotocol/sdk": "^1.24.0",
|
"@modelcontextprotocol/sdk": "^1.25.2",
|
||||||
"@playwright/test": "1.58.0-alpha-2026-01-07",
|
"@playwright/test": "1.59.0-alpha-1769217009000",
|
||||||
"@types/node": "^24.3.0"
|
"@types/node": "^24.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
packages/extension/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
dist/
|
||||||
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 571 B After Width: | Height: | Size: 571 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "Playwright MCP Bridge",
|
"name": "Playwright MCP Bridge",
|
||||||
"version": "0.0.55",
|
"version": "0.0.58",
|
||||||
"description": "Share browser tabs with Playwright MCP server",
|
"description": "Share browser tabs with Playwright MCP server",
|
||||||
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9nMS2b0WCohjVHPGb8D9qAdkbIngDqoAjTeSccHJijgcONejge+OJxOQOMLu7b0ovt1c9BiEJa5JcpM+EHFVGL1vluBxK71zmBy1m2f9vZF3HG0LSCp7YRkum9rAIEthDwbkxx6XTvpmAY5rjFa/NON6b9Hlbo+8peUSkoOK7HTwYnnI36asZ9eUTiveIf+DMPLojW2UX33vDWG2UKvMVDewzclb4+uLxAYshY7Mx8we/b44xu+Anb/EBLKjOPk9Yh541xJ5Ozc8EiP/5yxOp9c/lRiYUHaRW+4r0HKZyFt0eZ52ti2iM4Nfk7jRXR7an3JPsUIf5deC/1cVM/+1ZQIDAQAB",
|
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9nMS2b0WCohjVHPGb8D9qAdkbIngDqoAjTeSccHJijgcONejge+OJxOQOMLu7b0ovt1c9BiEJa5JcpM+EHFVGL1vluBxK71zmBy1m2f9vZF3HG0LSCp7YRkum9rAIEthDwbkxx6XTvpmAY5rjFa/NON6b9Hlbo+8peUSkoOK7HTwYnnI36asZ9eUTiveIf+DMPLojW2UX33vDWG2UKvMVDewzclb4+uLxAYshY7Mx8we/b44xu+Anb/EBLKjOPk9Yh541xJ5Ozc8EiP/5yxOp9c/lRiYUHaRW+4r0HKZyFt0eZ52ti2iM4Nfk7jRXR7an3JPsUIf5deC/1cVM/+1ZQIDAQAB",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@playwright/mcp-extension",
|
"name": "@playwright/mcp-extension",
|
||||||
"version": "0.0.55",
|
"version": "0.0.58",
|
||||||
"description": "Playwright MCP Browser Extension",
|
"description": "Playwright MCP Browser Extension",
|
||||||
"private": true,
|
"private": true,
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
"build": "tsc --project . && tsc --project tsconfig.ui.json && vite build && vite build --config vite.sw.config.mts",
|
"build": "tsc --project . && tsc --project tsconfig.ui.json && vite build && vite build --config vite.sw.config.mts",
|
||||||
"watch": "tsc --watch --project . & tsc --watch --project tsconfig.ui.json & vite build --watch & vite build --watch --config vite.sw.config.mts",
|
"watch": "tsc --watch --project . & tsc --watch --project tsconfig.ui.json & vite build --watch & vite build --watch --config vite.sw.config.mts",
|
||||||
"test": "playwright test",
|
"test": "playwright test",
|
||||||
|
"lint": "tsc --project .",
|
||||||
"clean": "rm -rf dist"
|
"clean": "rm -rf dist"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
import { defineConfig } from '@playwright/test';
|
import { defineConfig } from '@playwright/test';
|
||||||
|
|
||||||
import type { TestOptions } from '../tests/fixtures';
|
import type { TestOptions } from '../playwright-mcp/tests/fixtures';
|
||||||
|
|
||||||
export default defineConfig<TestOptions>({
|
export default defineConfig<TestOptions>({
|
||||||
testDir: './tests',
|
testDir: './tests',
|
||||||
@@ -17,11 +17,11 @@
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { chromium } from 'playwright';
|
import { chromium } from 'playwright';
|
||||||
import { test as base, expect } from '../../tests/fixtures';
|
import { test as base, expect } from '../../playwright-mcp/tests/fixtures';
|
||||||
|
|
||||||
import type { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
import type { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
||||||
import type { BrowserContext } from 'playwright';
|
import type { BrowserContext } from 'playwright';
|
||||||
import type { StartClient } from '../../tests/fixtures';
|
import type { StartClient } from '../../playwright-mcp/tests/fixtures';
|
||||||
|
|
||||||
type BrowserWithExtension = {
|
type BrowserWithExtension = {
|
||||||
userDataDir: string;
|
userDataDir: string;
|
||||||
@@ -134,7 +134,7 @@ test(`navigate with extension`, async ({ browserWithExtension, startClient, serv
|
|||||||
await selectorPage.getByRole('button', { name: 'Allow' }).click();
|
await selectorPage.getByRole('button', { name: 'Allow' }).click();
|
||||||
|
|
||||||
expect(await navigateResponse).toHaveResponse({
|
expect(await navigateResponse).toHaveResponse({
|
||||||
pageState: expect.stringContaining(`- generic [active] [ref=e1]: Hello, world!`),
|
snapshot: expect.stringContaining(`- generic [active] [ref=e1]: Hello, world!`),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -166,7 +166,7 @@ test(`snapshot of an existing page`, async ({ browserWithExtension, startClient,
|
|||||||
await selectorPage.locator('.tab-item', { hasText: 'Title' }).getByRole('button', { name: 'Connect' }).click();
|
await selectorPage.locator('.tab-item', { hasText: 'Title' }).getByRole('button', { name: 'Connect' }).click();
|
||||||
|
|
||||||
expect(await navigateResponse).toHaveResponse({
|
expect(await navigateResponse).toHaveResponse({
|
||||||
pageState: expect.stringContaining(`- generic [active] [ref=e1]: Hello, world!`),
|
snapshot: expect.stringContaining(`- generic [active] [ref=e1]: Hello, world!`),
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(browserContext.pages()).toHaveLength(4);
|
expect(browserContext.pages()).toHaveLength(4);
|
||||||
@@ -187,7 +187,7 @@ test(`extension not installed timeout`, async ({ browserWithExtension, startClie
|
|||||||
name: 'browser_navigate',
|
name: 'browser_navigate',
|
||||||
arguments: { url: server.HELLO_WORLD },
|
arguments: { url: server.HELLO_WORLD },
|
||||||
})).toHaveResponse({
|
})).toHaveResponse({
|
||||||
result: expect.stringContaining('Extension connection timeout. Make sure the "Playwright MCP Bridge" extension is installed.'),
|
error: expect.stringContaining('Extension connection timeout. Make sure the "Playwright MCP Bridge" extension is installed.'),
|
||||||
isError: true,
|
isError: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -216,7 +216,7 @@ testWithOldExtensionVersion(`works with old extension version`, async ({ browser
|
|||||||
await selectorPage.getByRole('button', { name: 'Allow' }).click();
|
await selectorPage.getByRole('button', { name: 'Allow' }).click();
|
||||||
|
|
||||||
expect(await navigateResponse).toHaveResponse({
|
expect(await navigateResponse).toHaveResponse({
|
||||||
pageState: expect.stringContaining(`- generic [active] [ref=e1]: Hello, world!`),
|
snapshot: expect.stringContaining(`- generic [active] [ref=e1]: Hello, world!`),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -242,7 +242,7 @@ test(`extension needs update`, async ({ browserWithExtension, startClient, serve
|
|||||||
await expect(confirmationPage.locator('.status-banner')).toContainText(`Playwright MCP version trying to connect requires newer extension version`);
|
await expect(confirmationPage.locator('.status-banner')).toContainText(`Playwright MCP version trying to connect requires newer extension version`);
|
||||||
|
|
||||||
expect(await navigateResponse).toHaveResponse({
|
expect(await navigateResponse).toHaveResponse({
|
||||||
result: expect.stringContaining('Extension connection timeout.'),
|
error: expect.stringContaining('Extension connection timeout.'),
|
||||||
isError: true,
|
isError: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -267,10 +267,9 @@ test(`custom executablePath`, async ({ startClient, server, useShortConnectionTi
|
|||||||
const navigateResponse = await client.callTool({
|
const navigateResponse = await client.callTool({
|
||||||
name: 'browser_navigate',
|
name: 'browser_navigate',
|
||||||
arguments: { url: server.HELLO_WORLD },
|
arguments: { url: server.HELLO_WORLD },
|
||||||
timeout: 1000,
|
|
||||||
});
|
});
|
||||||
expect(await navigateResponse).toHaveResponse({
|
expect(await navigateResponse).toHaveResponse({
|
||||||
result: expect.stringContaining('Extension connection timeout.'),
|
error: expect.stringContaining('Extension connection timeout.'),
|
||||||
isError: true,
|
isError: true,
|
||||||
});
|
});
|
||||||
expect(await fs.promises.readFile(test.info().outputPath('output.txt'), 'utf8')).toContain('Custom exec args: chrome-extension://jakfalbnbhgkpmoaakfflhflbfpkailf/connect.html?');
|
expect(await fs.promises.readFile(test.info().outputPath('output.txt'), 'utf8')).toContain('Custom exec args: chrome-extension://jakfalbnbhgkpmoaakfflhflbfpkailf/connect.html?');
|
||||||
@@ -300,6 +299,6 @@ test(`bypass connection dialog with token`, async ({ browserWithExtension, start
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(await navigateResponse).toHaveResponse({
|
expect(await navigateResponse).toHaveResponse({
|
||||||
pageState: expect.stringContaining(`- generic [active] [ref=e1]: Hello, world!`),
|
snapshot: expect.stringContaining(`- generic [active] [ref=e1]: Hello, world!`),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
2
packages/playwright-mcp/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
README.md
|
||||||
|
LICENSE
|
||||||
7
packages/playwright-mcp/.npmignore
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
**/*
|
||||||
|
!README.md
|
||||||
|
!LICENSE
|
||||||
|
!cli.js
|
||||||
|
!playwright-cli.js
|
||||||
|
!index.*
|
||||||
|
!config.d.ts
|
||||||
@@ -16,7 +16,17 @@
|
|||||||
|
|
||||||
import type * as playwright from 'playwright';
|
import type * as playwright from 'playwright';
|
||||||
|
|
||||||
export type ToolCapability = 'core' | 'core-tabs' | 'core-install' | 'vision' | 'pdf' | 'testing' | 'tracing';
|
export type ToolCapability =
|
||||||
|
'core' |
|
||||||
|
'core-input' |
|
||||||
|
'core-navigation' |
|
||||||
|
'core-tabs' |
|
||||||
|
'core-install' |
|
||||||
|
'core-input' |
|
||||||
|
'vision' |
|
||||||
|
'pdf' |
|
||||||
|
'testing' |
|
||||||
|
'tracing';
|
||||||
|
|
||||||
export type Config = {
|
export type Config = {
|
||||||
/**
|
/**
|
||||||
@@ -64,6 +74,11 @@ export type Config = {
|
|||||||
*/
|
*/
|
||||||
cdpHeaders?: Record<string, string>;
|
cdpHeaders?: Record<string, string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timeout in milliseconds for connecting to CDP endpoint. Defaults to 30000 (30 seconds). Pass 0 to disable timeout.
|
||||||
|
*/
|
||||||
|
cdpTimeout?: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remote endpoint to connect to an existing Playwright server.
|
* Remote endpoint to connect to an existing Playwright server.
|
||||||
*/
|
*/
|
||||||
@@ -142,6 +157,11 @@ export type Config = {
|
|||||||
*/
|
*/
|
||||||
outputDir?: string;
|
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?: {
|
console?: {
|
||||||
/**
|
/**
|
||||||
* The level of console messages to return. Each level includes the messages of more severe levels. Defaults to "info".
|
* The level of console messages to return. Each level includes the messages of more severe levels. Defaults to "info".
|
||||||
@@ -188,11 +208,16 @@ export type Config = {
|
|||||||
* When taking snapshots for responses, specifies the mode to use.
|
* When taking snapshots for responses, specifies the mode to use.
|
||||||
*/
|
*/
|
||||||
mode?: 'incremental' | 'full' | 'none';
|
mode?: 'incremental' | 'full' | 'none';
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to allow file uploads from anywhere on the file system.
|
* 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.
|
* By default (false), file uploads are restricted to paths within the MCP roots only.
|
||||||
*/
|
*/
|
||||||
allowUnrestrictedFileAccess?: boolean;
|
allowUnrestrictedFileAccess?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify the language to use for code generation.
|
||||||
|
*/
|
||||||
|
codegen?: 'typescript' | 'none';
|
||||||
};
|
};
|
||||||
45
packages/playwright-mcp/package.json
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"name": "@playwright/mcp",
|
||||||
|
"version": "0.0.58",
|
||||||
|
"description": "Playwright Tools for MCP",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/microsoft/playwright-mcp.git"
|
||||||
|
},
|
||||||
|
"homepage": "https://playwright.dev",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"author": {
|
||||||
|
"name": "Microsoft Corporation"
|
||||||
|
},
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"scripts": {
|
||||||
|
"lint": "node update-readme.js",
|
||||||
|
"test": "playwright test",
|
||||||
|
"ctest": "playwright test --project=chrome",
|
||||||
|
"ftest": "playwright test --project=firefox",
|
||||||
|
"wtest": "playwright test --project=webkit",
|
||||||
|
"dtest": "MCP_IN_DOCKER=1 playwright test --project=chromium-docker",
|
||||||
|
"build": "echo OK",
|
||||||
|
"npm-publish": "npm run lint && npm run test && npm publish",
|
||||||
|
"copy-config": "cp ../../../playwright/packages/playwright/src/mcp/config.d.ts . && perl -pi -e \"s|import type \\* as playwright from 'playwright-core';|import type * as playwright from 'playwright';|\" ./config.d.ts",
|
||||||
|
"roll": "npm run copy-config && npm run lint"
|
||||||
|
},
|
||||||
|
"exports": {
|
||||||
|
"./package.json": "./package.json",
|
||||||
|
".": {
|
||||||
|
"types": "./index.d.ts",
|
||||||
|
"default": "./index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"minimist": "^1.2.5",
|
||||||
|
"playwright": "1.59.0-alpha-1769217009000",
|
||||||
|
"playwright-core": "1.59.0-alpha-1769217009000"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"mcp": "cli.js",
|
||||||
|
"playwright-cli": "./playwright-cli.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
23
packages/playwright-mcp/playwright-cli.js
Executable file
@@ -0,0 +1,23 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { program } = require('playwright/lib/mcp/terminal/program');
|
||||||
|
const packageJSON = require('./package.json');
|
||||||
|
program({ version: packageJSON.version }).catch(e => {
|
||||||
|
console.error(e.message);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
32
packages/playwright-mcp/tests/cli.spec.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* 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 { test, expect } from '@playwright/test';
|
||||||
|
import childProcess from 'child_process';
|
||||||
|
|
||||||
|
function runCLI(args: string[]) {
|
||||||
|
return childProcess.spawnSync(process.execPath, [require.resolve('../playwright-cli'), ...args], { encoding: 'utf-8' });
|
||||||
|
}
|
||||||
|
|
||||||
|
test('prints help', async () => {
|
||||||
|
const { stdout } = runCLI(['--help']);
|
||||||
|
expect(stdout).toContain('Usage: playwright-cli <command>');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('prints version', async () => {
|
||||||
|
const { stdout } = runCLI(['--version']);
|
||||||
|
expect(stdout).toContain(require('../package.json').version);
|
||||||
|
});
|
||||||
@@ -33,7 +33,7 @@ test('browser_click', async ({ client, server }) => {
|
|||||||
arguments: { url: server.PREFIX },
|
arguments: { url: server.PREFIX },
|
||||||
})).toHaveResponse({
|
})).toHaveResponse({
|
||||||
code: `await page.goto('${server.PREFIX}');`,
|
code: `await page.goto('${server.PREFIX}');`,
|
||||||
pageState: expect.stringContaining(`- button \"Submit\" [ref=e2]`),
|
snapshot: expect.stringContaining(`- button \"Submit\" [ref=e2]`),
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(await client.callTool({
|
expect(await client.callTool({
|
||||||
@@ -44,6 +44,6 @@ test('browser_click', async ({ client, server }) => {
|
|||||||
},
|
},
|
||||||
})).toHaveResponse({
|
})).toHaveResponse({
|
||||||
code: `await page.getByRole('button', { name: 'Submit' }).click();`,
|
code: `await page.getByRole('button', { name: 'Submit' }).click();`,
|
||||||
pageState: expect.stringContaining(`button "Submit" [active] [ref=e2]`),
|
snapshot: expect.stringContaining(`button "Submit" [active] [ref=e2]`),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -22,11 +22,6 @@ test('browser_navigate', async ({ client, server }) => {
|
|||||||
arguments: { url: server.HELLO_WORLD },
|
arguments: { url: server.HELLO_WORLD },
|
||||||
})).toHaveResponse({
|
})).toHaveResponse({
|
||||||
code: `await page.goto('${server.HELLO_WORLD}');`,
|
code: `await page.goto('${server.HELLO_WORLD}');`,
|
||||||
pageState: `- Page URL: ${server.HELLO_WORLD}
|
snapshot: expect.stringContaining(`generic [active] [ref=e1]: Hello, world!`),
|
||||||
- Page Title: Title
|
|
||||||
- Page Snapshot:
|
|
||||||
\`\`\`yaml
|
|
||||||
- generic [active] [ref=e1]: Hello, world!
|
|
||||||
\`\`\``,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -229,7 +229,7 @@ export const expect = baseExpect.extend({
|
|||||||
expect(parsed).not.toEqual(expect.objectContaining(object));
|
expect(parsed).not.toEqual(expect.objectContaining(object));
|
||||||
else
|
else
|
||||||
expect(parsed).toEqual(expect.objectContaining(object));
|
expect(parsed).toEqual(expect.objectContaining(object));
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
return {
|
return {
|
||||||
pass: isNot,
|
pass: isNot,
|
||||||
message: () => e.message,
|
message: () => e.message,
|
||||||
@@ -250,10 +250,12 @@ function parseResponse(response: any) {
|
|||||||
const text = response.content[0].text;
|
const text = response.content[0].text;
|
||||||
const sections = parseSections(text);
|
const sections = parseSections(text);
|
||||||
|
|
||||||
|
const error = sections.get('Error');
|
||||||
const result = sections.get('Result');
|
const result = sections.get('Result');
|
||||||
const code = sections.get('Ran Playwright code');
|
const code = sections.get('Ran Playwright code');
|
||||||
const tabs = sections.get('Open tabs');
|
const tabs = sections.get('Open tabs');
|
||||||
const pageState = sections.get('Page state');
|
const pageState = sections.get('Page state');
|
||||||
|
const snapshot = sections.get('Snapshot');
|
||||||
const consoleMessages = sections.get('New console messages');
|
const consoleMessages = sections.get('New console messages');
|
||||||
const modalState = sections.get('Modal state');
|
const modalState = sections.get('Modal state');
|
||||||
const downloads = sections.get('Downloads');
|
const downloads = sections.get('Downloads');
|
||||||
@@ -262,10 +264,12 @@ function parseResponse(response: any) {
|
|||||||
const attachments = response.content.slice(1);
|
const attachments = response.content.slice(1);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
error,
|
||||||
result,
|
result,
|
||||||
code: codeNoFrame,
|
code: codeNoFrame,
|
||||||
tabs,
|
tabs,
|
||||||
pageState,
|
pageState,
|
||||||
|
snapshot,
|
||||||
consoleMessages,
|
consoleMessages,
|
||||||
modalState,
|
modalState,
|
||||||
downloads,
|
downloads,
|
||||||
@@ -18,14 +18,15 @@
|
|||||||
|
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const { zodToJsonSchema } = require('zod-to-json-schema')
|
|
||||||
const { execSync } = require('child_process');
|
const { execSync } = require('child_process');
|
||||||
|
|
||||||
const { browserTools } = require('playwright/lib/mcp/browser/tools');
|
const { browserTools } = require('playwright/lib/mcp/browser/tools');
|
||||||
|
|
||||||
const capabilities = {
|
const capabilities = {
|
||||||
|
'core-navigation': 'Core automation',
|
||||||
'core': 'Core automation',
|
'core': 'Core automation',
|
||||||
'core-tabs': 'Tab management',
|
'core-tabs': 'Tab management',
|
||||||
|
'core-input': 'Core automation',
|
||||||
'core-install': 'Browser installation',
|
'core-install': 'Browser installation',
|
||||||
'vision': 'Coordinate-based (opt-in via --caps=vision)',
|
'vision': 'Coordinate-based (opt-in via --caps=vision)',
|
||||||
'pdf': 'PDF generation (opt-in via --caps=pdf)',
|
'pdf': 'PDF generation (opt-in via --caps=pdf)',
|
||||||
@@ -33,7 +34,15 @@ const capabilities = {
|
|||||||
'tracing': 'Tracing (opt-in via --caps=tracing)',
|
'tracing': 'Tracing (opt-in via --caps=tracing)',
|
||||||
};
|
};
|
||||||
|
|
||||||
const toolsByCapability = Object.fromEntries(Object.entries(capabilities).map(([capability, title]) => [title, browserTools.filter(tool => tool.capability === capability).sort((a, b) => a.schema.name.localeCompare(b.schema.name))]));
|
/** @type {Record<string, any[]>} */
|
||||||
|
const toolsByCapability = {};
|
||||||
|
for (const [capability, title] of Object.entries(capabilities)) {
|
||||||
|
let tools = browserTools.filter(tool => tool.capability === capability && !tool.skillOnly);
|
||||||
|
tools = (toolsByCapability[title] || []).concat(tools);
|
||||||
|
toolsByCapability[title] = tools;
|
||||||
|
}
|
||||||
|
for (const [, tools] of Object.entries(toolsByCapability))
|
||||||
|
tools.sort((a, b) => a.schema.name.localeCompare(b.schema.name));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {any} tool
|
* @param {any} tool
|
||||||
@@ -47,7 +56,7 @@ function formatToolForReadme(tool) {
|
|||||||
lines.push(` - Title: ${tool.title}`);
|
lines.push(` - Title: ${tool.title}`);
|
||||||
lines.push(` - Description: ${tool.description}`);
|
lines.push(` - Description: ${tool.description}`);
|
||||||
|
|
||||||
const inputSchema = /** @type {any} */ (zodToJsonSchema(tool.inputSchema || {}));
|
const inputSchema = /** @type {any} */ (tool.inputSchema ? tool.inputSchema.toJSONSchema() : {});
|
||||||
const requiredParams = inputSchema.required || [];
|
const requiredParams = inputSchema.required || [];
|
||||||
if (inputSchema.properties && Object.keys(inputSchema.properties).length) {
|
if (inputSchema.properties && Object.keys(inputSchema.properties).length) {
|
||||||
lines.push(` - Parameters:`);
|
lines.push(` - Parameters:`);
|
||||||
@@ -143,14 +152,14 @@ async function updateConfig(content) {
|
|||||||
console.log('Updating config schema from config.d.ts...');
|
console.log('Updating config schema from config.d.ts...');
|
||||||
const configPath = path.join(__dirname, 'config.d.ts');
|
const configPath = path.join(__dirname, 'config.d.ts');
|
||||||
const configContent = await fs.promises.readFile(configPath, 'utf-8');
|
const configContent = await fs.promises.readFile(configPath, 'utf-8');
|
||||||
|
|
||||||
// Extract the Config type definition
|
// Extract the Config type definition
|
||||||
const configTypeMatch = configContent.match(/export type Config = (\{[\s\S]*?\n\});/);
|
const configTypeMatch = configContent.match(/export type Config = (\{[\s\S]*?\n\});/);
|
||||||
if (!configTypeMatch)
|
if (!configTypeMatch)
|
||||||
throw new Error('Config type not found in config.d.ts');
|
throw new Error('Config type not found in config.d.ts');
|
||||||
|
|
||||||
const configType = configTypeMatch[1]; // Use capture group to get just the object definition
|
const configType = configTypeMatch[1]; // Use capture group to get just the object definition
|
||||||
|
|
||||||
const startMarker = `<!--- Config generated by ${path.basename(__filename)} -->`;
|
const startMarker = `<!--- Config generated by ${path.basename(__filename)} -->`;
|
||||||
const endMarker = `<!--- End of config generated section -->`;
|
const endMarker = `<!--- End of config generated section -->`;
|
||||||
return updateSection(content, startMarker, endMarker, [
|
return updateSection(content, startMarker, endMarker, [
|
||||||
@@ -160,14 +169,25 @@ async function updateConfig(content) {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} filePath
|
||||||
|
*/
|
||||||
|
async function copyToPackage(filePath) {
|
||||||
|
await fs.promises.copyFile(path.join(__dirname, '../../', filePath), path.join(__dirname, filePath));
|
||||||
|
console.log(`${filePath} copied successfully`);
|
||||||
|
}
|
||||||
|
|
||||||
async function updateReadme() {
|
async function updateReadme() {
|
||||||
const readmePath = path.join(__dirname, 'README.md');
|
const readmePath = path.join(__dirname, '../../README.md');
|
||||||
const readmeContent = await fs.promises.readFile(readmePath, 'utf-8');
|
const readmeContent = await fs.promises.readFile(readmePath, 'utf-8');
|
||||||
const withTools = await updateTools(readmeContent);
|
const withTools = await updateTools(readmeContent);
|
||||||
const withOptions = await updateOptions(withTools);
|
const withOptions = await updateOptions(withTools);
|
||||||
const withConfig = await updateConfig(withOptions);
|
const withConfig = await updateConfig(withOptions);
|
||||||
await fs.promises.writeFile(readmePath, withConfig, 'utf-8');
|
await fs.promises.writeFile(readmePath, withConfig, 'utf-8');
|
||||||
console.log('README updated successfully');
|
console.log('README updated successfully');
|
||||||
|
|
||||||
|
await copyToPackage('README.md');
|
||||||
|
await copyToPackage('LICENSE');
|
||||||
}
|
}
|
||||||
|
|
||||||
updateReadme().catch(err => {
|
updateReadme().catch(err => {
|
||||||