mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
chore: implement document screenshots in BiDi (#11398)
This commit is contained in:
parent
923434bd56
commit
2bf28ea1e5
@ -15,5 +15,5 @@ export interface ElementScreenshotOptions extends ScreenshotOptions
|
||||
## Properties
|
||||
|
||||
| Property | Modifiers | Type | Description | Default |
|
||||
| -------------- | --------------------- | ------- | ----------- | ------- |
|
||||
| scrollIntoView | <code>optional</code> | boolean | | true |
|
||||
| -------------- | --------------------- | ------- | ----------- | ----------------- |
|
||||
| scrollIntoView | <code>optional</code> | boolean | | <code>true</code> |
|
||||
|
@ -13,8 +13,8 @@ export interface ScreenshotOptions
|
||||
## Properties
|
||||
|
||||
| Property | Modifiers | Type | Description | Default |
|
||||
| --------------------- | --------------------- | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- |
|
||||
| captureBeyondViewport | <code>optional</code> | boolean | Capture the screenshot beyond the viewport. | <code>true</code> |
|
||||
| --------------------- | --------------------- | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
|
||||
| captureBeyondViewport | <code>optional</code> | boolean | Capture the screenshot beyond the viewport. | <code>false</code> if there is no <code>clip</code>. <code>true</code> otherwise. |
|
||||
| clip | <code>optional</code> | [ScreenshotClip](./puppeteer.screenshotclip.md) | Specifies the region of the page to clip. | |
|
||||
| encoding | <code>optional</code> | 'base64' \| 'binary' | Encoding of the image. | <code>'binary'</code> |
|
||||
| fromSurface | <code>optional</code> | boolean | Capture the screenshot from the surface, rather than the view. | <code>true</code> |
|
||||
|
@ -109,7 +109,7 @@ export interface Point {
|
||||
*/
|
||||
export interface ElementScreenshotOptions extends ScreenshotOptions {
|
||||
/**
|
||||
* @defaultValue true
|
||||
* @defaultValue `true`
|
||||
*/
|
||||
scrollIntoView?: boolean;
|
||||
}
|
||||
@ -1348,21 +1348,12 @@ export abstract class ElementHandle<
|
||||
this: ElementHandle<Element>,
|
||||
options: Readonly<ElementScreenshotOptions> = {}
|
||||
): Promise<string | Buffer> {
|
||||
const {
|
||||
scrollIntoView = true,
|
||||
captureBeyondViewport = true,
|
||||
allowViewportExpansion = captureBeyondViewport,
|
||||
} = options;
|
||||
const {scrollIntoView = true} = options;
|
||||
|
||||
let clip = await this.#nonEmptyVisibleBoundingBox();
|
||||
|
||||
const page = this.frame.page();
|
||||
|
||||
await using _ =
|
||||
allowViewportExpansion && clip
|
||||
? await page._createTemporaryViewportContainingBox(clip)
|
||||
: null;
|
||||
|
||||
if (scrollIntoView) {
|
||||
await this.scrollIntoViewIfNeeded();
|
||||
|
||||
@ -1382,11 +1373,7 @@ export abstract class ElementHandle<
|
||||
clip.x += pageLeft;
|
||||
clip.y += pageTop;
|
||||
|
||||
return await page.screenshot({
|
||||
...options,
|
||||
captureBeyondViewport: false,
|
||||
clip,
|
||||
});
|
||||
return await page.screenshot({...options, clip});
|
||||
}
|
||||
|
||||
async #nonEmptyVisibleBoundingBox() {
|
||||
|
@ -85,7 +85,6 @@ import type {ScreenRecorder} from '../node/ScreenRecorder.js';
|
||||
import {assert} from '../util/assert.js';
|
||||
import {guarded} from '../util/decorators.js';
|
||||
import {
|
||||
AsyncDisposableStack,
|
||||
asyncDisposeSymbol,
|
||||
DisposableStack,
|
||||
disposeSymbol,
|
||||
@ -279,17 +278,9 @@ export interface ScreenshotOptions {
|
||||
/**
|
||||
* Capture the screenshot beyond the viewport.
|
||||
*
|
||||
* @defaultValue `true`
|
||||
* @defaultValue `false` if there is no `clip`. `true` otherwise.
|
||||
*/
|
||||
captureBeyondViewport?: boolean;
|
||||
/**
|
||||
* TODO(jrandolf): Investigate whether viewport expansion is a better
|
||||
* alternative for cross-browser screenshots as opposed to
|
||||
* `captureBeyondViewport`.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
allowViewportExpansion?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -555,7 +546,6 @@ export function setDefaultScreenshotOptions(options: ScreenshotOptions): void {
|
||||
options.omitBackground ??= false;
|
||||
options.encoding ??= 'binary';
|
||||
options.captureBeyondViewport ??= true;
|
||||
options.allowViewportExpansion ??= options.captureBeyondViewport;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2439,7 +2429,7 @@ export abstract class Page extends EventEmitter<PageEvents> {
|
||||
): Promise<Buffer | string> {
|
||||
await this.bringToFront();
|
||||
|
||||
// TODO: use structuredClone after Node 16 support is dropped.«
|
||||
// TODO: use structuredClone after Node 16 support is dropped.
|
||||
const options = {
|
||||
...userOptions,
|
||||
clip: userOptions.clip
|
||||
@ -2482,10 +2472,6 @@ export abstract class Page extends EventEmitter<PageEvents> {
|
||||
);
|
||||
}
|
||||
}
|
||||
assert(
|
||||
!options.clip || !options.fullPage,
|
||||
"'clip' and 'fullPage' are exclusive"
|
||||
);
|
||||
if (options.clip) {
|
||||
if (options.clip.width <= 0) {
|
||||
throw new Error("'width' in 'clip' must be positive.");
|
||||
@ -2500,32 +2486,15 @@ export abstract class Page extends EventEmitter<PageEvents> {
|
||||
options.clip =
|
||||
options.clip && roundRectangle(normalizeRectangle(options.clip));
|
||||
|
||||
await using stack = new AsyncDisposableStack();
|
||||
if (options.allowViewportExpansion || options.captureBeyondViewport) {
|
||||
if (options.fullPage) {
|
||||
const dimensions = await this.mainFrame()
|
||||
.isolatedRealm()
|
||||
.evaluate(() => {
|
||||
const {scrollHeight, scrollWidth} = document.documentElement;
|
||||
const {height: viewportHeight, width: viewportWidth} =
|
||||
window.visualViewport!;
|
||||
return {
|
||||
height: Math.max(scrollHeight, viewportHeight),
|
||||
width: Math.max(scrollWidth, viewportWidth),
|
||||
};
|
||||
});
|
||||
options.clip = {...dimensions, x: 0, y: 0};
|
||||
stack.use(
|
||||
await this._createTemporaryViewportContainingBox(options.clip)
|
||||
);
|
||||
} else if (options.clip && !options.captureBeyondViewport) {
|
||||
stack.use(
|
||||
options.clip &&
|
||||
(await this._createTemporaryViewportContainingBox(options.clip))
|
||||
);
|
||||
} else if (!options.clip) {
|
||||
options.captureBeyondViewport = false;
|
||||
if (options.clip) {
|
||||
throw new Error("'clip' and 'fullPage' are exclusive");
|
||||
}
|
||||
} else if (
|
||||
!options.clip &&
|
||||
userOptions.captureBeyondViewport === undefined
|
||||
) {
|
||||
options.captureBeyondViewport = false;
|
||||
}
|
||||
|
||||
const data = await this._screenshot(options);
|
||||
@ -2542,61 +2511,6 @@ export abstract class Page extends EventEmitter<PageEvents> {
|
||||
*/
|
||||
abstract _screenshot(options: Readonly<ScreenshotOptions>): Promise<string>;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
async _createTemporaryViewportContainingBox(
|
||||
clip: ScreenshotClip
|
||||
): Promise<AsyncDisposable> {
|
||||
const viewport = await this.mainFrame()
|
||||
.isolatedRealm()
|
||||
.evaluate(() => {
|
||||
return {
|
||||
pageLeft: window.visualViewport!.pageLeft,
|
||||
pageTop: window.visualViewport!.pageTop,
|
||||
width: window.visualViewport!.width,
|
||||
height: window.visualViewport!.height,
|
||||
};
|
||||
});
|
||||
await using stack = new AsyncDisposableStack();
|
||||
if (clip.x < viewport.pageLeft || clip.y < viewport.pageTop) {
|
||||
await this.evaluate(
|
||||
(left, top) => {
|
||||
window.scroll({left, top, behavior: 'instant'});
|
||||
},
|
||||
Math.floor(clip.x),
|
||||
Math.floor(clip.y)
|
||||
);
|
||||
stack.defer(async () => {
|
||||
await this.evaluate(
|
||||
(left, top) => {
|
||||
window.scroll({left, top, behavior: 'instant'});
|
||||
},
|
||||
viewport.pageLeft,
|
||||
viewport.pageTop
|
||||
).catch(debugError);
|
||||
});
|
||||
}
|
||||
if (
|
||||
clip.width + clip.x > viewport.width ||
|
||||
clip.height + clip.y > viewport.height
|
||||
) {
|
||||
const originalViewport = this.viewport() ?? {
|
||||
width: 0,
|
||||
height: 0,
|
||||
};
|
||||
// We add 1 for fractional x and y.
|
||||
await this.setViewport({
|
||||
width: Math.max(viewport.width, Math.ceil(clip.width + clip.x)),
|
||||
height: Math.max(viewport.height, Math.ceil(clip.height + clip.y)),
|
||||
});
|
||||
stack.defer(async () => {
|
||||
await this.setViewport(originalViewport).catch(debugError);
|
||||
});
|
||||
}
|
||||
return stack.move();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
@ -29,6 +29,7 @@ import {
|
||||
raceWith,
|
||||
} from '../../third_party/rxjs/rxjs.js';
|
||||
import type {CDPSession} from '../api/CDPSession.js';
|
||||
import type {BoundingBox} from '../api/ElementHandle.js';
|
||||
import type {WaitForOptions} from '../api/Frame.js';
|
||||
import {
|
||||
Page,
|
||||
@ -647,13 +648,7 @@ export class BidiPage extends Page {
|
||||
override async _screenshot(
|
||||
options: Readonly<ScreenshotOptions>
|
||||
): Promise<string> {
|
||||
const {clip, type, captureBeyondViewport, allowViewportExpansion, quality} =
|
||||
options;
|
||||
if (captureBeyondViewport && !allowViewportExpansion) {
|
||||
throw new UnsupportedOperation(
|
||||
`BiDi does not support 'captureBeyondViewport'. Use 'allowViewportExpansion'.`
|
||||
);
|
||||
}
|
||||
const {clip, type, captureBeyondViewport, quality} = options;
|
||||
if (options.omitBackground !== undefined && options.omitBackground) {
|
||||
throw new UnsupportedOperation(`BiDi does not support 'omitBackground'.`);
|
||||
}
|
||||
@ -665,6 +660,29 @@ export class BidiPage extends Page {
|
||||
if (options.fromSurface !== undefined && !options.fromSurface) {
|
||||
throw new UnsupportedOperation(`BiDi does not support 'fromSurface'.`);
|
||||
}
|
||||
|
||||
let box: BoundingBox | undefined;
|
||||
if (clip) {
|
||||
if (captureBeyondViewport) {
|
||||
box = clip;
|
||||
} else {
|
||||
const [pageLeft, pageTop] = await this.evaluate(() => {
|
||||
if (!window.visualViewport) {
|
||||
throw new Error('window.visualViewport is not supported.');
|
||||
}
|
||||
return [
|
||||
window.visualViewport.pageLeft,
|
||||
window.visualViewport.pageTop,
|
||||
] as const;
|
||||
});
|
||||
box = {
|
||||
...clip,
|
||||
x: clip.x - pageLeft,
|
||||
y: clip.y - pageTop,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (clip !== undefined && clip.scale !== undefined && clip.scale !== 1) {
|
||||
throw new UnsupportedOperation(
|
||||
`BiDi does not support 'scale' in 'clip'.`
|
||||
@ -675,14 +693,12 @@ export class BidiPage extends Page {
|
||||
result: {data},
|
||||
} = await this.#connection.send('browsingContext.captureScreenshot', {
|
||||
context: this.mainFrame()._id,
|
||||
origin: captureBeyondViewport ? 'document' : 'viewport',
|
||||
format: {
|
||||
type: `image/${type}`,
|
||||
quality: quality ? quality / 100 : undefined,
|
||||
},
|
||||
clip: clip && {
|
||||
type: 'box',
|
||||
...clip,
|
||||
...(quality !== undefined ? {quality: quality / 100} : {}),
|
||||
},
|
||||
...(box ? {clip: {type: 'box', ...box}} : {}),
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
@ -1166,7 +1166,7 @@
|
||||
{
|
||||
"testIdPattern": "[screenshot.spec] Screenshots Page.screenshot should get screenshot bigger than the viewport",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"parameters": ["cdp"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
@ -3348,13 +3348,19 @@
|
||||
"expectations": ["FAIL", "PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[screenshot.spec] Screenshots ElementHandle.screenshot should capture full element when larger than viewport",
|
||||
"testIdPattern": "[screenshot.spec] Screenshots ElementHandle.screenshot should work for an element with an offset",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["cdp", "firefox"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[screenshot.spec] Screenshots ElementHandle.screenshot should work for an element with an offset",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["firefox", "webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[screenshot.spec] Screenshots ElementHandle.screenshot should work for an element with an offset",
|
||||
"testIdPattern": "[screenshot.spec] Screenshots ElementHandle.screenshot should work with a rotated element",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["cdp", "firefox"],
|
||||
"expectations": ["FAIL"]
|
||||
@ -3362,7 +3368,7 @@
|
||||
{
|
||||
"testIdPattern": "[screenshot.spec] Screenshots ElementHandle.screenshot should work with a rotated element",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["cdp", "firefox"],
|
||||
"parameters": ["firefox", "webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
@ -3383,12 +3389,24 @@
|
||||
"parameters": ["firefox", "webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[screenshot.spec] Screenshots Page.screenshot should take fullPage screenshots",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["cdp", "firefox"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[screenshot.spec] Screenshots Page.screenshot should work",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["firefox", "webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[screenshot.spec] Screenshots Page.screenshot should work with odd clip size on Retina displays",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["firefox", "webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[stacktrace.spec] Stack trace should work for none error objects",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
@ -3713,12 +3731,6 @@
|
||||
"parameters": ["cdp", "firefox", "headless"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[screenshot.spec] Screenshots Page.screenshot should take fullPage screenshots",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["cdp", "firefox", "headless"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[screenshot.spec] Screenshots Page.screenshot should use scale for clip",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 266 B After Width: | Height: | Size: 346 B |
Binary file not shown.
Before Width: | Height: | Size: 873 B After Width: | Height: | Size: 459 B |
Loading…
Reference in New Issue
Block a user