diff --git a/src/browserContextFactory.ts b/src/browserContextFactory.ts index 2d3fcff..9646bc5 100644 --- a/src/browserContextFactory.ts +++ b/src/browserContextFactory.ts @@ -21,6 +21,8 @@ import path from 'path'; import * as playwright from 'playwright'; // @ts-ignore import { registryDirectory } from 'playwright-core/lib/server/registry/index'; +// @ts-ignore +import { startTraceViewerServer } from 'playwright-core/lib/server'; import { logUnhandledError, testDebug } from './log.js'; import { createHash } from './utils.js'; import { outputFile } from './config.js'; @@ -50,7 +52,6 @@ class BaseContextFactory implements BrowserContextFactory { readonly description: string; readonly config: FullConfig; protected _browserPromise: Promise | undefined; - protected _tracesDir: string | undefined; constructor(name: string, description: string, config: FullConfig) { this.name = name; @@ -58,11 +59,11 @@ class BaseContextFactory implements BrowserContextFactory { this.config = config; } - protected async _obtainBrowser(): Promise { + protected async _obtainBrowser(clientInfo: ClientInfo): Promise { if (this._browserPromise) return this._browserPromise; testDebug(`obtain browser (${this.name})`); - this._browserPromise = this._doObtainBrowser(); + this._browserPromise = this._doObtainBrowser(clientInfo); void this._browserPromise.then(browser => { browser.on('disconnected', () => { this._browserPromise = undefined; @@ -73,16 +74,13 @@ class BaseContextFactory implements BrowserContextFactory { return this._browserPromise; } - protected async _doObtainBrowser(): Promise { + protected async _doObtainBrowser(clientInfo: ClientInfo): Promise { throw new Error('Not implemented'); } async createContext(clientInfo: ClientInfo): Promise<{ browserContext: playwright.BrowserContext, close: () => Promise }> { - if (this.config.saveTrace) - this._tracesDir = await outputFile(this.config, clientInfo.rootPath, `traces-${Date.now()}`); - testDebug(`create browser context (${this.name})`); - const browser = await this._obtainBrowser(); + const browser = await this._obtainBrowser(clientInfo); const browserContext = await this._doCreateContext(browser); return { browserContext, close: () => this._closeBrowserContext(browserContext, browser) }; } @@ -108,11 +106,11 @@ class IsolatedContextFactory extends BaseContextFactory { super('isolated', 'Create a new isolated browser context', config); } - protected override async _doObtainBrowser(): Promise { + protected override async _doObtainBrowser(clientInfo: ClientInfo): Promise { await injectCdpPort(this.config.browser); const browserType = playwright[this.config.browser.browserName]; return browserType.launch({ - tracesDir: this._tracesDir, + tracesDir: await startTraceServer(this.config, clientInfo.rootPath), ...this.config.browser.launchOptions, handleSIGINT: false, handleSIGTERM: false, @@ -175,9 +173,7 @@ class PersistentContextFactory implements BrowserContextFactory { await injectCdpPort(this.config.browser); testDebug('create browser context (persistent)'); const userDataDir = this.config.browser.userDataDir ?? await this._createUserDataDir(clientInfo.rootPath); - let tracesDir: string | undefined; - if (this.config.saveTrace) - tracesDir = await outputFile(this.config, clientInfo.rootPath, `traces-${Date.now()}`); + const tracesDir = await startTraceServer(this.config, clientInfo.rootPath); this._userDataDirs.add(userDataDir); testDebug('lock user data dir', userDataDir); @@ -242,3 +238,16 @@ async function findFreePort(): Promise { server.on('error', reject); }); } + +async function startTraceServer(config: FullConfig, rootPath: string | undefined): Promise { + if (!config.saveTrace) + return undefined; + + const tracesDir = await outputFile(config, rootPath, `traces-${Date.now()}`); + const server = await startTraceViewerServer(); + const urlPrefix = server.urlPrefix('human-readable'); + const url = urlPrefix + '/trace/index.html?trace=' + tracesDir + '/trace.json'; + // eslint-disable-next-line no-console + console.error('\nTrace viewer listening on ' + url); + return tracesDir; +} diff --git a/src/mcp/server.ts b/src/mcp/server.ts index 065298e..6f06d82 100644 --- a/src/mcp/server.ts +++ b/src/mcp/server.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import debug from 'debug'; import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import { ManualPromise } from '../manualPromise.js'; @@ -23,6 +24,8 @@ import type { ImageContent, TextContent, Tool } from '@modelcontextprotocol/sdk/ import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; export type { Server } from '@modelcontextprotocol/sdk/server/index.js'; +const serverDebug = debug('pw:mcp:server'); + export type ClientCapabilities = { roots?: { listRoots?: boolean @@ -62,12 +65,14 @@ export function createServer(backend: ServerBackend, runHeartbeat: boolean): Ser }); server.setRequestHandler(ListToolsRequestSchema, async () => { + serverDebug('listTools'); const tools = backend.tools(); return { tools }; }); let heartbeatRunning = false; server.setRequestHandler(CallToolRequestSchema, async request => { + serverDebug('callTool', request); await initializedPromise; if (runHeartbeat && !heartbeatRunning) { diff --git a/src/program.ts b/src/program.ts index 24ebbae..13d9e32 100644 --- a/src/program.ts +++ b/src/program.ts @@ -15,8 +15,6 @@ */ import { program, Option } from 'commander'; -// @ts-ignore -import { startTraceViewerServer } from 'playwright-core/lib/server'; import * as mcpTransport from './mcp/transport.js'; import { commaSeparatedList, resolveCLIConfig, semicolonSeparatedList } from './config.js'; @@ -95,14 +93,6 @@ program serverBackendFactory = () => new BrowserServerBackend(config, browserContextFactory); } await mcpTransport.start(serverBackendFactory, config.server); - - if (config.saveTrace) { - const server = await startTraceViewerServer(); - const urlPrefix = server.urlPrefix('human-readable'); - const url = urlPrefix + '/trace/index.html?trace=' + config.browser.launchOptions.tracesDir + '/trace.json'; - // eslint-disable-next-line no-console - console.error('\nTrace viewer listening on ' + url); - } }); function setupExitWatchdog() {