feat(slim): support filepath in slim mode screenshot (#3296)

Closes https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/3281
This commit is contained in:
Alex Rudenko authored and GitHub committed 2026-10-21 18:48:16 +00:00
1 parent 11e7d47495
commit d399c4a41e
4 files changed
+58 -12

No files matched your search

+4 -2
View File
@@ -34,8 +34,10 @@
### `screenshot`
**Description:** Takes a [`screenshot`](#screenshot)
**Description:** Takes a png [`screenshot`](#screenshot)
**Parameters:** None
**Parameters:**
- **filePath** (string) _(optional)_: The absolute path, or a path relative to the current working directory, to save the [`screenshot`](#screenshot) to instead of attaching it to the response.
---
+6 -1
View File
@@ -570,7 +570,12 @@
},
{
"name": "screenshot",
"args": []
"args": [
{
"name": "file_path_length",
"argType": "number"
}
]
},
{
"name": "select_page",
+26 -8
View File
@@ -13,28 +13,46 @@ import {validateUrl} from '../../utils/url.js';
export const screenshot: PageToolFactory = definePageTool(() => ({
name: 'screenshot',
description: `Takes a screenshot`,
description: `Takes a png screenshot`,
annotations: {
category: ToolCategory.DEBUGGING,
// Not read-only due to filePath param.
readOnlyHint: false,
conditions: ['slim'],
},
schema: {},
schema: {
filePath: zod
.string()
.optional()
.describe(
'The absolute path, or a path relative to the current working directory, to save the screenshot to instead of attaching it to the response.',
),
},
blockedByDialog: true,
concurrency: 'page',
verifyFilesSchema: {},
verifyFilesSchema: {
filePath: true,
},
handler: async (request, response, context) => {
const page = request.page;
const screenshot = await page.pptrPage.screenshot({
type: 'png',
optimizeForSpeed: true,
});
const {filepath} = await context.saveTemporaryFile(
screenshot,
`screenshot.png`,
);
response.appendResponseLine(filepath);
if (request.params.filePath) {
const result = await context.saveFile(
screenshot,
request.params.filePath,
'.png',
);
response.appendResponseLine(result.filename);
} else {
const {filepath} = await context.saveTemporaryFile(
screenshot,
`screenshot.png`,
);
response.appendResponseLine(filepath);
}
},
}));
+22 -1
View File
@@ -14,7 +14,7 @@ import sinon from 'sinon';
import {evaluate, navigate, screenshot} from '../../../src/tools/slim/tools.js';
import {createHandlerMocks, createMockParsedArguments} from '../../mocks.js';
import {screenshots} from '../../snapshot.js';
import {withMcpContext} from '../../utils.js';
import {withMcpContext, createTempDir} from '../../utils.js';
describe('slim', () => {
it('evaluates', async () => {
@@ -192,6 +192,7 @@ describe('slim', () => {
const fixture = screenshots.basic;
const page = context.getSelectedMcpPage().pptrPage;
await page.setContent(fixture.html);
await screenshot(args).handler(
{params: {}, page: context.getSelectedMcpPage()},
response,
@@ -201,4 +202,24 @@ describe('slim', () => {
assert(fs.existsSync(response.responseLines.at(0)!));
});
});
it('with custom file path', async () => {
await withMcpContext(async (response, context, args) => {
const fixture = screenshots.basic;
const page = context.getSelectedMcpPage().pptrPage;
await page.setContent(fixture.html);
using tmpDir = createTempDir();
const filePath = path.join(tmpDir.path, 'test.png');
await screenshot(args).handler(
{params: {filePath}, page: context.getSelectedMcpPage()},
response,
context,
);
const responseFilePath = response.responseLines.at(0)!;
assert(path.isAbsolute(responseFilePath));
assert(fs.existsSync(responseFilePath));
assert.equal(await fs.promises.realpath(filePath), responseFilePath);
});
});
});