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
9 changes: 0 additions & 9 deletions packages/playwright-core/src/server/agent/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,15 +150,6 @@ export class Context {
text.push(`# Page snapshot\n${snapshot}`);

return {
_meta: {
'dev.lowire/state': {
'Page snapshot': snapshot
},
'dev.lowire/history': error ? [{
category: 'error',
content: stripAnsiEscapes(error.message),
}] : [],
},
isError: !!error,
content: [{ type: 'text', text: text.join('\n\n') }],
};
Expand Down
172 changes: 0 additions & 172 deletions packages/playwright/src/mcp/browser/actions.d.ts

This file was deleted.

4 changes: 2 additions & 2 deletions packages/playwright/src/mcp/browser/browserContextFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,13 +350,13 @@ export class SharedContextFactory implements BrowserContextFactory {
async function computeTracesDir(config: FullConfig, clientInfo: ClientInfo): Promise<string | undefined> {
if (!config.saveTrace && !config.capabilities?.includes('tracing'))
return;
return await outputFile(config, clientInfo, `traces`, { origin: 'code', reason: 'Collecting trace' });
return await outputFile(config, clientInfo, `traces`, { origin: 'code', title: 'Collecting trace' });
}

async function browserContextOptionsFromConfig(config: FullConfig, clientInfo: ClientInfo): Promise<playwright.BrowserContextOptions> {
const result = { ...config.browser.contextOptions };
if (config.saveVideo) {
const dir = await outputFile(config, clientInfo, `videos`, { origin: 'code', reason: 'Saving video' });
const dir = await outputFile(config, clientInfo, `videos`, { origin: 'code', title: 'Saving video' });
result.recordVideo = {
dir,
size: config.saveVideo,
Expand Down
25 changes: 15 additions & 10 deletions packages/playwright/src/mcp/browser/browserServerBackend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,25 +56,30 @@ export class BrowserServerBackend implements ServerBackend {

async callTool(name: string, rawArguments: mcpServer.CallToolRequest['params']['arguments']) {
const tool = this._tools.find(tool => tool.schema.name === name)!;
if (!tool)
throw new Error(`Tool "${name}" not found`);
if (!tool) {
return {
content: [{ type: 'text' as const, text: `### Error\nTool "${name}" not found` }],
isError: true,
};
}
const parsedArguments = tool.schema.inputSchema.parse(rawArguments || {}) as any;
const context = this._context!;
const response = new Response(context, name, parsedArguments);
response.logBegin();
const response = Response.create(context, name, parsedArguments);
context.setRunningTool(name);
let responseObject: mcpServer.CallToolResult;
try {
await tool.handle(context, parsedArguments, response);
await response.finish();
this._sessionLog?.logResponse(response);
responseObject = await response.build();
this._sessionLog?.logResponse(name, parsedArguments, responseObject);
} catch (error: any) {
response.addError(String(error));
return {
content: [{ type: 'text' as const, text: `### Error\n${String(error)}` }],
isError: true,
};
} finally {
context.setRunningTool(undefined);
}
response.logEnd();
const _meta = rawArguments?._meta as object | undefined;
return response.serialize({ _meta });
return responseObject;
}

serverClosed() {
Expand Down
8 changes: 6 additions & 2 deletions packages/playwright/src/mcp/browser/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export type CLIOptions = {
imageResponses?: 'allow' | 'omit';
sandbox?: boolean;
outputDir?: string;
outputMode?: 'file' | 'stdout';
port?: number;
proxyBypass?: string;
proxyServer?: string;
Expand Down Expand Up @@ -95,6 +96,7 @@ export const defaultConfig: FullConfig = {
saveTrace: false,
snapshot: {
mode: 'incremental',
output: 'stdout',
},
timeouts: {
action: 5000,
Expand All @@ -118,6 +120,7 @@ export type FullConfig = Config & {
server: NonNullable<Config['server']>,
snapshot: {
mode: 'incremental' | 'full' | 'none';
output: 'stdout' | 'file';
},
timeouts: {
action: number;
Expand Down Expand Up @@ -257,6 +260,7 @@ export function configFromCLIOptions(cliOptions: CLIOptions): Config {
secrets: cliOptions.secrets,
sharedBrowserContext: cliOptions.sharedBrowserContext,
snapshot: cliOptions.snapshotMode ? { mode: cliOptions.snapshotMode } : undefined,
outputMode: cliOptions.outputMode,
outputDir: cliOptions.outputDir,
imageResponses: cliOptions.imageResponses,
testIdAttribute: cliOptions.testIdAttribute,
Expand Down Expand Up @@ -338,10 +342,10 @@ export function outputDir(config: FullConfig, clientInfo: ClientInfo): string {
?? path.join(tmpDir(), String(clientInfo.timestamp));
}

export async function outputFile(config: FullConfig, clientInfo: ClientInfo, fileName: string, options: { origin: 'code' | 'llm' | 'web', reason: string }): Promise<string> {
export async function outputFile(config: FullConfig, clientInfo: ClientInfo, fileName: string, options: { origin: 'code' | 'llm' | 'web', title: string }): Promise<string> {
const file = await resolveFile(config, clientInfo, fileName, options);
await fs.promises.mkdir(path.dirname(file), { recursive: true });
debug('pw:mcp:file')(options.reason, file);
debug('pw:mcp:file')(options.title, file);
return file;
}

Expand Down
62 changes: 5 additions & 57 deletions packages/playwright/src/mcp/browser/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import { outputFile } from './config';
import type * as playwright from '../../../types/test';
import type { FullConfig } from './config';
import type { BrowserContextFactory, BrowserContextFactoryResult } 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';
Expand Down Expand Up @@ -116,7 +115,7 @@ export class Context {
return url;
}

async outputFile(fileName: string, options: { origin: 'code' | 'llm' | 'web', reason: string }): Promise<string> {
async outputFile(fileName: string, options: { origin: 'code' | 'llm' | 'web', title: string }): Promise<string> {
return outputFile(this.config, this._clientInfo, fileName, options);
}

Expand Down Expand Up @@ -219,8 +218,6 @@ export class Context {
(browserContext as any)._setAllowedDirectories(allRootPaths(this._clientInfo));
}
await this._setupRequestInterception(browserContext);
if (this.sessionLog)
await InputRecorder.create(this, browserContext);
for (const page of browserContext.pages())
this._onPageCreated(page);
browserContext.on('page', page => this._onPageCreated(page));
Expand All @@ -243,6 +240,10 @@ export class Context {
code: `process.env['${secretName}']`,
};
}

firstRootPath(): string | undefined {
return allRootPaths(this._clientInfo)[0];
}
}

function allRootPaths(clientInfo: ClientInfo): string[] {
Expand Down Expand Up @@ -278,56 +279,3 @@ function originOrHostGlob(originOrHost: string) {
// Support for legacy host-only mode.
return `*://${originOrHost}/**`;
}

export class InputRecorder {
private _context: Context;
private _browserContext: playwright.BrowserContext;

private constructor(context: Context, browserContext: playwright.BrowserContext) {
this._context = context;
this._browserContext = browserContext;
}

static async create(context: Context, browserContext: playwright.BrowserContext) {
const recorder = new InputRecorder(context, browserContext);
await recorder._initialize();
return recorder;
}

private async _initialize() {
const sessionLog = this._context.sessionLog!;
await (this._browserContext as any)._enableRecorder({
mode: 'recording',
recorderMode: 'api',
}, {
actionAdded: (page: playwright.Page, data: actions.ActionInContext, code: string) => {
if (this._context.isRunningTool())
return;
const tab = Tab.forPage(page);
if (tab)
sessionLog.logUserAction(data.action, tab, code, false);
},
actionUpdated: (page: playwright.Page, data: actions.ActionInContext, code: string) => {
if (this._context.isRunningTool())
return;
const tab = Tab.forPage(page);
if (tab)
sessionLog.logUserAction(data.action, tab, code, true);
},
signalAdded: (page: playwright.Page, data: actions.SignalInContext) => {
if (this._context.isRunningTool())
return;
if (data.signal.name !== 'navigation')
return;
const tab = Tab.forPage(page);
const navigateAction: actions.Action = {
name: 'navigate',
url: data.signal.url,
signals: [],
};
if (tab)
sessionLog.logUserAction(navigateAction, tab, `await page.goto('${data.signal.url}');`, false);
},
});
}
}
Loading
Loading