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
6 changes: 3 additions & 3 deletions .claude/skills/playwright-dev/mcp-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

### Step 1: Create the Tool File

Create `packages/playwright/src/mcp/browser/tools/<your-tool>.ts`.
Create `packages/playwright/src/tools/<your-tool>.ts`.

Import zod from the MCP bundle and use `defineTool` or `defineTabTool`:

Expand Down Expand Up @@ -115,7 +115,7 @@ export type ToolCapability =

### Step 3: Register the Tool

In `packages/playwright/src/mcp/browser/tools.ts`:
In `packages/playwright/src/tools/tools.ts`:

```typescript
import myTool from './tools/myTool';
Expand Down Expand Up @@ -331,7 +331,7 @@ export type Config = {
};
```

### 2. CLI options type: `packages/playwright/src/mcp/browser/config.ts`
### 2. CLI options type: `packages/playwright/src/mcp/config.ts`

Add to `CLIOptions` type:

Expand Down
1 change: 1 addition & 0 deletions packages/playwright-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"./lib/cli/client/program": "./lib/cli/client/program.js",
"./lib/mcpBundle": "./lib/mcpBundle.js",
"./lib/mcp/exports": "./lib/mcp/exports.js",
"./lib/tools/exports": "./lib/tools/exports.js",
"./lib/mcp/index": "./lib/mcp/index.js",
"./lib/remote/playwrightServer": "./lib/remote/playwrightServer.js",
"./lib/server": "./lib/server/index.js",
Expand Down
6 changes: 0 additions & 6 deletions packages/playwright-core/src/cli/client/DEPS.list
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,3 @@

[registry.ts]
"strict"

[devtoolsApp.ts]
../../../
../../server/registry/index.ts
../../server/utils/
../../utils/
12 changes: 6 additions & 6 deletions packages/playwright-core/src/cli/client/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import path from 'path';
import { createClientInfo, Registry, resolveSessionName } from './registry';
import { Session, renderResolvedConfig } from './session';

import type { Config } from '../../mcp/config';
import type { Config } from '../../mcp/config.d';
import type { ClientInfo, SessionFile } from './registry';

