UNPKG

@applitools/screenshoter

Version:

Applitools universal screenshoter for web and native applications

180 lines (145 loc) 6.92 kB
const utils = require('@applitools/utils') const {makeImage} = require('@applitools/image') const makeTakeViewportScreenshot = require('./take-viewport-screenshot') const calculateScreenshotRegion = require('./calculate-screenshot-region') async function takeStitchedScreenshot({ logger, context, scroller, region, captureStatusBar, overlap = {top: 10, bottom: 50}, framed, wait, stabilization, debug, lazyLoad, }) { logger.verbose('Taking full image of...') if (await scroller.element.isPager()) overlap = {top: 0, bottom: 0} const driver = context.driver const environment = await driver.getEnvironment() const viewport = await driver.getViewport() const takeViewportScreenshot = await makeTakeViewportScreenshot({logger, driver, stabilization, debug}) const scrollerState = await scroller.preserveState() const initialOffset = region ? utils.geometry.location(region) : {x: 0, y: 0} const preMoveOffset = await scroller.getInnerOffset() const postMoveOffset = await scroller.moveTo(initialOffset) const expectedRemainingOffset = utils.geometry.offsetNegative(initialOffset, postMoveOffset) if (typeof wait === 'function') await wait() // using `wait` as a number should not happen with new core versions, but we still support it for backward copatability else await utils.general.sleep(wait) const contentSize = await scroller.getContentSize({lazyLoad}) logger.verbose( 'preMoveOffset', preMoveOffset, 'initialOffset', initialOffset, 'postMoveOffset', postMoveOffset, 'context.isMain', context.isMain, ) logger.verbose('Getting initial image...') let image = await takeViewportScreenshot({name: 'initial', captureStatusBar}) const firstImage = framed ? makeImage(image) : null const scrollerRegion = await scroller.getClientRegion() const targetRegion = region ? utils.geometry.intersect(utils.geometry.region(postMoveOffset, scrollerRegion), region) : scrollerRegion logger.log('Target region calculated: ', targetRegion) const cropRegion = utils.geometry.round(await driver.getRegionInViewport(context, targetRegion)) logger.log('Crop region calculated: ', cropRegion) if (utils.geometry.isEmpty(cropRegion)) throw new Error('Screenshot region is out of viewport') image.crop(captureStatusBar ? utils.geometry.offset(cropRegion, {x: 0, y: viewport.statusBarSize}) : cropRegion) await image.debug({...debug, name: 'initial', suffix: 'region'}) const contentRegion = utils.geometry.region({x: 0, y: 0}, contentSize) logger.verbose('Scroller size:', contentRegion) if (region) region = utils.geometry.intersect(region, contentRegion) else region = contentRegion if (targetRegion.x !== region.x && targetRegion.width !== region.width) { logger.verbose('Compensating for region width differences...') logger.verbose( 'region before compensation and the values to be used (a.k.a. expectedRemainingOffset)', region, expectedRemainingOffset, ) region.x += expectedRemainingOffset.x region.width += expectedRemainingOffset.x logger.verbose('region after compensation', region) } region = utils.geometry.floor(region) const partSize = lazyLoad && environment.isNative ? {width: image.size.width, height: image.size.height / 2} : image.size const [initialRegion, ...partRegions] = utils.geometry.divide(region, partSize, overlap) logger.verbose('Part regions', partRegions) logger.verbose('Creating stitched image composition container') const stitchedImage = makeImage({auto: true}) logger.verbose('Adding initial image...') stitchedImage.copy(image, {x: 0, y: 0}) logger.verbose('Getting the rest of the image parts...') let lastImage = framed ? makeImage(firstImage) : null let scrollerRegionShift = {x: 0, y: 0} for (const partRegion of partRegions) { const partName = `${partRegion.x}_${partRegion.y}_${partRegion.width}x${partRegion.height}` logger.verbose(`Processing part ${partName}`) // compensate scroller region being shifted and top overlap const compensateOffset = { x: scrollerRegionShift.x + 0, y: scrollerRegionShift.y + (initialRegion.y !== partRegion.y ? overlap.top : 0), } const requiredOffset = utils.geometry.offsetNegative(utils.geometry.location(partRegion), compensateOffset) logger.verbose('Move to', requiredOffset) let actualOffset = await scroller.moveTo(requiredOffset) // actual scroll position after scrolling might be not equal to required position due to // scrollable region shift during scrolling so actual scroll position should be corrected if (!utils.geometry.equals(actualOffset, requiredOffset) && environment.isNative) { const actualScrollerRegion = await scroller.getClientRegion() scrollerRegionShift = {x: scrollerRegion.x - actualScrollerRegion.x, y: scrollerRegion.y - actualScrollerRegion.y} } actualOffset = utils.geometry.offset(actualOffset, scrollerRegionShift) const remainingOffset = { x: requiredOffset.x - actualOffset.x - expectedRemainingOffset.x + compensateOffset.x, y: requiredOffset.y - actualOffset.y - expectedRemainingOffset.y + compensateOffset.y, } const cropPartRegion = { x: cropRegion.x + remainingOffset.x, y: cropRegion.y + remainingOffset.y, width: partRegion.width, height: partRegion.height, } logger.verbose('Actual offset is', actualOffset, ', remaining offset is', remainingOffset) logger.verbose('cropPartRegion is', cropPartRegion) await utils.general.sleep(wait) if (utils.geometry.isEmpty(cropPartRegion) || !utils.geometry.isIntersected(cropRegion, cropPartRegion)) continue logger.verbose('Getting image...') image = await takeViewportScreenshot({name: partName}) lastImage = framed ? makeImage(image) : null logger.verbose('cropping...') image.crop(cropPartRegion) await image.debug({...debug, name: partName, suffix: 'region'}) const pasteOffset = utils.geometry.offsetNegative(utils.geometry.location(partRegion), initialOffset) stitchedImage.copy(image, pasteOffset) } logger.verbose('restoring scroller state', scrollerState) await scroller.restoreState(scrollerState) await stitchedImage.debug({...debug, name: 'stitched'}) if (framed) { stitchedImage.frame( firstImage, lastImage, captureStatusBar ? utils.geometry.offset(cropRegion, {x: 0, y: viewport.statusBarSize}) : cropRegion, ) await stitchedImage.debug({...debug, name: 'framed'}) return { image: stitchedImage, region: calculateScreenshotRegion({stitchedImage, preMoveOffset, postMoveOffset}), } } else { return { image: stitchedImage, region: calculateScreenshotRegion({cropRegion, stitchedImage, preMoveOffset, postMoveOffset}), } } } module.exports = takeStitchedScreenshot