Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@applitools/screenshoter

Package Overview
Dependencies
Maintainers
22
Versions
173
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@applitools/screenshoter - npm Package Compare versions

Comparing version 3.0.8 to 3.1.0

test/e2e/android.spec.js

16

.bongo/dry-run/package-lock.json

@@ -7,12 +7,18 @@ {

"version": "file:../dry-run.tgz",
"integrity": "sha512-ET3GIWJY2aZvEteoEj3CkP5d5d38EoIj1maN65Sv/F+NusrZHaaCpuc/goOKL1JqFnHM8QiMHVywZlBZ3WGXmg==",
"integrity": "sha512-FDyK0CsvbGvDngoG0ZWcaoBwko0y1SsQKnJrGzsgsw6baI/yYlA2z7V8NdhOhS+js2sQ8YblsMWd+oTj5ZPFZQ==",
"requires": {
"@applitools/utils": "1.2.0",
"@applitools/snippets": "2.1.4",
"@applitools/utils": "1.2.1",
"png-async": "0.9.4"
}
},
"@applitools/snippets": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@applitools/snippets/-/snippets-2.1.4.tgz",
"integrity": "sha512-Jmp+DM9Kj24+ByMaqoKxhMNsefsSshN5+MLyMyrFurb1FGRGoSMmAT5bZCI7zKFG02pxZmjBmenMhpNQbzZR/A=="
},
"@applitools/utils": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@applitools/utils/-/utils-1.2.0.tgz",
"integrity": "sha512-NzynRgRavbTns5M85DJXXaMY/57xBte3F/tnK5nr3DSBhKTQnkjXIFwryPU5bVwEcpoGEVF0T1Gyq37WnDe38Q=="
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@applitools/utils/-/utils-1.2.1.tgz",
"integrity": "sha512-lIHYqgirhkyL+xAc9+FwidAvcI7apB/6Y7uyVimHdTRjjIwAqoazymwYiFZiia+q81F/smPLvMw2RUwU6wi13w=="
},

