-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Screenshotting] Add captureBeyondViewport: false to workaround a res… #131877
Changes from all commits
21dfdd0
a2c1ccc
cbd1926
5ab1168
3fdfd6f
14fdb7c
bb1eb1d
f94a107
973bfd8
b86bcda
734d3bb
cebafbf
fe3619a
66abad0
0a94e1c
d5a4848
9a0157f
6371a3f
2378b05
a12c31b
97784a7
de2c8b8
6ddff84
cb59beb
40fdb9e
a3384c6
e868fcc
6790d1a
cc69062
1a24814
d1176cc
307cf8e
b6d505c
f879440
91def4e
aecfeeb
d1edaaa
a0c7b63
0b1d84a
55ab21b
9018483
d22353e
01b3034
85ec2a1
2eaff1d
e1928ba
3543568
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,12 +35,6 @@ export interface ElementPosition { | |
}; | ||
} | ||
|
||
export interface Viewport { | ||
zoom: number; | ||
width: number; | ||
height: number; | ||
} | ||
|
||
interface OpenOptions { | ||
context?: Context; | ||
headers: Headers; | ||
|
@@ -203,7 +197,7 @@ export class HeadlessChromiumDriver { | |
} | ||
|
||
/* | ||
* Call Page.screenshot and return a base64-encoded string of the image | ||
* Receive a PNG buffer of the page screenshot from Chromium | ||
*/ | ||
async screenshot(elementPosition: ElementPosition): Promise<Buffer | undefined> { | ||
const { boundingClientRect, scroll } = elementPosition; | ||
|
@@ -214,6 +208,7 @@ export class HeadlessChromiumDriver { | |
height: boundingClientRect.height, | ||
width: boundingClientRect.width, | ||
}, | ||
captureBeyondViewport: false, // workaround for an internal resize. See: https://github.com/puppeteer/puppeteer/issues/7043 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This fixes the blank visualizations |
||
}); | ||
|
||
if (Buffer.isBuffer(screenshot)) { | ||
|
@@ -263,14 +258,18 @@ export class HeadlessChromiumDriver { | |
await this.page.waitForFunction(fn, { timeout, polling: WAIT_FOR_DELAY_MS }, ...args); | ||
} | ||
|
||
/** | ||
* Setting the viewport is required to ensure that all capture elements are visible: anything not in the | ||
* viewport can not be captured. | ||
*/ | ||
async setViewport( | ||
{ width: _width, height: _height, zoom }: Viewport, | ||
{ width: _width, height: _height, zoom }: { zoom: number; width: number; height: number }, | ||
logger: Logger | ||
): Promise<void> { | ||
const width = Math.floor(_width); | ||
const height = Math.floor(_height); | ||
|
||
logger.debug(`Setting viewport to: width=${width} height=${height} zoom=${zoom}`); | ||
logger.debug(`Setting viewport to: width=${width} height=${height} scaleFactor=${zoom}`); | ||
|
||
await this.page.setViewport({ | ||
width, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,44 +5,38 @@ | |
* 2.0. | ||
*/ | ||
|
||
import type { Logger } from '@kbn/core/server'; | ||
import type { ScreenshotModePluginSetup } from '@kbn/screenshot-mode-plugin/server'; | ||
import { getDataPath } from '@kbn/utils'; | ||
import { spawn } from 'child_process'; | ||
import _ from 'lodash'; | ||
import del from 'del'; | ||
import fs from 'fs'; | ||
import { uniq } from 'lodash'; | ||
import path from 'path'; | ||
import puppeteer, { Browser, ConsoleMessage, HTTPRequest, Page } from 'puppeteer'; | ||
import puppeteer, { Browser, ConsoleMessage, HTTPRequest, Page, Viewport } from 'puppeteer'; | ||
import { createInterface } from 'readline'; | ||
import * as Rx from 'rxjs'; | ||
import { | ||
catchError, | ||
concatMap, | ||
ignoreElements, | ||
map, | ||
concatMap, | ||
mergeMap, | ||
reduce, | ||
takeUntil, | ||
tap, | ||
} from 'rxjs/operators'; | ||
import type { Logger } from '@kbn/core/server'; | ||
import type { ScreenshotModePluginSetup } from '@kbn/screenshot-mode-plugin/server'; | ||
import { ConfigType } from '../../../config'; | ||
import { errors } from '../../../../common'; | ||
import { getChromiumDisconnectedError } from '..'; | ||
import { errors } from '../../../../common'; | ||
import { ConfigType } from '../../../config'; | ||
import { safeChildProcess } from '../../safe_child_process'; | ||
import { HeadlessChromiumDriver } from '../driver'; | ||
import { args } from './args'; | ||
import { getMetrics, PerformanceMetrics } from './metrics'; | ||
|
||
interface CreatePageOptions { | ||
browserTimezone?: string; | ||
defaultViewport: { | ||
/** Size in pixels */ | ||
width?: number; | ||
/** Size in pixels */ | ||
height?: number; | ||
}; | ||
defaultViewport: { width?: number }; | ||
openUrlTimeout: number; | ||
} | ||
|
||
|
@@ -63,10 +57,16 @@ interface ClosePageResult { | |
metrics?: PerformanceMetrics; | ||
} | ||
|
||
export const DEFAULT_VIEWPORT = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I refined this object more to align to what Puppeteer expects, and moved it to the |
||
width: 1950, | ||
height: 1200, | ||
}; | ||
/** | ||
* Size of the desired initial viewport. This is needed to render the app before elements load into their | ||
* layout. Once the elements are positioned, the viewport must be *resized* to include the entire element container. | ||
*/ | ||
export const DEFAULT_VIEWPORT: Required<Pick<Viewport, 'width' | 'height' | 'deviceScaleFactor'>> = | ||
{ | ||
width: 1950, | ||
height: 1200, | ||
deviceScaleFactor: 1, | ||
}; | ||
|
||
// Default args used by pptr | ||
// https://github.com/puppeteer/puppeteer/blob/13ea347/src/node/Launcher.ts#L168 | ||
|
@@ -138,6 +138,19 @@ export class HeadlessChromiumDriverFactory { | |
|
||
const chromiumArgs = this.getChromiumArgs(); | ||
logger.debug(`Chromium launch args set to: ${chromiumArgs}`); | ||
|
||
// We set the viewport width using the client-side layout info to reduce the chances of | ||
// browser reflow. Only the window height is expected to be adjusted dramatically | ||
// before taking a screenshot, to ensure the elements to capture are contained in the viewport. | ||
const viewport = { | ||
...DEFAULT_VIEWPORT, | ||
width: defaultViewport.width ?? DEFAULT_VIEWPORT.width, | ||
}; | ||
|
||
logger.debug( | ||
`Launching with viewport: width=${viewport.width} height=${viewport.height} scaleFactor=${viewport.deviceScaleFactor}` | ||
); | ||
|
||
(async () => { | ||
let browser: Browser | undefined; | ||
try { | ||
|
@@ -148,13 +161,7 @@ export class HeadlessChromiumDriverFactory { | |
ignoreHTTPSErrors: true, | ||
handleSIGHUP: false, | ||
args: chromiumArgs, | ||
|
||
// We optionally set this at page creation to reduce the chances of | ||
// browser reflow. In most cases only the height needs to be adjusted | ||
// before taking a screenshot. | ||
// NOTE: _.defaults assigns to the target object, so we copy it. | ||
// NOTE NOTE: _.defaults is not the same as { ...DEFAULT_VIEWPORT, ...defaultViewport } | ||
defaultViewport: _.defaults({ ...defaultViewport }, DEFAULT_VIEWPORT), | ||
defaultViewport: viewport, | ||
env: { | ||
TZ: browserTimezone, | ||
}, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -61,6 +61,7 @@ describe('Create Layout', () => { | |
}, | ||
"useReportingBranding": true, | ||
"viewport": Object { | ||
"deviceScaleFactor": 1, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I hardened the types in this PR, to make sure the scale factor is always part of the viewport info |
||
"height": 1200, | ||
"width": 1950, | ||
}, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not needed, and is confusing:
puppeteer.Viewport
is a similar but slightly different interface.