@browserless/screenshot
Version:
Take a clean screenshot of any website
125 lines (109 loc) • 4 kB
JavaScript
const debug = require('debug-logfmt')('browserless:screenshot')
const createGoto = require('@browserless/goto')
const pReflect = require('p-reflect')
const isWhiteScreenshot = require('./is-white-screenshot')
const waitForPrism = require('./pretty')
const timeSpan = require('./time-span')
const overlay = require('./overlay')
const getBoundingClientRect = element => {
const { top, left, height, width, x, y } = element.getBoundingClientRect()
return { top, left, height, width, x, y }
}
const waitForImagesOnViewport = page =>
page.$$eval('img[src]:not([aria-hidden="true"])', elements =>
Promise.all(
elements
.filter(el => {
if (el.naturalHeight === 0 || el.naturalWidth === 0) return false
const { top, left, bottom, right } = el.getBoundingClientRect()
return (
top >= 0 &&
left >= 0 &&
bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
right <= (window.innerWidth || document.documentElement.clientWidth)
)
})
.map(el => el.decode())
)
)
const waitForElement = async (page, element) => {
const screenshotOpts = {}
if (element) {
await page.waitForSelector(element, { visible: true })
screenshotOpts.clip = await page.$eval(element, getBoundingClientRect)
screenshotOpts.fullPage = false
return screenshotOpts
}
return screenshotOpts
}
module.exports = ({ goto, ...gotoOpts }) => {
goto = goto || createGoto(gotoOpts)
return function screenshot (page) {
return async (
url,
{
element,
codeScheme = 'atom-dark',
overlay: overlayOpts = {},
waitUntil = 'auto',
...opts
} = {}
) => {
let screenshot
let response
const beforeScreenshot = response => {
const timeout = goto.timeouts.action(goto.timeouts.base(opts.timeout))
return Promise.all(
[
{
fn: () => page.evaluate('document.fonts.ready'),
debug: 'beforeScreenshot:fontsReady'
},
{
fn: () => waitForPrism(page, response, { codeScheme, ...opts }),
debug: 'beforeScreenshot:waitForPrism'
},
{
fn: () => waitForImagesOnViewport(page),
debug: 'beforeScreenshot:waitForImagesOnViewport'
}
].map(({ fn, ...opts }) => goto.run({ fn: fn(), ...opts, timeout }))
)
}
const takeScreenshot = async opts => {
screenshot = await page.screenshot(opts)
const isWhite = await isWhiteScreenshot(screenshot)
if (isWhite) {
await goto.waitUntilAuto(page, opts)
screenshot = await page.screenshot(opts)
}
return { isWhite }
}
page.on('dialog', dialog => pReflect(dialog.dismiss()))
const timeScreenshot = timeSpan()
if (waitUntil !== 'auto') {
;({ response } = await goto(page, { ...opts, url, waitUntil }))
const [screenshotOpts] = await Promise.all([
waitForElement(page, element),
beforeScreenshot(response)
])
screenshot = await page.screenshot({ ...opts, ...screenshotOpts })
debug('screenshot', { waitUntil, duration: timeScreenshot() })
} else {
;({ response } = await goto(page, { ...opts, url, waitUntil, waitUntilAuto }))
async function waitUntilAuto (page, { response }) {
const [screenshotOpts] = await Promise.all([
waitForElement(page, element),
beforeScreenshot(response)
])
const { isWhite } = await takeScreenshot({ ...opts, ...screenshotOpts })
debug('screenshot', { waitUntil, isWhite, duration: timeScreenshot() })
}
}
return Object.keys(overlayOpts).length === 0
? screenshot
: overlay(screenshot, { ...opts, ...overlayOpts, viewport: page.viewport() })
}
}
}