diff --git a/packages/playwright/src/mcp/browser/browserContextFactory.ts b/packages/playwright/src/mcp/browser/browserContextFactory.ts index de52f1052516a..d3dc2d84dd453 100644 --- a/packages/playwright/src/mcp/browser/browserContextFactory.ts +++ b/packages/playwright/src/mcp/browser/browserContextFactory.ts @@ -24,10 +24,12 @@ import { registryDirectory } from 'playwright-core/lib/server/registry/index'; import { startTraceViewerServer } from 'playwright-core/lib/server'; import { findBrowserProcess, getBrowserExecPath } from './processUtils'; import { logUnhandledError, testDebug } from '../log'; -import { outputFile } from './config'; +import { outputFile } from './config'; +import { firstRootPath } from '../sdk/server'; import type { FullConfig } from './config'; import type { LaunchOptions } from '../../../../playwright-core/src/client/types'; +import type { ClientInfo } from '../sdk/server'; export function contextFactory(config: FullConfig): BrowserContextFactory { if (config.browser.remoteEndpoint) @@ -39,8 +41,6 @@ export function contextFactory(config: FullConfig): BrowserContextFactory { return new PersistentContextFactory(config); } -export type ClientInfo = { name?: string, version?: string, rootPath?: string }; - export interface BrowserContextFactory { createContext(clientInfo: ClientInfo, abortSignal: AbortSignal, toolName: string | undefined): Promise<{ browserContext: playwright.BrowserContext, close: () => Promise }>; } @@ -105,7 +105,7 @@ class IsolatedContextFactory extends BaseContextFactory { protected override async _doObtainBrowser(clientInfo: ClientInfo): Promise { await injectCdpPort(this.config.browser); const browserType = playwright[this.config.browser.browserName]; - const tracesDir = await outputFile(this.config, clientInfo.rootPath, `traces`); + const tracesDir = await outputFile(this.config, clientInfo, `traces`); if (this.config.saveTrace) await startTraceServer(this.config, tracesDir); return browserType.launch({ @@ -171,8 +171,8 @@ class PersistentContextFactory implements BrowserContextFactory { async createContext(clientInfo: ClientInfo): Promise<{ browserContext: playwright.BrowserContext, close: () => Promise }> { await injectCdpPort(this.config.browser); testDebug('create browser context (persistent)'); - const userDataDir = this.config.browser.userDataDir ?? await this._createUserDataDir(clientInfo.rootPath); - const tracesDir = await outputFile(this.config, clientInfo.rootPath, `traces`); + const userDataDir = this.config.browser.userDataDir ?? await this._createUserDataDir(clientInfo); + const tracesDir = await outputFile(this.config, clientInfo, `traces`); if (this.config.saveTrace) await startTraceServer(this.config, tracesDir); @@ -218,10 +218,11 @@ class PersistentContextFactory implements BrowserContextFactory { testDebug('close browser context complete (persistent)'); } - private async _createUserDataDir(rootPath: string | undefined) { + private async _createUserDataDir(clientInfo: ClientInfo) { const dir = process.env.PWMCP_PROFILES_DIR_FOR_TEST ?? registryDirectory; const browserToken = this.config.browser.launchOptions?.channel ?? this.config.browser?.browserName; // Hesitant putting hundreds of files into the user's workspace, so using it for hashing instead. + const rootPath = firstRootPath(clientInfo); const rootPathToken = rootPath ? `-${createHash(rootPath)}` : ''; const result = path.join(dir, `mcp-${browserToken}${rootPathToken}`); await fs.promises.mkdir(result, { recursive: true }); diff --git a/packages/playwright/src/mcp/browser/browserServerBackend.ts b/packages/playwright/src/mcp/browser/browserServerBackend.ts index 0bed7f22faa4d..bd7ab7fbda7be 100644 --- a/packages/playwright/src/mcp/browser/browserServerBackend.ts +++ b/packages/playwright/src/mcp/browser/browserServerBackend.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { fileURLToPath } from 'url'; import { FullConfig } from './config'; import { Context } from './context'; import { logUnhandledError } from '../log'; @@ -41,19 +40,13 @@ export class BrowserServerBackend implements ServerBackend { this._tools = filteredTools(config); } - async initialize(server: mcpServer.Server, clientVersion: mcpServer.ClientVersion, roots: mcpServer.Root[]): Promise { - let rootPath: string | undefined; - if (roots.length > 0) { - const firstRootUri = roots[0]?.uri; - const url = firstRootUri ? new URL(firstRootUri) : undefined; - rootPath = url ? fileURLToPath(url) : undefined; - } - this._sessionLog = this._config.saveSession ? await SessionLog.create(this._config, rootPath) : undefined; + async initialize(server: mcpServer.Server, clientInfo: mcpServer.ClientInfo): Promise { + this._sessionLog = this._config.saveSession ? await SessionLog.create(this._config, clientInfo) : undefined; this._context = new Context({ config: this._config, browserContextFactory: this._browserContextFactory, sessionLog: this._sessionLog, - clientInfo: { ...clientVersion, rootPath }, + clientInfo, }); } diff --git a/packages/playwright/src/mcp/browser/config.ts b/packages/playwright/src/mcp/browser/config.ts index 1f9f0aa48e191..4e7f6e9d9679b 100644 --- a/packages/playwright/src/mcp/browser/config.ts +++ b/packages/playwright/src/mcp/browser/config.ts @@ -17,11 +17,15 @@ import fs from 'fs'; import os from 'os'; import path from 'path'; + import { devices } from 'playwright-core'; import { dotenv } from 'playwright-core/lib/utilsBundle'; +import { firstRootPath } from '../sdk/server'; + import type * as playwright from '../../../types/test'; import type { Config, ToolCapability } from '../config'; +import type { ClientInfo } from '../sdk/server'; export type CLIOptions = { allowedOrigins?: string[]; @@ -265,10 +269,11 @@ async function loadConfig(configFile: string | undefined): Promise { } } -export async function outputFile(config: FullConfig, rootPath: string | undefined, name: string): Promise { +export async function outputFile(config: FullConfig, clientInfo: ClientInfo, name: string): Promise { + const rootPath = firstRootPath(clientInfo); const outputDir = config.outputDir ?? (rootPath ? path.join(rootPath, '.playwright-mcp') : undefined) - ?? path.join(os.tmpdir(), 'playwright-mcp-output', sanitizeForFilePath(new Date().toISOString())); + ?? path.join(process.env.PW_TMPDIR_FOR_TEST ?? os.tmpdir(), 'playwright-mcp-output', String(clientInfo.timestamp)); await fs.promises.mkdir(outputDir, { recursive: true }); const fileName = sanitizeForFilePath(name); diff --git a/packages/playwright/src/mcp/browser/context.ts b/packages/playwright/src/mcp/browser/context.ts index 3b9584802bb81..c353a87ee683b 100644 --- a/packages/playwright/src/mcp/browser/context.ts +++ b/packages/playwright/src/mcp/browser/context.ts @@ -23,10 +23,11 @@ import * as codegen from './codegen'; import type * as playwright from '../../../types/test'; import type { FullConfig } from './config'; -import type { BrowserContextFactory, ClientInfo } from './browserContextFactory'; +import type { BrowserContextFactory } from './browserContextFactory'; import type * as actions from './actions'; import type { SessionLog } from './sessionLog'; import type { Tracing } from '../../../../playwright-core/src/client/tracing'; +import type { ClientInfo } from '../sdk/server'; const testDebug = debug('pw:mcp:test'); @@ -113,7 +114,7 @@ export class Context { } async outputFile(name: string): Promise { - return outputFile(this.config, this._clientInfo.rootPath, name); + return outputFile(this.config, this._clientInfo, name); } private _onPageCreated(page: playwright.Page) { diff --git a/packages/playwright/src/mcp/browser/sessionLog.ts b/packages/playwright/src/mcp/browser/sessionLog.ts index 7e80087454eb5..4b39ea4c76388 100644 --- a/packages/playwright/src/mcp/browser/sessionLog.ts +++ b/packages/playwright/src/mcp/browser/sessionLog.ts @@ -24,6 +24,7 @@ import { outputFile } from './config'; import type { FullConfig } from './config'; import type * as actions from './actions'; import type { Tab, TabSnapshot } from './tab'; +import type * as mcpServer from '../sdk/server'; type LogEntry = { timestamp: number; @@ -51,8 +52,8 @@ export class SessionLog { this._file = path.join(this._folder, 'session.md'); } - static async create(config: FullConfig, rootPath: string | undefined): Promise { - const sessionFolder = await outputFile(config, rootPath, `session-${Date.now()}`); + static async create(config: FullConfig, clientInfo: mcpServer.ClientInfo): Promise { + const sessionFolder = await outputFile(config, clientInfo, `session-${Date.now()}`); await fs.promises.mkdir(sessionFolder, { recursive: true }); // eslint-disable-next-line no-console console.error(`Session: ${sessionFolder}`); diff --git a/packages/playwright/src/mcp/extension/cdpRelay.ts b/packages/playwright/src/mcp/extension/cdpRelay.ts index 37284a0b966e0..b2258d70efe63 100644 --- a/packages/playwright/src/mcp/extension/cdpRelay.ts +++ b/packages/playwright/src/mcp/extension/cdpRelay.ts @@ -34,7 +34,7 @@ import { logUnhandledError } from '../log'; import * as protocol from './protocol'; import type websocket from 'ws'; -import type { ClientInfo } from '../browser/browserContextFactory'; +import type { ClientInfo } from '../sdk/server'; import type { ExtensionCommand, ExtensionEvents } from './protocol'; import type { WebSocket, WebSocketServer } from 'playwright-core/lib/utilsBundle'; diff --git a/packages/playwright/src/mcp/extension/extensionContextFactory.ts b/packages/playwright/src/mcp/extension/extensionContextFactory.ts index ed7a21c9b16c8..c59cff05c9376 100644 --- a/packages/playwright/src/mcp/extension/extensionContextFactory.ts +++ b/packages/playwright/src/mcp/extension/extensionContextFactory.ts @@ -20,7 +20,8 @@ import { debug } from 'playwright-core/lib/utilsBundle'; import { startHttpServer } from '../sdk/http'; import { CDPRelayServer } from './cdpRelay'; -import type { BrowserContextFactory, ClientInfo } from '../browser/browserContextFactory'; +import type { BrowserContextFactory } from '../browser/browserContextFactory'; +import type { ClientInfo } from '../sdk/server'; const debugLogger = debug('pw:mcp:relay'); diff --git a/packages/playwright/src/mcp/sdk/mdb.ts b/packages/playwright/src/mcp/sdk/mdb.ts index b979a90416206..e8b64eb85705f 100644 --- a/packages/playwright/src/mcp/sdk/mdb.ts +++ b/packages/playwright/src/mcp/sdk/mdb.ts @@ -34,15 +34,15 @@ export class MDBBackend implements mcpServer.ServerBackend { private _stack: { client: Client, toolNames: string[], resultPromise: ManualPromise | undefined }[] = []; private _interruptPromise: ManualPromise | undefined; private _topLevelBackend: mcpServer.ServerBackend; - private _roots: mcpServer.Root[] | undefined; + private _clientInfo: mcpServer.ClientInfo | undefined; constructor(topLevelBackend: mcpServer.ServerBackend) { this._topLevelBackend = topLevelBackend; } - async initialize(server: mcpServer.Server, clientVersion: mcpServer.ClientVersion, roots: mcpServer.Root[]): Promise { - if (!this._roots) - this._roots = roots; + async initialize(server: mcpServer.Server, clientInfo: mcpServer.ClientInfo): Promise { + if (!this._clientInfo) + this._clientInfo = clientInfo; } async listTools(): Promise { @@ -107,8 +107,8 @@ export class MDBBackend implements mcpServer.ServerBackend { private async _pushClient(transport: Transport, introMessage?: string): Promise { mdbDebug('pushing client to the stack'); - const client = new mcpBundle.Client({ name: 'Internal client', version: '0.0.0' }, { capabilities: { roots: {} } }); - client.setRequestHandler(mcpBundle.ListRootsRequestSchema, () => ({ roots: this._roots || [] })); + const client = new mcpBundle.Client({ name: 'Pushing client', version: '0.0.0' }, { capabilities: { roots: {} } }); + client.setRequestHandler(mcpBundle.ListRootsRequestSchema, () => ({ roots: this._clientInfo?.roots || [] })); client.setRequestHandler(mcpBundle.PingRequestSchema, () => ({})); await client.connect(transport); mdbDebug('connected to the new client'); @@ -169,7 +169,7 @@ export async function runOnPauseBackendLoop(backend: mcpServer.ServerBackend, in await mcpHttp.installHttpTransport(httpServer, factory); const url = mcpHttp.httpAddressToString(httpServer.address()); - const client = new mcpBundle.Client({ name: 'Internal client', version: '0.0.0' }); + const client = new mcpBundle.Client({ name: 'On-pause client', version: '0.0.0' }); client.setRequestHandler(mcpBundle.PingRequestSchema, () => ({})); const transport = new mcpBundle.StreamableHTTPClientTransport(new URL(process.env.PLAYWRIGHT_DEBUGGER_MCP!)); await client.connect(transport); @@ -205,8 +205,8 @@ class ServerBackendWithCloseListener implements mcpServer.ServerBackend { this._backend = backend; } - async initialize(server: mcpServer.Server, clientVersion: mcpServer.ClientVersion, roots: mcpServer.Root[]): Promise { - await this._backend.initialize?.(server, clientVersion, roots); + async initialize(server: mcpServer.Server, clientInfo: mcpServer.ClientInfo): Promise { + await this._backend.initialize?.(server, clientInfo); } async listTools(): Promise { diff --git a/packages/playwright/src/mcp/sdk/proxyBackend.ts b/packages/playwright/src/mcp/sdk/proxyBackend.ts index c80b30062f57f..c55e5f97d6768 100644 --- a/packages/playwright/src/mcp/sdk/proxyBackend.ts +++ b/packages/playwright/src/mcp/sdk/proxyBackend.ts @@ -18,7 +18,7 @@ import { debug } from 'playwright-core/lib/utilsBundle'; import * as mcpBundle from './bundle'; -import type { ServerBackend, ClientVersion, Root, Server } from './server'; +import type { ServerBackend, ClientInfo, Server } from './server'; import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; import type { Tool, CallToolResult, CallToolRequest } from '@modelcontextprotocol/sdk/types.js'; import type { Client } from '@modelcontextprotocol/sdk/client/index.js'; @@ -36,15 +36,15 @@ export class ProxyBackend implements ServerBackend { private _mcpProviders: MCPProvider[]; private _currentClient: Client | undefined; private _contextSwitchTool: Tool; - private _roots: Root[] = []; + private _clientInfo: ClientInfo | undefined; constructor(mcpProviders: MCPProvider[]) { this._mcpProviders = mcpProviders; this._contextSwitchTool = this._defineContextSwitchTool(); } - async initialize(server: Server, clientVersion: ClientVersion, roots: Root[]): Promise { - this._roots = roots; + async initialize(server: Server, clientInfo: ClientInfo): Promise { + this._clientInfo = clientInfo; } async listTools(): Promise { @@ -124,7 +124,7 @@ export class ProxyBackend implements ServerBackend { listRoots: true, }, }); - client.setRequestHandler(mcpBundle.ListRootsRequestSchema, () => ({ roots: this._roots })); + client.setRequestHandler(mcpBundle.ListRootsRequestSchema, () => ({ roots: this._clientInfo?.roots || [] })); client.setRequestHandler(mcpBundle.PingRequestSchema, () => ({})); const transport = await factory.connect(); diff --git a/packages/playwright/src/mcp/sdk/server.ts b/packages/playwright/src/mcp/sdk/server.ts index 8e54afd91c266..81e2f40e83343 100644 --- a/packages/playwright/src/mcp/sdk/server.ts +++ b/packages/playwright/src/mcp/sdk/server.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import { fileURLToPath } from 'url'; + import { debug } from 'playwright-core/lib/utilsBundle'; import * as mcpBundle from './bundle'; @@ -28,10 +30,15 @@ import type { Server } from '@modelcontextprotocol/sdk/server/index.js'; const serverDebug = debug('pw:mcp:server'); -export type ClientVersion = { name: string, version: string }; +export type ClientInfo = { + name: string; + version: string; + roots: Root[]; + timestamp: number; +}; export interface ServerBackend { - initialize?(server: Server, clientVersion: ClientVersion, roots: Root[]): Promise; + initialize?(server: Server, clientInfo: ClientInfo): Promise; listTools(): Promise; callTool(name: string, args: CallToolRequest['params']['arguments']): Promise; serverClosed?(server: Server): void; @@ -93,8 +100,15 @@ const initializeServer = async (server: Server, backend: ServerBackend, runHeart const { roots } = await server.listRoots(); clientRoots = roots; } - const clientVersion = server.getClientVersion() ?? { name: 'unknown', version: 'unknown' }; - await backend.initialize?.(server, clientVersion, clientRoots); + + const clientInfo: ClientInfo = { + name: server.getClientVersion()?.name ?? 'unknown', + version: server.getClientVersion()?.version ?? 'unknown', + roots: clientRoots, + timestamp: Date.now(), + }; + + await backend.initialize?.(server, clientInfo); if (runHeartbeat) startHeartbeat(server); }; @@ -145,3 +159,11 @@ export async function start(serverBackendFactory: ServerBackendFactory, options: // eslint-disable-next-line no-console console.error(message); } + +export function firstRootPath(clientInfo: ClientInfo): string | undefined { + if (clientInfo.roots.length === 0) + return undefined; + const firstRootUri = clientInfo.roots[0]?.uri; + const url = firstRootUri ? new URL(firstRootUri) : undefined; + return url ? fileURLToPath(url) : undefined; +} diff --git a/packages/playwright/src/mcp/test/browserBackend.ts b/packages/playwright/src/mcp/test/browserBackend.ts index 00329fc0785a5..38d2a7e22b624 100644 --- a/packages/playwright/src/mcp/test/browserBackend.ts +++ b/packages/playwright/src/mcp/test/browserBackend.ts @@ -22,8 +22,8 @@ import { BrowserServerBackend } from '../browser/browserServerBackend'; import type * as playwright from '../../../index'; import type { Page } from '../../../../playwright-core/src/client/page'; -import type { BrowserContextFactory, ClientInfo } from '../browser/browserContextFactory'; - +import type { BrowserContextFactory } from '../browser/browserContextFactory'; +import type { ClientInfo } from '../sdk/server'; export async function runBrowserBackendOnError(page: playwright.Page, message: () => string) { const testInfo = currentTestInfo(); diff --git a/packages/playwright/src/mcp/test/testBackend.ts b/packages/playwright/src/mcp/test/testBackend.ts index 40d716fb34584..eb1255a624b0a 100644 --- a/packages/playwright/src/mcp/test/testBackend.ts +++ b/packages/playwright/src/mcp/test/testBackend.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -import { fileURLToPath } from 'url'; - import * as mcp from '../sdk/exports'; import { TestContext } from './testContext'; import { listTests, runTests, debugTest, setupPage } from './testTools.js'; @@ -36,20 +34,16 @@ export class TestServerBackend implements mcp.ServerBackend { this._configOption = configOption; } - async initialize(server: mcp.Server, clientVersion: mcp.ClientVersion, roots: mcp.Root[]): Promise { + async initialize(server: mcp.Server, clientInfo: mcp.ClientInfo): Promise { if (this._configOption) { this._context.setConfigLocation(resolveConfigLocation(this._configOption)); return; } - if (roots.length > 0) { - const firstRootUri = roots[0]?.uri; - const url = firstRootUri ? new URL(firstRootUri) : undefined; - const folder = url ? fileURLToPath(url) : undefined; - if (folder) { - this._context.setConfigLocation(resolveConfigLocation(folder)); - return; - } + const rootPath = mcp.firstRootPath(clientInfo); + if (rootPath) { + this._context.setConfigLocation(resolveConfigLocation(rootPath)); + return; } throw new Error('No config option or MCP root path provided'); diff --git a/packages/playwright/src/mcp/vscode/host.ts b/packages/playwright/src/mcp/vscode/host.ts index 15332a32c6226..e3c0a8916d144 100644 --- a/packages/playwright/src/mcp/vscode/host.ts +++ b/packages/playwright/src/mcp/vscode/host.ts @@ -26,8 +26,8 @@ import { contextFactory } from '../browser/browserContextFactory'; import type { z as zod } from 'zod'; import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; -import type { ClientVersion, ServerBackend } from '../sdk/server'; -import type { Root, Tool, CallToolResult, CallToolRequest } from '@modelcontextprotocol/sdk/types.js'; +import type { ClientInfo, ServerBackend } from '../sdk/server'; +import type { Tool, CallToolResult, CallToolRequest } from '@modelcontextprotocol/sdk/types.js'; import type { Browser, BrowserContext, BrowserServer } from 'playwright'; import type { Client } from '@modelcontextprotocol/sdk/client/index.js'; @@ -47,8 +47,7 @@ class VSCodeProxyBackend implements ServerBackend { private _currentClient: Client | undefined; private _contextSwitchTool: Tool; - private _roots: Root[] = []; - private _clientVersion?: ClientVersion; + private _clientInfo?: ClientInfo; private _context?: BrowserContext; private _browser?: Browser; private _browserServer?: BrowserServer; @@ -57,9 +56,8 @@ class VSCodeProxyBackend implements ServerBackend { this._contextSwitchTool = this._defineContextSwitchTool(); } - async initialize(server: mcpServer.Server, clientVersion: ClientVersion, roots: Root[]): Promise { - this._clientVersion = clientVersion; - this._roots = roots; + async initialize(server: mcpServer.Server, clientInfo: ClientInfo): Promise { + this._clientInfo = clientInfo; const transport = await this._defaultTransportFactory(this); await this._setCurrentClient(transport); } @@ -166,13 +164,13 @@ class VSCodeProxyBackend implements ServerBackend { await this._currentClient?.close(); this._currentClient = undefined; - const client = new mcpBundle.Client(this._clientVersion!); + const client = new mcpBundle.Client({ name: this._clientInfo!.name, version: this._clientInfo!.version }); client.registerCapabilities({ roots: { listRoots: true, }, }); - client.setRequestHandler(mcpBundle.ListRootsRequestSchema, () => ({ roots: this._roots })); + client.setRequestHandler(mcpBundle.ListRootsRequestSchema, () => ({ roots: this._clientInfo!.roots })); client.setRequestHandler(mcpBundle.PingRequestSchema, () => ({})); await client.connect(transport); diff --git a/packages/playwright/src/mcp/vscode/main.ts b/packages/playwright/src/mcp/vscode/main.ts index 5eb46569a3a0b..67ca6a3ac8e61 100644 --- a/packages/playwright/src/mcp/vscode/main.ts +++ b/packages/playwright/src/mcp/vscode/main.ts @@ -17,10 +17,11 @@ import * as mcpBundle from '../sdk/bundle'; import * as mcpServer from '../sdk/server'; import { BrowserServerBackend } from '../browser/browserServerBackend'; -import { BrowserContextFactory, ClientInfo } from '../browser/browserContextFactory'; +import { BrowserContextFactory } from '../browser/browserContextFactory'; import type { FullConfig } from '../browser/config'; import type { BrowserContext } from 'playwright-core'; +import type { ClientInfo } from '../sdk/server'; class VSCodeBrowserContextFactory implements BrowserContextFactory { name = 'vscode'; diff --git a/tests/mcp/fixtures.ts b/tests/mcp/fixtures.ts index 49f6cd6632d80..f08d23b086f89 100644 --- a/tests/mcp/fixtures.ts +++ b/tests/mcp/fixtures.ts @@ -48,6 +48,7 @@ export type StartClient = (options?: { config?: Config, roots?: { name: string, uri: string }[], rootsResponseDelay?: number, + env?: NodeJS.ProcessEnv, }) => Promise<{ client: Client, stderr: () => string }>; @@ -112,7 +113,8 @@ export const test = serverTest.extend { if (process.env.PWMCP_DEBUG) @@ -176,7 +178,7 @@ export const test = serverTest.extend { @@ -187,7 +189,7 @@ async function createTransport(mcpServerType: TestOptions['mcpServerType'], args cwd: test.info().outputPath(), stderr: 'pipe', env: { - ...process.env, + ...env, DEBUG: process.env.DEBUG ? `${process.env.DEBUG},pw:mcp:test` : 'pw:mcp:test', DEBUG_COLORS: '0', DEBUG_HIDE_DATE: '1', diff --git a/tests/mcp/mdb.spec.ts b/tests/mcp/mdb.spec.ts index 4ec782eb8a4ef..43d77661adbb2 100644 --- a/tests/mcp/mdb.spec.ts +++ b/tests/mcp/mdb.spec.ts @@ -139,7 +139,7 @@ async function startMDBAndCLI(): Promise<{ mdbUrl: string }> { } async function createMDBClient(mdbUrl: string, roots: any[] | undefined = undefined): Promise<{ client: Client, close: () => Promise }> { - const client = new Client({ name: 'Internal client', version: '0.0.0' }, roots ? { capabilities: { roots: {} } } : undefined); + const client = new Client({ name: 'Test client', version: '0.0.0' }, roots ? { capabilities: { roots: {} } } : undefined); if (roots) client.setRequestHandler(mcpBundle.ListRootsRequestSchema, () => ({ roots })); const transport = new StreamableHTTPClientTransport(new URL(mdbUrl)); @@ -158,8 +158,8 @@ class CLIBackend { constructor(private readonly mdbUrlBox: { mdbUrl: string | undefined }) {} - async initialize(server, clientVersion, roots) { - this._roots = roots; + async initialize(server, clientInfo) { + this._roots = clientInfo.roots; } async listTools() { @@ -197,8 +197,8 @@ class CLIBackend { class GDBBackend { private _roots: any[] | undefined; - async initialize(server, clientVersion, roots) { - this._roots = roots; + async initialize(server, clientVersion) { + this._roots = clientVersion.roots; } async listTools() { diff --git a/tests/mcp/tracing.spec.ts b/tests/mcp/tracing.spec.ts index 9a1d88a4ca60d..c790aa926c9bd 100644 --- a/tests/mcp/tracing.spec.ts +++ b/tests/mcp/tracing.spec.ts @@ -67,3 +67,41 @@ test('check that trace is saved with browser_start_tracing', async ({ startClien expect.stringMatching(/trace-\d+\.trace/), ]); }); + +test('check that trace is saved with browser_start_tracing (no output dir)', async ({ startClient, server }, testInfo) => { + const outputDir = testInfo.outputPath(); + + const { client } = await startClient({ + args: ['--caps=tracing'], + env: { ...process.env, PW_TMPDIR_FOR_TEST: outputDir }, + }); + + expect(await client.callTool({ + name: 'browser_start_tracing', + })).toHaveResponse({ + result: expect.stringContaining(`Tracing started, saving to ${outputDir}`), + }); + + expect(await client.callTool({ + name: 'browser_navigate', + arguments: { url: server.HELLO_WORLD }, + })).toHaveResponse({ + code: expect.stringContaining(`page.goto('http://localhost`), + }); + + expect(await client.callTool({ + name: 'browser_stop_tracing', + })).toHaveResponse({ + result: expect.stringMatching(/trace-\d+.trace/) + }); + + const folders = await fs.promises.readdir(path.join(outputDir, 'playwright-mcp-output')); + expect(folders.length).toBe(1); + expect(folders[0]).toMatch(/\d+/); + const files = await fs.promises.readdir(path.join(outputDir, 'playwright-mcp-output', folders[0], 'traces')); + expect(files).toEqual([ + 'resources', + expect.stringMatching(/trace-\d+\.network/), + expect.stringMatching(/trace-\d+\.trace/), + ]); +});