Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 🎭 Playwright

[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) <!-- GEN:chromium-version-badge -->[![Chromium version](https://img.shields.io/badge/chromium-150.0.7871.46-blue.svg?logo=google-chrome)](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[![Firefox version](https://img.shields.io/badge/firefox-151.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[![WebKit version](https://img.shields.io/badge/webkit-26.5-blue.svg?logo=safari)](https://webkit.org/)<!-- GEN:stop --> [![Join Discord](https://img.shields.io/badge/join-discord-informational)](https://aka.ms/playwright/discord)
[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) <!-- GEN:chromium-version-badge -->[![Chromium version](https://img.shields.io/badge/chromium-151.0.7922.10-blue.svg?logo=google-chrome)](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[![Firefox version](https://img.shields.io/badge/firefox-151.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[![WebKit version](https://img.shields.io/badge/webkit-26.5-blue.svg?logo=safari)](https://webkit.org/)<!-- GEN:stop --> [![Join Discord](https://img.shields.io/badge/join-discord-informational)](https://aka.ms/playwright/discord)

## [Documentation](https://playwright.dev) | [API reference](https://playwright.dev/docs/api/class-playwright)

Expand Down Expand Up @@ -296,7 +296,7 @@ The [Playwright VS Code extension](https://marketplace.visualstudio.com/items?it

| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium<sup>1</sup> <!-- GEN:chromium-version -->150.0.7871.46<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Chromium<sup>1</sup> <!-- GEN:chromium-version -->151.0.7922.10<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->26.5<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Firefox <!-- GEN:firefox-version -->151.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |

Expand Down
196 changes: 98 additions & 98 deletions packages/isomorphic/deviceDescriptorsSource.json

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions packages/playwright-core/browsers.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@
"browsers": [
{
"name": "chromium",
"revision": "1231",
"revision": "1232",
"installByDefault": true,
"browserVersion": "150.0.7871.46",
"browserVersion": "151.0.7922.10",
"title": "Chrome for Testing"
},
{
"name": "chromium-headless-shell",
"revision": "1231",
"revision": "1232",
"installByDefault": true,
"browserVersion": "150.0.7871.46",
"browserVersion": "151.0.7922.10",
"title": "Chrome Headless Shell"
},
{
Expand Down Expand Up @@ -45,7 +45,7 @@
},
{
"name": "webkit",
"revision": "2323",
"revision": "2325",
"installByDefault": true,
"revisionOverrides": {
"mac14": "2251",
Expand Down
168 changes: 155 additions & 13 deletions packages/playwright-core/src/server/chromium/protocol.d.ts

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions packages/playwright-core/src/server/registry/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,8 +384,8 @@ const DOWNLOAD_PATHS: Record<string, DownloadPaths> = {
'mac14-arm64': 'builds/webkit/%s/webkit-mac-14-arm64.zip',
'mac15': 'builds/webkit/%s/webkit-mac-15.zip',
'mac15-arm64': 'builds/webkit/%s/webkit-mac-15-arm64.zip',
'mac26': 'builds/webkit/%s/webkit-mac-15.zip',
'mac26-arm64': 'builds/webkit/%s/webkit-mac-15-arm64.zip',
'mac26': 'builds/webkit/%s/webkit-mac-26.zip',
'mac26-arm64': 'builds/webkit/%s/webkit-mac-26-arm64.zip',
'win64': 'builds/webkit/%s/webkit-win64.zip',
},
'ffmpeg': {
Expand Down
8 changes: 6 additions & 2 deletions packages/playwright-core/src/server/webkit/wkBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ export class WKBrowserContext extends BrowserContext {
async doGetCookies(urls: string[]): Promise<channels.NetworkCookie[]> {
const { cookies } = await this._browser._browserSession.send('Playwright.getAllCookies', { browserContextId: this._browserContextId });
return network.filterCookies(cookies.map((c: channels.NetworkCookie) => {
const { name, value, domain, path, expires, httpOnly, secure, sameSite } = c;
const { name, value, domain, path, expires, httpOnly, secure, sameSite, partitionKey } = c;
const copy: channels.NetworkCookie = {
name,
value,
Expand All @@ -262,13 +262,15 @@ export class WKBrowserContext extends BrowserContext {
secure,
sameSite,
};
if (partitionKey)
copy.partitionKey = partitionKey;
return copy;
}), urls);
}

async addCookies(cookies: channels.SetNetworkCookie[]) {
const cc = network.rewriteCookies(cookies).map(c => {
const { name, value, domain, path, expires, httpOnly, secure, sameSite } = c;
const { name, value, domain, path, expires, httpOnly, secure, sameSite, partitionKey } = c;
const copy: Protocol.Playwright.SetCookieParam = {
name,
value,
Expand All @@ -280,6 +282,8 @@ export class WKBrowserContext extends BrowserContext {
sameSite,
session: expires === -1 || expires === undefined,
};
if (partitionKey)
copy.partitionKey = partitionKey;
return copy;
});
await this._browser._browserSession.send('Playwright.setCookies', { cookies: cc, browserContextId: this._browserContextId });
Expand Down
45 changes: 40 additions & 5 deletions packages/playwright-core/src/tools/backend/find.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const find = defineTabTool({
schema: {
name: 'browser_find',
title: 'Find in page snapshot',
description: 'Search the accessibility snapshot of the current page for text or a regular expression. Returns matching snapshot nodes with a few lines of surrounding context (like search snippets), which is cheaper than capturing the whole snapshot when you only need to locate an element and its ref.',
description: 'Search the accessibility snapshot of the current page for text or a regular expression. Returns matching snapshot nodes with a few lines of surrounding context (like search snippets), each shown under its path from the root of the tree, which is cheaper than capturing the whole snapshot when you only need to locate an element and its ref.',
inputSchema: z.object({
text: z.string().optional().describe('Plain text to search for in the page snapshot (case-insensitive substring match). Provide either text or regex, not both.'),
regex: z.string().optional().refine(v => !v || isValidRegex(v), { message: 'Invalid regular expression' }).describe('Regular expression to search for in the page snapshot. Matching is case-sensitive by default; wrap the pattern in slashes to add flags, e.g. "/error/i" for case-insensitive. Provide either text or regex, not both.'),
Expand Down Expand Up @@ -61,6 +61,7 @@ const find = defineTabTool({

const snapshot = await tab.page.ariaSnapshot({ mode: 'ai' });
const lines = snapshot.split('\n');
const indents = lines.map(indentOf);
const matchedLines: number[] = [];
for (let i = 0; i < lines.length; i++) {
if (matches(lines[i]))
Expand All @@ -84,15 +85,31 @@ const find = defineTabTool({
windows.push({ start, end });
}

const snippets = windows.map(window => lines.slice(window.start, window.end + 1).join('\n'));
const path = new Set<number>();
for (const match of matchedLines) {
path.add(match);
for (const ancestor of ancestorIndices(lines, indents, match))
path.add(ancestor);
}

const snippets = windows.map(window => {
const indices = ancestorIndices(lines, indents, window.start);
for (let i = window.start; i <= window.end; i++)
indices.push(i);
const out: string[] = [];
for (let i = 0; i < indices.length; i++) {
const index = indices[i];
if (i > 0 && index > indices[i - 1] + 1 && !path.has(index) && !path.has(indices[i - 1]))
out.push(' '.repeat(indents[index]) + '...');
out.push(lines[index]);
}
return out.join('\n');
});
const matchWord = matchedLines.length === 1 ? 'match' : 'matches';
response.addTextResult(`Found ${matchedLines.length} ${matchWord} for ${query}:\n\n${snippets.join('\n\n----\n\n')}`);
},
});

// Accept either a bare pattern or a `/pattern/flags` literal, mirroring the
// test runner's forceRegExp. Matching is line-oriented, so the global flag is
// dropped: it only makes `.test()` stateful without changing which lines match.
function compileRegex(source: string): RegExp {
const literal = /^\/(.*)\/([a-z]*)$/.exec(source);
const pattern = literal ? literal[1] : source;
Expand All @@ -109,6 +126,24 @@ function isValidRegex(source: string): boolean {
}
}

function indentOf(line: string): number {
return line.length - line.trimStart().length;
}

function ancestorIndices(lines: string[], indents: number[], index: number): number[] {
const result: number[] = [];
let indent = indents[index];
for (let i = index - 1; i >= 0 && indent > 0; i--) {
if (!lines[i].trim())
continue;
if (indents[i] < indent) {
result.push(i);
indent = indents[i];
}
}
return result.reverse();
}

export default [
find,
];
2 changes: 2 additions & 0 deletions packages/playwright-core/src/tools/cli-client/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ type AttachOptions = {
type OpenOptions = {
browser?: string;
config?: string;
device?: string;
headed?: boolean;
mobile?: boolean;
persistent?: boolean;
profile?: string;
};
Expand Down
4 changes: 4 additions & 0 deletions packages/playwright-core/src/tools/cli-client/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ export class Session {
];
if (cliArgs.headed)
args.push('--headed');
if (cliArgs.mobile)
args.push('--mobile');
if (cliArgs.device)
args.push(`--device=${cliArgs.device}`);
if (cliArgs.browser)
args.push(`--browser=${cliArgs.browser}`);
if (cliArgs.persistent)
Expand Down
6 changes: 6 additions & 0 deletions packages/playwright-core/src/tools/cli-client/skill/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,12 @@ playwright-cli open --browser=firefox
playwright-cli open --browser=webkit
playwright-cli open --browser=msedge

# Emulate a generic mobile device (Pixel 10 for Chromium, iPhone 17 for WebKit).
# Prefer this when a mobile layout is acceptable: mobile pages are usually
# lighter, so snapshots are smaller and cheaper.
playwright-cli open --mobile
playwright-cli open --device="iPhone 15"

# Use persistent profile (by default profile is in-memory)
playwright-cli open --persistent
# Use persistent profile with custom directory
Expand Down
2 changes: 2 additions & 0 deletions packages/playwright-core/src/tools/cli-daemon/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ const open = declareCommand({
options: z.object({
browser: z.string().optional().describe('Browser or chrome channel to use, possible values: chrome, firefox, webkit, msedge.'),
config: z.string().optional().describe('Path to the configuration file, defaults to .playwright/cli.config.json'),
device: z.string().optional().describe('Emulate a specific device, for example "iPhone 15".'),
headed: z.boolean().optional().describe('Run browser in headed mode'),
mobile: z.boolean().optional().describe('Emulate a generic mobile device (Pixel 10 for Chromium, iPhone 17 for WebKit). Mobile pages are usually lighter, which saves tokens.'),
persistent: z.boolean().optional().describe('Use persistent browser profile'),
profile: z.string().optional().describe('Path to a persistent user data directory.'),
}),
Expand Down
2 changes: 2 additions & 0 deletions packages/playwright-core/src/tools/cli-daemon/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import type { Command } from 'commander';
export function decorateProgram(program: Command) {
program.argument('[session-name]', 'name of the session to create or connect to', 'default')
.option('--headed', 'run in headed mode (non-headless)')
.option('--device <device>', 'emulate a specific device, for example "iPhone 15"')
.option('--mobile', 'emulate a generic mobile device (Pixel 10 for Chromium, iPhone 17 for WebKit)')
.option('--extension', 'run with the extension')
.option('--browser <name>', 'browser to use (chromium, chrome, firefox, webkit)')
.option('--persistent', 'use a persistent browser context')
Expand Down
19 changes: 16 additions & 3 deletions packages/playwright-core/src/tools/mcp/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export type CLIOptions = {
initPage?: string[];
isolated?: boolean;
imageResponses?: 'allow' | 'omit';
mobile?: boolean;
sandbox?: boolean;
outputDir?: string;
outputMaxSize?: number;
Expand Down Expand Up @@ -155,7 +156,9 @@ export async function resolveCLIConfigForCLI(daemonProfilesDir: string, sessionN
cdpEndpoint: options.cdp,
config: options.config,
browser: options.browser,
device: options.device,
headless: options.headed ? false : undefined,
mobile: options.mobile,
extension: options.extension,
userDataDir: options.profile,
snapshotMode: 'full',
Expand Down Expand Up @@ -212,7 +215,7 @@ async function validateBrowserConfig(browser: MergedConfig['browser']): Promise<
browser.launchOptions.channel = 'chrome';
}

if (browser.browserName === 'chromium' && browser.launchOptions.chromiumSandbox === undefined) {
if (browserName === 'chromium' && browser.launchOptions.chromiumSandbox === undefined) {
if (process.platform === 'linux')
browser.launchOptions.chromiumSandbox = browser.launchOptions.channel !== 'chromium' && browser.launchOptions.channel !== 'chrome-for-testing';
else
Expand Down Expand Up @@ -291,11 +294,20 @@ function configFromCLIOptions(cliOptions: CLIOptions): Config & { configFile?: s
if (cliOptions.sandbox !== undefined)
launchOptions.chromiumSandbox = cliOptions.sandbox;

if (cliOptions.device && cliOptions.cdpEndpoint)
let device = cliOptions.device;
if (cliOptions.mobile) {
if (device)
throw new Error('Cannot use --mobile together with --device, pick one.');
if (browserName === 'firefox')
throw new Error('--mobile is not supported with the Firefox browser.');
device = browserName === 'webkit' ? 'iPhone 17' : 'Pixel 10';
}

if (device && cliOptions.cdpEndpoint)
throw new Error('Device emulation is not supported with cdpEndpoint.');

// Context options
const contextOptions: playwrightTypes.BrowserContextOptions = cliOptions.device ? playwright.devices[cliOptions.device] : {};
const contextOptions: playwrightTypes.BrowserContextOptions = device ? playwright.devices[device] : {};

if (cliOptions.proxyServer) {
const proxy: playwrightTypes.LaunchOptions['proxy'] = { server: cliOptions.proxyServer };
Expand Down Expand Up @@ -409,6 +421,7 @@ export function configFromEnv(env?: NodeJS.ProcessEnv): Config & { configFile?:
options.isolated = envToBoolean(e.PLAYWRIGHT_MCP_ISOLATED);
if (e.PLAYWRIGHT_MCP_IMAGE_RESPONSES)
options.imageResponses = enumParser<'allow' | 'omit'>('--image-responses', ['allow', 'omit'], e.PLAYWRIGHT_MCP_IMAGE_RESPONSES);
options.mobile = envToBoolean(e.PLAYWRIGHT_MCP_MOBILE);
options.sandbox = envToBoolean(e.PLAYWRIGHT_MCP_SANDBOX);
options.outputDir = envToString(e.PLAYWRIGHT_MCP_OUTPUT_DIR);
options.outputMaxSize = numberParser(e.PLAYWRIGHT_MCP_OUTPUT_MAX_SIZE);
Expand Down
1 change: 1 addition & 0 deletions packages/playwright-core/src/tools/mcp/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export function decorateMCPCommand(command: Command) {
.option('--config <path>', 'path to the configuration file.')
.option('--console-level <level>', 'level of console messages to return: "error", "warning", "info", "debug". Each level includes the messages of more severe levels.', enumParser.bind(null, '--console-level', ['error', 'warning', 'info', 'debug']))
.option('--device <device>', 'device to emulate, for example: "iPhone 15"')
.option('--mobile', 'emulate a generic mobile device (Pixel 10 for Chromium, iPhone 17 for WebKit). Mobile pages are usually lighter, which saves tokens. Cannot be combined with --device.')
.option('--executable-path <path>', 'path to the browser executable.')
.option('--extension', 'Connect to a running browser instance (Edge/Chrome only). Requires the "Playwright Extension" to be installed.')
.option('--endpoint <endpoint>', 'Bound browser endpoint to connect to.')
Expand Down
Loading
Loading