Skip to content
Open
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
14 changes: 12 additions & 2 deletions src/tools/screenshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,20 @@ async function getSourceBox(
return {x: 0, y: 0, width: dims.width, height: dims.height};
}
const viewport = page.viewport();
if (!viewport) {
if (viewport) {
return {x: 0, y: 0, width: viewport.width, height: viewport.height};
}
// The browser is launched and connected with `defaultViewport: null`, so
// `page.viewport()` stays null until something emulates one. Fall back to the
// window's own dimensions, which is the area a viewport screenshot captures.
const dims = await page.evaluate(() => ({
width: window.innerWidth,
height: window.innerHeight,
}));
if (dims.width <= 0 || dims.height <= 0) {
return undefined;
}
return {x: 0, y: 0, width: viewport.width, height: viewport.height};
return {x: 0, y: 0, width: dims.width, height: dims.height};
}

function computeDownscaleClip(
Expand Down
37 changes: 37 additions & 0 deletions tests/tools/screenshot.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,43 @@ describe('screenshot', () => {
});
});

it('downscales viewport screenshot when no viewport is emulated', async () => {
const tool = screenshot({
screenshotMaxWidth: 100,
} as ParsedArguments);
await withMcpContext(async (response, context) => {
const page = context.getSelectedMcpPage().pptrPage;
// No setViewport call here: the browser is launched and connected with
// `defaultViewport: null`, so this is what a page looks like unless the
// emulate tool has set a viewport.
assert.equal(page.viewport(), null);
await page.setContent(
html`<div style="width:100vw;height:100vh;background:red"></div>`,
);
const source = await page.evaluate(() => ({
width: window.innerWidth,
height: window.innerHeight,
}));

await tool.handler(
{params: {format: 'png'}, page: context.getSelectedMcpPage()},
response,
context,
);

assert.equal(response.images.length, 1);
const buf = Buffer.from(response.images[0].data, 'base64');
assert.equal(pngWidth(buf), 100);
// The window size comes from the environment rather than an emulated
// viewport, so allow a pixel of rounding slack on the derived height.
const expectedHeight = Math.round(source.height * (100 / source.width));
assert.ok(
Math.abs(pngHeight(buf) - expectedHeight) <= 1,
`expected height ~${expectedHeight}, got ${pngHeight(buf)}`,
);
});
});

it('downscales using the smaller scale when both max-width and max-height are set', async () => {
const tool = screenshot({
screenshotMaxWidth: 400,
Expand Down