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
2 changes: 1 addition & 1 deletion packages/playwright-core/src/tools/backend/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export type ContextConfig = {
capabilities?: ToolCapability[];
codegen?: 'typescript' | 'python' | 'java' | 'csharp' | 'none';
console?: { level?: 'error' | 'warning' | 'info' | 'debug' };
imageResponses?: 'allow' | 'omit';
imageResponses?: 'allow' | 'omit' | 'only';
network?: {
allowedOrigins?: string[];
blockedOrigins?: string[];
Expand Down
21 changes: 8 additions & 13 deletions packages/playwright-core/src/tools/backend/response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,10 +171,10 @@ export class Response {
const rawSections = ['Error', 'Result', 'Snapshot'] as const;
const sections = this._raw ? allSections.filter(section => rawSections.includes(section.title as typeof rawSections[number])) : allSections;

const isError = sections.some(section => section.isError);
let serializedText: string;
if (this._json) {
const payload: Record<string, unknown> = {};
const isError = sections.some(section => section.isError);
if (isError)
payload.isError = true;
for (const section of sections) {
Expand Down Expand Up @@ -214,23 +214,18 @@ export class Response {
serializedText = text.join('\n');
}

const content: (TextContent | ImageContent)[] = [
{
type: 'text',
text: sanitizeUnicode(this._context.redactSecrets(serializedText)),
}
const imageResponses = this._context.config.imageResponses ?? 'allow';
const images: ImageContent[] = imageResponses === 'omit' ? [] : this._imageResults.map(imageResult => ({ type: 'image', data: imageResult.data.toString('base64'), mimeType: `image/${imageResult.imageType}` }));
const imagesOnly = imageResponses === 'only' && images.length > 0 && !isError;
const content: (TextContent | ImageContent)[] = imagesOnly ? images : [
Comment thread
yury-s marked this conversation as resolved.
{ type: 'text', text: sanitizeUnicode(this._context.redactSecrets(serializedText)) },
...images,
];

// Image attachments.
if (this._context.config.imageResponses !== 'omit') {
for (const imageResult of this._imageResults)
content.push({ type: 'image', data: imageResult.data.toString('base64'), mimeType: `image/${imageResult.imageType}` });
}

return {
content,
...(this._isClose ? { isClose: true } : {}),
...(sections.some(section => section.isError) ? { isError: true } : {}),
...(isError ? { isError: true } : {}),
};
}

Expand Down
5 changes: 3 additions & 2 deletions packages/playwright-core/src/tools/mcp/config.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,9 +225,10 @@ export type Config = {
};

/**
* Whether to send image responses to the client. Can be "allow", "omit", or "auto". Defaults to "auto", which sends images if the client can display them.
* Whether to send image responses to the client. Can be "allow", "omit", or "only". Defaults to "allow".
* With "only", a response that carries an image consists of the image parts alone, without the text part.
*/
imageResponses?: 'allow' | 'omit';
imageResponses?: 'allow' | 'omit' | 'only';

snapshot?: {
/**
Expand Down
4 changes: 2 additions & 2 deletions packages/playwright-core/src/tools/mcp/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export type CLIOptions = {
initScript?: string[];
initPage?: string[];
isolated?: boolean;
imageResponses?: 'allow' | 'omit';
imageResponses?: 'allow' | 'omit' | 'only';
mobile?: boolean;
sandbox?: boolean;
outputDir?: string;
Expand Down Expand Up @@ -439,7 +439,7 @@ export function configFromEnv(env?: NodeJS.ProcessEnv): Config & { configFile?:
options.initScript = [initScript];
options.isolated = envToBoolean(e.PLAYWRIGHT_MCP_ISOLATED);
if (e.PLAYWRIGHT_MCP_IMAGE_RESPONSES)
options.imageResponses = enumParser<'allow' | 'omit'>('--image-responses', ['allow', 'omit'], e.PLAYWRIGHT_MCP_IMAGE_RESPONSES);
options.imageResponses = enumParser<'allow' | 'omit' | 'only'>('--image-responses', ['allow', 'omit', 'only'], e.PLAYWRIGHT_MCP_IMAGE_RESPONSES);
options.mobile = envToBoolean(e.PLAYWRIGHT_MCP_MOBILE);
options.sandbox = envToBoolean(e.PLAYWRIGHT_MCP_SANDBOX);
options.outputDir = envToString(e.PLAYWRIGHT_MCP_OUTPUT_DIR);
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright-core/src/tools/mcp/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export function decorateMCPCommand(command: Command) {
.option('--init-page <path...>', 'path to TypeScript file to evaluate on Playwright page object')
.option('--init-script <path...>', 'path to JavaScript file to add as an initialization script. The script will be evaluated in every page before any of the page\'s scripts. Can be specified multiple times.')
.option('--isolated', 'keep the browser profile in memory, do not save it to disk.')
.option('--image-responses <mode>', 'whether to send image responses to the client. Can be "allow" or "omit", Defaults to "allow".', enumParser.bind(null, '--image-responses', ['allow', 'omit']))
.option('--image-responses <mode>', 'whether to send image responses to the client. Can be "allow", "omit" or "only". With "only", a response that carries an image consists of the image parts alone, without the text part. Defaults to "allow".', enumParser.bind(null, '--image-responses', ['allow', 'omit', 'only']))
.option('--no-sandbox', 'disable the sandbox for all process types that are normally sandboxed.')
.option('--output-dir <path>', 'path to the directory for automatically named output files, for example a screenshot taken without an explicit file name. Files with an explicit name are resolved against the workspace root instead and are not affected by this option.')
.option('--output-max-size <bytes>', 'Threshold for evicting old output files, in bytes.', numberParser)
Expand Down
38 changes: 38 additions & 0 deletions tests/mcp/screenshot.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,44 @@ test('browser_take_screenshot (imageResponses=omit)', async ({ startClient, serv
});
});

test('browser_take_screenshot (imageResponses=only)', async ({ startClient, server }, testInfo) => {
const outputDir = testInfo.outputPath('output');
const { client } = await startClient({
args: ['--image-responses=only'],
config: { outputDir },
});

await client.callTool({
name: 'browser_navigate',
arguments: { url: server.HELLO_WORLD },
});

expect(await client.callTool({
name: 'browser_take_screenshot',
})).toEqual({
content: [
{
data: expect.any(String),
mimeType: 'image/png',
type: 'image',
},
],
});
expect(fs.readdirSync(outputDir).filter(f => f.endsWith('.png'))).toHaveLength(1);

expect(await client.callTool({
name: 'browser_take_screenshot',
arguments: { filename: 'screenshot.png' },
})).toEqual({
content: [
{
text: expect.stringContaining('[Screenshot of viewport](./screenshot.png)'),
type: 'text',
},
],
});
});

test('browser_take_screenshot (fullPage: true)', async ({ startClient, server }, testInfo) => {
const { client } = await startClient({
config: { outputDir: testInfo.outputPath('output') },
Expand Down
Loading