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
29 changes: 22 additions & 7 deletions packages/playwright-core/src/tools/cli-client/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,23 @@ type GlobalOptions = {
version?: boolean;
};

type OpenOptions = {
type AttachOptions = {
config?: string;
cdp?: string;
endpoint?: string;
extension?: boolean | string;
};

type OpenOptions = {
browser?: string;
config?: string;
extension?: boolean;
headed?: boolean;
persistent?: boolean;
profile?: string;
};

const globalOptions: (keyof (GlobalOptions & OpenOptions))[] = [
const globalOptions: (keyof (GlobalOptions & OpenOptions & AttachOptions))[] = [
'cdp',
'endpoint',
'browser',
'config',
Expand All @@ -62,7 +68,7 @@ const globalOptions: (keyof (GlobalOptions & OpenOptions))[] = [
'version',
];

const booleanOptions: (keyof (GlobalOptions & OpenOptions & { all?: boolean }))[] = [
const booleanOptions: (keyof (GlobalOptions & OpenOptions & AttachOptions & { all?: boolean }))[] = [
'all',
'help',
'raw',
Expand Down Expand Up @@ -140,9 +146,18 @@ export async function program(options?: { embedderVersion?: string}) {
return;
}
case 'attach': {
const attachTarget = args._[1];
const attachSessionName = explicitSessionName(args.session as string) ?? attachTarget;
args.endpoint = attachTarget;
const attachTarget = args._[1] as string | undefined;
if (attachTarget && (args.cdp || args.endpoint || args.extension)) {
console.error(`Error: cannot use target name with --cdp, --endpoint, or --extension`);
process.exit(1);
}
if (attachTarget)
args.endpoint = attachTarget;
if (typeof args.extension === 'string') {
args.browser = args.extension;
args.extension = true;
}
const attachSessionName = explicitSessionName(args.session as string) ?? attachTarget ?? sessionName;
args.session = attachSessionName;
await startSession(attachSessionName, registry, clientInfo, args);
return;
Expand Down
6 changes: 1 addition & 5 deletions packages/playwright-core/src/tools/cli-client/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,5 @@ export function explicitSessionName(sessionName?: string): string | undefined {
}

export function resolveSessionName(sessionName?: string): string {
if (sessionName)
return sessionName;
if (process.env.PLAYWRIGHT_CLI_SESSION)
return process.env.PLAYWRIGHT_CLI_SESSION;
return 'default';
return explicitSessionName(sessionName) || 'default';
}
2 changes: 2 additions & 0 deletions packages/playwright-core/src/tools/cli-client/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ export class Session {
args.push(`--profile=${cliArgs.profile}`);
if (cliArgs.config)
args.push(`--config=${cliArgs.config}`);
if (cliArgs.cdp)
args.push(`--cdp=${cliArgs.cdp}`);
if (cliArgs.endpoint || process.env.PLAYWRIGHT_CLI_SESSION)
args.push(`--endpoint=${cliArgs.endpoint || process.env.PLAYWRIGHT_CLI_SESSION}`);

Expand Down
5 changes: 3 additions & 2 deletions packages/playwright-core/src/tools/cli-client/skill/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,14 +182,15 @@ playwright-cli open --browser=chrome
playwright-cli open --browser=firefox
playwright-cli open --browser=webkit
playwright-cli open --browser=msedge
# Connect to browser via extension
playwright-cli open --extension

# Use persistent profile (by default profile is in-memory)
playwright-cli open --persistent
# Use persistent profile with custom directory
playwright-cli open --profile=/path/to/profile

# Connect to browser via extension
playwright-cli attach --extension

# Start with config file
playwright-cli open --config=my-config.json

Expand Down
8 changes: 5 additions & 3 deletions packages/playwright-core/src/tools/cli-daemon/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ 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'),
extension: z.boolean().optional().describe('Connect to browser extension'),
headed: z.boolean().optional().describe('Run browser in headed mode'),
persistent: z.boolean().optional().describe('Use persistent browser profile'),
profile: z.string().optional().describe('Use persistent browser profile, store profile in specified directory.'),
Expand All @@ -65,11 +64,14 @@ const attach = declareCommand({
description: 'Attach to a running Playwright browser',
category: 'core',
args: z.object({
name: z.string().describe('Name or endpoint of the browser to attach to'),
name: z.string().optional().describe('Bound browser name to attach to'),
}),
options: z.object({
cdp: z.string().optional().describe('Connect to an existing browser via CDP endpoint URL.'),
endpoint: z.string().optional().describe('Playwright browser server endpoint to attach to.'),
extension: z.union([z.boolean(), z.string()]).optional().describe('Connect to browser extension, optionally specify browser name (e.g. --extension=chrome)'),
config: z.string().optional().describe('Path to the configuration file, defaults to .playwright/cli.config.json'),
session: z.string().optional().describe('Session name alias (defaults to the attach target name)'),
session: z.string().optional().describe('Session name (defaults to bound browser name or "default")'),
}),
toolName: 'browser_snapshot',
toolParams: () => ({ filename: '<auto>' }),
Expand Down
1 change: 1 addition & 0 deletions packages/playwright-core/src/tools/cli-daemon/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ program.argument('[session-name]', 'name of the session to create or connect to'
.option('--persistent', 'use a persistent browser context')
.option('--profile <path>', 'path to the user data dir')
.option('--config <path>', 'path to the config file; by default uses .playwright/cli.config.json in the project directory and ~/.playwright/cli.config.json as global config')
.option('--cdp <url>', 'connect to an existing browser via CDP endpoint URL')
.option('--endpoint <endpoint>', 'attach to a running Playwright browser endpoint')
.option('--init-workspace', 'initialize workspace')
.option('--init-skills <value>', 'install skills for the given agent type ("claude" or "agents")')
Expand Down
3 changes: 2 additions & 1 deletion packages/playwright-core/src/tools/mcp/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ export async function resolveCLIConfigForCLI(daemonProfilesDir: string, sessionN

const daemonOverrides = configFromCLIOptions({
endpoint: options.endpoint,
cdpEndpoint: options.cdp,
config: options.config,
browser: options.browser,
headless: options.headed ? false : undefined,
Expand All @@ -161,7 +162,7 @@ export async function resolveCLIConfigForCLI(daemonProfilesDir: string, sessionN
result = mergeConfig(result, daemonOverrides);

if (result.browser.isolated === undefined)
result.browser.isolated = !options.profile && !options.persistent && !result.browser.userDataDir && !result.browser.remoteEndpoint && !result.extension;
result.browser.isolated = !options.profile && !options.persistent && !result.browser.userDataDir && !result.browser.remoteEndpoint && !result.browser.cdpEndpoint && !result.extension;

if (!result.extension && !result.browser.isolated && !result.browser.userDataDir && !result.browser.remoteEndpoint) {
// No custom value provided, use the daemon data dir.
Expand Down
12 changes: 12 additions & 0 deletions tests/mcp/cli-session.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,18 @@ workspace1:
- data-dir: <in-memory>
- run \`playwright-cli attach \"foobar\"\` to attach`);
});

test('attach --cdp', async ({ cli, cdpServer, server }) => {
const context = await cdpServer.start();
await context.pages()[0].goto(server.HELLO_WORLD);
const { output, snapshot } = await cli('attach', `--cdp=${cdpServer.endpoint}`);
expect(output).toContain(`### Page
- Page URL: ${server.HELLO_WORLD}
- Page Title: Title`);
expect(snapshot).toContain(`- generic [active] [ref=e1]: Hello, world!`);
await cli('goto', 'data:text/html,<title>CDP Title</title>');
expect(await context.pages()[0].title()).toBe('CDP Title');
});
});

const version = 'v' + require('../../packages/playwright-core/package.json').version;
Loading