@applitools/screenshoter 3.2.8 → 3.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.bongo/dry-run/package-lock.json +49 -6
- package/.bongo/dry-run/package.json +5 -0
- package/.bongo/dry-run.tgz +0 -0
- package/CHANGELOG.md +19 -0
- package/index.js +2 -1
- package/package.json +7 -4
- package/src/find-image-pattern.js +11 -38
- package/src/image.js +107 -64
- package/src/take-screenshot.js +136 -160
- package/src/take-simple-screenshot.js +25 -0
- package/src/take-stitched-screenshot.js +34 -40
- package/src/take-viewport-screenshot.js +179 -16
- package/test/e2e/android.spec.js +120 -13
- package/test/e2e/external.spec.js +155 -0
- package/test/e2e/ios.spec.js +142 -12
- package/test/e2e/web-ios.spec.js +19 -6
- package/test/e2e/web.spec.js +33 -21
- package/test/fixtures/android/app-fully-non-scrollable.png +0 -0
- package/test/fixtures/android/app-fully-recycler.png +0 -0
- package/test/fixtures/android/app-fully-scroll-statusbar.png +0 -0
- package/test/fixtures/android/app-fully-scroll.png +0 -0
- package/test/fixtures/android/app-statusbar.png +0 -0
- package/test/fixtures/android/x-app-fully-collapsing.png +0 -0
- package/test/fixtures/android/x-app-fully-recycler.png +0 -0
- package/test/fixtures/android/x-element-fully.png +0 -0
- package/test/fixtures/external/agl.png +0 -0
- package/test/fixtures/image/{house.combined-higher-wider.png → house.framed-higher-wider.png} +0 -0
- package/test/fixtures/image/{house.combined-higher.png → house.framed-higher.png} +0 -0
- package/test/fixtures/image/house.framed-shorter-thinner.png +0 -0
- package/test/fixtures/image/{house.combined-wider.png → house.framed-wider.png} +0 -0
- package/test/fixtures/ios/app-fully-collapsing.png +0 -0
- package/test/fixtures/ios/app-fully-collection.png +0 -0
- package/test/fixtures/ios/app-fully-overlapped-statusbar.png +0 -0
- package/test/fixtures/ios/app-fully-overlapped.png +0 -0
- package/test/fixtures/ios/app-fully-scroll-statusbar.png +0 -0
- package/test/fixtures/ios/app-fully-scroll.png +0 -0
- package/test/fixtures/ios/app-fully-superview.png +0 -0
- package/test/fixtures/ios/app-fully-table.png +0 -0
- package/test/fixtures/ios/app-statusbar.png +0 -0
- package/test/fixtures/ios/app.png +0 -0
- package/test/fixtures/ios/element-fully.png +0 -0
- package/test/fixtures/ios/element.png +0 -0
- package/test/fixtures/ios/region.png +0 -0
- package/test/fixtures/ios/webview-fully.png +0 -0
- package/test/fixtures/ios/webview.png +0 -0
- package/test/fixtures/pattern/iPad_5th_landscape.png +0 -0
- package/test/fixtures/pattern/iPad_5th_portrait.png +0 -0
- package/test/fixtures/pattern/iPad_9th_landscape.png +0 -0
- package/test/fixtures/pattern/iPad_9th_portrait.png +0 -0
- package/test/fixtures/pattern/iPhone_11_landscape.png +0 -0
- package/test/fixtures/pattern/iPhone_11_portrait.png +0 -0
- package/test/fixtures/pattern/iPhone_13_landscape.png +0 -0
- package/test/fixtures/pattern/iPhone_13_portrait.png +0 -0
- package/test/fixtures/pattern/iPhone_SE_landscape.png +0 -0
- package/test/fixtures/pattern/iPhone_SE_portrait.png +0 -0
- package/test/fixtures/pattern/iPhone_XS_portrait_noviewport.png +0 -0
- package/test/fixtures/web/frame-fully.png +0 -0
- package/test/fixtures/web/frame.png +0 -0
- package/test/fixtures/web/inner-element-fully.png +0 -0
- package/test/fixtures/web/inner-element.png +0 -0
- package/test/fixtures/web/inner-region-fully.png +0 -0
- package/test/fixtures/web/inner-region.png +0 -0
- package/test/fixtures/web/page-fully.png +0 -0
- package/test/fixtures/web/page.png +0 -0
- package/test/fixtures/web/region-fully.png +0 -0
- package/test/fixtures/web/region.png +0 -0
- package/test/fixtures/web-ios/page-fully.png +0 -0
- package/test/it/find-pattern.spec.js +16 -11
- package/test/it/image.spec.js +42 -15
- package/docker-compose.yaml +0 -29
- package/src/calculate-screenshot-regions.js +0 -31
- package/src/screenshoter.js +0 -158
- package/test/fixtures/pattern/iPad_Air_portrait.png +0 -0
- package/test/fixtures/pattern/iPhone_5S_landscape.png +0 -0
- package/test/fixtures/pattern/iPhone_XR_perfecto_landscape.png +0 -0
- package/test/fixtures/pattern/iPhone_XS_Max_perfecto_landscape.png +0 -0
- package/test/fixtures/pattern/iPhone_XS_landscape.png +0 -0
- package/test/fixtures/pattern/iPhone_XS_portrait.png +0 -0
- package/test/fixtures/pattern/iPhone_X_perfecto_portrait.png +0 -0
- package/test/util/spec-driver.js +0 -288
package/src/take-screenshot.js
CHANGED
|
@@ -1,183 +1,159 @@
|
|
|
1
1
|
const utils = require('@applitools/utils')
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
2
|
+
const makeScroller = require('./scroller')
|
|
3
|
+
const scrollIntoViewport = require('./scroll-into-viewport')
|
|
4
|
+
const takeStitchedScreenshot = require('./take-stitched-screenshot')
|
|
5
|
+
const takeSimpleScreenshot = require('./take-simple-screenshot')
|
|
6
|
+
|
|
7
|
+
async function takeScreenshot({
|
|
8
|
+
driver,
|
|
9
|
+
frames = [],
|
|
10
|
+
region,
|
|
11
|
+
fully,
|
|
12
|
+
scrollingMode,
|
|
13
|
+
hideScrollbars,
|
|
14
|
+
hideCaret,
|
|
15
|
+
withStatusBar,
|
|
16
|
+
overlap,
|
|
17
|
+
framed,
|
|
18
|
+
wait,
|
|
19
|
+
stabilization,
|
|
20
|
+
hooks,
|
|
21
|
+
debug,
|
|
22
|
+
logger,
|
|
23
|
+
}) {
|
|
24
|
+
// screenshot of a window/app was requested (fully or viewport)
|
|
25
|
+
const window = !region && (!frames || frames.length === 0)
|
|
26
|
+
// framed screenshots could be taken only when screenshot of window/app fully was requested
|
|
27
|
+
framed = framed && fully && window
|
|
28
|
+
// screenshots with status bar could be taken only when screenshot of app or framed app fully was requested
|
|
29
|
+
withStatusBar = withStatusBar && driver.isNative && window && (!fully || framed)
|
|
30
|
+
scrollingMode = driver.isNative ? 'scroll' : scrollingMode
|
|
31
|
+
|
|
32
|
+
const activeContext = driver.currentContext
|
|
33
|
+
const context =
|
|
34
|
+
frames.length > 0
|
|
35
|
+
? await activeContext.context(frames.reduce((parent, frame) => ({...frame, parent}), null))
|
|
36
|
+
: activeContext
|
|
37
|
+
|
|
38
|
+
// traverse from main context to target context to hide scrollbars and preserve context state (scroll/translate position)
|
|
39
|
+
for (const nextContext of context.path) {
|
|
40
|
+
const scrollingElement = await nextContext.getScrollingElement()
|
|
41
|
+
// unlike web apps, native apps do not always have scrolling element
|
|
42
|
+
if (scrollingElement) {
|
|
43
|
+
if (driver.isWeb && hideScrollbars) await scrollingElement.hideScrollbars()
|
|
44
|
+
await scrollingElement.preserveState()
|
|
22
45
|
}
|
|
23
46
|
}
|
|
24
47
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
function makeTakeDefaultScreenshot({driver, stabilization = {}, debug, logger}) {
|
|
29
|
-
const calculateScaleRatio = makeCalculateScaleRatio({driver})
|
|
30
|
-
return async function takeScreenshot({name} = {}) {
|
|
31
|
-
logger.verbose('Taking screenshot...')
|
|
32
|
-
const image = makeImage(await driver.takeScreenshot())
|
|
33
|
-
await image.debug({...debug, name, suffix: 'original'})
|
|
48
|
+
// blur active element in target context
|
|
49
|
+
const activeElement = driver.isWeb && hideCaret ? await context.blurElement() : null
|
|
34
50
|
|
|
35
|
-
|
|
36
|
-
else image.scale(await calculateScaleRatio(image.width))
|
|
51
|
+
const target = await getTarget({window, context, region, fully, scrollingMode, logger})
|
|
37
52
|
|
|
38
|
-
|
|
53
|
+
if (driver.isWeb && hideScrollbars) await target.scroller.hideScrollbars()
|
|
39
54
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return image
|
|
43
|
-
}
|
|
44
|
-
}
|
|
55
|
+
try {
|
|
56
|
+
if (!window) await scrollIntoViewport({...target, logger})
|
|
45
57
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const originalContext = driver.currentContext
|
|
51
|
-
await driver.mainContext.focus()
|
|
52
|
-
const image = makeImage(await driver.takeScreenshot())
|
|
53
|
-
await originalContext.focus()
|
|
54
|
-
await image.debug({...debug, name, suffix: 'original'})
|
|
58
|
+
const screenshot =
|
|
59
|
+
fully && target.scroller
|
|
60
|
+
? await takeStitchedScreenshot({...target, withStatusBar, overlap, framed, wait, stabilization, debug, logger})
|
|
61
|
+
: await takeSimpleScreenshot({...target, withStatusBar, wait, stabilization, debug, logger})
|
|
55
62
|
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return image
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function makeTakeSafari11Screenshot({driver, stabilization = {}, debug, logger}) {
|
|
68
|
-
const calculateScaleRatio = makeCalculateScaleRatio({driver})
|
|
69
|
-
let viewportSize
|
|
70
|
-
|
|
71
|
-
return async function takeScreenshot({name} = {}) {
|
|
72
|
-
logger.verbose('Taking safari 11 driver screenshot...')
|
|
73
|
-
const image = makeImage(await driver.takeScreenshot())
|
|
74
|
-
await image.debug({...debug, name, suffix: 'original'})
|
|
75
|
-
|
|
76
|
-
if (stabilization.scale) image.scale(stabilization.scale)
|
|
77
|
-
else image.scale(await calculateScaleRatio(image.width))
|
|
78
|
-
|
|
79
|
-
if (stabilization.rotate) image.rotate(stabilization.rotate)
|
|
80
|
-
|
|
81
|
-
if (stabilization.crop) image.crop(stabilization.crop)
|
|
82
|
-
else {
|
|
83
|
-
if (!viewportSize) viewportSize = await driver.getViewportSize()
|
|
84
|
-
const viewportLocation = await driver.mainContext.execute(snippets.getElementScrollOffset, [])
|
|
85
|
-
image.crop(utils.geometry.region(viewportLocation, viewportSize))
|
|
63
|
+
if (hooks && hooks.afterScreenshot) {
|
|
64
|
+
// imitate image-like state for the hook
|
|
65
|
+
if (window && fully && target.scroller) {
|
|
66
|
+
await target.scroller.moveTo({x: 0, y: 0}, await driver.mainContext.getScrollingElement())
|
|
67
|
+
}
|
|
68
|
+
await hooks.afterScreenshot({driver, scroller: target.scroller, screenshot})
|
|
86
69
|
}
|
|
87
70
|
|
|
88
|
-
return
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
function makeTakeMarkedScreenshot({driver, stabilization = {}, debug, logger}) {
|
|
93
|
-
const calculateScaleRatio = makeCalculateScaleRatio({driver})
|
|
94
|
-
let viewportRegion
|
|
95
|
-
|
|
96
|
-
return async function takeScreenshot({name} = {}) {
|
|
97
|
-
logger.verbose('Taking viewport screenshot (using markers)...')
|
|
98
|
-
const image = makeImage(await driver.takeScreenshot())
|
|
99
|
-
await image.debug({...debug, name, suffix: 'original'})
|
|
100
|
-
|
|
101
|
-
if (stabilization.scale) image.scale(stabilization.scale)
|
|
102
|
-
else image.scale(await calculateScaleRatio(image.width))
|
|
103
|
-
|
|
104
|
-
if (stabilization.rotate) image.rotate(stabilization.rotate)
|
|
105
|
-
|
|
106
|
-
if (stabilization.crop) image.crop(stabilization.crop)
|
|
107
|
-
else {
|
|
108
|
-
if (!viewportRegion) viewportRegion = await getViewportRegion()
|
|
109
|
-
image.crop(viewportRegion)
|
|
110
|
-
await image.debug({...debug, name, suffix: 'viewport'})
|
|
71
|
+
return screenshot
|
|
72
|
+
} finally {
|
|
73
|
+
if (target.scroller) {
|
|
74
|
+
await target.scroller.restoreScrollbars()
|
|
111
75
|
}
|
|
112
76
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
async function getViewportRegion() {
|
|
117
|
-
const marker = await driver.mainContext.execute(snippets.addPageMarker)
|
|
118
|
-
try {
|
|
119
|
-
const image = makeImage(await driver.takeScreenshot())
|
|
120
|
-
|
|
121
|
-
if (stabilization.rotate) await image.rotate(stabilization.rotate)
|
|
77
|
+
// if there was active element and we have blurred it, then restore focus
|
|
78
|
+
if (activeElement) await context.focusElement(activeElement)
|
|
122
79
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
if (
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return utils.geometry.region(utils.geometry.scale(markerLocation, 1 / driver.pixelRatio), viewportSize)
|
|
131
|
-
} finally {
|
|
132
|
-
await driver.mainContext.execute(snippets.cleanupPageMarker)
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function makeTakeNativeScreenshot({driver, stabilization = {}, debug, logger}) {
|
|
138
|
-
return async function takeScreenshot({name, withStatusBar} = {}) {
|
|
139
|
-
logger.verbose('Taking native driver screenshot...')
|
|
140
|
-
const image = makeImage(await driver.takeScreenshot())
|
|
141
|
-
await image.debug({...debug, name, suffix: 'original'})
|
|
142
|
-
|
|
143
|
-
if (stabilization.scale) image.scale(stabilization.scale)
|
|
144
|
-
else image.scale(1 / driver.pixelRatio)
|
|
145
|
-
|
|
146
|
-
if (stabilization.rotate) image.rotate(stabilization.rotate)
|
|
147
|
-
|
|
148
|
-
if (stabilization.crop) image.crop(stabilization.crop)
|
|
149
|
-
else {
|
|
150
|
-
const viewportSize = await driver.getViewportSize()
|
|
151
|
-
const cropRegion = withStatusBar
|
|
152
|
-
? {x: 0, y: 0, width: viewportSize.width, height: viewportSize.height + driver.statusBarHeight}
|
|
153
|
-
: {x: 0, y: driver.statusBarHeight, width: viewportSize.width, height: viewportSize.height}
|
|
154
|
-
image.crop(cropRegion)
|
|
155
|
-
await image.debug({...debug, name, suffix: `viewport${withStatusBar ? '-with-statusbar' : ''}`})
|
|
80
|
+
// traverse from target context to the main context to restore scrollbars and context states
|
|
81
|
+
for (const prevContext of context.path.reverse()) {
|
|
82
|
+
const scrollingElement = await prevContext.getScrollingElement()
|
|
83
|
+
if (scrollingElement) {
|
|
84
|
+
if (driver.isWeb && hideScrollbars) await scrollingElement.restoreScrollbars()
|
|
85
|
+
await scrollingElement.restoreState()
|
|
86
|
+
}
|
|
156
87
|
}
|
|
157
88
|
|
|
158
|
-
|
|
89
|
+
// restore focus on original active context
|
|
90
|
+
await activeContext.focus()
|
|
159
91
|
}
|
|
160
92
|
}
|
|
161
93
|
|
|
162
|
-
function
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
94
|
+
async function getTarget({window, context, region, fully, scrollingMode, logger}) {
|
|
95
|
+
if (window) {
|
|
96
|
+
// window/app
|
|
97
|
+
const scrollingElement = await context.main.getScrollingElement()
|
|
98
|
+
return {
|
|
99
|
+
context: context.main,
|
|
100
|
+
scroller: scrollingElement ? makeScroller({element: scrollingElement, scrollingMode, logger}) : null,
|
|
101
|
+
}
|
|
102
|
+
} else if (region) {
|
|
103
|
+
if (utils.types.has(region, ['x', 'y', 'width', 'height'])) {
|
|
104
|
+
// region by coordinates
|
|
105
|
+
const scrollingElement = await context.getScrollingElement()
|
|
106
|
+
return {
|
|
107
|
+
context,
|
|
108
|
+
region,
|
|
109
|
+
scroller: scrollingElement ? makeScroller({element: scrollingElement, scrollingMode, logger}) : null,
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
// region by element or selector
|
|
113
|
+
const element = await context.element(region)
|
|
114
|
+
if (!element) throw new Error('Element not found!')
|
|
115
|
+
|
|
116
|
+
const elementContext = element.context
|
|
117
|
+
|
|
118
|
+
if (fully) {
|
|
119
|
+
const isScrollable = await element.isScrollable()
|
|
120
|
+
// if element is scrollable, then take screenshot of the full element content, otherwise take screenshot of full element
|
|
121
|
+
const region = isScrollable ? null : await element.getRegion()
|
|
122
|
+
const scrollingElement = isScrollable ? element : await elementContext.getScrollingElement()
|
|
123
|
+
// css stitching could be applied only to root element of its context
|
|
124
|
+
scrollingMode = scrollingMode === 'css' && !(await scrollingElement.isRoot()) ? 'mixed' : scrollingMode
|
|
125
|
+
return {
|
|
126
|
+
context: elementContext,
|
|
127
|
+
region,
|
|
128
|
+
scroller: scrollingElement ? makeScroller({element: scrollingElement, scrollingMode, logger}) : null,
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
const scrollingElement = await context.getScrollingElement()
|
|
132
|
+
return {
|
|
133
|
+
context: elementContext,
|
|
134
|
+
region: await element.getRegion(),
|
|
135
|
+
scroller: scrollingElement ? makeScroller({element: scrollingElement, scrollingMode, logger}) : null,
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
} else if (!context.isMain) {
|
|
140
|
+
// context
|
|
141
|
+
if (fully) {
|
|
142
|
+
const scrollingElement = await context.getScrollingElement()
|
|
143
|
+
return {
|
|
144
|
+
context,
|
|
145
|
+
scroller: scrollingElement ? makeScroller({logger, element: scrollingElement, scrollingMode}) : null,
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
148
|
+
const scrollingElement = await context.parent.getScrollingElement()
|
|
149
|
+
const element = await context.getContextElement()
|
|
150
|
+
return {
|
|
151
|
+
context: context.parent,
|
|
152
|
+
region: await element.getRegion(), // IMHO we should use CLIENT (without borders) region here
|
|
153
|
+
scroller: scrollingElement ? makeScroller({logger, element: scrollingElement, scrollingMode}) : null,
|
|
154
|
+
}
|
|
176
155
|
}
|
|
177
|
-
|
|
178
|
-
const scaledImageWidth = Math.round(imageWidth / driver.pixelRatio)
|
|
179
|
-
return viewportWidth / scaledImageWidth / driver.pixelRatio
|
|
180
156
|
}
|
|
181
157
|
}
|
|
182
158
|
|
|
183
|
-
module.exports =
|
|
159
|
+
module.exports = takeScreenshot
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const utils = require('@applitools/utils')
|
|
2
|
+
const makeTakeViewportScreenshot = require('./take-viewport-screenshot')
|
|
3
|
+
|
|
4
|
+
async function takeSimpleScreenshot({context, region, withStatusBar, wait, stabilization, debug = {}, logger}) {
|
|
5
|
+
logger.verbose('Taking image of...')
|
|
6
|
+
|
|
7
|
+
const driver = context.driver
|
|
8
|
+
const takeViewportScreenshot = makeTakeViewportScreenshot({logger, driver, stabilization, debug})
|
|
9
|
+
|
|
10
|
+
await utils.general.sleep(wait)
|
|
11
|
+
|
|
12
|
+
const image = await takeViewportScreenshot({withStatusBar})
|
|
13
|
+
|
|
14
|
+
if (region) {
|
|
15
|
+
const cropRegion = await driver.getRegionInViewport(context, region)
|
|
16
|
+
if (utils.geometry.isEmpty(cropRegion)) throw new Error('Screenshot region is out of viewport')
|
|
17
|
+
image.crop(cropRegion)
|
|
18
|
+
await image.debug({path: debug.path, suffix: 'region'})
|
|
19
|
+
return {image, region: cropRegion}
|
|
20
|
+
} else {
|
|
21
|
+
return {image, region: utils.geometry.region({x: 0, y: 0}, image.size)}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = takeSimpleScreenshot
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const utils = require('@applitools/utils')
|
|
2
2
|
const makeImage = require('./image')
|
|
3
|
-
const
|
|
3
|
+
const makeTakeViewportScreenshot = require('./take-viewport-screenshot')
|
|
4
4
|
|
|
5
5
|
async function takeStitchedScreenshot({
|
|
6
6
|
logger,
|
|
@@ -8,7 +8,7 @@ async function takeStitchedScreenshot({
|
|
|
8
8
|
scroller,
|
|
9
9
|
region,
|
|
10
10
|
withStatusBar,
|
|
11
|
-
overlap = 50,
|
|
11
|
+
overlap = {top: 10, bottom: 50},
|
|
12
12
|
framed,
|
|
13
13
|
wait,
|
|
14
14
|
stabilization,
|
|
@@ -17,7 +17,7 @@ async function takeStitchedScreenshot({
|
|
|
17
17
|
logger.verbose('Taking full image of...')
|
|
18
18
|
|
|
19
19
|
const driver = context.driver
|
|
20
|
-
const
|
|
20
|
+
const takeViewportScreenshot = makeTakeViewportScreenshot({logger, driver, stabilization, debug})
|
|
21
21
|
const scrollerState = await scroller.preserveState()
|
|
22
22
|
|
|
23
23
|
const initialOffset = region ? utils.geometry.location(region) : {x: 0, y: 0}
|
|
@@ -26,26 +26,26 @@ async function takeStitchedScreenshot({
|
|
|
26
26
|
|
|
27
27
|
await utils.general.sleep(wait)
|
|
28
28
|
|
|
29
|
+
const contentSize = await scroller.getContentSize()
|
|
30
|
+
|
|
29
31
|
logger.verbose('Getting initial image...')
|
|
30
|
-
let image = await
|
|
32
|
+
let image = await takeViewportScreenshot({name: 'initial', withStatusBar})
|
|
31
33
|
const firstImage = framed ? makeImage(image) : null
|
|
32
34
|
|
|
35
|
+
const scrollerRegion = await scroller.getClientRegion()
|
|
33
36
|
const targetRegion = region
|
|
34
|
-
? utils.geometry.intersect(
|
|
35
|
-
|
|
36
|
-
region,
|
|
37
|
-
)
|
|
38
|
-
: await scroller.getClientRegion()
|
|
37
|
+
? utils.geometry.intersect(utils.geometry.region(await scroller.getInnerOffset(), scrollerRegion), region)
|
|
38
|
+
: scrollerRegion
|
|
39
39
|
|
|
40
40
|
// TODO the solution should not check driver specifics,
|
|
41
41
|
// in this case target region coordinate should be already related to the scrolling element of the context
|
|
42
|
-
|
|
42
|
+
let cropRegion = driver.isNative ? targetRegion : await driver.getRegionInViewport(context, targetRegion)
|
|
43
43
|
|
|
44
44
|
logger.verbose('cropping...')
|
|
45
45
|
image.crop(withStatusBar ? utils.geometry.offset(cropRegion, {x: 0, y: driver.statusBarHeight}) : cropRegion)
|
|
46
46
|
await image.debug({...debug, name: 'initial', suffix: 'region'})
|
|
47
47
|
|
|
48
|
-
const contentRegion = utils.geometry.region({x: 0, y: 0},
|
|
48
|
+
const contentRegion = utils.geometry.region({x: 0, y: 0}, contentSize)
|
|
49
49
|
logger.verbose(`Scroller size: ${contentRegion}`)
|
|
50
50
|
|
|
51
51
|
if (region) region = utils.geometry.intersect(region, contentRegion)
|
|
@@ -53,37 +53,41 @@ async function takeStitchedScreenshot({
|
|
|
53
53
|
|
|
54
54
|
region = utils.geometry.round(region)
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
const padding = {top: overlap, bottom: overlap}
|
|
58
|
-
const [initialRegion, ...partRegions] = utils.geometry.divide(region, utils.geometry.round(image.size), padding)
|
|
56
|
+
const [initialRegion, ...partRegions] = utils.geometry.divide(region, image.size, overlap)
|
|
59
57
|
logger.verbose('Part regions', partRegions)
|
|
60
58
|
|
|
61
59
|
logger.verbose('Creating stitched image composition container')
|
|
62
|
-
const stitchedImage = makeImage({
|
|
60
|
+
const stitchedImage = makeImage({auto: true})
|
|
63
61
|
|
|
64
62
|
logger.verbose('Adding initial image...')
|
|
65
|
-
|
|
63
|
+
stitchedImage.copy(image, {x: 0, y: 0})
|
|
66
64
|
|
|
67
65
|
logger.verbose('Getting the rest of the image parts...')
|
|
68
66
|
|
|
69
|
-
let
|
|
70
|
-
let
|
|
67
|
+
let lastImage = firstImage
|
|
68
|
+
let scrollerRegionShift = {x: 0, y: 0}
|
|
71
69
|
for (const partRegion of partRegions) {
|
|
72
70
|
const partName = `${partRegion.x}_${partRegion.y}_${partRegion.width}x${partRegion.height}`
|
|
73
71
|
logger.verbose(`Processing part ${partName}`)
|
|
74
72
|
|
|
75
|
-
const compensateOffset = {x: 0, y: initialRegion.y !== partRegion.y ?
|
|
73
|
+
const compensateOffset = {x: 0, y: initialRegion.y !== partRegion.y ? overlap.top : 0}
|
|
76
74
|
const requiredOffset = utils.geometry.offsetNegative(utils.geometry.location(partRegion), compensateOffset)
|
|
77
75
|
|
|
78
76
|
logger.verbose(`Move to ${requiredOffset}`)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
)
|
|
77
|
+
let actualOffset = await scroller.moveTo(requiredOffset)
|
|
78
|
+
// actual scroll position after scrolling might be not equal to required position due to
|
|
79
|
+
// scrollable region shift during scrolling so actual scroll position should be corrected
|
|
80
|
+
if (!utils.geometry.equals(actualOffset, requiredOffset) && driver.isNative) {
|
|
81
|
+
const actualScrollerRegion = await scroller.getClientRegion()
|
|
82
|
+
scrollerRegionShift = {x: scrollerRegion.x - actualScrollerRegion.x, y: scrollerRegion.y - actualScrollerRegion.y}
|
|
83
|
+
}
|
|
84
|
+
actualOffset = utils.geometry.offset(actualOffset, scrollerRegionShift)
|
|
85
|
+
|
|
86
|
+
const remainingOffset = {
|
|
87
|
+
x: requiredOffset.x - actualOffset.x - expectedRemainingOffset.x + compensateOffset.x,
|
|
88
|
+
y: requiredOffset.y - actualOffset.y - expectedRemainingOffset.y + compensateOffset.y,
|
|
89
|
+
}
|
|
90
|
+
|
|
87
91
|
const cropPartRegion = {
|
|
88
92
|
x: cropRegion.x + remainingOffset.x,
|
|
89
93
|
y: cropRegion.y + remainingOffset.y,
|
|
@@ -97,7 +101,7 @@ async function takeStitchedScreenshot({
|
|
|
97
101
|
if (utils.geometry.isEmpty(cropPartRegion) || !utils.geometry.isIntersected(cropRegion, cropPartRegion)) continue
|
|
98
102
|
|
|
99
103
|
logger.verbose('Getting image...')
|
|
100
|
-
image = await
|
|
104
|
+
image = await takeViewportScreenshot({name: partName})
|
|
101
105
|
lastImage = framed ? makeImage(image) : null
|
|
102
106
|
|
|
103
107
|
logger.verbose('cropping...')
|
|
@@ -105,25 +109,15 @@ async function takeStitchedScreenshot({
|
|
|
105
109
|
await image.debug({...debug, name: partName, suffix: 'region'})
|
|
106
110
|
|
|
107
111
|
const pasteOffset = utils.geometry.offsetNegative(utils.geometry.location(partRegion), initialOffset)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
stitchedSize = {width: pasteOffset.x + image.width, height: pasteOffset.y + image.height}
|
|
112
|
+
stitchedImage.copy(image, pasteOffset)
|
|
111
113
|
}
|
|
112
114
|
|
|
113
115
|
await scroller.restoreState(scrollerState)
|
|
114
116
|
|
|
115
|
-
logger.verbose(`Extracted entire size: ${region}`)
|
|
116
|
-
logger.verbose(`Actual stitched size: ${stitchedSize}`)
|
|
117
|
-
|
|
118
|
-
if (stitchedSize.width < stitchedImage.width || stitchedSize.height < stitchedImage.height) {
|
|
119
|
-
logger.verbose('Trimming unnecessary margins...')
|
|
120
|
-
stitchedImage.crop(utils.geometry.region({x: 0, y: 0}, stitchedSize))
|
|
121
|
-
}
|
|
122
|
-
|
|
123
117
|
await stitchedImage.debug({...debug, name: 'stitched'})
|
|
124
118
|
|
|
125
119
|
if (framed) {
|
|
126
|
-
|
|
120
|
+
stitchedImage.frame(
|
|
127
121
|
firstImage,
|
|
128
122
|
lastImage,
|
|
129
123
|
withStatusBar ? utils.geometry.offset(cropRegion, {x: 0, y: driver.statusBarHeight}) : cropRegion,
|