type MinimistArgs = {
Expand Down Expand Up @@ -171,7 +171,7 @@ export async function program(options?: { embedderVersion?: string}) {
await installBrowser();
return;
case 'show': {
const daemonScript = path.join(__dirname, 'devtoolsApp.js');
const daemonScript = require.resolve('../../devtools/devtoolsApp.js');
const child = spawn(process.execPath, [daemonScript], {
detached: true,
stdio: 'ignore',
Expand Down Expand Up @@ -294,7 +294,7 @@ async function killAllDaemons(): Promise<void> {
const result = execSync(
`powershell -NoProfile -NonInteractive -Command `
+ `"Get-CimInstance Win32_Process `
+ `| Where-Object { $_.CommandLine -like '*-server*' -and $_.CommandLine -like '*--daemon-dir*' } `
+ `| Where-Object { $_.CommandLine -like '*-server*' -and $_.CommandLine -like '*--daemon-*' } `
+ `| ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue; $_.ProcessId }"`,
{ encoding: 'utf-8' }
);
Expand All @@ -308,7 +308,7 @@ async function killAllDaemons(): Promise<void> {
const result = execSync('ps aux', { encoding: 'utf-8' });
const lines = result.split('\n');
for (const line of lines) {
if ((line.includes('-server')) && line.includes('--daemon-dir')) {
if ((line.includes('-server')) && line.includes('--daemon-')) {
const parts = line.trim().split(/\s+/);
const pid = parts[1];
if (pid && /^\d+$/.test(pid)) {
Expand Down Expand Up @@ -385,8 +385,8 @@ async function renderSessionStatus(clientInfo: ClientInfo, session: Session) {
text.push(` - status: ${canConnect ? 'open' : 'closed'}`);
if (canConnect && !session.isCompatible(clientInfo))
text.push(` - version: v${config.version} [incompatible please re-open]`);
if (config.resolvedConfig)
text.push(...renderResolvedConfig(config.resolvedConfig));
if (config.browser)
text.push(...renderResolvedConfig(config));
return text.join('\n');
}

Expand Down
8 changes: 6 additions & 2 deletions packages/playwright-core/src/cli/client/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import fs from 'fs';
import os from 'os';
import path from 'path';

import type { FullConfig } from '../../mcp/browser/config';
import type * as playwright from '../../..';

export type ClientInfo = {
version: string;
Expand All @@ -37,7 +37,11 @@ export type SessionConfig = {
persistent?: boolean;
};
workspaceDir?: string;
resolvedConfig?: FullConfig;
browser: {
browserName: string;
launchOptions: playwright.LaunchOptions;
userDataDir?: string;
};
};

export type SessionFile = {
Expand Down
11 changes: 5 additions & 6 deletions packages/playwright-core/src/cli/client/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import path from 'path';
import { compareSemver, SocketConnection } from './socketConnection';
import { resolveSessionName } from './registry';

import type { FullConfig } from '../../mcp/browser/config';
import type { SessionConfig, ClientInfo, SessionFile } from './registry';

type MinimistArgs = {
Expand Down Expand Up @@ -219,16 +218,16 @@ export class Session {
}
}

export function renderResolvedConfig(resolvedConfig: FullConfig) {
const channel = resolvedConfig.browser.launchOptions.channel ?? resolvedConfig.browser.browserName;
export function renderResolvedConfig(config: SessionConfig) {
const channel = config.browser.launchOptions.channel ?? config.browser.browserName;
const lines = [];
if (channel)
lines.push(` - browser-type: ${channel}`);
if (resolvedConfig.browser.isolated)
if (!config.cli.persistent)
lines.push(` - user-data-dir: <in-memory>`);
else
lines.push(` - user-data-dir: ${resolvedConfig.browser.userDataDir}`);
lines.push(` - headed: ${!resolvedConfig.browser.launchOptions.headless}`);
lines.push(` - user-data-dir: ${config.browser.userDataDir}`);
lines.push(` - headed: ${!config.browser.launchOptions.headless}`);
return lines;
}

Expand Down
4 changes: 2 additions & 2 deletions packages/playwright-core/src/cli/daemon/DEPS.list
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
[*]
../../mcp/browser/**
../../mcp/extension/**
../client/socketConnection.ts
../client/registry.ts
../../tools/
../../utilsBundle.ts
../../utils/
../../mcpBundle.ts
../../mcp/
../../server/utils/
10 changes: 0 additions & 10 deletions packages/playwright-core/src/cli/daemon/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -754,15 +754,6 @@ const tracingStop = declareCommand({
toolParams: () => ({}),
});

const tracingShow = declareCommand({
name: 'tracing-show',
description: 'Open trace viewer for the recorded trace',
category: 'devtools',
args: z.object({}),
toolName: 'browser_show_tracing',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what happened to it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be using skill instead of this as discussed previously

toolParams: () => ({}),
});

const videoStart = declareCommand({
name: 'video-start',
description: 'Start video recording',
Expand Down Expand Up @@ -974,7 +965,6 @@ const commandsArray: AnyCommandSchema[] = [
networkRequests,
tracingStart,
tracingStop,
tracingShow,
videoStart,
videoStop,
devtoolsShow,
Expand Down
32 changes: 15 additions & 17 deletions packages/playwright-core/src/cli/daemon/daemon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,24 @@ import fs from 'fs';
import net from 'net';
import os from 'os';
import path from 'path';
import url from 'url';

import { calculateSha1 } from '../../utils';
import { debug } from '../../utilsBundle';
import { decorateServer } from '../../server/utils/network';
import { gracefullyProcessExitDoNotHang } from '../../server/utils/processLauncher';

import { BrowserServerBackend } from '../../mcp/browser/browserServerBackend';
import { browserTools } from '../../mcp/browser/tools';
import { BrowserServerBackend } from '../../tools/browserServerBackend';
import { browserTools } from '../../tools/tools';
import { SocketConnection } from '../client/socketConnection';
import { commands } from './commands';
import { parseCommand } from './command';
import { createClientInfo } from '../client/registry';

import type * as playwright from '../../..';
import type * as tools from '../../tools/exports';
import type * as mcp from '../../mcp/exports';
import type { SessionConfig, ClientInfo } from '../client/registry';
import type { BrowserContext } from '../../client/browserContext';

const daemonDebug = debug('pw:daemon');

Expand All @@ -51,15 +52,15 @@ async function socketExists(socketPath: string): Promise<boolean> {
export async function startMcpDaemonServer(
sessionName: string,
browserContext: playwright.BrowserContext,
mcpConfig: mcp.FullConfig,
mcpConfig: tools.ContextConfig,
clientInfo = createClientInfo(),
persistent?: boolean,
): Promise<string> {
const sessionConfig = createSessionConfig(clientInfo, sessionName, persistent);
const sessionConfig = createSessionConfig(clientInfo, sessionName, browserContext, persistent);
const { socketPath } = sessionConfig;

// Clean up existing socket file on Unix
if (os.platform() !== 'win32' && await socketExists(socketPath)) {
if (process.platform !== 'win32' && await socketExists(socketPath)) {
daemonDebug(`Socket already exists, removing: ${socketPath}`);
try {
await fs.promises.unlink(socketPath);
Expand All @@ -70,15 +71,7 @@ export async function startMcpDaemonServer(
}

const backend = new BrowserServerBackend(mcpConfig, browserContext, browserTools);
await backend.initialize({
name: 'playwright-cli',
version: sessionConfig.version,
roots: [{
uri: url.pathToFileURL(process.cwd()).href,
name: 'cwd',
}],
timestamp: Date.now(),
});
await backend.initialize({ cwd: process.cwd() });

await fs.promises.mkdir(path.dirname(socketPath), { recursive: true });

Expand Down Expand Up @@ -129,7 +122,6 @@ export async function startMcpDaemonServer(
server.listen(socketPath, () => resolve());
});

sessionConfig.resolvedConfig = mcpConfig;
await saveSessionFile(clientInfo, sessionConfig);
return socketPath;
}
Expand Down Expand Up @@ -167,13 +159,19 @@ function daemonSocketPath(clientInfo: ClientInfo, sessionName: string): string {
return path.join(socketsDir, clientInfo.workspaceDirHash, socketName);
}

function createSessionConfig(clientInfo: ClientInfo, sessionName: string, persistent?: boolean): SessionConfig {
function createSessionConfig(clientInfo: ClientInfo, sessionName: string, browserContext: playwright.BrowserContext, persistent?: boolean): SessionConfig {
const bc = browserContext as BrowserContext;
return {
name: sessionName,
version: clientInfo.version,
timestamp: Date.now(),
socketPath: daemonSocketPath(clientInfo, sessionName),
workspaceDir: clientInfo.workspaceDir,
cli: { persistent },
browser: {
browserName: bc.browser()!.browserType().name(),
launchOptions: bc.browser()!._options,
userDataDir: bc.browser()?._userDataDir,
},
};
}
21 changes: 6 additions & 15 deletions packages/playwright-core/src/cli/daemon/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,18 @@
/* eslint-disable no-console */

import fs from 'fs';
import url from 'url';
import path from 'path';

import { startMcpDaemonServer } from './daemon';
import { setupExitWatchdog } from '../../mcp/browser/watchdog';
import { contextFactory } from '../../mcp/browser/browserContextFactory';
import { ExtensionContextFactory } from '../../mcp/extension/extensionContextFactory';
import * as configUtils from '../../mcp/browser/config';
import { setupExitWatchdog } from '../../mcp/watchdog';
import { contextFactory } from '../../mcp/browserContextFactory';
import { ExtensionContextFactory } from '../../mcp/extensionContextFactory';
import * as configUtils from '../../mcp/config';
import { gracefullyProcessExitDoNotHang } from '../../utils';
import { ClientInfo, createClientInfo } from '../client/registry';

import type { Command } from '../../utilsBundle';
import type { FullConfig } from '../../mcp/browser/config';
import type { FullConfig } from '../../mcp/config';

export function decorateCLICommand(command: Command, version: string) {
command
Expand All @@ -46,15 +45,7 @@ export function decorateCLICommand(command: Command, version: string) {
setupExitWatchdog();
const clientInfo = createClientInfo();
const mcpConfig = await resolveCLIConfig(clientInfo, sessionName, options);
const mcpClientInfo = {
name: 'playwright-cli',
version: require('../../../package.json').version,
roots: [{
uri: url.pathToFileURL(process.cwd()).href,
name: 'cwd'
}],
timestamp: Date.now(),
};
const mcpClientInfo = { cwd: process.cwd() };

try {
const extensionContextFactory = new ExtensionContextFactory(mcpConfig.browser.launchOptions.channel || 'chrome', mcpConfig.browser.userDataDir, mcpConfig.browser.launchOptions.executablePath);
Expand Down
4 changes: 3 additions & 1 deletion packages/playwright-core/src/client/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export class Browser extends ChannelOwner<channels.BrowserChannel> implements ap
_shouldCloseConnectionOnClose = false;
_browserType!: BrowserType;
_options: LaunchOptions = {};
_userDataDir: string | undefined;
readonly _name: string;
private _path: string | undefined;
_closeReason: string | undefined;
Expand Down Expand Up @@ -89,12 +90,13 @@ export class Browser extends ChannelOwner<channels.BrowserChannel> implements ap
return context;
}

_connectToBrowserType(browserType: BrowserType, browserOptions: LaunchOptions, logger: Logger | undefined) {
_connectToBrowserType(browserType: BrowserType, browserOptions: LaunchOptions, logger: Logger | undefined, userDataDir?: string) {
// Note: when using connect(), `browserType` is different from `this._parent`.
// This is why browser type is not wired up in the constructor,
// and instead this separate method is called later on.
this._browserType = browserType;
this._options = browserOptions;
this._userDataDir = userDataDir;
this._logger = logger;
for (const context of this._contexts)
this._setupBrowserContext(context);
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright-core/src/client/browserType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
const context = await this._wrapApiCall(async () => {
const result = await this._channel.launchPersistentContext(persistentParams);
const browser = Browser.from(result.browser);
browser._connectToBrowserType(this, options, logger);
browser._connectToBrowserType(this, options, logger, userDataDir);
const context = BrowserContext.from(result.context);
await context._initializeHarFromOptions(options.recordHar);
return context;
Expand Down
7 changes: 7 additions & 0 deletions packages/playwright-core/src/devtools/DEPS.list
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[*]
../../
../server/registry/index.ts
../server/utils/
../utils/
../cli/client/registry.ts
../cli/client/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,17 @@ import os from 'os';
import net from 'net';


import { chromium } from '../../..';
import { HttpServer } from '../../server/utils/httpServer';
import { gracefullyProcessExitDoNotHang } from '../../server/utils/processLauncher';
import { findChromiumChannelBestEffort, registryDirectory } from '../../server/registry/index';
import { calculateSha1 } from '../../utils';
import { createClientInfo, Registry } from './registry';
import { Session } from './session';
import { chromium } from '../..';
import { HttpServer } from '../server/utils/httpServer';
import { gracefullyProcessExitDoNotHang } from '../server/utils/processLauncher';
import { findChromiumChannelBestEffort, registryDirectory } from '../server/registry/index';
import { calculateSha1 } from '../utils';
import { createClientInfo, Registry } from '../cli/client/registry';
import { Session } from '../cli/client/session';

import type http from 'http';
import type { Page } from '../../..';
import type { ClientInfo, SessionFile } from './registry';
import type { Page } from '../../types/types';
import type { ClientInfo, SessionFile } from '../cli/client/registry';
import type { SessionStatus } from '@devtools/sessionModel';

function readBody(request: http.IncomingMessage): Promise<any> {
Expand Down
13 changes: 9 additions & 4 deletions packages/playwright-core/src/mcp/DEPS.list
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
[*]
../../
./sdk/
./browser/
./browser/tools/
./extension/
../cli/
../tools/
../utils/
../utils/isomorphic/
../utilsBundle.ts
../mcpBundle.ts
../server/
../server/registry/
../server/utils/
../client/
../cli/
Loading
Loading