Skip to content
Closed
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: 10 additions & 4 deletions packages/playwright-core/src/server/chromium/crPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,18 +287,24 @@ export class CRPage implements PageDelegate {
// When taking screenshots with documentRect (based on the page content, not viewport),
// ignore current page scale.
const clip = { ...documentRect, scale: viewportRect ? visualViewport.scale : 1 };
if (scale === 'css') {
// deviceScaleFactor override does not affect layout metrics, so if it is set,
// we use its value rather than computed one.
const deviceScaleFactor = this._mainFrameSession._metricsOverride?.deviceScaleFactor || contentSize.width / cssContentSize.width || 1;
const deviceScaleFactor = this._mainFrameSession._metricsOverride?.deviceScaleFactor || contentSize.width / cssContentSize.width || 1;
if (scale === 'css')
clip.scale /= deviceScaleFactor;
if (format === 'webp') {
const renderScale = scale === 'css' ? clip.scale : clip.scale * deviceScaleFactor;
const width = Math.ceil(clip.width * renderScale);
const height = Math.ceil(clip.height * renderScale);
if (width > 16383 || height > 16383)
throw new Error('Cannot take screenshot larger than 16383 pixels on any dimension when using webp format');
}
const result = await progress.race(this._mainFrameSession._client.send('Page.captureScreenshot', { format, quality, clip, captureBeyondViewport: !fitsViewport }));
if (!fitsViewport && this._browserContext._options.hasTouch) {
// Capturing beyond viewport resets touch emulation in Chromium, so we re-apply it.
// See https://issues.chromium.org/issues/558509412 and https://github.com/microsoft/playwright/issues/42607.
await progress.race(this._mainFrameSession._client.send('Emulation.setTouchEmulationEnabled', { enabled: true }));
}
if (!result.data)
throw new Error('Failed to take screenshot, received empty image data from browser');
return Buffer.from(result.data, 'base64');
}

Expand Down
2 changes: 2 additions & 0 deletions packages/playwright-core/src/server/screenshotter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,8 @@ export class Screenshotter {
try {
const quality = format === 'jpeg' ? options.quality ?? 80 : format === 'webp' ? options.quality ?? 100 : undefined;
const buffer = await this._page.delegate.takeScreenshot(progress, format, documentRect, viewportRect, quality, fitsViewport, options.scale || 'device');
if (!buffer.byteLength)
throw new Error('Failed to take screenshot, received empty image data from browser');
await progress.race(cleanupHighlight());
if (shouldSetDefaultBackground)
await progress.race(this._page.delegate.setBackgroundColor());
Expand Down
12 changes: 7 additions & 5 deletions packages/playwright-core/src/server/webkit/webview/wvPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -765,13 +765,15 @@ export class WVPage implements PageDelegate {
throw new Error('Method not implemented');
}

private validateScreenshotDimension(side: number, omitDeviceScaleFactor: boolean) {
private validateScreenshotDimension(side: number, omitDeviceScaleFactor: boolean, format: string) {
if (!omitDeviceScaleFactor && this._page.browserContext._options.deviceScaleFactor)
side = Math.ceil(side * this._page.browserContext._options.deviceScaleFactor);
if (format === 'webp' && side > 16383)
throw new Error('Cannot take screenshot larger than 16383 pixels on any dimension when using webp format');
// Cairo based implementations (Linux and Windows) have hard limit of 32767
// (see https://github.com/microsoft/playwright/issues/16727).
if (process.platform === 'darwin')
return;
if (!omitDeviceScaleFactor && this._page.browserContext._options.deviceScaleFactor)
side = Math.ceil(side * this._page.browserContext._options.deviceScaleFactor);
if (side > 32767)
throw new Error('Cannot take screenshot larger than 32767 pixels on any dimension');
}
Expand All @@ -781,8 +783,8 @@ export class WVPage implements PageDelegate {
const omitDeviceScaleFactor = scale === 'css';
if (omitDeviceScaleFactor)
throw new Error('css screenshots are not implemented');
this.validateScreenshotDimension(rect.width, omitDeviceScaleFactor);
this.validateScreenshotDimension(rect.height, omitDeviceScaleFactor);
this.validateScreenshotDimension(rect.width, omitDeviceScaleFactor, format);
this.validateScreenshotDimension(rect.height, omitDeviceScaleFactor, format);
const result = await progress.race(this._session.send('Page.snapshotRect', { ...rect, coordinateSystem: documentRect ? 'Page' : 'Viewport' }));
const prefix = 'data:image/png;base64,';
let buffer: Buffer = Buffer.from(result.dataURL.substr(prefix.length), 'base64');
Expand Down
12 changes: 7 additions & 5 deletions packages/playwright-core/src/server/webkit/wkPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -862,22 +862,24 @@ export class WKPage implements PageDelegate {
return 0;
}

private validateScreenshotDimension(side: number, omitDeviceScaleFactor: boolean) {
private validateScreenshotDimension(side: number, omitDeviceScaleFactor: boolean, format: string) {
if (!omitDeviceScaleFactor && this._page.browserContext._options.deviceScaleFactor)
side = Math.ceil(side * this._page.browserContext._options.deviceScaleFactor);
if (format === 'webp' && side > 16383)
throw new Error('Cannot take screenshot larger than 16383 pixels on any dimension when using webp format');
// Cairo based implementations (Linux and Windows) have hard limit of 32767
// (see https://github.com/microsoft/playwright/issues/16727).
if (process.platform === 'darwin')
return;
if (!omitDeviceScaleFactor && this._page.browserContext._options.deviceScaleFactor)
side = Math.ceil(side * this._page.browserContext._options.deviceScaleFactor);
if (side > 32767)
throw new Error('Cannot take screenshot larger than 32767 pixels on any dimension');
}

async takeScreenshot(progress: Progress, format: string, documentRect: types.Rect | undefined, viewportRect: types.Rect | undefined, quality: number | undefined, fitsViewport: boolean, scale: 'css' | 'device'): Promise<Buffer> {
const rect = (documentRect || viewportRect)!;
const omitDeviceScaleFactor = scale === 'css';
this.validateScreenshotDimension(rect.width, omitDeviceScaleFactor);
this.validateScreenshotDimension(rect.height, omitDeviceScaleFactor);
this.validateScreenshotDimension(rect.width, omitDeviceScaleFactor, format);
this.validateScreenshotDimension(rect.height, omitDeviceScaleFactor, format);
// WebKit on macOS has no built-in WebP encoder, so capture a PNG and re-encode it.
const recodePngToWebp = format === 'webp' && process.platform === 'darwin';
const result = await progress.race(this._session.send('Page.snapshotRect', { ...rect, coordinateSystem: documentRect ? 'Page' : 'Viewport', omitDeviceScaleFactor, format: (recodePngToWebp ? 'png' : format) as 'png' | 'jpeg' | 'webp', quality: recodePngToWebp ? undefined : quality }));
Expand Down
2 changes: 2 additions & 0 deletions packages/playwright-core/src/tools/backend/screenshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ const screenshot = defineTabTool({
const screenshotTargetLabel = params.target ? params.element || 'element' : (params.fullPage ? 'full page' : 'viewport');
const target = params.target ? await tab.targetLocator({ element: params.element, target: params.target }) : null;
const data = target ? await target.locator.screenshot(options) : await tab.page.screenshot(options);
if (!data.length)
throw new Error('Failed to take screenshot, received empty image data');

const resolvedFile = await response.resolveClientOutputFile({ prefix: target ? 'element' : 'page', ext: fileType, suggestedFilename: params.filename }, `Screenshot of ${screenshotTargetLabel}`);

Expand Down
23 changes: 23 additions & 0 deletions tests/mcp/screenshot.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -497,3 +497,26 @@ test('browser_take_screenshot (viewport without snapshot)', async ({ startClient
],
});
});

test('browser_take_screenshot (webp dimension limit)', async ({ startClient, server }, testInfo) => {
const outputDir = testInfo.outputPath('output');
const { client } = await startClient({
config: { outputDir },
});
server.setRoute('/tall.html', (req, res) => {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('<style>body { margin: 0; }</style><div style="height: 16384px; background: red;"></div>');
});
await client.callTool({
name: 'browser_navigate',
arguments: { url: server.PREFIX + '/tall.html' },
});

const response = await client.callTool({
name: 'browser_take_screenshot',
arguments: { type: 'webp', fullPage: true },
});
expect(response.content?.[0]?.text).toContain('Cannot take screenshot larger than 16383 pixels on any dimension when using webp format');
const imageFiles = fs.existsSync(outputDir) ? fs.readdirSync(outputDir).filter(f => f.endsWith('.webp')) : [];
expect(imageFiles).toHaveLength(0);
});
36 changes: 36 additions & 0 deletions tests/page/page-screenshot.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -984,6 +984,42 @@ it('should throw if screenshot size is too large', async ({ page, browserName, i
}
});

it('should throw if webp screenshot size exceeds dimension limit', async ({ page, browserName }) => {
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/42717' });
it.skip(browserName === 'firefox', 'Firefox does not implement webp dimension limit');
{
await page.setContent(`<style>body {margin: 0; padding: 0;}</style><div style='min-height: 16383px; background: red;'></div>`);
const result = await page.screenshot({ type: 'webp', fullPage: true });
expect(result).toBeTruthy();
}
{
await page.setContent(`<style>body {margin: 0; padding: 0;}</style><div style='min-height: 16384px; background: red;'></div>`);
const exception = await page.screenshot({ type: 'webp', fullPage: true }).catch(e => e);
expect(exception.message).toContain('Cannot take screenshot larger than 16383 pixels on any dimension when using webp format');
}
});

it('should throw if webp screenshot size exceeds dimension limit with device scale factor', async ({ browser, browserName }) => {
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/42717' });
it.skip(browserName === 'firefox', 'Firefox does not implement webp dimension limit');
const context = await browser.newContext({ viewport: { width: 500, height: 500 }, deviceScaleFactor: 2 });
const page = await context.newPage();
{
await page.setContent(`<style>body {margin: 0; padding: 0;}</style><div style='min-height: 8191px; background: red;'></div>`);
const result = await page.screenshot({ type: 'webp', fullPage: true });
expect(result).toBeTruthy();
}
{
await page.setContent(`<style>body {margin: 0; padding: 0;}</style><div style='min-height: 8192px; background: red;'></div>`);
const exception = await page.screenshot({ type: 'webp', fullPage: true }).catch(e => e);
expect(exception.message).toContain('Cannot take screenshot larger than 16383 pixels on any dimension when using webp format');

const image = await page.screenshot({ type: 'webp', fullPage: true, scale: 'css' });
expect(image).toBeTruthy();
}
await context.close();
});

it('page screenshot should capture css transform', async function({ page, browserName, isElectron, isAndroid }) {
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/26447' });
it.fixme(browserName === 'webkit');
Expand Down