Compare commits

...

11 Commits

Author SHA1 Message Date
Pavel Feldman
08bd91d119 chore: mark v0.0.39 (#1059) 2025-09-19 07:55:26 -07:00
Pavel Feldman
ef83729796 chore: roll Playwright to latest (#1060) 2025-09-19 07:55:12 -07:00
Pavel Feldman
a70854c02f chore: roll Playwright to latest (#1056) 2025-09-18 16:39:32 -07:00
Pavel Feldman
d42e0e12fc chore: mark v0.0.38 (#1049) 2025-09-17 09:42:45 -07:00
Pavel Feldman
335315f158 chore: roll Playwright to latest (#1050) 2025-09-17 09:38:19 -07:00
Tejas Kumar
c49dedb83f docs: fix programmatic usage (#1047) 2025-09-17 07:22:12 -07:00
Zeke
0bfcb05422 docs: fix cursor mcp link redirect error (#1037) 2025-09-15 11:21:12 -07:00
dependabot[bot]
ca0820c55e chore(deps-dev): bump vite from 5.4.19 to 5.4.20 in /extension (#1029) 2025-09-10 15:38:22 -07:00
Yury Semikhatsky
45fee738bc feat(extension): bypass connection dialog when token is provided (#1024)
<img width="698" height="551" alt="image"
src="https://github.com/user-attachments/assets/feee2375-7654-42cb-b9fa-f48aee5dd045"
/>


Reference: https://github.com/microsoft/playwright-mcp/issues/965
2025-09-10 15:33:31 -07:00
Yury Semikhatsky
dc3419023f chore: roll playwright to 1.56.0-alpha-2025-09-10 (#1027) 2025-09-10 10:16:40 -07:00
Dmytro Durach
2036f8a9d5 fix(docker): path issue with Claude Code (#1018) 2025-09-08 11:22:57 -07:00
20 changed files with 1461 additions and 181 deletions

View File

@@ -51,6 +51,7 @@ 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

179
README.md
View File

@@ -76,7 +76,7 @@ For more information, see the [Codex MCP documentation](https://github.com/opena
#### Click the button to install:
[<img src="https://cursor.com/deeplink/mcp-install-dark.svg" alt="Install in Cursor">](cursor://anysphere.cursor-deeplink/mcp/install?name=Playwright&config=eyJjb21tYW5kIjoibnB4IEBwbGF5d3JpZ2h0L21jcEBsYXRlc3QifQ%3D%3D)
[<img src="https://cursor.com/deeplink/mcp-install-dark.svg" alt="Install in Cursor">](https://cursor.com/en/install-mcp?name=Playwright&config=eyJjb21tYW5kIjoibnB4IEBwbGF5d3JpZ2h0L21jcEBsYXRlc3QifQ%3D%3D)
#### Or install manually:
@@ -180,65 +180,78 @@ Playwright MCP server supports following arguments. They can be provided in the
```
> npx @playwright/mcp@latest --help
--allowed-origins <origins> semicolon-separated list of origins to allow
the browser to request. Default is to allow
all.
--blocked-origins <origins> semicolon-separated list of origins to block
the browser from requesting. Blocklist is
evaluated before allowlist. If used without
the allowlist, requests not matching the
blocklist are still allowed.
--block-service-workers block service workers
--browser <browser> browser or chrome channel to use, possible
values: chrome, firefox, webkit, msedge.
--caps <caps> comma-separated list of additional
capabilities to enable, possible values:
vision, pdf.
--cdp-endpoint <endpoint> CDP endpoint to connect to.
--cdp-header <headers...> CDP headers to send with the connect request,
multiple can be specified.
--config <path> path to the configuration file.
--device <device> device to emulate, for example: "iPhone 15"
--executable-path <path> path to the browser executable.
--extension Connect to a running browser instance
(Edge/Chrome only). Requires the "Playwright
MCP Bridge" browser extension to be installed.
--headless run browser in headless mode, headed by
default
--host <host> host to bind server to. Default is localhost.
Use 0.0.0.0 to bind to all interfaces.
--ignore-https-errors ignore https errors
--isolated keep the browser profile in memory, do not
save it to disk.
--image-responses <mode> whether to send image responses to the client.
Can be "allow" or "omit", Defaults to "allow".
--no-sandbox disable the sandbox for all process types that
are normally sandboxed.
--output-dir <path> path to the directory for output files.
--port <port> port to listen on for SSE transport.
--proxy-bypass <bypass> comma-separated domains to bypass proxy, for
example ".com,chromium.org,.domain.com"
--proxy-server <proxy> specify proxy server, for example
"http://myproxy:3128" or
"socks5://myproxy:8080"
--save-session Whether to save the Playwright MCP session
into the output directory.
--save-trace Whether to save the Playwright Trace of the
session into the output directory.
--secrets <path> path to a file containing secrets in the
dotenv format
--storage-state <path> path to the storage state file for isolated
sessions.
--timeout-action <timeout> specify action timeout in milliseconds,
defaults to 5000ms
--timeout-navigation <timeout> specify navigation timeout in milliseconds,
defaults to 60000ms
--user-agent <ua string> specify user agent string
--user-data-dir <path> path to the user data directory. If not
specified, a temporary directory will be
created.
--viewport-size <size> specify browser viewport size in pixels, for
example "1280, 720"
--allowed-origins <origins> semicolon-separated list of origins to
allow the browser to request. Default is
to allow all.
--blocked-origins <origins> semicolon-separated list of origins to
block the browser from requesting.
Blocklist is evaluated before allowlist.
If used without the allowlist, requests
not matching the blocklist are still
allowed.
--block-service-workers block service workers
--browser <browser> browser or chrome channel to use,
possible values: chrome, firefox,
webkit, msedge.
--caps <caps> comma-separated list of additional
capabilities to enable, possible values:
vision, pdf.
--cdp-endpoint <endpoint> CDP endpoint to connect to.
--cdp-header <headers...> CDP headers to send with the connect
request, multiple can be specified.
--config <path> path to the configuration file.
--device <device> device to emulate, for example: "iPhone
15"
--executable-path <path> path to the browser executable.
--extension Connect to a running browser instance
(Edge/Chrome only). Requires the
"Playwright MCP Bridge" browser
extension to be installed.
--grant-permissions <permissions...> List of permissions to grant to the
browser context, for example
"geolocation", "clipboard-read",
"clipboard-write".
--headless run browser in headless mode, headed by
default
--host <host> host to bind server to. Default is
localhost. Use 0.0.0.0 to bind to all
interfaces.
--ignore-https-errors ignore https errors
--isolated keep the browser profile in memory, do
not save it to disk.
--image-responses <mode> whether to send image responses to the
client. Can be "allow" or "omit",
Defaults to "allow".
--no-sandbox disable the sandbox for all process
types that are normally sandboxed.
--output-dir <path> path to the directory for output files.
--port <port> port to listen on for SSE transport.
--proxy-bypass <bypass> comma-separated domains to bypass proxy,
for example
".com,chromium.org,.domain.com"
--proxy-server <proxy> specify proxy server, for example
"http://myproxy:3128" or
"socks5://myproxy:8080"
--save-session Whether to save the Playwright MCP
session into the output directory.
--save-trace Whether to save the Playwright Trace of
the session into the output directory.
--secrets <path> path to a file containing secrets in the
dotenv format
--shared-browser-context reuse the same browser context between
all connected HTTP clients.
--storage-state <path> path to the storage state file for
isolated sessions.
--timeout-action <timeout> specify action timeout in milliseconds,
defaults to 5000ms
--timeout-navigation <timeout> specify navigation timeout in
milliseconds, defaults to 60000ms
--user-agent <ua string> specify user agent string
--user-data-dir <path> path to the user data directory. If not
specified, a temporary directory will be
created.
--viewport-size <size> specify browser viewport size in pixels,
for example "1280, 720"
```
<!--- End of options generated section -->
@@ -431,7 +444,7 @@ http.createServer(async (req, res) => {
// Creates a headless Playwright MCP server with SSE transport
const connection = await createConnection({ browser: { launchOptions: { headless: true } } });
const transport = new SSEServerTransport('/messages', res);
await connection.sever.connect(transport);
await connection.connect(transport);
// ...
});
@@ -503,7 +516,7 @@ http.createServer(async (req, res) => {
- Title: Upload files
- Description: Upload one or multiple files
- Parameters:
- `paths` (array): The absolute paths to the files to upload. Can be a single file or multiple files.
- `paths` (array, optional): The absolute paths to the files to upload. Can be single file or multiple files. If omitted, file chooser is cancelled.
- Read-only: **false**
<!-- NOTE: This has been generated via update-readme.js -->
@@ -722,48 +735,6 @@ http.createServer(async (req, res) => {
<details>
<summary><b>Verify (opt-in via --caps=verify)</b></summary>
<!-- NOTE: This has been generated via update-readme.js -->
- **browser_verify_element_visible**
- Title: Verify element visible
- Description: Verify element is visible on the page
- Parameters:
- `role` (string): ROLE of the element. Can be found in the snapshot like this: `- {ROLE} "Accessible Name":`
- `accessibleName` (string): ACCESSIBLE_NAME of the element. Can be found in the snapshot like this: `- role "{ACCESSIBLE_NAME}"`
- Read-only: **true**
<!-- NOTE: This has been generated via update-readme.js -->
- **browser_verify_list_visible**
- Title: Verify list visible
- Description: Verify list is visible on the page
- Parameters:
- `element` (string): Human-readable list description
- `ref` (string): Exact target element reference that points to the list
- `items` (array): Items to verify
- Read-only: **true**
<!-- NOTE: This has been generated via update-readme.js -->
- **browser_verify_text_visible**
- Title: Verify text visible
- Description: Verify text is visible on the page. Prefer browser_verify_element_visible if possible.
- Parameters:
- `text` (string): TEXT to verify. Can be found in the snapshot like this: `- role "Accessible Name": {TEXT}` or like this: `- text: {TEXT}`
- Read-only: **true**
<!-- NOTE: This has been generated via update-readme.js -->
- **browser_verify_value**
- Title: Verify value
- Description: Verify element value
- Parameters:
- `type` (string): Type of the element
- `element` (string): Human-readable element description
- `ref` (string): Exact target element reference that points to the element
- `value` (string): Value to verify. For checkbox, use "true" or "false".
- Read-only: **true**
</details>
<details>

View File

@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "Playwright MCP Bridge",
"version": "0.0.37",
"version": "0.0.39",
"description": "Share browser tabs with Playwright MCP server",
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9nMS2b0WCohjVHPGb8D9qAdkbIngDqoAjTeSccHJijgcONejge+OJxOQOMLu7b0ovt1c9BiEJa5JcpM+EHFVGL1vluBxK71zmBy1m2f9vZF3HG0LSCp7YRkum9rAIEthDwbkxx6XTvpmAY5rjFa/NON6b9Hlbo+8peUSkoOK7HTwYnnI36asZ9eUTiveIf+DMPLojW2UX33vDWG2UKvMVDewzclb4+uLxAYshY7Mx8we/b44xu+Anb/EBLKjOPk9Yh541xJ5Ozc8EiP/5yxOp9c/lRiYUHaRW+4r0HKZyFt0eZ52ti2iM4Nfk7jRXR7an3JPsUIf5deC/1cVM/+1ZQIDAQAB",
"permissions": [

View File

@@ -16,7 +16,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"typescript": "^5.8.2",
"vite": "^5.0.0",
"vite": "^5.4.20",
"vite-plugin-static-copy": "^3.1.1"
},
"engines": {
@@ -1796,10 +1796,11 @@
}
},
"node_modules/vite": {
"version": "5.4.19",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz",
"integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==",
"version": "5.4.20",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.20.tgz",
"integrity": "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.21.3",
"postcss": "^8.4.43",

View File

@@ -1,6 +1,6 @@
{
"name": "@playwright/mcp-extension",
"version": "0.0.37",
"version": "0.0.39",
"description": "Playwright MCP Browser Extension",
"private": true,
"repository": {
@@ -29,7 +29,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"typescript": "^5.8.2",
"vite": "^5.0.0",
"vite": "^5.4.20",
"vite-plugin-static-copy": "^3.1.1"
}
}

View File

@@ -0,0 +1,70 @@
/*
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.
*/
.auth-token-section {
margin: 16px 0;
padding: 16px;
background-color: #f6f8fa;
border-radius: 6px;
}
.auth-token-description {
font-size: 12px;
color: #656d76;
margin-bottom: 12px;
}
.auth-token-container {
display: flex;
align-items: center;
gap: 8px;
background-color: #ffffff;
padding: 8px;
}
.auth-token-code {
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
font-size: 12px;
color: #1f2328;
border: none;
flex: 1;
padding: 0;
word-break: break-all;
}
.auth-token-refresh {
flex: none;
height: 24px;
width: 24px;
border: none;
outline: none;
color: var(--color-fg-muted);
background: transparent;
padding: 4px;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 4px;
}
.auth-token-refresh svg {
margin: 0;
}
.auth-token-refresh:not(:disabled):hover {
background-color: var(--color-btn-selected-bg);
}

View File

@@ -0,0 +1,68 @@
/**
* 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 React, { useCallback, useState } from 'react';
import { CopyToClipboard } from './copyToClipboard';
import * as icons from './icons';
import './authToken.css';
export const AuthTokenSection: React.FC<{}> = ({}) => {
const [authToken, setAuthToken] = useState<string>(getOrCreateAuthToken);
const onRegenerateToken = useCallback(() => {
const newToken = generateAuthToken();
localStorage.setItem('auth-token', newToken);
setAuthToken(newToken);
}, []);
return (
<div className='auth-token-section'>
<div className='auth-token-description'>
Set this environment variable to bypass the connection dialog:
</div>
<div className='auth-token-container'>
<code className='auth-token-code'>PLAYWRIGHT_MCP_EXTENSION_TOKEN={authToken}</code>
<button className='auth-token-refresh' title='Generate new token' aria-label='Generate new token'onClick={onRegenerateToken}>{icons.refresh()}</button>
<CopyToClipboard value={authToken} />
</div>
</div>
);
};
function generateAuthToken(): string {
// Generate a cryptographically secure random token
const array = new Uint8Array(32);
crypto.getRandomValues(array);
// Convert to base64 and make it URL-safe
return btoa(String.fromCharCode.apply(null, Array.from(array)))
.replace(/[+/=]/g, match => {
switch (match) {
case '+': return '-';
case '/': return '_';
case '=': return '';
default: return match;
}
});
}
export const getOrCreateAuthToken = (): string => {
let token = localStorage.getItem('auth-token');
if (!token) {
token = generateAuthToken();
localStorage.setItem('auth-token', token);
}
return token;
}

891
extension/src/ui/colors.css Normal file
View File

@@ -0,0 +1,891 @@
/* The MIT License (MIT)
Copyright (c) 2021 GitHub Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. */
:root {
--color-canvas-default-transparent: rgba(255,255,255,0);
--color-marketing-icon-primary: #218bff;
--color-marketing-icon-secondary: #54aeff;
--color-diff-blob-addition-num-text: #24292f;
--color-diff-blob-addition-fg: #24292f;
--color-diff-blob-addition-num-bg: #CCFFD8;
--color-diff-blob-addition-line-bg: #E6FFEC;
--color-diff-blob-addition-word-bg: #ABF2BC;
--color-diff-blob-deletion-num-text: #24292f;
--color-diff-blob-deletion-fg: #24292f;
--color-diff-blob-deletion-num-bg: #FFD7D5;
--color-diff-blob-deletion-line-bg: #FFEBE9;
--color-diff-blob-deletion-word-bg: rgba(255,129,130,0.4);
--color-diff-blob-hunk-num-bg: rgba(84,174,255,0.4);
--color-diff-blob-expander-icon: #57606a;
--color-diff-blob-selected-line-highlight-mix-blend-mode: multiply;
--color-diffstat-deletion-border: rgba(27,31,36,0.15);
--color-diffstat-addition-border: rgba(27,31,36,0.15);
--color-diffstat-addition-bg: #2da44e;
--color-search-keyword-hl: #fff8c5;
--color-prettylights-syntax-comment: #6e7781;
--color-prettylights-syntax-constant: #0550ae;
--color-prettylights-syntax-entity: #8250df;
--color-prettylights-syntax-storage-modifier-import: #24292f;
--color-prettylights-syntax-entity-tag: #116329;
--color-prettylights-syntax-keyword: #cf222e;
--color-prettylights-syntax-string: #0a3069;
--color-prettylights-syntax-variable: #953800;
--color-prettylights-syntax-brackethighlighter-unmatched: #82071e;
--color-prettylights-syntax-invalid-illegal-text: #f6f8fa;
--color-prettylights-syntax-invalid-illegal-bg: #82071e;
--color-prettylights-syntax-carriage-return-text: #f6f8fa;
--color-prettylights-syntax-carriage-return-bg: #cf222e;
--color-prettylights-syntax-string-regexp: #116329;
--color-prettylights-syntax-markup-list: #3b2300;
--color-prettylights-syntax-markup-heading: #0550ae;
--color-prettylights-syntax-markup-italic: #24292f;
--color-prettylights-syntax-markup-bold: #24292f;
--color-prettylights-syntax-markup-deleted-text: #82071e;
--color-prettylights-syntax-markup-deleted-bg: #FFEBE9;
--color-prettylights-syntax-markup-inserted-text: #116329;
--color-prettylights-syntax-markup-inserted-bg: #dafbe1;
--color-prettylights-syntax-markup-changed-text: #953800;
--color-prettylights-syntax-markup-changed-bg: #ffd8b5;
--color-prettylights-syntax-markup-ignored-text: #eaeef2;
--color-prettylights-syntax-markup-ignored-bg: #0550ae;
--color-prettylights-syntax-meta-diff-range: #8250df;
--color-prettylights-syntax-brackethighlighter-angle: #57606a;
--color-prettylights-syntax-sublimelinter-gutter-mark: #8c959f;
--color-prettylights-syntax-constant-other-reference-link: #0a3069;
--color-codemirror-text: #24292f;
--color-codemirror-bg: #ffffff;
--color-codemirror-gutters-bg: #ffffff;
--color-codemirror-guttermarker-text: #ffffff;
--color-codemirror-guttermarker-subtle-text: #6e7781;
--color-codemirror-linenumber-text: #57606a;
--color-codemirror-cursor: #24292f;
--color-codemirror-selection-bg: rgba(84,174,255,0.4);
--color-codemirror-activeline-bg: rgba(234,238,242,0.5);
--color-codemirror-matchingbracket-text: #24292f;
--color-codemirror-lines-bg: #ffffff;
--color-codemirror-syntax-comment: #24292f;
--color-codemirror-syntax-constant: #0550ae;
--color-codemirror-syntax-entity: #8250df;
--color-codemirror-syntax-keyword: #cf222e;
--color-codemirror-syntax-storage: #cf222e;
--color-codemirror-syntax-string: #0a3069;
--color-codemirror-syntax-support: #0550ae;
--color-codemirror-syntax-variable: #953800;
--color-checks-bg: #24292f;
--color-checks-run-border-width: 0px;
--color-checks-container-border-width: 0px;
--color-checks-text-primary: #f6f8fa;
--color-checks-text-secondary: #8c959f;
--color-checks-text-link: #54aeff;
--color-checks-btn-icon: #afb8c1;
--color-checks-btn-hover-icon: #f6f8fa;
--color-checks-btn-hover-bg: rgba(255,255,255,0.125);
--color-checks-input-text: #eaeef2;
--color-checks-input-placeholder-text: #8c959f;
--color-checks-input-focus-text: #8c959f;
--color-checks-input-bg: #32383f;
--color-checks-input-shadow: none;
--color-checks-donut-error: #fa4549;
--color-checks-donut-pending: #bf8700;
--color-checks-donut-success: #2da44e;
--color-checks-donut-neutral: #afb8c1;
--color-checks-dropdown-text: #afb8c1;
--color-checks-dropdown-bg: #32383f;
--color-checks-dropdown-border: #424a53;
--color-checks-dropdown-shadow: rgba(27,31,36,0.3);
--color-checks-dropdown-hover-text: #f6f8fa;
--color-checks-dropdown-hover-bg: #424a53;
--color-checks-dropdown-btn-hover-text: #f6f8fa;
--color-checks-dropdown-btn-hover-bg: #32383f;
--color-checks-scrollbar-thumb-bg: #57606a;
--color-checks-header-label-text: #d0d7de;
--color-checks-header-label-open-text: #f6f8fa;
--color-checks-header-border: #32383f;
--color-checks-header-icon: #8c959f;
--color-checks-line-text: #d0d7de;
--color-checks-line-num-text: rgba(140,149,159,0.75);
--color-checks-line-timestamp-text: #8c959f;
--color-checks-line-hover-bg: #32383f;
--color-checks-line-selected-bg: rgba(33,139,255,0.15);
--color-checks-line-selected-num-text: #54aeff;
--color-checks-line-dt-fm-text: #24292f;
--color-checks-line-dt-fm-bg: #9a6700;
--color-checks-gate-bg: rgba(125,78,0,0.15);
--color-checks-gate-text: #d0d7de;
--color-checks-gate-waiting-text: #afb8c1;
--color-checks-step-header-open-bg: #32383f;
--color-checks-step-error-text: #ff8182;
--color-checks-step-warning-text: #d4a72c;
--color-checks-logline-text: #8c959f;
--color-checks-logline-num-text: rgba(140,149,159,0.75);
--color-checks-logline-debug-text: #c297ff;
--color-checks-logline-error-text: #d0d7de;
--color-checks-logline-error-num-text: #ff8182;
--color-checks-logline-error-bg: rgba(164,14,38,0.15);
--color-checks-logline-warning-text: #d0d7de;
--color-checks-logline-warning-num-text: #d4a72c;
--color-checks-logline-warning-bg: rgba(125,78,0,0.15);
--color-checks-logline-command-text: #54aeff;
--color-checks-logline-section-text: #4ac26b;
--color-checks-ansi-black: #24292f;
--color-checks-ansi-black-bright: #32383f;
--color-checks-ansi-white: #d0d7de;
--color-checks-ansi-white-bright: #d0d7de;
--color-checks-ansi-gray: #8c959f;
--color-checks-ansi-red: #ff8182;
--color-checks-ansi-red-bright: #ffaba8;
--color-checks-ansi-green: #4ac26b;
--color-checks-ansi-green-bright: #6fdd8b;
--color-checks-ansi-yellow: #d4a72c;
--color-checks-ansi-yellow-bright: #eac54f;
--color-checks-ansi-blue: #54aeff;
--color-checks-ansi-blue-bright: #80ccff;
--color-checks-ansi-magenta: #c297ff;
--color-checks-ansi-magenta-bright: #d8b9ff;
--color-checks-ansi-cyan: #76e3ea;
--color-checks-ansi-cyan-bright: #b3f0ff;
--color-project-header-bg: #24292f;
--color-project-sidebar-bg: #ffffff;
--color-project-gradient-in: #ffffff;
--color-project-gradient-out: rgba(255,255,255,0);
--color-mktg-success: rgba(36,146,67,1);
--color-mktg-info: rgba(19,119,234,1);
--color-mktg-bg-shade-gradient-top: rgba(27,31,36,0.065);
--color-mktg-bg-shade-gradient-bottom: rgba(27,31,36,0);
--color-mktg-btn-bg-top: hsla(228,82%,66%,1);
--color-mktg-btn-bg-bottom: #4969ed;
--color-mktg-btn-bg-overlay-top: hsla(228,74%,59%,1);
--color-mktg-btn-bg-overlay-bottom: #3355e0;
--color-mktg-btn-text: #ffffff;
--color-mktg-btn-primary-bg-top: hsla(137,56%,46%,1);
--color-mktg-btn-primary-bg-bottom: #2ea44f;
--color-mktg-btn-primary-bg-overlay-top: hsla(134,60%,38%,1);
--color-mktg-btn-primary-bg-overlay-bottom: #22863a;
--color-mktg-btn-primary-text: #ffffff;
--color-mktg-btn-enterprise-bg-top: hsla(249,100%,72%,1);
--color-mktg-btn-enterprise-bg-bottom: #6f57ff;
--color-mktg-btn-enterprise-bg-overlay-top: hsla(248,65%,63%,1);
--color-mktg-btn-enterprise-bg-overlay-bottom: #614eda;
--color-mktg-btn-enterprise-text: #ffffff;
--color-mktg-btn-outline-text: #4969ed;
--color-mktg-btn-outline-border: rgba(73,105,237,0.3);
--color-mktg-btn-outline-hover-text: #3355e0;
--color-mktg-btn-outline-hover-border: rgba(51,85,224,0.5);
--color-mktg-btn-outline-focus-border: #4969ed;
--color-mktg-btn-outline-focus-border-inset: rgba(73,105,237,0.5);
--color-mktg-btn-dark-text: #ffffff;
--color-mktg-btn-dark-border: rgba(255,255,255,0.3);
--color-mktg-btn-dark-hover-text: #ffffff;
--color-mktg-btn-dark-hover-border: rgba(255,255,255,0.5);
--color-mktg-btn-dark-focus-border: #ffffff;
--color-mktg-btn-dark-focus-border-inset: rgba(255,255,255,0.5);
--color-avatar-bg: #ffffff;
--color-avatar-border: rgba(27,31,36,0.15);
--color-avatar-stack-fade: #afb8c1;
--color-avatar-stack-fade-more: #d0d7de;
--color-avatar-child-shadow: -2px -2px 0 rgba(255,255,255,0.8);
--color-topic-tag-border: rgba(0,0,0,0);
--color-select-menu-backdrop-border: rgba(0,0,0,0);
--color-select-menu-tap-highlight: rgba(175,184,193,0.5);
--color-select-menu-tap-focus-bg: #b6e3ff;
--color-overlay-shadow: 0 1px 3px rgba(27,31,36,0.12), 0 8px 24px rgba(66,74,83,0.12);
--color-header-text: rgba(255,255,255,0.7);
--color-header-bg: #24292f;
--color-header-logo: #ffffff;
--color-header-search-bg: #24292f;
--color-header-search-border: #57606a;
--color-sidenav-selected-bg: #ffffff;
--color-menu-bg-active: rgba(0,0,0,0);
--color-control-transparent-bg-hover: #818b981a;
--color-input-disabled-bg: rgba(175,184,193,0.2);
--color-timeline-badge-bg: #eaeef2;
--color-ansi-black: #24292f;
--color-ansi-black-bright: #57606a;
--color-ansi-white: #6e7781;
--color-ansi-white-bright: #8c959f;
--color-ansi-gray: #6e7781;
--color-ansi-red: #cf222e;
--color-ansi-red-bright: #a40e26;
--color-ansi-green: #116329;
--color-ansi-green-bright: #1a7f37;
--color-ansi-yellow: #4d2d00;
--color-ansi-yellow-bright: #633c01;
--color-ansi-blue: #0969da;
--color-ansi-blue-bright: #218bff;
--color-ansi-magenta: #8250df;
--color-ansi-magenta-bright: #a475f9;
--color-ansi-cyan: #1b7c83;
--color-ansi-cyan-bright: #3192aa;
--color-btn-text: #24292f;
--color-btn-bg: #f6f8fa;
--color-btn-border: rgba(27,31,36,0.15);
--color-btn-shadow: 0 1px 0 rgba(27,31,36,0.04);
--color-btn-inset-shadow: inset 0 1px 0 rgba(255,255,255,0.25);
--color-btn-hover-bg: #f3f4f6;
--color-btn-hover-border: rgba(27,31,36,0.15);
--color-btn-active-bg: hsla(220,14%,93%,1);
--color-btn-active-border: rgba(27,31,36,0.15);
--color-btn-selected-bg: hsla(220,14%,94%,1);
--color-btn-focus-bg: #f6f8fa;
--color-btn-focus-border: rgba(27,31,36,0.15);
--color-btn-focus-shadow: 0 0 0 3px rgba(9,105,218,0.3);
--color-btn-shadow-active: inset 0 0.15em 0.3em rgba(27,31,36,0.15);
--color-btn-shadow-input-focus: 0 0 0 0.2em rgba(9,105,218,0.3);
--color-btn-counter-bg: rgba(27,31,36,0.08);
--color-btn-primary-text: #ffffff;
--color-btn-primary-bg: #2da44e;
--color-btn-primary-border: rgba(27,31,36,0.15);
--color-btn-primary-shadow: 0 1px 0 rgba(27,31,36,0.1);
--color-btn-primary-inset-shadow: inset 0 1px 0 rgba(255,255,255,0.03);
--color-btn-primary-hover-bg: #2c974b;
--color-btn-primary-hover-border: rgba(27,31,36,0.15);
--color-btn-primary-selected-bg: hsla(137,55%,36%,1);
--color-btn-primary-selected-shadow: inset 0 1px 0 rgba(0,45,17,0.2);
--color-btn-primary-disabled-text: rgba(255,255,255,0.8);
--color-btn-primary-disabled-bg: #94d3a2;
--color-btn-primary-disabled-border: rgba(27,31,36,0.15);
--color-btn-primary-focus-bg: #2da44e;
--color-btn-primary-focus-border: rgba(27,31,36,0.15);
--color-btn-primary-focus-shadow: 0 0 0 3px rgba(45,164,78,0.4);
--color-btn-primary-icon: rgba(255,255,255,0.8);
--color-btn-primary-counter-bg: rgba(255,255,255,0.2);
--color-btn-outline-text: #0969da;
--color-btn-outline-hover-text: #ffffff;
--color-btn-outline-hover-bg: #0969da;
--color-btn-outline-hover-border: rgba(27,31,36,0.15);
--color-btn-outline-hover-shadow: 0 1px 0 rgba(27,31,36,0.1);
--color-btn-outline-hover-inset-shadow: inset 0 1px 0 rgba(255,255,255,0.03);
--color-btn-outline-hover-counter-bg: rgba(255,255,255,0.2);
--color-btn-outline-selected-text: #ffffff;
--color-btn-outline-selected-bg: hsla(212,92%,42%,1);
--color-btn-outline-selected-border: rgba(27,31,36,0.15);
--color-btn-outline-selected-shadow: inset 0 1px 0 rgba(0,33,85,0.2);
--color-btn-outline-disabled-text: rgba(9,105,218,0.5);
--color-btn-outline-disabled-bg: #f6f8fa;
--color-btn-outline-disabled-counter-bg: rgba(9,105,218,0.05);
--color-btn-outline-focus-border: rgba(27,31,36,0.15);
--color-btn-outline-focus-shadow: 0 0 0 3px rgba(5,80,174,0.4);
--color-btn-outline-counter-bg: rgba(9,105,218,0.1);
--color-btn-danger-text: #cf222e;
--color-btn-danger-hover-text: #ffffff;
--color-btn-danger-hover-bg: #a40e26;
--color-btn-danger-hover-border: rgba(27,31,36,0.15);
--color-btn-danger-hover-shadow: 0 1px 0 rgba(27,31,36,0.1);
--color-btn-danger-hover-inset-shadow: inset 0 1px 0 rgba(255,255,255,0.03);
--color-btn-danger-hover-counter-bg: rgba(255,255,255,0.2);
--color-btn-danger-selected-text: #ffffff;
--color-btn-danger-selected-bg: hsla(356,72%,44%,1);
--color-btn-danger-selected-border: rgba(27,31,36,0.15);
--color-btn-danger-selected-shadow: inset 0 1px 0 rgba(76,0,20,0.2);
--color-btn-danger-disabled-text: rgba(207,34,46,0.5);
--color-btn-danger-disabled-bg: #f6f8fa;
--color-btn-danger-disabled-counter-bg: rgba(207,34,46,0.05);
--color-btn-danger-focus-border: rgba(27,31,36,0.15);
--color-btn-danger-focus-shadow: 0 0 0 3px rgba(164,14,38,0.4);
--color-btn-danger-counter-bg: rgba(207,34,46,0.1);
--color-btn-danger-icon: #cf222e;
--color-btn-danger-hover-icon: #ffffff;
--color-underlinenav-icon: #6e7781;
--color-underlinenav-border-hover: rgba(175,184,193,0.2);
--color-fg-default: #24292f;
--color-fg-muted: #57606a;
--color-fg-subtle: #6e7781;
--color-fg-on-emphasis: #ffffff;
--color-canvas-default: #ffffff;
--color-canvas-overlay: #ffffff;
--color-canvas-inset: #f6f8fa;
--color-canvas-subtle: #f6f8fa;
--color-border-default: #d0d7de;
--color-border-muted: hsla(210,18%,87%,1);
--color-border-subtle: rgba(27,31,36,0.15);
--color-shadow-small: 0 1px 0 rgba(27,31,36,0.04);
--color-shadow-medium: 0 3px 6px rgba(140,149,159,0.15);
--color-shadow-large: 0 8px 24px rgba(140,149,159,0.2);
--color-shadow-extra-large: 0 12px 28px rgba(140,149,159,0.3);
--color-neutral-emphasis-plus: #24292f;
--color-neutral-emphasis: #6e7781;
--color-neutral-muted: rgba(175,184,193,0.2);
--color-neutral-subtle: rgba(234,238,242,0.5);
--color-accent-fg: #0969da;
--color-accent-emphasis: #0969da;
--color-accent-muted: rgba(84,174,255,0.4);
--color-accent-subtle: #ddf4ff;
--color-success-fg: #1a7f37;
--color-success-emphasis: #2da44e;
--color-success-muted: rgba(74,194,107,0.4);
--color-success-subtle: #dafbe1;
--color-attention-fg: #9a6700;
--color-attention-emphasis: #bf8700;
--color-attention-muted: rgba(212,167,44,0.4);
--color-attention-subtle: #fff8c5;
--color-severe-fg: #bc4c00;
--color-severe-emphasis: #bc4c00;
--color-severe-muted: rgba(251,143,68,0.4);
--color-severe-subtle: #fff1e5;
--color-danger-fg: #cf222e;
--color-danger-emphasis: #cf222e;
--color-danger-muted: rgba(255,129,130,0.4);
--color-danger-subtle: #FFEBE9;
--color-done-fg: #8250df;
--color-done-emphasis: #8250df;
--color-done-muted: rgba(194,151,255,0.4);
--color-done-subtle: #fbefff;
--color-sponsors-fg: #bf3989;
--color-sponsors-emphasis: #bf3989;
--color-sponsors-muted: rgba(255,128,200,0.4);
--color-sponsors-subtle: #ffeff7;
--color-primer-canvas-backdrop: rgba(27,31,36,0.5);
--color-primer-canvas-sticky: rgba(255,255,255,0.95);
--color-primer-border-active: #FD8C73;
--color-primer-border-contrast: rgba(27,31,36,0.1);
--color-primer-shadow-highlight: inset 0 1px 0 rgba(255,255,255,0.25);
--color-primer-shadow-inset: inset 0 1px 0 rgba(208,215,222,0.2);
--color-primer-shadow-focus: 0 0 0 3px rgba(9,105,218,0.3);
--color-scale-black: #1b1f24;
--color-scale-white: #ffffff;
--color-scale-gray-0: #f6f8fa;
--color-scale-gray-1: #eaeef2;
--color-scale-gray-2: #d0d7de;
--color-scale-gray-3: #afb8c1;
--color-scale-gray-4: #8c959f;
--color-scale-gray-5: #6e7781;
--color-scale-gray-6: #57606a;
--color-scale-gray-7: #424a53;
--color-scale-gray-8: #32383f;
--color-scale-gray-9: #24292f;
--color-scale-blue-0: #ddf4ff;
--color-scale-blue-1: #b6e3ff;
--color-scale-blue-2: #80ccff;
--color-scale-blue-3: #54aeff;
--color-scale-blue-4: #218bff;
--color-scale-blue-5: #0969da;
--color-scale-blue-6: #0550ae;
--color-scale-blue-7: #033d8b;
--color-scale-blue-8: #0a3069;
--color-scale-blue-9: #002155;
--color-scale-green-0: #dafbe1;
--color-scale-green-1: #aceebb;
--color-scale-green-2: #6fdd8b;
--color-scale-green-3: #4ac26b;
--color-scale-green-4: #2da44e;
--color-scale-green-5: #1a7f37;
--color-scale-green-6: #116329;
--color-scale-green-7: #044f1e;
--color-scale-green-8: #003d16;
--color-scale-green-9: #002d11;
--color-scale-yellow-0: #fff8c5;
--color-scale-yellow-1: #fae17d;
--color-scale-yellow-2: #eac54f;
--color-scale-yellow-3: #d4a72c;
--color-scale-yellow-4: #bf8700;
--color-scale-yellow-5: #9a6700;
--color-scale-yellow-6: #7d4e00;
--color-scale-yellow-7: #633c01;
--color-scale-yellow-8: #4d2d00;
--color-scale-yellow-9: #3b2300;
--color-scale-orange-0: #fff1e5;
--color-scale-orange-1: #ffd8b5;
--color-scale-orange-2: #ffb77c;
--color-scale-orange-3: #fb8f44;
--color-scale-orange-4: #e16f24;
--color-scale-orange-5: #bc4c00;
--color-scale-orange-6: #953800;
--color-scale-orange-7: #762c00;
--color-scale-orange-8: #5c2200;
--color-scale-orange-9: #471700;
--color-scale-red-0: #FFEBE9;
--color-scale-red-1: #ffcecb;
--color-scale-red-2: #ffaba8;
--color-scale-red-3: #ff8182;
--color-scale-red-4: #fa4549;
--color-scale-red-5: #cf222e;
--color-scale-red-6: #a40e26;
--color-scale-red-7: #82071e;
--color-scale-red-8: #660018;
--color-scale-red-9: #4c0014;
--color-scale-purple-0: #fbefff;
--color-scale-purple-1: #ecd8ff;
--color-scale-purple-2: #d8b9ff;
--color-scale-purple-3: #c297ff;
--color-scale-purple-4: #a475f9;
--color-scale-purple-5: #8250df;
--color-scale-purple-6: #6639ba;
--color-scale-purple-7: #512a97;
--color-scale-purple-8: #3e1f79;
--color-scale-purple-9: #2e1461;
--color-scale-pink-0: #ffeff7;
--color-scale-pink-1: #ffd3eb;
--color-scale-pink-2: #ffadda;
--color-scale-pink-3: #ff80c8;
--color-scale-pink-4: #e85aad;
--color-scale-pink-5: #bf3989;
--color-scale-pink-6: #99286e;
--color-scale-pink-7: #772057;
--color-scale-pink-8: #611347;
--color-scale-pink-9: #4d0336;
--color-scale-coral-0: #FFF0EB;
--color-scale-coral-1: #FFD6CC;
--color-scale-coral-2: #FFB4A1;
--color-scale-coral-3: #FD8C73;
--color-scale-coral-4: #EC6547;
--color-scale-coral-5: #C4432B;
--color-scale-coral-6: #9E2F1C;
--color-scale-coral-7: #801F0F;
--color-scale-coral-8: #691105;
--color-scale-coral-9: #510901
}
@media(prefers-color-scheme: dark) {
:root {
--color-canvas-default-transparent: rgba(13,17,23,0);
--color-marketing-icon-primary: #79c0ff;
--color-marketing-icon-secondary: #1f6feb;
--color-diff-blob-addition-num-text: #c9d1d9;
--color-diff-blob-addition-fg: #c9d1d9;
--color-diff-blob-addition-num-bg: rgba(63,185,80,0.3);
--color-diff-blob-addition-line-bg: rgba(46,160,67,0.15);
--color-diff-blob-addition-word-bg: rgba(46,160,67,0.4);
--color-diff-blob-deletion-num-text: #c9d1d9;
--color-diff-blob-deletion-fg: #c9d1d9;
--color-diff-blob-deletion-num-bg: rgba(248,81,73,0.3);
--color-diff-blob-deletion-line-bg: rgba(248,81,73,0.15);
--color-diff-blob-deletion-word-bg: rgba(248,81,73,0.4);
--color-diff-blob-hunk-num-bg: rgba(56,139,253,0.4);
--color-diff-blob-expander-icon: #8b949e;
--color-diff-blob-selected-line-highlight-mix-blend-mode: screen;
--color-diffstat-deletion-border: rgba(240,246,252,0.1);
--color-diffstat-addition-border: rgba(240,246,252,0.1);
--color-diffstat-addition-bg: #3fb950;
--color-search-keyword-hl: rgba(210,153,34,0.4);
--color-prettylights-syntax-comment: #8b949e;
--color-prettylights-syntax-constant: #79c0ff;
--color-prettylights-syntax-entity: #d2a8ff;
--color-prettylights-syntax-storage-modifier-import: #c9d1d9;
--color-prettylights-syntax-entity-tag: #7ee787;
--color-prettylights-syntax-keyword: #ff7b72;
--color-prettylights-syntax-string: #a5d6ff;
--color-prettylights-syntax-variable: #ffa657;
--color-prettylights-syntax-brackethighlighter-unmatched: #f85149;
--color-prettylights-syntax-invalid-illegal-text: #f0f6fc;
--color-prettylights-syntax-invalid-illegal-bg: #8e1519;
--color-prettylights-syntax-carriage-return-text: #f0f6fc;
--color-prettylights-syntax-carriage-return-bg: #b62324;
--color-prettylights-syntax-string-regexp: #7ee787;
--color-prettylights-syntax-markup-list: #f2cc60;
--color-prettylights-syntax-markup-heading: #1f6feb;
--color-prettylights-syntax-markup-italic: #c9d1d9;
--color-prettylights-syntax-markup-bold: #c9d1d9;
--color-prettylights-syntax-markup-deleted-text: #ffdcd7;
--color-prettylights-syntax-markup-deleted-bg: #67060c;
--color-prettylights-syntax-markup-inserted-text: #aff5b4;
--color-prettylights-syntax-markup-inserted-bg: #033a16;
--color-prettylights-syntax-markup-changed-text: #ffdfb6;
--color-prettylights-syntax-markup-changed-bg: #5a1e02;
--color-prettylights-syntax-markup-ignored-text: #c9d1d9;
--color-prettylights-syntax-markup-ignored-bg: #1158c7;
--color-prettylights-syntax-meta-diff-range: #d2a8ff;
--color-prettylights-syntax-brackethighlighter-angle: #8b949e;
--color-prettylights-syntax-sublimelinter-gutter-mark: #484f58;
--color-prettylights-syntax-constant-other-reference-link: #a5d6ff;
--color-codemirror-text: #c9d1d9;
--color-codemirror-bg: #0d1117;
--color-codemirror-gutters-bg: #0d1117;
--color-codemirror-guttermarker-text: #0d1117;
--color-codemirror-guttermarker-subtle-text: #484f58;
--color-codemirror-linenumber-text: #8b949e;
--color-codemirror-cursor: #c9d1d9;
--color-codemirror-selection-bg: rgba(56,139,253,0.4);
--color-codemirror-activeline-bg: rgba(110,118,129,0.1);
--color-codemirror-matchingbracket-text: #c9d1d9;
--color-codemirror-lines-bg: #0d1117;
--color-codemirror-syntax-comment: #8b949e;
--color-codemirror-syntax-constant: #79c0ff;
--color-codemirror-syntax-entity: #d2a8ff;
--color-codemirror-syntax-keyword: #ff7b72;
--color-codemirror-syntax-storage: #ff7b72;
--color-codemirror-syntax-string: #a5d6ff;
--color-codemirror-syntax-support: #79c0ff;
--color-codemirror-syntax-variable: #ffa657;
--color-checks-bg: #010409;
--color-checks-run-border-width: 1px;
--color-checks-container-border-width: 1px;
--color-checks-text-primary: #c9d1d9;
--color-checks-text-secondary: #8b949e;
--color-checks-text-link: #58a6ff;
--color-checks-btn-icon: #8b949e;
--color-checks-btn-hover-icon: #c9d1d9;
--color-checks-btn-hover-bg: rgba(110,118,129,0.1);
--color-checks-input-text: #8b949e;
--color-checks-input-placeholder-text: #484f58;
--color-checks-input-focus-text: #c9d1d9;
--color-checks-input-bg: #161b22;
--color-checks-input-shadow: none;
--color-checks-donut-error: #f85149;
--color-checks-donut-pending: #d29922;
--color-checks-donut-success: #2ea043;
--color-checks-donut-neutral: #8b949e;
--color-checks-dropdown-text: #c9d1d9;
--color-checks-dropdown-bg: #161b22;
--color-checks-dropdown-border: #30363d;
--color-checks-dropdown-shadow: rgba(1,4,9,0.3);
--color-checks-dropdown-hover-text: #c9d1d9;
--color-checks-dropdown-hover-bg: rgba(110,118,129,0.1);
--color-checks-dropdown-btn-hover-text: #c9d1d9;
--color-checks-dropdown-btn-hover-bg: rgba(110,118,129,0.1);
--color-checks-scrollbar-thumb-bg: rgba(110,118,129,0.4);
--color-checks-header-label-text: #8b949e;
--color-checks-header-label-open-text: #c9d1d9;
--color-checks-header-border: #21262d;
--color-checks-header-icon: #8b949e;
--color-checks-line-text: #8b949e;
--color-checks-line-num-text: #484f58;
--color-checks-line-timestamp-text: #484f58;
--color-checks-line-hover-bg: rgba(110,118,129,0.1);
--color-checks-line-selected-bg: rgba(56,139,253,0.15);
--color-checks-line-selected-num-text: #58a6ff;
--color-checks-line-dt-fm-text: #f0f6fc;
--color-checks-line-dt-fm-bg: #9e6a03;
--color-checks-gate-bg: rgba(187,128,9,0.15);
--color-checks-gate-text: #8b949e;
--color-checks-gate-waiting-text: #d29922;
--color-checks-step-header-open-bg: #161b22;
--color-checks-step-error-text: #f85149;
--color-checks-step-warning-text: #d29922;
--color-checks-logline-text: #8b949e;
--color-checks-logline-num-text: #484f58;
--color-checks-logline-debug-text: #a371f7;
--color-checks-logline-error-text: #8b949e;
--color-checks-logline-error-num-text: #484f58;
--color-checks-logline-error-bg: rgba(248,81,73,0.15);
--color-checks-logline-warning-text: #8b949e;
--color-checks-logline-warning-num-text: #d29922;
--color-checks-logline-warning-bg: rgba(187,128,9,0.15);
--color-checks-logline-command-text: #58a6ff;
--color-checks-logline-section-text: #3fb950;
--color-checks-ansi-black: #0d1117;
--color-checks-ansi-black-bright: #161b22;
--color-checks-ansi-white: #b1bac4;
--color-checks-ansi-white-bright: #b1bac4;
--color-checks-ansi-gray: #6e7681;
--color-checks-ansi-red: #ff7b72;
--color-checks-ansi-red-bright: #ffa198;
--color-checks-ansi-green: #3fb950;
--color-checks-ansi-green-bright: #56d364;
--color-checks-ansi-yellow: #d29922;
--color-checks-ansi-yellow-bright: #e3b341;
--color-checks-ansi-blue: #58a6ff;
--color-checks-ansi-blue-bright: #79c0ff;
--color-checks-ansi-magenta: #bc8cff;
--color-checks-ansi-magenta-bright: #d2a8ff;
--color-checks-ansi-cyan: #76e3ea;
--color-checks-ansi-cyan-bright: #b3f0ff;
--color-project-header-bg: #0d1117;
--color-project-sidebar-bg: #161b22;
--color-project-gradient-in: #161b22;
--color-project-gradient-out: rgba(22,27,34,0);
--color-mktg-success: rgba(41,147,61,1);
--color-mktg-info: rgba(42,123,243,1);
--color-mktg-bg-shade-gradient-top: rgba(1,4,9,0.065);
--color-mktg-bg-shade-gradient-bottom: rgba(1,4,9,0);
--color-mktg-btn-bg-top: hsla(228,82%,66%,1);
--color-mktg-btn-bg-bottom: #4969ed;
--color-mktg-btn-bg-overlay-top: hsla(228,74%,59%,1);
--color-mktg-btn-bg-overlay-bottom: #3355e0;
--color-mktg-btn-text: #f0f6fc;
--color-mktg-btn-primary-bg-top: hsla(137,56%,46%,1);
--color-mktg-btn-primary-bg-bottom: #2ea44f;
--color-mktg-btn-primary-bg-overlay-top: hsla(134,60%,38%,1);
--color-mktg-btn-primary-bg-overlay-bottom: #22863a;
--color-mktg-btn-primary-text: #f0f6fc;
--color-mktg-btn-enterprise-bg-top: hsla(249,100%,72%,1);
--color-mktg-btn-enterprise-bg-bottom: #6f57ff;
--color-mktg-btn-enterprise-bg-overlay-top: hsla(248,65%,63%,1);
--color-mktg-btn-enterprise-bg-overlay-bottom: #614eda;
--color-mktg-btn-enterprise-text: #f0f6fc;
--color-mktg-btn-outline-text: #f0f6fc;
--color-mktg-btn-outline-border: rgba(240,246,252,0.3);
--color-mktg-btn-outline-hover-text: #f0f6fc;
--color-mktg-btn-outline-hover-border: rgba(240,246,252,0.5);
--color-mktg-btn-outline-focus-border: #f0f6fc;
--color-mktg-btn-outline-focus-border-inset: rgba(240,246,252,0.5);
--color-mktg-btn-dark-text: #f0f6fc;
--color-mktg-btn-dark-border: rgba(240,246,252,0.3);
--color-mktg-btn-dark-hover-text: #f0f6fc;
--color-mktg-btn-dark-hover-border: rgba(240,246,252,0.5);
--color-mktg-btn-dark-focus-border: #f0f6fc;
--color-mktg-btn-dark-focus-border-inset: rgba(240,246,252,0.5);
--color-avatar-bg: rgba(240,246,252,0.1);
--color-avatar-border: rgba(240,246,252,0.1);
--color-avatar-stack-fade: #30363d;
--color-avatar-stack-fade-more: #21262d;
--color-avatar-child-shadow: -2px -2px 0 #0d1117;
--color-topic-tag-border: rgba(0,0,0,0);
--color-select-menu-backdrop-border: #484f58;
--color-select-menu-tap-highlight: rgba(48,54,61,0.5);
--color-select-menu-tap-focus-bg: #0c2d6b;
--color-overlay-shadow: 0 0 0 1px #30363d, 0 16px 32px rgba(1,4,9,0.85);
--color-header-text: rgba(240,246,252,0.7);
--color-header-bg: #161b22;
--color-header-logo: #f0f6fc;
--color-header-search-bg: #0d1117;
--color-header-search-border: #30363d;
--color-sidenav-selected-bg: #21262d;
--color-menu-bg-active: #161b22;
--color-control-transparent-bg-hover: #656c7633;
--color-input-disabled-bg: rgba(110,118,129,0);
--color-timeline-badge-bg: #21262d;
--color-ansi-black: #484f58;
--color-ansi-black-bright: #6e7681;
--color-ansi-white: #b1bac4;
--color-ansi-white-bright: #f0f6fc;
--color-ansi-gray: #6e7681;
--color-ansi-red: #ff7b72;
--color-ansi-red-bright: #ffa198;
--color-ansi-green: #3fb950;
--color-ansi-green-bright: #56d364;
--color-ansi-yellow: #d29922;
--color-ansi-yellow-bright: #e3b341;
--color-ansi-blue: #58a6ff;
--color-ansi-blue-bright: #79c0ff;
--color-ansi-magenta: #bc8cff;
--color-ansi-magenta-bright: #d2a8ff;
--color-ansi-cyan: #39c5cf;
--color-ansi-cyan-bright: #56d4dd;
--color-btn-text: #c9d1d9;
--color-btn-bg: #21262d;
--color-btn-border: rgba(240,246,252,0.1);
--color-btn-shadow: 0 0 transparent;
--color-btn-inset-shadow: 0 0 transparent;
--color-btn-hover-bg: #30363d;
--color-btn-hover-border: #8b949e;
--color-btn-active-bg: hsla(212,12%,18%,1);
--color-btn-active-border: #6e7681;
--color-btn-selected-bg: #161b22;
--color-btn-focus-bg: #21262d;
--color-btn-focus-border: #8b949e;
--color-btn-focus-shadow: 0 0 0 3px rgba(139,148,158,0.3);
--color-btn-shadow-active: inset 0 0.15em 0.3em rgba(1,4,9,0.15);
--color-btn-shadow-input-focus: 0 0 0 0.2em rgba(31,111,235,0.3);
--color-btn-counter-bg: #30363d;
--color-btn-primary-text: #ffffff;
--color-btn-primary-bg: #238636;
--color-btn-primary-border: rgba(240,246,252,0.1);
--color-btn-primary-shadow: 0 0 transparent;
--color-btn-primary-inset-shadow: 0 0 transparent;
--color-btn-primary-hover-bg: #2ea043;
--color-btn-primary-hover-border: rgba(240,246,252,0.1);
--color-btn-primary-selected-bg: #238636;
--color-btn-primary-selected-shadow: 0 0 transparent;
--color-btn-primary-disabled-text: rgba(240,246,252,0.5);
--color-btn-primary-disabled-bg: rgba(35,134,54,0.6);
--color-btn-primary-disabled-border: rgba(240,246,252,0.1);
--color-btn-primary-focus-bg: #238636;
--color-btn-primary-focus-border: rgba(240,246,252,0.1);
--color-btn-primary-focus-shadow: 0 0 0 3px rgba(46,164,79,0.4);
--color-btn-primary-icon: #f0f6fc;
--color-btn-primary-counter-bg: rgba(240,246,252,0.2);
--color-btn-outline-text: #58a6ff;
--color-btn-outline-hover-text: #58a6ff;
--color-btn-outline-hover-bg: #30363d;
--color-btn-outline-hover-border: rgba(240,246,252,0.1);
--color-btn-outline-hover-shadow: 0 1px 0 rgba(1,4,9,0.1);
--color-btn-outline-hover-inset-shadow: inset 0 1px 0 rgba(240,246,252,0.03);
--color-btn-outline-hover-counter-bg: rgba(240,246,252,0.2);
--color-btn-outline-selected-text: #f0f6fc;
--color-btn-outline-selected-bg: #0d419d;
--color-btn-outline-selected-border: rgba(240,246,252,0.1);
--color-btn-outline-selected-shadow: 0 0 transparent;
--color-btn-outline-disabled-text: rgba(88,166,255,0.5);
--color-btn-outline-disabled-bg: #0d1117;
--color-btn-outline-disabled-counter-bg: rgba(31,111,235,0.05);
--color-btn-outline-focus-border: rgba(240,246,252,0.1);
--color-btn-outline-focus-shadow: 0 0 0 3px rgba(17,88,199,0.4);
--color-btn-outline-counter-bg: rgba(31,111,235,0.1);
--color-btn-danger-text: #f85149;
--color-btn-danger-hover-text: #f0f6fc;
--color-btn-danger-hover-bg: #da3633;
--color-btn-danger-hover-border: #f85149;
--color-btn-danger-hover-shadow: 0 0 transparent;
--color-btn-danger-hover-inset-shadow: 0 0 transparent;
--color-btn-danger-hover-icon: #f0f6fc;
--color-btn-danger-hover-counter-bg: rgba(255,255,255,0.2);
--color-btn-danger-selected-text: #ffffff;
--color-btn-danger-selected-bg: #b62324;
--color-btn-danger-selected-border: #ff7b72;
--color-btn-danger-selected-shadow: 0 0 transparent;
--color-btn-danger-disabled-text: rgba(248,81,73,0.5);
--color-btn-danger-disabled-bg: #0d1117;
--color-btn-danger-disabled-counter-bg: rgba(218,54,51,0.05);
--color-btn-danger-focus-border: #f85149;
--color-btn-danger-focus-shadow: 0 0 0 3px rgba(248,81,73,0.4);
--color-btn-danger-counter-bg: rgba(218,54,51,0.1);
--color-btn-danger-icon: #f85149;
--color-underlinenav-icon: #484f58;
--color-underlinenav-border-hover: rgba(110,118,129,0.4);
--color-fg-default: #c9d1d9;
--color-fg-muted: #8b949e;
--color-fg-subtle: #484f58;
--color-fg-on-emphasis: #f0f6fc;
--color-canvas-default: #0d1117;
--color-canvas-overlay: #161b22;
--color-canvas-inset: #010409;
--color-canvas-subtle: #161b22;
--color-border-default: #30363d;
--color-border-muted: #21262d;
--color-border-subtle: rgba(240,246,252,0.1);
--color-shadow-small: 0 0 transparent;
--color-shadow-medium: 0 3px 6px #010409;
--color-shadow-large: 0 8px 24px #010409;
--color-shadow-extra-large: 0 12px 48px #010409;
--color-neutral-emphasis-plus: #6e7681;
--color-neutral-emphasis: #6e7681;
--color-neutral-muted: rgba(110,118,129,0.4);
--color-neutral-subtle: rgba(110,118,129,0.1);
--color-accent-fg: #58a6ff;
--color-accent-emphasis: #1f6feb;
--color-accent-muted: rgba(56,139,253,0.4);
--color-accent-subtle: rgba(56,139,253,0.15);
--color-success-fg: #3fb950;
--color-success-emphasis: #238636;
--color-success-muted: rgba(46,160,67,0.4);
--color-success-subtle: rgba(46,160,67,0.15);
--color-attention-fg: #d29922;
--color-attention-emphasis: #9e6a03;
--color-attention-muted: rgba(187,128,9,0.4);
--color-attention-subtle: rgba(187,128,9,0.15);
--color-severe-fg: #db6d28;
--color-severe-emphasis: #bd561d;
--color-severe-muted: rgba(219,109,40,0.4);
--color-severe-subtle: rgba(219,109,40,0.15);
--color-danger-fg: #f85149;
--color-danger-emphasis: #da3633;
--color-danger-muted: rgba(248,81,73,0.4);
--color-danger-subtle: rgba(248,81,73,0.15);
--color-done-fg: #a371f7;
--color-done-emphasis: #8957e5;
--color-done-muted: rgba(163,113,247,0.4);
--color-done-subtle: rgba(163,113,247,0.15);
--color-sponsors-fg: #db61a2;
--color-sponsors-emphasis: #bf4b8a;
--color-sponsors-muted: rgba(219,97,162,0.4);
--color-sponsors-subtle: rgba(219,97,162,0.15);
--color-primer-canvas-backdrop: rgba(1,4,9,0.8);
--color-primer-canvas-sticky: rgba(13,17,23,0.95);
--color-primer-border-active: #F78166;
--color-primer-border-contrast: rgba(240,246,252,0.2);
--color-primer-shadow-highlight: 0 0 transparent;
--color-primer-shadow-inset: 0 0 transparent;
--color-primer-shadow-focus: 0 0 0 3px #0c2d6b;
--color-scale-black: #010409;
--color-scale-white: #f0f6fc;
--color-scale-gray-0: #f0f6fc;
--color-scale-gray-1: #c9d1d9;
--color-scale-gray-2: #b1bac4;
--color-scale-gray-3: #8b949e;
--color-scale-gray-4: #6e7681;
--color-scale-gray-5: #484f58;
--color-scale-gray-6: #30363d;
--color-scale-gray-7: #21262d;
--color-scale-gray-8: #161b22;
--color-scale-gray-9: #0d1117;
--color-scale-blue-0: #cae8ff;
--color-scale-blue-1: #a5d6ff;
--color-scale-blue-2: #79c0ff;
--color-scale-blue-3: #58a6ff;
--color-scale-blue-4: #388bfd;
--color-scale-blue-5: #1f6feb;
--color-scale-blue-6: #1158c7;
--color-scale-blue-7: #0d419d;
--color-scale-blue-8: #0c2d6b;
--color-scale-blue-9: #051d4d;
--color-scale-green-0: #aff5b4;
--color-scale-green-1: #7ee787;
--color-scale-green-2: #56d364;
--color-scale-green-3: #3fb950;
--color-scale-green-4: #2ea043;
--color-scale-green-5: #238636;
--color-scale-green-6: #196c2e;
--color-scale-green-7: #0f5323;
--color-scale-green-8: #033a16;
--color-scale-green-9: #04260f;
--color-scale-yellow-0: #f8e3a1;
--color-scale-yellow-1: #f2cc60;
--color-scale-yellow-2: #e3b341;
--color-scale-yellow-3: #d29922;
--color-scale-yellow-4: #bb8009;
--color-scale-yellow-5: #9e6a03;
--color-scale-yellow-6: #845306;
--color-scale-yellow-7: #693e00;
--color-scale-yellow-8: #4b2900;
--color-scale-yellow-9: #341a00;
--color-scale-orange-0: #ffdfb6;
--color-scale-orange-1: #ffc680;
--color-scale-orange-2: #ffa657;
--color-scale-orange-3: #f0883e;
--color-scale-orange-4: #db6d28;
--color-scale-orange-5: #bd561d;
--color-scale-orange-6: #9b4215;
--color-scale-orange-7: #762d0a;
--color-scale-orange-8: #5a1e02;
--color-scale-orange-9: #3d1300;
--color-scale-red-0: #ffdcd7;
--color-scale-red-1: #ffc1ba;
--color-scale-red-2: #ffa198;
--color-scale-red-3: #ff7b72;
--color-scale-red-4: #f85149;
--color-scale-red-5: #da3633;
--color-scale-red-6: #b62324;
--color-scale-red-7: #8e1519;
--color-scale-red-8: #67060c;
--color-scale-red-9: #490202;
--color-scale-purple-0: #eddeff;
--color-scale-purple-1: #e2c5ff;
--color-scale-purple-2: #d2a8ff;
--color-scale-purple-3: #bc8cff;
--color-scale-purple-4: #a371f7;
--color-scale-purple-5: #8957e5;
--color-scale-purple-6: #6e40c9;
--color-scale-purple-7: #553098;
--color-scale-purple-8: #3c1e70;
--color-scale-purple-9: #271052;
--color-scale-pink-0: #ffdaec;
--color-scale-pink-1: #ffbedd;
--color-scale-pink-2: #ff9bce;
--color-scale-pink-3: #f778ba;
--color-scale-pink-4: #db61a2;
--color-scale-pink-5: #bf4b8a;
--color-scale-pink-6: #9e3670;
--color-scale-pink-7: #7d2457;
--color-scale-pink-8: #5e103e;
--color-scale-pink-9: #42062a;
--color-scale-coral-0: #FFDDD2;
--color-scale-coral-1: #FFC2B2;
--color-scale-coral-2: #FFA28B;
--color-scale-coral-3: #F78166;
--color-scale-coral-4: #EA6045;
--color-scale-coral-5: #CF462D;
--color-scale-coral-6: #AC3220;
--color-scale-coral-7: #872012;
--color-scale-coral-8: #640D04;
--color-scale-coral-9: #460701
}
}

View File

@@ -203,4 +203,60 @@ body {
cursor: pointer;
padding: 0;
font: inherit;
}
}
/* Auth token section */
.auth-token-section {
margin: 16px 0;
padding: 16px;
background-color: #f6f8fa;
border-radius: 6px;
}
.auth-token-description {
font-size: 12px;
color: #656d76;
margin-bottom: 12px;
}
.auth-token-container {
display: flex;
align-items: center;
gap: 8px;
background-color: #ffffff;
padding: 8px;
}
.auth-token-code {
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
font-size: 12px;
color: #1f2328;
border: none;
flex: 1;
padding: 0;
word-break: break-all;
}
.auth-token-refresh {
flex: none;
height: 24px;
width: 24px;
border: none;
outline: none;
color: var(--color-fg-muted);
background: transparent;
padding: 4px;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 4px;
}
.auth-token-refresh svg {
margin: 0;
}
.auth-token-refresh:not(:disabled):hover {
background-color: var(--color-btn-selected-bg);
}

View File

@@ -14,9 +14,11 @@
* limitations under the License.
*/
import React, { useState, useEffect, useCallback } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { createRoot } from 'react-dom/client';
import { Button, TabItem } from './tabItem';
import { Button, TabItem } from './tabItem';
import { AuthTokenSection, getOrCreateAuthToken } from './authToken';
import type { TabInfo } from './tabItem';
type Status =
@@ -37,54 +39,69 @@ const ConnectApp: React.FC = () => {
const [newTab, setNewTab] = useState<boolean>(false);
useEffect(() => {
const params = new URLSearchParams(window.location.search);
const relayUrl = params.get('mcpRelayUrl');
const runAsync = async () => {
const params = new URLSearchParams(window.location.search);
const relayUrl = params.get('mcpRelayUrl');
if (!relayUrl) {
setShowButtons(false);
setStatus({ type: 'error', message: 'Missing mcpRelayUrl parameter in URL.' });
return;
}
if (!relayUrl) {
setShowButtons(false);
setStatus({ type: 'error', message: 'Missing mcpRelayUrl parameter in URL.' });
return;
}
setMcpRelayUrl(relayUrl);
setMcpRelayUrl(relayUrl);
try {
const client = JSON.parse(params.get('client') || '{}');
const info = `${client.name}/${client.version}`;
setClientInfo(info);
setStatus({
type: 'connecting',
message: `🎭 Playwright MCP started from "${info}" is trying to connect. Do you want to continue?`
});
} catch (e) {
setStatus({ type: 'error', message: 'Failed to parse client version.' });
return;
}
try {
const client = JSON.parse(params.get('client') || '{}');
const info = `${client.name}/${client.version}`;
setClientInfo(info);
setStatus({
type: 'connecting',
message: `🎭 Playwright MCP started from "${info}" is trying to connect. Do you want to continue?`
});
} catch (e) {
setStatus({ type: 'error', message: 'Failed to parse client version.' });
return;
}
const parsedVersion = parseInt(params.get('protocolVersion') ?? '', 10);
const requiredVersion = isNaN(parsedVersion) ? 1 : parsedVersion;
if (requiredVersion > SUPPORTED_PROTOCOL_VERSION) {
const extensionVersion = chrome.runtime.getManifest().version;
setShowButtons(false);
setShowTabList(false);
setStatus({
type: 'error',
versionMismatch: {
extensionVersion,
}
});
return;
}
const parsedVersion = parseInt(params.get('protocolVersion') ?? '', 10);
const requiredVersion = isNaN(parsedVersion) ? 1 : parsedVersion;
if (requiredVersion > SUPPORTED_PROTOCOL_VERSION) {
const extensionVersion = chrome.runtime.getManifest().version;
setShowButtons(false);
setShowTabList(false);
setStatus({
type: 'error',
versionMismatch: {
extensionVersion,
}
});
return;
}
void connectToMCPRelay(relayUrl);
const expectedToken = getOrCreateAuthToken();
const token = params.get('token');
if (token === expectedToken) {
await connectToMCPRelay(relayUrl);
await handleConnectToTab();
return;
}
if (token) {
handleReject('Invalid token provided.');
return;
}
// If this is a browser_navigate command, hide the tab list and show simple allow/reject
if (params.get('newTab') === 'true') {
setNewTab(true);
setShowTabList(false);
} else {
void loadTabs();
}
await connectToMCPRelay(relayUrl);
// If this is a browser_navigate command, hide the tab list and show simple allow/reject
if (params.get('newTab') === 'true') {
setNewTab(true);
setShowTabList(false);
} else {
await loadTabs();
}
};
void runAsync();
}, []);
const handleReject = useCallback((message: string) => {
@@ -94,7 +111,6 @@ const ConnectApp: React.FC = () => {
}, []);
const connectToMCPRelay = useCallback(async (mcpRelayUrl: string) => {
const response = await chrome.runtime.sendMessage({ type: 'connectToMCPRelay', mcpRelayUrl });
if (!response.success)
handleReject(response.error);
@@ -174,6 +190,10 @@ const ConnectApp: React.FC = () => {
</div>
)}
{status?.type === 'connecting' && (
<AuthTokenSection />
)}
{showTabList && (
<div>
<div className='tab-section-title'>

View File

@@ -0,0 +1,39 @@
/*
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.
*/
.copy-icon {
flex: none;
height: 24px;
width: 24px;
border: none;
outline: none;
color: var(--color-fg-muted);
background: transparent;
padding: 4px;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 4px;
}
.copy-icon svg {
margin: 0;
}
.copy-icon:not(:disabled):hover {
background-color: var(--color-btn-selected-bg);
}

View File

@@ -0,0 +1,54 @@
/**
* 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 * as React from 'react';
import * as icons from './icons';
import './copyToClipboard.css';
type CopyToClipboardProps = {
value: string;
};
/**
* A copy to clipboard button.
*/
export const CopyToClipboard: React.FunctionComponent<CopyToClipboardProps> = ({ value }) => {
type IconType = 'copy' | 'check' | 'cross';
const [icon, setIcon] = React.useState<IconType>('copy');
React.useEffect(() => {
setIcon('copy');
}, [value]);
React.useEffect(() => {
if (icon === 'check') {
const timeout = setTimeout(() => {
setIcon('copy');
}, 3000);
return () => clearTimeout(timeout);
}
}, [icon]);
const handleCopy = React.useCallback(() => {
navigator.clipboard.writeText(value).then(() => {
setIcon('check');
}, () => {
setIcon('cross');
});
}, [value]);
const iconElement = icon === 'check' ? icons.check() : icon === 'cross' ? icons.cross() : icons.copy();
return <button className='copy-icon' title='Copy to clipboard' aria-label='Copy to clipboard' onClick={handleCopy}>{iconElement}</button>;
};

View 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.
*/
.octicon {
display: inline-block;
overflow: visible !important;
vertical-align: text-bottom;
fill: currentColor;
margin-right: 7px;
flex: none;
}
.color-icon-success {
color: var(--color-success-fg) !important;
}
.color-text-danger {
color: var(--color-danger-fg) !important;
}

View File

@@ -0,0 +1,43 @@
/*
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 './icons.css';
import './colors.css';
export const cross = () => {
return <svg className='octicon color-text-danger' viewBox='0 0 16 16' version='1.1' width='16' height='16' aria-hidden='true'>
<path fillRule='evenodd' d='M3.72 3.72a.75.75 0 011.06 0L8 6.94l3.22-3.22a.75.75 0 111.06 1.06L9.06 8l3.22 3.22a.75.75 0 11-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 01-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 010-1.06z'></path>
</svg>;
};
export const check = () => {
return <svg aria-hidden='true' height='16' viewBox='0 0 16 16' version='1.1' width='16' data-view-component='true' className='octicon color-icon-success'>
<path fillRule='evenodd' d='M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z'></path>
</svg>;
};
export const copy = () => {
return <svg className='octicon' viewBox='0 0 16 16' width='16' height='16' aria-hidden='true'>
<path d='M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z'></path>
<path d='M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z'></path>
</svg>;
};
export const refresh = () => {
return <svg className='octicon' viewBox="0 0 16 16" width="16" height="16" aria-hidden='true'>
<path d="M1.705 8.005a.75.75 0 0 1 .834.656 5.5 5.5 0 0 0 9.592 2.97l-1.204-1.204a.25.25 0 0 1 .177-.427h3.646a.25.25 0 0 1 .25.25v3.646a.25.25 0 0 1-.427.177l-1.38-1.38A7.002 7.002 0 0 1 1.05 8.84a.75.75 0 0 1 .656-.834ZM8 2.5a5.487 5.487 0 0 0-4.131 1.869l1.204 1.204A.25.25 0 0 1 4.896 6H1.25A.25.25 0 0 1 1 5.75V2.104a.25.25 0 0 1 .427-.177l1.38 1.38A7.002 7.002 0 0 1 14.95 7.16a.75.75 0 0 1-1.49.178A5.5 5.5 0 0 0 8 2.5Z"></path>
</svg>;
};

View File

@@ -19,6 +19,7 @@ import { createRoot } from 'react-dom/client';
import { Button, TabItem } from './tabItem';
import type { TabInfo } from './tabItem';
import { AuthTokenSection } from './authToken';
interface ConnectionStatus {
isConnected: boolean;
@@ -97,6 +98,7 @@ const StatusApp: React.FC = () => {
No MCP clients are currently connected.
</div>
)}
<AuthTokenSection />
</div>
</div>
);

View File

@@ -304,3 +304,33 @@ test(`custom executablePath`, async ({ startClient, server, useShortConnectionTi
});
expect(await fs.promises.readFile(test.info().outputPath('output.txt'), 'utf8')).toContain('Custom exec args: chrome-extension://jakfalbnbhgkpmoaakfflhflbfpkailf/connect.html?');
});
test(`bypass connection dialog with token`, async ({ browserWithExtension, startClient, server }) => {
const browserContext = await browserWithExtension.launch();
const page = await browserContext.newPage();
await page.goto('chrome-extension://jakfalbnbhgkpmoaakfflhflbfpkailf/status.html');
const token = await page.locator('.auth-token-code').textContent();
const [name, value] = token?.split('=') || [];
const { client } = await startClient({
args: [`--extension`],
extensionToken: value,
config: {
browser: {
userDataDir: browserWithExtension.userDataDir,
}
},
});
const navigateResponse = await client.callTool({
name: 'browser_navigate',
arguments: { url: server.HELLO_WORLD },
});
expect(await navigateResponse).toHaveResponse({
pageState: expect.stringContaining(`- generic [active] [ref=e1]: Hello, world!`),
});
});

32
package-lock.json generated
View File

@@ -1,23 +1,23 @@
{
"name": "@playwright/mcp",
"version": "0.0.37",
"version": "0.0.39",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@playwright/mcp",
"version": "0.0.37",
"version": "0.0.39",
"license": "Apache-2.0",
"dependencies": {
"playwright": "1.56.0-alpha-2025-09-06",
"playwright-core": "1.56.0-alpha-2025-09-06"
"playwright": "1.56.0-alpha-1758292576000",
"playwright-core": "1.56.0-alpha-1758292576000"
},
"bin": {
"mcp-server-playwright": "cli.js"
},
"devDependencies": {
"@modelcontextprotocol/sdk": "^1.17.5",
"@playwright/test": "1.56.0-alpha-2025-09-06",
"@playwright/test": "1.56.0-alpha-1758292576000",
"@types/node": "^24.3.0",
"zod-to-json-schema": "^3.24.6"
},
@@ -50,13 +50,13 @@
}
},
"node_modules/@playwright/test": {
"version": "1.56.0-alpha-2025-09-06",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.0-alpha-2025-09-06.tgz",
"integrity": "sha512-BhjZwYn26kGwWVoF9et+4RnSb/HuJ2cbzDPACWdkBApEb+2fBb060cOQZ63hZ+IIeQUd2Q4TCZbH5U9tvXD23A==",
"version": "1.56.0-alpha-1758292576000",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.0-alpha-1758292576000.tgz",
"integrity": "sha512-U5SLVseO2I8yDg2lSMPrlTR08KBTyIrFFi1EP23LSxdQ+jKsnOdQdHzUCY+qXQWIMC24eNtgPWYPmh9hgf1kAA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright": "1.56.0-alpha-2025-09-06"
"playwright": "1.56.0-alpha-1758292576000"
},
"bin": {
"playwright": "cli.js"
@@ -825,12 +825,12 @@
}
},
"node_modules/playwright": {
"version": "1.56.0-alpha-2025-09-06",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.0-alpha-2025-09-06.tgz",
"integrity": "sha512-suVjiF5eeUtIqFq5E/5LGgkV0/bRSik87N+M7uLsjPQrKln9QHbZt3cy7Zybicj3ZqTBWWHvpN9b4cnpg6hS0g==",
"version": "1.56.0-alpha-1758292576000",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.0-alpha-1758292576000.tgz",
"integrity": "sha512-XRTRlArx9KgiGsboXUpJR7ZDazUPfO51t1nrQ+w883e02/IDNxcqPpQXJcFAy4nFqG925r//VR9AyseVfw1AWg==",
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.56.0-alpha-2025-09-06"
"playwright-core": "1.56.0-alpha-1758292576000"
},
"bin": {
"playwright": "cli.js"
@@ -843,9 +843,9 @@
}
},
"node_modules/playwright-core": {
"version": "1.56.0-alpha-2025-09-06",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.0-alpha-2025-09-06.tgz",
"integrity": "sha512-B2s/cuqYuu+mT4hIHG8gIOXjCSKh0Np1gJNCp0CrDk/UTLB74gThwXiyPAJU0fADIQH6Dv1glv8ZvKTDVT8Fng==",
"version": "1.56.0-alpha-1758292576000",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.0-alpha-1758292576000.tgz",
"integrity": "sha512-qXLOCI9RhulhdvNjFglvgoyUw3N49putP8iU0uhoZ+mE3lEXAJNy/v1znCRvhjwvfsGsabO9+Xe4sfvu2GFGCw==",
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"

View File

@@ -1,6 +1,6 @@
{
"name": "@playwright/mcp",
"version": "0.0.37",
"version": "0.0.39",
"description": "Playwright Tools for MCP",
"repository": {
"type": "git",
@@ -33,15 +33,15 @@
}
},
"dependencies": {
"playwright": "1.56.0-alpha-2025-09-06",
"playwright-core": "1.56.0-alpha-2025-09-06"
"playwright": "1.56.0-alpha-1758292576000",
"playwright-core": "1.56.0-alpha-1758292576000"
},
"bin": {
"mcp-server-playwright": "cli.js"
},
"devDependencies": {
"@modelcontextprotocol/sdk": "^1.17.5",
"@playwright/test": "1.56.0-alpha-2025-09-06",
"@playwright/test": "1.56.0-alpha-1758292576000",
"@types/node": "^24.3.0",
"zod-to-json-schema": "^3.24.6"
}

View File

@@ -46,6 +46,7 @@ export type StartClient = (options?: {
config?: Config,
roots?: { name: string, uri: string }[],
rootsResponseDelay?: number,
extensionToken?: string,
}) => Promise<{ client: Client, stderr: () => string }>;
@@ -102,7 +103,7 @@ export const test = baseTest.extend<TestFixtures & TestOptions, WorkerFixtures>(
};
});
}
const { transport, stderr } = await createTransport(args, mcpMode, testInfo.outputPath('ms-playwright'));
const { transport, stderr } = await createTransport(args, mcpMode, testInfo.outputPath('ms-playwright'), options?.extensionToken);
let stderrBuffer = '';
stderr?.on('data', data => {
if (process.env.PWMCP_DEBUG)
@@ -181,7 +182,7 @@ export const test = baseTest.extend<TestFixtures & TestOptions, WorkerFixtures>(
},
});
async function createTransport(args: string[], mcpMode: TestOptions['mcpMode'], profilesDir: string): Promise<{
async function createTransport(args: string[], mcpMode: TestOptions['mcpMode'], profilesDir: string, extensionToken?: string): Promise<{
transport: Transport,
stderr: Stream | null,
}> {
@@ -208,6 +209,7 @@ async function createTransport(args: string[], mcpMode: TestOptions['mcpMode'],
DEBUG_COLORS: '0',
DEBUG_HIDE_DATE: '1',
PWMCP_PROFILES_DIR_FOR_TEST: profilesDir,
...(extensionToken ? { PLAYWRIGHT_MCP_EXTENSION_TOKEN: extensionToken } : {}),
},
});
return {

View File

@@ -21,7 +21,7 @@ const path = require('path')
const { zodToJsonSchema } = require('zod-to-json-schema')
const { execSync } = require('child_process');
const { allTools } = require('playwright/lib/mcp/browser/tools');
const { browserTools } = require('playwright/lib/mcp/browser/tools');
const capabilities = {
'core': 'Core automation',
@@ -33,7 +33,7 @@ const capabilities = {
'tracing': 'Tracing (opt-in via --caps=tracing)',
};
const toolsByCapability = Object.fromEntries(Object.entries(capabilities).map(([capability, title]) => [title, allTools.filter(tool => tool.capability === capability).sort((a, b) => a.schema.name.localeCompare(b.schema.name))]));
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))]));
/**
* @param {any} tool