@@ -19,0 +25,0 @@ "png-async": {

@@ -7,2 +7,8 @@

## 3.1.0 - 2021/8/4
- improve support of native devices
- updated to @applitools/snippets@2.1.4 (from 2.1.3)
- updated to @applitools/utils@1.2.1 (from 1.2.0)
## 3.0.8 - 2021/5/24

@@ -14,4 +20,4 @@

fixed image cropping algorithm to not copy data into a heap
optimized image rotation and image copping algorithms
- fixed image cropping algorithm to not copy data into a heap
- optimized image rotation and image copping algorithms

@@ -18,0 +24,0 @@ ## 3.0.6 - 2021/5/11

{
"name": "@applitools/screenshoter",
"version": "3.0.8",
"version": "3.1.0",
"description": "Applitools universal screenshoter for web and native applications",

@@ -34,5 +34,5 @@ "keywords": [

"lint": "eslint . --ext .js",
"test": "yarn test:it && yarn test:coverage",
"test": "yarn test:it && yarn test:e2e",
"test:it": "mocha --no-timeouts './test/it/*.spec.js'",
"test:coverage": "mocha --no-timeouts './test/coverage/*.spec.js'",
"test:e2e": "mocha --no-timeouts './test/e2e/*.spec.js'",
"docker:setup": "node ../scripts/scripts/generate-docker-compose-config.js && docker-compose up -d",

@@ -52,7 +52,8 @@ "docker:teardown": "docker-compose down",

"dependencies": {
"@applitools/utils": "1.2.0",
"@applitools/snippets": "2.1.4",
"@applitools/utils": "1.2.1",
"png-async": "0.9.4"
},
"devDependencies": {
"@applitools/driver": "1.0.6",
"@applitools/driver": "1.1.0",
"@applitools/sdk-release-kit": "0.13.0",

@@ -59,0 +60,0 @@ "eslint": "^7.9.0",

function findPattern(image, pattern) {
for (let pixel = 0; pixel < image.info.width * image.info.height; ++pixel) {
for (let pixel = 0; pixel < image.width * image.height; ++pixel) {
if (isPattern(image, pixel, pattern)) {
return {
x: (pixel % image.info.width) - pattern.offset,
y: Math.floor(pixel / image.info.width) - pattern.offset,
x: (pixel % image.width) - pattern.offset,
y: Math.floor(pixel / image.width) - pattern.offset,
}

@@ -14,2 +14,3 @@ }

function isPattern(image, index, pattern) {
const channels = 4
const roundNumber = pattern.size - Math.floor(pattern.size / 2)

@@ -20,3 +21,3 @@ for (const [chunkIndex, chunkColor] of pattern.mask.entries()) {

const sideLength = pattern.size - round * 2
const stepsNumber = sideLength * 4 - 4
const stepsNumber = sideLength * channels - channels
const threshold = Math.min((roundNumber - round) * 10 + 10, 100)

@@ -47,3 +48,3 @@ for (let step = 0; step < stepsNumber; ++step) {

function pixelColorAt(image, index, threshold = 0) {
const channels = image.info.channels
const channels = 4
const r = image.data[index * channels]

@@ -50,0 +51,0 @@ const g = image.data[index * channels + 1]

const fs = require('fs')
const stream = require('stream')
const path = require('path')
const png = require('png-async')

@@ -19,8 +20,19 @@ const utils = require('@applitools/utils')

size = extractPngSize(data)
} else {
} else if (data.isImage) {
image = data.toObject()
size = data.size
} else if (utils.types.has(data, ['width', 'height'])) {
image = fromSize(data)
size = data
} else {
throw new Error('Unable to create an image abstraction from unknown data')
}
return {
get isImage() {
return true
},
get size() {
return image.width && image.height ? {width: image.width, height: image.height} : size
},
get width() {

@@ -37,3 +49,3 @@ return image.width || size.width

async crop(region) {
image = await crop(await image, region)
image = await extract(await image, region)
return this

@@ -49,2 +61,13 @@ },

},
async replace(image2, region) {
image = await replace(await image, image2, region)
return this
},
async debug(debug) {
if (!debug || !debug.path) return
const timestamp = new Date().toISOString().replace(/[-T:.]/g, '_')
const filename =
['screenshot', timestamp, debug.name, debug.suffix].filter(part => part).join('_') + '.png'
return toFile(await image, path.join(debug.path, filename)).catch(() => null)
},
async toObject() {

@@ -67,2 +90,8 @@ image = await image

function extractPngSize(buffer) {
return buffer.slice(12, 16).toString('ascii') === 'IHDR'
? {width: buffer.readUInt32BE(16), height: buffer.readUInt32BE(20)}
: {width: 0, height: 0}
}
async function fromBuffer(buffer) {

@@ -109,8 +138,2 @@ return new Promise((resolve, reject) => {

function extractPngSize(buffer) {
return buffer.slice(12, 16).toString('ascii') === 'IHDR'
? {width: buffer.readUInt32BE(16), height: buffer.readUInt32BE(20)}
: {width: 0, height: 0}
}
async function scale(image, scaleRatio) {

@@ -145,3 +168,3 @@ if (scaleRatio === 1) return image

async function crop(image, region) {
async function extract(image, region) {
if (utils.types.has(region, ['left', 'right', 'top', 'bottom'])) {

@@ -161,25 +184,20 @@ region = {

const extracted = new png.Image({filterType: 4, width: dstWidth, height: dstHeight})
if (srcX === 0 && dstWidth === image.width) {
const srcOffset = srcY * image.width * 4
const dstLength = dstWidth * dstHeight * 4
image.data = image.data.subarray(srcOffset, srcOffset + dstLength)
image.width = dstWidth
image.height = dstHeight
return image
extracted.data.set(image.data.subarray(srcOffset, srcOffset + dstLength))
} else {
const chunkLength = dstWidth * 4
for (let chunk = 0; chunk < dstHeight; ++chunk) {
const srcOffset = ((srcY + chunk) * image.width + srcX) * 4
extracted.data.set(
image.data.subarray(srcOffset, srcOffset + chunkLength),
chunk * chunkLength,
)
}
}
const cropped = Buffer.alloc(dstWidth * dstHeight * 4)
const chunkLength = dstWidth * 4
for (let chunk = 0; chunk < dstHeight; ++chunk) {
const srcOffset = ((srcY + chunk) * image.width + srcX) * 4
cropped.set(image.data.subarray(srcOffset, srcOffset + chunkLength), chunk * chunkLength)
}
image.data = cropped
image.width = dstWidth
image.height = dstHeight
return image
return extracted
}

@@ -190,5 +208,4 @@

const dstImage = {
data: Buffer.alloc(image.data.length),
}
const dstImage = new png.Image({filterType: 4, width: image.width, height: image.height})
if (degrees === 90) {

@@ -222,10 +239,6 @@ dstImage.width = image.height

} else {
return image
return dstImage.data.set(image.data)
}
image.data = dstImage.data
image.width = dstImage.width
image.height = dstImage.height
return image
return dstImage
}

@@ -242,3 +255,2 @@

dstImage.data.set(srcImage.data.subarray(0, srcWidth * srcHeight * 4), dstOffset)
return dstImage

@@ -257,2 +269,104 @@ }

async function replace(baseImage, srcImage, region) {
region = utils.geometry.intersect(
{x: 0, y: 0, width: baseImage.width, height: baseImage.height},
region,
)
if (
region.x === 0 &&
region.y === 0 &&
region.width >= baseImage.width &&
region.height >= baseImage.height
) {
return srcImage
}
if (region.width === srcImage.width && region.height === srcImage.height) {
await copy(baseImage, srcImage, {x: region.x, y: region.y})
return baseImage
}
const dstImage = new png.Image({
filterType: 4,
width: baseImage.width - region.width + srcImage.width,
height: baseImage.height - region.height + srcImage.height,
})
if (region.width === srcImage.width) {
const topImage = await extract(baseImage, {
x: 0,
y: 0,
width: baseImage.width,
height: region.y + region.height,
})
await copy(dstImage, topImage, {x: 0, y: 0})
} else if (region.height === srcImage.height) {
const leftImage = await extract(baseImage, {
x: 0,
y: 0,
width: region.x + region.width,
height: baseImage.height,
})
await copy(dstImage, leftImage, {x: 0, y: 0})
} else {
const topLeftImage = await extract(baseImage, {
x: 0,
y: 0,
width: region.x + region.width,
height: region.y + region.height,
})
await copy(dstImage, topLeftImage, {x: 0, y: 0})
const topRightImage = await extract(baseImage, {
x: region.x + region.width,
y: 0,
width: baseImage.width - (region.x + region.width),
height: region.y + region.height,
})
await copy(dstImage, topRightImage, {x: region.x + srcImage.width, y: 0})
}
await copy(dstImage, srcImage, {x: region.x, y: region.y})
if (baseImage.height > region.y + region.height) {
if (region.width === srcImage.width) {
const bottomImage = await extract(baseImage, {
x: 0,
y: region.y + region.height,
width: baseImage.width,
height: baseImage.height - (region.y + region.height),
})
await copy(dstImage, bottomImage, {x: 0, y: region.y + srcImage.height})
} else if (region.height === srcImage.height) {
const rightImage = await extract(baseImage, {
x: region.x + region.width,
y: 0,
width: baseImage.width - (region.x + region.width),
height: baseImage.height,
})
await copy(dstImage, rightImage, {x: region.x + srcImage.width, y: 0})
} else {
const bottomLeftImage = await extract(baseImage, {
x: 0,
y: region.y + region.height,
width: region.x + region.width,
height: baseImage.height - (region.y + region.height),
})
await copy(dstImage, bottomLeftImage, {x: 0, y: region.y + srcImage.height})
const bottomRightImage = await extract(baseImage, {
x: region.x + region.width,
y: region.y + region.height,
width: baseImage.width - (region.x + region.width),
height: baseImage.height - (region.y + region.height),
})
await copy(dstImage, bottomRightImage, {
x: region.x + srcImage.width,
y: region.y + srcImage.height,
})
}
}
return dstImage
}
function _interpolateCubic(x0, x1, x2, x3, t) {

@@ -259,0 +373,0 @@ const a0 = x3 - x2 - x0 + x1

const utils = require('@applitools/utils')
const snippets = require('@applitools/snippets')
const makeScroller = require('./scroller')

@@ -17,4 +18,6 @@ const takeStitchedScreenshot = require('./takeStitchedScreenshot')

overlap,
framed,
wait,
dom,
lazyRestorePageState,
stabilization,

@@ -25,3 +28,2 @@ debug,

const originalContext = driver.currentContext
const defaultScroller = makeScroller({logger, scrollingMode})

@@ -32,8 +34,9 @@ const targetContext =

: originalContext
const scrollingStates = []
for (const nextContext of targetContext.path) {
const scrollingElement = await nextContext.getScrollRootElement()
if (hideScrollbars) await scrollingElement.hideScrollbars()
const scrollingState = await defaultScroller.getState(scrollingElement)
scrollingStates.push(scrollingState)
const scrollingElement = await nextContext.getScrollingElement()
if (scrollingElement) {
if (driver.isWeb && hideScrollbars) await scrollingElement.hideScrollbars()
await scrollingElement.preserveState()
}
}

@@ -43,5 +46,7 @@

const window = !target && (!frames || frames.length === 0)
const {context, scroller, region} = await getTargetArea({
logger,
context: targetContext,
window,
target,

@@ -52,5 +57,8 @@ fully,

const scrollerState = await scroller.getState()
// IMHO problem with scrollbars should be solved by extracting client size of the content (without scrollbars),
// here we use a historical solution
if (driver.isWeb && (hideScrollbars || fully)) await scroller.element.hideScrollbars()
const scrollerState = await scroller.preserveState()
await scrollIntoViewport({logger, context, scroller, region})
if (!window) await scrollIntoViewport({logger, context, scroller, region})

@@ -65,2 +73,3 @@ try {

overlap,
framed: framed && !region,
wait,

@@ -72,16 +81,30 @@ stabilization,

if (!dom) return screenshot
if (dom) {
// temporary solution
if (fully) {
await context.execute(snippets.setElementAttributes, [
scroller.element,
{'data-applitools-scroll': true},
])
}
// temporary solution
if (fully) {
await context.execute(
'arguments[0].setAttribute("data-applitools-scroll", "true")',
scroller.element,
)
const scrollingElement = await context.main.getScrollingElement()
await scroller.moveTo({x: 0, y: 0}, scrollingElement)
screenshot.dom = await takeDomCapture()
}
return {...screenshot, dom: await takeDomCapture()}
// ---
if (lazyRestorePageState) {
screenshot.restorePageState = restorePageState
}
return screenshot
} finally {
await scroller.restoreState(scrollerState)
if (!lazyRestorePageState) await restorePageState()
}
async function restorePageState() {
if (scroller.element) {
await scroller.element.restoreScrollbars()
await scroller.restoreState(scrollerState)
}

@@ -91,6 +114,7 @@ if (hideCaret && activeElement) await targetContext.focusElement(activeElement)

for (const prevContext of targetContext.path.reverse()) {
const scrollingElement = await prevContext.getScrollRootElement()
if (hideScrollbars) await scrollingElement.restoreScrollbars()
const scrollingState = scrollingStates.shift()
await defaultScroller.restoreState(scrollingState, scrollingElement)
const scrollingElement = await prevContext.getScrollingElement()
if (scrollingElement) {
if (driver.isWeb && hideScrollbars) await scrollingElement.restoreScrollbars()
await scrollingElement.restoreState()
}
}

@@ -102,6 +126,12 @@

async function getTargetArea({logger, context, target, fully, scrollingMode}) {
if (target) {
async function getTargetArea({logger, context, target, window, fully, scrollingMode}) {
if (window) {
const scrollingElement = await context.main.getScrollingElement()
return {
context: context.main,
scroller: makeScroller({logger, element: scrollingElement, scrollingMode}),
}
} else if (target) {
if (utils.types.has(target, ['x', 'y', 'width', 'height'])) {
const scrollingElement = await context.getScrollRootElement()
const scrollingElement = await context.getScrollingElement()
return {

@@ -118,17 +148,20 @@ context,

const isScrollable = await element.isScrollable()
const scrollingElement = isScrollable ? element : await context.getScrollRootElement()
const scrollingElement = isScrollable ? element : await context.getScrollingElement()
return {
context,
region: isScrollable ? null : await element.getRect(),
region: isScrollable ? null : await element.getRegion(),
scroller: makeScroller({
logger,
element: scrollingElement,
scrollingMode: isScrollable && scrollingMode === 'css' ? 'mixed' : scrollingMode,
scrollingMode:
scrollingMode === 'css' && !(await scrollingElement.isRoot())
? 'mixed'
: scrollingMode,
}),
}
} else {
const scrollingElement = await context.getScrollRootElement()
const scrollingElement = await context.getScrollingElement()
return {
context,
region: await element.getRect(),
region: await element.getRegion(),
scroller: makeScroller({logger, element: scrollingElement, scrollingMode}),

@@ -139,11 +172,11 @@ }

} else if (!context.isMain && !fully) {
const scrollingElement = await context.parent.getScrollRootElement()
const element = await context.getFrameElement()
const scrollingElement = await context.parent.getScrollingElement()
const element = await context.getContextElement()
return {
context: context.parent,
region: await element.getClientRect(),
region: await element.getRegion(), // IMHO we should use CLIENT (without borders) region here
scroller: makeScroller({logger, element: scrollingElement, scrollingMode}),
}
} else {
const scrollingElement = await context.getScrollRootElement()
const scrollingElement = await context.getScrollingElement()
return {

@@ -150,0 +183,0 @@ context,

@@ -10,4 +10,4 @@ const utils = require('@applitools/utils')

getInnerOffset,
getSize,
getClientRect,
getContentSize,
getClientRegion,
getScrollOffset,

@@ -19,3 +19,3 @@ getTranslateOffset,

shiftTo,
getState,
preserveState,
restoreState,

@@ -36,3 +36,3 @@ }

async function getSize() {
async function getContentSize() {
const size = await element.getContentSize()

@@ -42,4 +42,4 @@ return size

async function getClientRect() {
const region = await element.getClientRect()
async function getClientRegion() {
const region = await element.getClientRegion()
// const location = await element.context.getLocationInPage()

@@ -85,2 +85,3 @@ return region

try {
// offset = {x: Math.max(offset.x, 0), y: Math.max(offset.y, 0)}
const scrollOffset = await element.scrollTo(offset)

@@ -97,2 +98,3 @@ return scrollOffset

try {
// offset = {x: Math.max(offset.x, 0), y: Math.max(offset.y, 0)}
await element.scrollTo({x: 0, y: 0})

@@ -110,2 +112,3 @@ const translateOffset = await element.translateTo(offset)

try {
// offset = {x: Math.max(offset.x, 0), y: Math.max(offset.y, 0)}
const scrollOffset = await element.scrollTo(offset)

@@ -123,7 +126,5 @@ const remainingOffset = utils.geometry.offsetNegative(offset, scrollOffset)

async function getState(element = defaultElement) {
async function preserveState(element = defaultElement) {
try {
const scroll = await element.getScrollOffset()
const transforms = await element.getTransforms()
return {scroll, transforms}
return element.preserveState()
} catch (err) {

@@ -137,8 +138,3 @@ logger.verbose(`Failed to get current transforms!.`, err)

try {
if (state.scroll) {
await element.scrollTo(state.scroll)
}
if (state.transforms) {
await element.setTransforms(state.transforms)
}
await element.restoreState(state)
} catch (err) {

@@ -145,0 +141,0 @@ logger.verbose(`Failed to restore state!.`, err)

@@ -8,19 +8,19 @@ const utils = require('@applitools/utils')

}
const elementContextRect = region ? {...region} : await scroller.getClientRect()
const elementContextRegion = region ? {...region} : await scroller.getClientRegion()
const contextViewportLocation = await context.getLocationInViewport()
const elementViewportRect = utils.geometry.offset(elementContextRect, contextViewportLocation)
const viewportRect = await context.main.getRect()
if (utils.geometry.contains(viewportRect, elementViewportRect)) return {x: 0, y: 0}
const elementViewportRegion = utils.geometry.offset(elementContextRegion, contextViewportLocation)
const viewportRegion = await context.main.getRegion()
if (utils.geometry.contains(viewportRegion, elementViewportRegion)) return {x: 0, y: 0}
let currentContext = context
let remainingOffset = {x: elementContextRect.x, y: elementContextRect.y}
let remainingOffset = {x: elementContextRegion.x, y: elementContextRegion.y}
while (currentContext) {
const scrollRootElement = await currentContext.getScrollRootElement()
const scrollRootOffset = scrollRootElement
? await scrollRootElement.getClientRect().then(rect => ({x: rect.x, y: rect.y}))
const scrollingElement = await currentContext.getScrollingElement()
const scrollingElementOffset = scrollingElement
? utils.geometry.location(await scrollingElement.getClientRegion())
: {x: 0, y: 0}
const actualOffset = await scroller.moveTo(
utils.geometry.offsetNegative(remainingOffset, scrollRootOffset),
scrollRootElement,
utils.geometry.offsetNegative(remainingOffset, scrollingElementOffset),
scrollingElement,
)

@@ -30,3 +30,3 @@

utils.geometry.offsetNegative(remainingOffset, actualOffset),
await currentContext.getClientLocation(),
utils.geometry.location(await currentContext.getClientRegion()),
)

@@ -33,0 +33,0 @@ currentContext = currentContext.parent

const utils = require('@applitools/utils')
const saveScreenshot = require('./saveScreenshot')
const snippets = require('@applitools/snippets')
const findPattern = require('./findPattern')

@@ -11,18 +11,17 @@ const makeCalculateScaleRatio = require('./calculateScaleRatio')

return makeTakeNativeScreenshot(options)
} else if (driver.userAgent) {
if (driver.userAgent.browser === 'Firefox') {
try {
const browserVersion = Number.parseInt(driver.userAgent.browserMajorVersion, 10)
if (browserVersion >= 48 && browserVersion <= 72) {
return makeTakeMainContextScreenshot(options)
}
} catch (ignored) {}
} else if (driver.userAgent.browser === 'Safari') {
if (driver.userAgent.os === 'iOS' || driver.isIOS) {
return makeTakeMarkedScreenshot(options)
} else if (this._driver.userAgent.browserMajorVersion === '11') {
return makeTakeSafari11Screenshot(options)
} else if (driver.browserName === 'Firefox') {
try {
const browserVersion = Number.parseInt(driver.browserVersion, 10)
if (browserVersion >= 48 && browserVersion <= 72) {
return makeTakeMainContextScreenshot(options)
}
} catch (ignored) {}
} else if (driver.browserName === 'Safari') {
if (driver.isIOS) {
return makeTakeMarkedScreenshot(options)
} else if (driver.browserVersion === '11') {
return makeTakeSafari11Screenshot(options)
}
}
return makeTakeDefaultScreenshot(options)

@@ -36,7 +35,7 @@ }

const image = makeImage(await driver.takeScreenshot())
await saveScreenshot(image, {path: debug.path, name, suffix: 'original', logger})
await image.debug({path: debug.path, name, suffix: 'original'})
if (stabilization.rotate) {
await image.rotate(stabilization.rotate)
await saveScreenshot(image, {path: debug.path, name, suffix: 'rotated', logger})
await image.debug({path: debug.path, name, suffix: 'rotated'})
}

@@ -46,3 +45,3 @@

await image.crop(stabilization.crop)
await saveScreenshot(image, {path: debug.path, name, suffix: 'cropped', logger})
await image.debug({path: debug.path, name, suffix: 'cropped'})
}

@@ -55,7 +54,7 @@

const viewportSize = await driver.getViewportSize()
const documentSize = await driver.mainContext.getDocumentSize()
const documentSize = await driver.mainContext.getContentSize()
calculateScaleRatio = makeCalculateScaleRatio({
viewportWidth: viewportSize.width,
documentWidth: documentSize.width,
pixelRatio: await driver.getPixelRatio(),
pixelRatio: driver.pixelRatio,
})

@@ -65,3 +64,3 @@ }

}
await saveScreenshot(image, {path: debug.path, name, suffix: 'scaled', logger})
await image.debug({path: debug.path, name, suffix: 'scaled'})

@@ -80,7 +79,7 @@ return image

await originalContext.focus()
await saveScreenshot(image, {path: debug.path, name, suffix: 'original', logger})
await image.debug({path: debug.path, name, suffix: 'original'})
if (stabilization.rotate) {
await image.rotate(stabilization.rotate)
await saveScreenshot(image, {path: debug.path, name, suffix: 'rotated', logger})
await image.debug({path: debug.path, name, suffix: 'rotated'})
}

@@ -90,3 +89,3 @@

await image.crop(stabilization.crop)
await saveScreenshot(image, {path: debug.path, name, suffix: 'cropped', logger})
await image.debug({path: debug.path, name, suffix: 'cropped'})
}

@@ -99,7 +98,7 @@

const viewportSize = await driver.getViewportSize()
const documentSize = await driver.mainContext.getDocumentSize()
const documentSize = await driver.mainContext.getContentSize()
calculateScaleRatio = makeCalculateScaleRatio({
viewportWidth: viewportSize.width,
documentWidth: documentSize.width,
pixelRatio: await driver.getPixelRatio(),
pixelRatio: driver.pixelRatio,
})

@@ -109,3 +108,3 @@ }

}
await saveScreenshot(image, {path: debug.path, name, suffix: 'scaled', logger})
await image.debug({path: debug.path, name, suffix: 'scaled'})

@@ -117,3 +116,2 @@ return image

function makeTakeSafari11Screenshot({logger, driver, stabilization = {}, debug = {}}) {
let pixelRatio = null
let viewportSize = null

@@ -125,7 +123,7 @@ let calculateScaleRatio = null

const image = makeImage(await driver.takeScreenshot())
await saveScreenshot(image, {path: debug.path, name, suffix: 'original', logger})
await image.debug({path: debug.path, name, suffix: 'original'})
if (stabilization.rotate) {
await image.rotate(stabilization.rotate)
await saveScreenshot(image, {path: debug.path, name, suffix: 'rotated', logger})
await image.debug({path: debug.path, name, suffix: 'rotated'})
}

@@ -136,8 +134,9 @@

} else {
if (!pixelRatio) pixelRatio = await driver.getPixelRatio()
if (!viewportSize) viewportSize = await driver.getViewportSize()
const viewportLocation = await driver.mainContext.getScrollOffset()
await image.crop(utils.geometry.scale({...viewportLocation, ...viewportSize}, pixelRatio))
const viewportLocation = await driver.mainContext.execute(snippets.getElementScrollOffset, [])
await image.crop(
utils.geometry.scale({...viewportLocation, ...viewportSize}, driver.pixelRatio),
)
}
await saveScreenshot(image, {path: debug.path, name, suffix: 'cropped', logger})
await image.debug({path: debug.path, name, suffix: 'cropped'})

@@ -148,9 +147,8 @@ if (stabilization.scale) {

if (!calculateScaleRatio) {
if (!pixelRatio) pixelRatio = await driver.getPixelRatio()
if (!viewportSize) viewportSize = await driver.getViewportSize()
const documentSize = await driver.mainContext.getDocumentSize()
const documentSize = await driver.mainContext.getContentSize()
calculateScaleRatio = makeCalculateScaleRatio({
viewportWidth: viewportSize.width,
documentWidth: documentSize.width,
pixelRatio,
pixelRatio: driver.pixelRatio,
})

@@ -160,3 +158,3 @@ }

}
await saveScreenshot(image, {path: debug.path, name, suffix: 'scaled', logger})
await image.debug({path: debug.path, name, suffix: 'scaled'})

@@ -174,7 +172,7 @@ return image

const image = makeImage(await driver.takeScreenshot())
await saveScreenshot(image, {path: debug.path, name, suffix: 'original', logger})
await image.debug({path: debug.path, name, suffix: 'original'})
if (stabilization.rotate) {
await image.rotate(stabilization.rotate)
await saveScreenshot(image, {path: debug.path, name, suffix: 'rotated', logger})
await image.debug({path: debug.path, name, suffix: 'rotated'})
}

@@ -188,3 +186,3 @@

}
await saveScreenshot(image, {path: debug.path, name, suffix: 'cropped', logger})
await image.debug({path: debug.path, name, suffix: 'cropped'})

@@ -196,7 +194,7 @@ if (stabilization.scale) {

const viewportSize = await driver.getViewportSize()
const documentSize = await driver.mainContext.getDocumentSize()
const documentSize = await driver.mainContext.getContentSize()
calculateScaleRatio = makeCalculateScaleRatio({
viewportWidth: viewportSize.width,
documentWidth: documentSize.width,
pixelRatio: await driver.getPixelRatio(),
pixelRatio: driver.pixelRatio,
})

@@ -206,3 +204,3 @@ }

}
await saveScreenshot(image, {path: debug.path, name, suffix: 'scaled', logger})
await image.debug({path: debug.path, name, suffix: 'scaled'})

@@ -213,3 +211,3 @@ return image

async function getViewportRegion() {
const marker = await driver.addPageMarker()
const marker = await driver.mainContext.execute(snippets.addPageMarker)
try {

@@ -219,3 +217,3 @@ const image = makeImage(await driver.takeScreenshot())

await saveScreenshot(image, 'marker') // TODO fix
await image.debug('marker') // TODix

@@ -225,9 +223,8 @@ const markerLocation = findPattern(await image.toObject(), marker)

const pixelRation = await driver.getPixelRatio()
const viewportSize = await driver.getViewportSize()
const scaledViewportSize = utils.geometry.scale(viewportSize, pixelRation)
const scaledViewportSize = utils.geometry.scale(viewportSize, driver.pixelRatio)
return {...markerLocation, ...scaledViewportSize}
} finally {
await driver.cleanupPageMarker()
await driver.mainContext.execute(snippets.cleanupPageMarker)
}

@@ -241,11 +238,9 @@ }

const image = makeImage(
stabilization.crop || process.env.APPLITOOLS_SKIP_MOBILE_NATIVE_SCREENSHOT_HOOK
? await driver.takeScreenshot()
: await takeViewportScreenshot(),
stabilization.crop ? await driver.takeScreenshot() : await takeViewportScreenshot(),
)
await saveScreenshot(image, {path: debug.path, name, suffix: 'original', logger})
await image.debug({path: debug.path, name, suffix: 'original'})
if (stabilization.rotate) {
await image.rotate(stabilization.rotate)
await saveScreenshot(image, {path: debug.path, name, suffix: 'rotated', logger})
await image.debug({path: debug.path, name, suffix: 'rotated'})
}

@@ -255,3 +250,3 @@

await image.crop(stabilization.crop)
await saveScreenshot(image, {path: debug.path, name, suffix: 'cropped', logger})
await image.debug({path: debug.path, name, suffix: 'cropped'})
}

@@ -261,7 +256,7 @@

await image.scale(stabilization.scale)
await saveScreenshot(image, {path: debug.path, name, suffix: 'scaled', logger})
} else {
await image.scale(1 / driver.pixelRatio)
}
await image.debug({path: debug.path, name, suffix: 'scaled'})
process.env.APPLITOOLS_SKIP_MOBILE_NATIVE_SCREENSHOT_HOOK = undefined // TODO remove
return image

@@ -271,3 +266,3 @@ }

async function takeViewportScreenshot() {
const base64 = await driver.execute('mobile: viewportScreenshot')
const base64 = await driver.execute('mobile:viewportScreenshot')
// trimming line breaks since 3rd party grid providers can return them

@@ -274,0 +269,0 @@ return base64.replace(/\r\n/g, '')

const utils = require('@applitools/utils')
const makeImage = require('./image')
const makeTakeScreenshot = require('./takeScreenshot')
const saveScreenshot = require('./saveScreenshot')

@@ -12,2 +11,3 @@ async function takeStitchedScreenshot({

overlap = 50,
framed,
wait,

@@ -19,3 +19,3 @@ stabilization,

const scrollerRegion = utils.geometry.region({x: 0, y: 0}, await scroller.getSize())
const scrollerRegion = utils.geometry.region({x: 0, y: 0}, await scroller.getContentSize())
logger.verbose(`Scroller size: ${scrollerRegion}`)

@@ -34,8 +34,17 @@

let image = await takeScreenshot({name: 'initial'})
const frameImage = framed ? makeImage(image) : null
const cropRegion = await context.getRegionInViewport(region || (await scroller.getClientRect()))
const targetRegion = region || (await scroller.getClientRegion())
// TODO the solution should not check driver specifics,
// in this case target region coordinate should be already related to the scrolling element of the context
const cropRegion = driver.isNative
? targetRegion
: await driver.getRegionInViewport(context, targetRegion)
console.log({cropRegion})
logger.verbose('cropping...')
await image.crop(cropRegion)
await saveScreenshot(image, {path: debug.path, name: 'initial', suffix: 'region', logger})
await image.debug({path: debug.path, name: 'initial', suffix: 'region'})

@@ -52,6 +61,5 @@ if (region) region = utils.geometry.intersect(region, scrollerRegion)

const partSize = {width: image.width, height: Math.max(image.height - overlap, 10)}
logger.verbose(`Image part size: ${partSize}`)
const [_, ...partRegions] = utils.geometry.divide(region, partSize)
// TODO padding should be provided from args instead of overlap
const padding = {top: driver.isNative ? overlap : 0, bottom: overlap}
const [initialRegion, ...partRegions] = utils.geometry.divide(region, image.size, padding)
logger.verbose('Part regions', partRegions)

@@ -69,12 +77,13 @@

for (const partRegion of partRegions) {
const partOffset = {x: partRegion.x, y: partRegion.y}
const partSize = {width: partRegion.width, height: partRegion.height}
const partName = `${partRegion.x}_${partRegion.y}_${partRegion.width}x${partRegion.height}`
logger.verbose(`Processing part ${partName}`)
logger.verbose(`Move to ${partOffset}`)
const actualOffset = await scroller.moveTo(partOffset)
const topPadding = initialRegion.y !== partRegion.y ? padding.top : 0
const partOffset = utils.geometry.location(partRegion)
const requiredOffset = utils.geometry.offsetNegative(partOffset, {x: 0, y: topPadding})
logger.verbose(`Move to ${requiredOffset}`)
const actualOffset = await scroller.moveTo(requiredOffset)
const remainingOffset = utils.geometry.offsetNegative(
utils.geometry.offsetNegative(partOffset, actualOffset),
utils.geometry.offsetNegative(requiredOffset, actualOffset),
expectedRemainingOffset,

@@ -84,5 +93,5 @@ )

x: cropRegion.x + remainingOffset.x,
y: cropRegion.y + remainingOffset.y,
width: Math.min(cropRegion.width, partSize.width),
height: Math.min(cropRegion.height, partSize.height),
y: cropRegion.y + remainingOffset.y + topPadding,
width: Math.min(cropRegion.width, partRegion.width),
height: Math.min(cropRegion.height, partRegion.height),
}

@@ -94,17 +103,17 @@ logger.verbose(`Actual offset is ${actualOffset}, remaining offset is ${remainingOffset}`)

// TODO maybe remove
if (!utils.geometry.isEmpty(cropPartRegion)) {
logger.verbose('Getting image...')
image = await takeScreenshot({name: partName})
if (utils.geometry.isEmpty(cropPartRegion)) continue
logger.verbose('cropping...')
await image.crop(cropPartRegion)
await saveScreenshot(image, {path: debug.path, name: partName, suffix: 'region', logger})
logger.verbose('Getting image...')
image = await takeScreenshot({name: partName})
await composition.copy(
await image.toObject(),
utils.geometry.offsetNegative(partOffset, initialOffset),
)
logger.verbose('cropping...')
await image.crop(cropPartRegion)
await image.debug({path: debug.path, name: partName, suffix: 'region'})
stitchedSize = {width: partOffset.x + image.width, height: partOffset.y + image.height}
}
await composition.copy(
await image.toObject(),
utils.geometry.offsetNegative(partOffset, initialOffset),
)
stitchedSize = {width: partOffset.x + image.width, height: partOffset.y + image.height}
}

@@ -125,6 +134,25 @@

await saveScreenshot(composition, {path: debug.path, name: 'stitched', logger})
return {image: composition, region: cropRegion}
await composition.debug({path: debug.path, name: 'stitched'})
const locationInMainContext = await context.getLocationInMainContext()
if (frameImage) {
await frameImage.replace(await composition.toObject(), cropRegion)
await frameImage.debug({path: debug.path, name: 'framed'})
return {
image: frameImage,
region: utils.geometry.region({x: 0, y: 0}, frameImage.size),
}
} else {
return {
image: composition,
region: utils.geometry.region(
utils.geometry.offset(locationInMainContext, targetRegion),
composition.size,
),
}
}
}
module.exports = takeStitchedScreenshot
const utils = require('@applitools/utils')
const makeTakeScreenshot = require('./takeScreenshot')
const saveScreenshot = require('./saveScreenshot')

@@ -16,8 +15,9 @@ async function takeViewportScreenshot({logger, context, region, wait, stabilization, debug = {}}) {

if (region) {
const cropRegion = await context.getRegionInViewport(region)
const cropRegion = await driver.getRegionInViewport(context, region)
if (utils.geometry.isEmpty(cropRegion)) throw new Error('Screenshot region is out of viewport')
await image.crop(cropRegion)
await saveScreenshot(image, {path: debug.path, suffix: 'region', logger})
return {image, region: cropRegion}
await image.debug({path: debug.path, suffix: 'region'})
return {image, region: utils.geometry.offset(region, await context.getLocationInMainContext())}
} else {
return {image, region: {x: 0, y: 0, width: image.width, height: image.height}}
return {image, region: utils.geometry.region(await context.main.getInnerOffset(), image.size)}
}

@@ -24,0 +24,0 @@ }

@@ -66,3 +66,3 @@ const assert = require('assert')

const composition = makeImage({width: image.width, height: image.height * 2})
await composition.copy(image, {x: 0, y: 0})
await composition.copy(image, {x: 0.1, y: 0.2})
await composition.copy(image, {x: 0, y: image.height})

@@ -82,2 +82,34 @@ const actual = await composition.toObject()

})
it('should replace region in image with a higher and wider image', async () => {
const image = await makeImage('./test/fixtures/image/house.png')
const replace = await makeImage({width: 200, height: 200}).toObject()
replace.data.fill(Buffer.from([0xff, 0, 0, 0xff]))
await image.replace(replace, {x: 200, y: 200, width: 100, height: 100})
const actual = await image.toObject()
const expected = await makeImage(
'./test/fixtures/image/house.replaced-higher-wider.png',
).toObject()
assert.ok(pixelmatch(actual.data, expected.data, null, expected.width, expected.height) === 0)
})
it('should replace region in image with a higher image', async () => {
const image = await makeImage('./test/fixtures/image/house.png')
const replace = await makeImage({width: 200, height: 200}).toObject()
replace.data.fill(Buffer.from([0, 0xff, 0, 0xff]))
await image.replace(replace, {x: 200, y: 200, width: 200, height: 100})
const actual = await image.toObject()
const expected = await makeImage('./test/fixtures/image/house.replaced-higher.png').toObject()
assert.ok(pixelmatch(actual.data, expected.data, null, expected.width, expected.height) === 0)
})
it('should replace region in image with a higher image', async () => {
const image = await makeImage('./test/fixtures/image/house.png')
const replace = await makeImage({width: 200, height: 200}).toObject()
replace.data.fill(Buffer.from([0, 0, 0xff, 0xff]))
await image.replace(replace, {x: 200, y: 200, width: 100, height: 200})
const actual = await image.toObject()
const expected = await makeImage('./test/fixtures/image/house.replaced-wider.png').toObject()
assert.ok(pixelmatch(actual.data, expected.data, null, expected.width, expected.height) === 0)
})
})
const webdriverio = require('webdriverio')
const utils = require('@applitools/utils')
const driver = require('@applitools/driver')
const {Driver} = require('@applitools/driver')

@@ -15,7 +15,5 @@ // #region HELPERS

function transformSelector(selector) {
if (utils.types.has(selector, ['type', 'selector'])) {
if (selector.type === 'css') return `css selector:${selector.selector}`
else if (selector.type === 'xpath') return `xpath:${selector.selector}`
}
return selector
if (!utils.types.has(selector, ['type', 'selector'])) return selector
else if (selector.type === 'css') return `css selector:${selector.selector}`
else return `${selector.type}:${selector.selector}`
}

@@ -27,4 +25,5 @@

function isDriver(page) {
return page.constructor.name === 'Browser'
function isDriver(browser) {
if (!browser) return false
return browser.constructor.name === 'Browser'
}

@@ -90,31 +89,28 @@ function isElement(element) {

}
async function getWindowRect(browser) {
async function getElementRegion(browser, element) {
const extendedElement = await browser.$(element)
if (utils.types.isFunction(extendedElement, 'getRect')) {
return extendedElement.getRect()
} else {
const size = await extendedElement.getSize()
const location = utils.types.has(size, ['x', 'y']) ? size : await extendedElement.getLocation()
return {x: location.x, y: location.y, width: size.width, height: size.height}
}
}
async function getElementAttribute(browser, element, name) {
return browser.getElementAttribute(extractElementId(element), name)
}
async function getWindowSize(browser) {
if (utils.types.isFunction(browser.getWindowRect)) {
return browser.getWindowRect()
} else {
const rect = {x: 0, y: 0, width: 0, height: 0}
if (utils.types.isFunction(browser.getWindowPosition)) {
const location = await browser.getWindowPosition()
rect.x = location.x
rect.y = location.y
}
if (utils.types.isFunction(browser.getWindowSize)) {
const size = await browser.getWindowSize()
rect.width = size.width
rect.height = size.height
}
return rect
} else if (utils.types.isFunction(browser.getWindowSize)) {
return await browser.getWindowSize()
}
}
async function setWindowRect(browser, rect = {}) {
const {x = null, y = null, width = null, height = null} = rect
async function setWindowSize(browser, {width, height} = {}) {
if (utils.types.isFunction(browser.setWindowRect)) {
await browser.setWindowRect(x, y, width, height)
await browser.setWindowRect(0, 0, width, height)
} else {
if (utils.types.isFunction(browser.setWindowPosition) && x !== null && y !== null) {
await browser.setWindowPosition(x, y)
}
if (utils.types.isFunction(browser.setWindowSize) && width !== null && height !== null) {
await browser.setWindowSize(width, height)
}
await browser.setWindowPosition(0, 0)
await browser.setWindowSize(width, height)
}

@@ -127,3 +123,3 @@ }

async function getDriverInfo(browser) {
return {
const driverInfo = {
sessionId: browser.sessionId,

@@ -139,3 +135,23 @@ isMobile: browser.isMobile,

browserVersion: browser.capabilities.browserVersion,
pixelRatio: browser.capabilities.pixelRatio,
}
if (driverInfo.isNative) {
const {pixelRatio, viewportRect} =
browser.capabilities.viewportRect && browser.capabilities.pixelRatio
? browser.capabilities
: await browser.getSession()
driverInfo.pixelRatio = pixelRatio
if (viewportRect) {
driverInfo.viewportRegion = {
x: viewportRect.left,
y: viewportRect.top,
width: viewportRect.width,
height: viewportRect.height,
}
}
}
return driverInfo
}

@@ -150,4 +166,11 @@ async function takeScreenshot(driver) {

if (isSelector(element)) element = await findElement(browser, element)
return element.click()
const extendedElement = await browser.$(element)
await extendedElement.click()
}
async function performAction(browser, actions) {
return browser.touchAction(actions)
}
async function getElementText(browser, element) {
return browser.getElementText(extractElementId(element))
}

@@ -169,4 +192,6 @@ // #endregion

findElements,
getWindowRect,
setWindowRect,
getElementRegion,
getElementAttribute,
getWindowSize,
setWindowSize,
getOrientation,

@@ -177,21 +202,84 @@ getDriverInfo,

click,
performAction,
getElementText,
}
async function makeDriver() {
const browser = await webdriverio.remote({
protocol: 'http',
hostname: 'localhost',
path: '/wd/hub',
port: 4444,
logLevel: 'silent',
capabilities: {
browserName: 'chrome',
async function makeDriver({type = 'web'} = {}) {
const capabilities = {
web: {
protocol: 'http',
hostname: 'localhost',
path: '/wd/hub',
port: 4444,
logLevel: 'silent',
capabilities: {
browserName: 'chrome',
},
},
})
android: {
protocol: 'https',
hostname: 'ondemand.saucelabs.com',
path: '/wd/hub',
port: 443,
logLevel: 'silent',
capabilities: {
name: 'Android Screenshoter Test',
browserName: '',
platformName: 'Android',
platformVersion: '7.0',
appiumVersion: '1.20.2',
deviceName: 'Samsung Galaxy S8 FHD GoogleAPI Emulator',
automationName: 'uiautomator2',
app: 'https://applitools.jfrog.io/artifactory/Examples/android/1.3/app-debug.apk',
username: process.env.SAUCE_USERNAME,
accessKey: process.env.SAUCE_ACCESS_KEY,
},
},
androidx: {
protocol: 'https',
hostname: 'ondemand.saucelabs.com',
path: '/wd/hub',
port: 443,
logLevel: 'silent',
capabilities: {
name: 'AndroidX Screenshoter Test',
browserName: '',
platformName: 'Android',
platformVersion: '10.0',
appiumVersion: '1.20.2',
deviceName: 'Google Pixel 3a XL GoogleAPI Emulator',
automationName: 'uiautomator2',
app: 'https://applitools.jfrog.io/artifactory/Examples/androidx/1.2.0/app_androidx.apk',
username: process.env.SAUCE_USERNAME,
accessKey: process.env.SAUCE_ACCESS_KEY,
},
},
ios: {
protocol: 'https',
hostname: 'ondemand.saucelabs.com',
path: '/wd/hub',
port: 443,
logLevel: 'silent',
capabilities: {
name: 'iOS Screenshoter Test',
deviceName: 'iPhone 11 Pro Simulator',
platformName: 'iOS',
platformVersion: '13.4',
appiumVersion: '1.19.2',
automationName: 'XCUITest',
app:
'https://applitools.jfrog.io/artifactory/Examples/IOSTestApp/1.5/app/IOSTestApp-1.5.zip',
username: process.env.SAUCE_USERNAME,
accessKey: process.env.SAUCE_ACCESS_KEY,
},
},
}
const logger = {log: () => null, verbose: () => null}
const browser = await webdriverio.remote(capabilities[type])
return [driver.makeDriver(spec, logger, browser), () => browser.deleteSession()]
const logger = {log: () => {}, warn: () => {}, error: () => {}}
return [new Driver({spec, logger, driver: browser}), () => browser.deleteSession()]
}
module.exports = makeDriver

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc