@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/test/it/image.spec.js
CHANGED
|
@@ -78,42 +78,69 @@ describe('image', () => {
|
|
|
78
78
|
assert.strictEqual(actual.height, 50000)
|
|
79
79
|
})
|
|
80
80
|
|
|
81
|
-
it('should
|
|
81
|
+
it('should frame image in a higher and wider region', async () => {
|
|
82
82
|
const image = makeImage('./test/fixtures/image/house.png')
|
|
83
83
|
const srcImage = makeImage({
|
|
84
84
|
width: 200,
|
|
85
85
|
height: 200,
|
|
86
86
|
data: Buffer.alloc(200 * 200 * 4, Buffer.from([0xff, 0, 0, 0xff])),
|
|
87
87
|
})
|
|
88
|
-
const combinedImage = await srcImage.
|
|
88
|
+
const combinedImage = await srcImage.frame(image, image, {x: 200, y: 200, width: 100, height: 100})
|
|
89
89
|
const actual = await combinedImage.toObject()
|
|
90
|
-
const expected = await makeImage('./test/fixtures/image/house.
|
|
90
|
+
const expected = await makeImage('./test/fixtures/image/house.framed-higher-wider.png').toObject()
|
|
91
91
|
assert.ok(pixelmatch(actual.data, expected.data, null, expected.width, expected.height) === 0)
|
|
92
92
|
})
|
|
93
93
|
|
|
94
|
-
it('should
|
|
94
|
+
it('should frame image in a higher region', async () => {
|
|
95
95
|
const image = await makeImage('./test/fixtures/image/house.png')
|
|
96
96
|
const srcImage = makeImage({
|
|
97
97
|
width: 200,
|
|
98
98
|
height: 200,
|
|
99
99
|
data: Buffer.alloc(200 * 200 * 4, Buffer.from([0, 0xff, 0, 0xff])),
|
|
100
100
|
})
|
|
101
|
-
const combinedImage = await srcImage.
|
|
101
|
+
const combinedImage = await srcImage.frame(image, image, {x: 200, y: 200, width: 200, height: 100})
|
|
102
102
|
const actual = await combinedImage.toObject()
|
|
103
|
-
const expected = await makeImage('./test/fixtures/image/house.
|
|
103
|
+
const expected = await makeImage('./test/fixtures/image/house.framed-higher.png').toObject()
|
|
104
104
|
assert.ok(pixelmatch(actual.data, expected.data, null, expected.width, expected.height) === 0)
|
|
105
105
|
})
|
|
106
106
|
|
|
107
|
-
it('should
|
|
107
|
+
it('should frame image in a wider region', async () => {
|
|
108
108
|
const image = await makeImage('./test/fixtures/image/house.png')
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
height: 200
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
109
|
+
const data = Buffer.alloc(200 * 200 * 4, Buffer.from([0, 0, 0xff, 0xff]))
|
|
110
|
+
const actual = await makeImage({width: 200, height: 200, data})
|
|
111
|
+
.frame(image, image, {x: 200, y: 200, width: 100, height: 200})
|
|
112
|
+
.toObject()
|
|
113
|
+
const expected = await makeImage('./test/fixtures/image/house.framed-wider.png').toObject()
|
|
114
|
+
assert.ok(pixelmatch(actual.data, expected.data, null, expected.width, expected.height) === 0)
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('should frame image in a shorter and thinner region', async () => {
|
|
118
|
+
const image = await makeImage('./test/fixtures/image/house.png')
|
|
119
|
+
const data = Buffer.alloc(200 * 200 * 4, Buffer.from([0xff, 0, 0xff, 0xff]))
|
|
120
|
+
const actual = await makeImage({width: 200, height: 200, data})
|
|
121
|
+
.frame(image, image, {x: 100, y: 100, width: 250, height: 250})
|
|
122
|
+
.toObject()
|
|
123
|
+
const expected = await makeImage('./test/fixtures/image/house.framed-shorter-thinner.png').toObject()
|
|
124
|
+
assert.ok(pixelmatch(actual.data, expected.data, null, expected.width, expected.height) === 0)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('should frame image in a shorter region', async () => {
|
|
128
|
+
const image = await makeImage('./test/fixtures/image/house.png')
|
|
129
|
+
const data = Buffer.alloc(200 * 200 * 4, Buffer.from([0xff, 0, 0xff, 0xff]))
|
|
130
|
+
const actual = await makeImage({width: 200, height: 200, data})
|
|
131
|
+
.frame(image, image, {x: 100, y: 100, width: 200, height: 250})
|
|
132
|
+
.toObject()
|
|
133
|
+
const expected = await makeImage('./test/fixtures/image/house.framed-shorter-thinner.png').toObject()
|
|
134
|
+
assert.ok(pixelmatch(actual.data, expected.data, null, expected.width, expected.height) === 0)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('should frame image in a thinner region', async () => {
|
|
138
|
+
const image = await makeImage('./test/fixtures/image/house.png')
|
|
139
|
+
const data = Buffer.alloc(200 * 200 * 4, Buffer.from([0xff, 0, 0xff, 0xff]))
|
|
140
|
+
const actual = await makeImage({width: 200, height: 200, data})
|
|
141
|
+
.frame(image, image, {x: 100, y: 100, width: 250, height: 200})
|
|
142
|
+
.toObject()
|
|
143
|
+
const expected = await makeImage('./test/fixtures/image/house.framed-shorter-thinner.png').toObject()
|
|
117
144
|
assert.ok(pixelmatch(actual.data, expected.data, null, expected.width, expected.height) === 0)
|
|
118
145
|
})
|
|
119
146
|
})
|
package/docker-compose.yaml
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": "3.4",
|
|
3
|
-
"services": {
|
|
4
|
-
"chrome": {
|
|
5
|
-
"image": "selenium/standalone-chrome",
|
|
6
|
-
"environment": [
|
|
7
|
-
"SE_NODE_OVERRIDE_MAX_SESSIONS=true",
|
|
8
|
-
"SE_NODE_MAX_SESSIONS=15"
|
|
9
|
-
],
|
|
10
|
-
"volumes": [
|
|
11
|
-
"/dev/shm:/dev/shm"
|
|
12
|
-
],
|
|
13
|
-
"network_mode": "host"
|
|
14
|
-
},
|
|
15
|
-
"firefox": {
|
|
16
|
-
"image": "selenium/standalone-firefox",
|
|
17
|
-
"environment": [
|
|
18
|
-
"SE_NODE_OVERRIDE_MAX_SESSIONS=true",
|
|
19
|
-
"SE_NODE_MAX_SESSIONS=15"
|
|
20
|
-
],
|
|
21
|
-
"volumes": [
|
|
22
|
-
"/dev/shm:/dev/shm"
|
|
23
|
-
],
|
|
24
|
-
"ports": [
|
|
25
|
-
"4445:4444"
|
|
26
|
-
]
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
const utils = require('@applitools/utils')
|
|
2
|
-
|
|
3
|
-
async function calculateScreenshotRegions({context, screenshotRegion, regions}) {
|
|
4
|
-
const screenshotRegions = []
|
|
5
|
-
for (const region of regions) {
|
|
6
|
-
screenshotRegions.push(await transformRegion(region))
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
return screenshotRegions
|
|
10
|
-
|
|
11
|
-
async function transformRegion(region) {
|
|
12
|
-
if (utils.types.has(region, ['x', 'y', 'width', 'height'])) {
|
|
13
|
-
// if someday different coordinate systems will be supported (context or app based), the conversion will happen here
|
|
14
|
-
return [regions]
|
|
15
|
-
}
|
|
16
|
-
const elements = await context.elements(region)
|
|
17
|
-
return elements.reduce(async (regions, element) => {
|
|
18
|
-
regions = await regions
|
|
19
|
-
const region = await element.getRegion()
|
|
20
|
-
regions.push({
|
|
21
|
-
x: Math.max(0, region.x - screenshotRegion.x),
|
|
22
|
-
y: Math.max(0, region.y - screenshotRegion.y),
|
|
23
|
-
width: region.width,
|
|
24
|
-
height: region.height,
|
|
25
|
-
})
|
|
26
|
-
return regions
|
|
27
|
-
}, [])
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
module.exports = calculateScreenshotRegions
|
package/src/screenshoter.js
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
const utils = require('@applitools/utils')
|
|
2
|
-
const makeScroller = require('./scroller')
|
|
3
|
-
const scrollIntoViewport = require('./scroll-into-viewport')
|
|
4
|
-
const takeStitchedScreenshot = require('./take-stitched-screenshot')
|
|
5
|
-
const takeViewportScreenshot = require('./take-viewport-screenshot')
|
|
6
|
-
|
|
7
|
-
async function screenshoter({
|
|
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
|
-
|
|
31
|
-
const activeContext = driver.currentContext
|
|
32
|
-
const context =
|
|
33
|
-
frames.length > 0
|
|
34
|
-
? await activeContext.context(frames.reduce((parent, frame) => ({...frame, parent}), null))
|
|
35
|
-
: activeContext
|
|
36
|
-
|
|
37
|
-
// traverse from main context to target context to hide scrollbars and preserve context state (scroll/translate position)
|
|
38
|
-
for (const nextContext of context.path) {
|
|
39
|
-
const scrollingElement = await nextContext.getScrollingElement()
|
|
40
|
-
// unlike web apps, native apps do not always have scrolling element
|
|
41
|
-
if (scrollingElement) {
|
|
42
|
-
if (driver.isWeb && hideScrollbars) await scrollingElement.hideScrollbars()
|
|
43
|
-
await scrollingElement.preserveState()
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// blur active element in target context
|
|
48
|
-
const activeElement = driver.isWeb && hideCaret ? await context.blurElement() : null
|
|
49
|
-
|
|
50
|
-
const target = await getTarget({window, context, region, fully, scrollingMode, logger})
|
|
51
|
-
|
|
52
|
-
if (driver.isWeb && hideScrollbars) await target.scroller.hideScrollbars()
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
if (!window) await scrollIntoViewport({...target, logger})
|
|
56
|
-
|
|
57
|
-
const screenshot =
|
|
58
|
-
fully && target.scroller
|
|
59
|
-
? await takeStitchedScreenshot({...target, withStatusBar, overlap, framed, wait, stabilization, debug, logger})
|
|
60
|
-
: await takeViewportScreenshot({...target, withStatusBar, wait, stabilization, debug, logger})
|
|
61
|
-
|
|
62
|
-
if (hooks && hooks.afterScreenshot) {
|
|
63
|
-
// imitate image-like state for the hook
|
|
64
|
-
if (window && fully && target.scroller) {
|
|
65
|
-
await target.scroller.moveTo({x: 0, y: 0}, await driver.mainContext.getScrollingElement())
|
|
66
|
-
}
|
|
67
|
-
await hooks.afterScreenshot({driver, scroller: target.scroller, screenshot})
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return screenshot
|
|
71
|
-
} finally {
|
|
72
|
-
if (target.scroller) {
|
|
73
|
-
await target.scroller.restoreScrollbars()
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// if there was active element and we have blurred it, then restore focus
|
|
77
|
-
if (activeElement) await context.focusElement(activeElement)
|
|
78
|
-
|
|
79
|
-
// traverse from target context to the main context to restore scrollbars and context states
|
|
80
|
-
for (const prevContext of context.path.reverse()) {
|
|
81
|
-
const scrollingElement = await prevContext.getScrollingElement()
|
|
82
|
-
if (scrollingElement) {
|
|
83
|
-
if (driver.isWeb && hideScrollbars) await scrollingElement.restoreScrollbars()
|
|
84
|
-
await scrollingElement.restoreState()
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// restore focus on original active context
|
|
89
|
-
await activeContext.focus()
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
async function getTarget({window, context, region, fully, scrollingMode, logger}) {
|
|
94
|
-
if (window) {
|
|
95
|
-
// window/app
|
|
96
|
-
const scrollingElement = await context.main.getScrollingElement()
|
|
97
|
-
return {
|
|
98
|
-
context: context.main,
|
|
99
|
-
scroller: scrollingElement ? makeScroller({element: scrollingElement, scrollingMode, logger}) : null,
|
|
100
|
-
}
|
|
101
|
-
} else if (region) {
|
|
102
|
-
if (utils.types.has(region, ['x', 'y', 'width', 'height'])) {
|
|
103
|
-
// region by coordinates
|
|
104
|
-
const scrollingElement = await context.getScrollingElement()
|
|
105
|
-
return {
|
|
106
|
-
context,
|
|
107
|
-
region,
|
|
108
|
-
scroller: scrollingElement ? makeScroller({element: scrollingElement, scrollingMode, logger}) : null,
|
|
109
|
-
}
|
|
110
|
-
} else {
|
|
111
|
-
// region by element or selector
|
|
112
|
-
const element = await context.element(region)
|
|
113
|
-
if (!element) throw new Error('Element not found!')
|
|
114
|
-
|
|
115
|
-
const elementContext = element.context
|
|
116
|
-
|
|
117
|
-
if (fully) {
|
|
118
|
-
const isScrollable = await element.isScrollable()
|
|
119
|
-
// if element is scrollable, then take screenshot of the full element content, otherwise take screenshot of full element
|
|
120
|
-
const region = isScrollable ? null : await element.getRegion()
|
|
121
|
-
const scrollingElement = isScrollable ? element : await elementContext.getScrollingElement()
|
|
122
|
-
// css stitching could be applied only to root element of its context
|
|
123
|
-
scrollingMode = scrollingMode === 'css' && !(await scrollingElement.isRoot()) ? 'mixed' : scrollingMode
|
|
124
|
-
return {
|
|
125
|
-
context: elementContext,
|
|
126
|
-
region,
|
|
127
|
-
scroller: scrollingElement ? makeScroller({element: scrollingElement, scrollingMode, logger}) : null,
|
|
128
|
-
}
|
|
129
|
-
} else {
|
|
130
|
-
const scrollingElement = await context.getScrollingElement()
|
|
131
|
-
return {
|
|
132
|
-
context: elementContext,
|
|
133
|
-
region: await element.getRegion(),
|
|
134
|
-
scroller: scrollingElement ? makeScroller({element: scrollingElement, scrollingMode, logger}) : null,
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
} else if (!context.isMain) {
|
|
139
|
-
// context
|
|
140
|
-
if (fully) {
|
|
141
|
-
const scrollingElement = await context.getScrollingElement()
|
|
142
|
-
return {
|
|
143
|
-
context,
|
|
144
|
-
scroller: scrollingElement ? makeScroller({logger, element: scrollingElement, scrollingMode}) : null,
|
|
145
|
-
}
|
|
146
|
-
} else {
|
|
147
|
-
const scrollingElement = await context.parent.getScrollingElement()
|
|
148
|
-
const element = await context.getContextElement()
|
|
149
|
-
return {
|
|
150
|
-
context: context.parent,
|
|
151
|
-
region: await element.getRegion(), // IMHO we should use CLIENT (without borders) region here
|
|
152
|
-
scroller: scrollingElement ? makeScroller({logger, element: scrollingElement, scrollingMode}) : null,
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
module.exports = screenshoter
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/test/util/spec-driver.js
DELETED
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
const webdriverio = require('webdriverio')
|
|
2
|
-
const utils = require('@applitools/utils')
|
|
3
|
-
|
|
4
|
-
// #region HELPERS
|
|
5
|
-
|
|
6
|
-
const LEGACY_ELEMENT_ID = 'ELEMENT'
|
|
7
|
-
const ELEMENT_ID = 'element-6066-11e4-a52e-4f735466cecf'
|
|
8
|
-
|
|
9
|
-
function extractElementId(element) {
|
|
10
|
-
return element.elementId || element[ELEMENT_ID] || element[LEGACY_ELEMENT_ID]
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// #endregion
|
|
14
|
-
|
|
15
|
-
// #region UTILITY
|
|
16
|
-
|
|
17
|
-
function isDriver(browser) {
|
|
18
|
-
if (!browser) return false
|
|
19
|
-
return browser.constructor.name === 'Browser'
|
|
20
|
-
}
|
|
21
|
-
function isElement(element) {
|
|
22
|
-
if (!element) return false
|
|
23
|
-
return Boolean(element.elementId || element[ELEMENT_ID] || element[LEGACY_ELEMENT_ID])
|
|
24
|
-
}
|
|
25
|
-
function isSelector(selector) {
|
|
26
|
-
return utils.types.isString(selector) || utils.types.isFunction(selector)
|
|
27
|
-
}
|
|
28
|
-
function transformElement(element) {
|
|
29
|
-
const elementId = extractElementId(element)
|
|
30
|
-
return {[ELEMENT_ID]: elementId, [LEGACY_ELEMENT_ID]: elementId}
|
|
31
|
-
}
|
|
32
|
-
function transformSelector(selector) {
|
|
33
|
-
if (utils.types.has(selector, 'selector')) {
|
|
34
|
-
if (!utils.types.has(selector, 'type')) return selector.selector
|
|
35
|
-
if (selector.type === 'css') return `css selector:${selector.selector}`
|
|
36
|
-
else return `${selector.type}:${selector.selector}`
|
|
37
|
-
}
|
|
38
|
-
return selector
|
|
39
|
-
}
|
|
40
|
-
function extractSelector(element) {
|
|
41
|
-
return element.selector
|
|
42
|
-
}
|
|
43
|
-
async function isEqualElements(browser, element1, element2) {
|
|
44
|
-
// NOTE: wdio wraps puppeteer and generate ids by itself just incrementing a counter
|
|
45
|
-
// NOTE: appium for ios could return different ids for same element
|
|
46
|
-
if (browser.isDevTools || browser.isIOS) {
|
|
47
|
-
return browser.execute((element1, element2) => element1 === element2, element1, element2).catch(() => false)
|
|
48
|
-
}
|
|
49
|
-
if (!element1 || !element2) return false
|
|
50
|
-
const elementId1 = extractElementId(element1)
|
|
51
|
-
const elementId2 = extractElementId(element2)
|
|
52
|
-
return elementId1 === elementId2
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// #endregion
|
|
56
|
-
|
|
57
|
-
// #region COMMANDS
|
|
58
|
-
|
|
59
|
-
async function executeScript(browser, script, ...args) {
|
|
60
|
-
return browser.execute(script, ...args)
|
|
61
|
-
}
|
|
62
|
-
async function mainContext(browser) {
|
|
63
|
-
await browser.switchToFrame(null)
|
|
64
|
-
return browser
|
|
65
|
-
}
|
|
66
|
-
async function parentContext(browser) {
|
|
67
|
-
await browser.switchToParentFrame()
|
|
68
|
-
return browser
|
|
69
|
-
}
|
|
70
|
-
async function childContext(browser, element) {
|
|
71
|
-
await browser.switchToFrame(element)
|
|
72
|
-
return browser
|
|
73
|
-
}
|
|
74
|
-
async function findElement(browser, selector) {
|
|
75
|
-
const element = await browser.$(selector)
|
|
76
|
-
return !element.error ? element : null
|
|
77
|
-
}
|
|
78
|
-
async function findElements(browser, selector) {
|
|
79
|
-
const elements = await browser.$$(selector)
|
|
80
|
-
return Array.from(elements)
|
|
81
|
-
}
|
|
82
|
-
async function getElementRegion(browser, element) {
|
|
83
|
-
const extendedElement = await browser.$(element)
|
|
84
|
-
if (utils.types.isFunction(extendedElement, 'getRect')) {
|
|
85
|
-
return extendedElement.getRect()
|
|
86
|
-
} else {
|
|
87
|
-
const size = await extendedElement.getSize()
|
|
88
|
-
const location = utils.types.has(size, ['x', 'y']) ? size : await extendedElement.getLocation()
|
|
89
|
-
return {x: location.x, y: location.y, width: size.width, height: size.height}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
async function getElementAttribute(browser, element, name) {
|
|
93
|
-
return browser.getElementAttribute(extractElementId(element), name)
|
|
94
|
-
}
|
|
95
|
-
async function getWindowSize(browser) {
|
|
96
|
-
if (utils.types.isFunction(browser.getWindowRect)) {
|
|
97
|
-
return browser.getWindowRect()
|
|
98
|
-
} else if (utils.types.isFunction(browser.getWindowSize)) {
|
|
99
|
-
return await browser.getWindowSize()
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
async function setWindowSize(browser, {width, height} = {}) {
|
|
103
|
-
if (utils.types.isFunction(browser.setWindowRect)) {
|
|
104
|
-
await browser.setWindowRect(0, 0, width, height)
|
|
105
|
-
} else {
|
|
106
|
-
await browser.setWindowPosition(0, 0)
|
|
107
|
-
await browser.setWindowSize(width, height)
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
async function getOrientation(browser) {
|
|
111
|
-
const orientation = await browser.getOrientation()
|
|
112
|
-
return orientation.toLowerCase()
|
|
113
|
-
}
|
|
114
|
-
async function getDriverInfo(browser) {
|
|
115
|
-
const driverInfo = {
|
|
116
|
-
sessionId: browser.sessionId,
|
|
117
|
-
isMobile: browser.isMobile,
|
|
118
|
-
isNative: browser.isMobile && !browser.capabilities.browserName,
|
|
119
|
-
deviceName: browser.capabilities.desired
|
|
120
|
-
? browser.capabilities.desired.deviceName
|
|
121
|
-
: browser.capabilities.deviceName,
|
|
122
|
-
platformName: browser.capabilities.platformName || browser.capabilities.platform,
|
|
123
|
-
platformVersion: browser.capabilities.platformVersion,
|
|
124
|
-
browserName: browser.capabilities.browserName,
|
|
125
|
-
browserVersion: browser.capabilities.browserVersion,
|
|
126
|
-
pixelRatio: browser.capabilities.pixelRatio,
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (driverInfo.isNative) {
|
|
130
|
-
const capabilities = utils.types.has(browser.capabilities, ['pixelRatio', 'viewportRect', 'statBarHeight'])
|
|
131
|
-
? browser.capabilities
|
|
132
|
-
: await browser.getSession()
|
|
133
|
-
|
|
134
|
-
driverInfo.pixelRatio = capabilities.pixelRatio
|
|
135
|
-
|
|
136
|
-
try {
|
|
137
|
-
const {statusBar, navigationBar} = await browser.getSystemBars()
|
|
138
|
-
driverInfo.statusBarHeight = statusBar.visible ? statusBar.height : 0
|
|
139
|
-
driverInfo.navigationBarHeight = navigationBar.visible ? navigationBar.height : 0
|
|
140
|
-
} catch (err) {
|
|
141
|
-
driverInfo.statusBarHeight = capabilities.statBarHeight || (capabilities.viewportRect || {}).top || 0
|
|
142
|
-
driverInfo.navigationBarHeight = 0
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return driverInfo
|
|
147
|
-
}
|
|
148
|
-
async function takeScreenshot(driver) {
|
|
149
|
-
return driver.takeScreenshot()
|
|
150
|
-
}
|
|
151
|
-
async function visit(browser, url) {
|
|
152
|
-
return browser.url(url)
|
|
153
|
-
}
|
|
154
|
-
async function click(browser, element) {
|
|
155
|
-
if (isSelector(element)) element = await findElement(browser, element)
|
|
156
|
-
const extendedElement = await browser.$(element)
|
|
157
|
-
await extendedElement.click()
|
|
158
|
-
}
|
|
159
|
-
async function performAction(browser, actions) {
|
|
160
|
-
return browser.touchAction(actions)
|
|
161
|
-
}
|
|
162
|
-
async function getElementText(browser, element) {
|
|
163
|
-
return browser.getElementText(extractElementId(element))
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// #endregion
|
|
167
|
-
|
|
168
|
-
async function build({type = 'web'} = {}) {
|
|
169
|
-
const capabilities = {
|
|
170
|
-
web: {
|
|
171
|
-
protocol: 'http',
|
|
172
|
-
hostname: 'localhost',
|
|
173
|
-
path: '/wd/hub',
|
|
174
|
-
port: 4444,
|
|
175
|
-
logLevel: 'silent',
|
|
176
|
-
capabilities: {
|
|
177
|
-
browserName: 'chrome',
|
|
178
|
-
},
|
|
179
|
-
},
|
|
180
|
-
android: {
|
|
181
|
-
protocol: 'https',
|
|
182
|
-
hostname: 'ondemand.saucelabs.com',
|
|
183
|
-
path: '/wd/hub',
|
|
184
|
-
port: 443,
|
|
185
|
-
logLevel: 'silent',
|
|
186
|
-
capabilities: {
|
|
187
|
-
name: 'Android Screenshoter Test',
|
|
188
|
-
browserName: '',
|
|
189
|
-
platformName: 'Android',
|
|
190
|
-
platformVersion: '7.0',
|
|
191
|
-
appiumVersion: '1.20.2',
|
|
192
|
-
deviceName: 'Samsung Galaxy S8 FHD GoogleAPI Emulator',
|
|
193
|
-
automationName: 'uiautomator2',
|
|
194
|
-
app: 'https://applitools.jfrog.io/artifactory/Examples/android/1.3/app-debug.apk',
|
|
195
|
-
username: process.env.SAUCE_USERNAME,
|
|
196
|
-
accessKey: process.env.SAUCE_ACCESS_KEY,
|
|
197
|
-
},
|
|
198
|
-
},
|
|
199
|
-
androidx: {
|
|
200
|
-
protocol: 'https',
|
|
201
|
-
hostname: 'ondemand.saucelabs.com',
|
|
202
|
-
path: '/wd/hub',
|
|
203
|
-
port: 443,
|
|
204
|
-
logLevel: 'silent',
|
|
205
|
-
capabilities: {
|
|
206
|
-
name: 'AndroidX Screenshoter Test',
|
|
207
|
-
browserName: '',
|
|
208
|
-
platformName: 'Android',
|
|
209
|
-
platformVersion: '10.0',
|
|
210
|
-
appiumVersion: '1.20.2',
|
|
211
|
-
deviceName: 'Google Pixel 3a XL GoogleAPI Emulator',
|
|
212
|
-
automationName: 'uiautomator2',
|
|
213
|
-
app: 'https://applitools.jfrog.io/artifactory/Examples/androidx/1.2.0/app_androidx.apk',
|
|
214
|
-
username: process.env.SAUCE_USERNAME,
|
|
215
|
-
accessKey: process.env.SAUCE_ACCESS_KEY,
|
|
216
|
-
},
|
|
217
|
-
},
|
|
218
|
-
ios: {
|
|
219
|
-
protocol: 'https',
|
|
220
|
-
hostname: 'ondemand.saucelabs.com',
|
|
221
|
-
path: '/wd/hub',
|
|
222
|
-
port: 443,
|
|
223
|
-
logLevel: 'silent',
|
|
224
|
-
capabilities: {
|
|
225
|
-
name: 'iOS Screenshoter Test',
|
|
226
|
-
deviceName: 'iPhone 11 Pro Simulator',
|
|
227
|
-
platformName: 'iOS',
|
|
228
|
-
platformVersion: '13.4',
|
|
229
|
-
appiumVersion: '1.19.2',
|
|
230
|
-
automationName: 'XCUITest',
|
|
231
|
-
app: 'https://applitools.jfrog.io/artifactory/Examples/IOSTestApp/1.5/app/IOSTestApp-1.5.zip',
|
|
232
|
-
username: process.env.SAUCE_USERNAME,
|
|
233
|
-
accessKey: process.env.SAUCE_ACCESS_KEY,
|
|
234
|
-
},
|
|
235
|
-
},
|
|
236
|
-
'web-ios': {
|
|
237
|
-
protocol: 'https',
|
|
238
|
-
hostname: 'ondemand.saucelabs.com',
|
|
239
|
-
path: '/wd/hub',
|
|
240
|
-
port: 443,
|
|
241
|
-
logLevel: 'silent',
|
|
242
|
-
capabilities: {
|
|
243
|
-
name: 'iOS Web Screenshoter Test',
|
|
244
|
-
deviceName: 'iPhone 11 Pro Simulator',
|
|
245
|
-
browserName: 'safari',
|
|
246
|
-
platformName: 'iOS',
|
|
247
|
-
platformVersion: '14.5',
|
|
248
|
-
appiumVersion: '1.20.1',
|
|
249
|
-
automationName: 'XCUITest',
|
|
250
|
-
username: process.env.SAUCE_USERNAME,
|
|
251
|
-
accessKey: process.env.SAUCE_ACCESS_KEY,
|
|
252
|
-
},
|
|
253
|
-
},
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const browser = await webdriverio.remote(capabilities[type])
|
|
257
|
-
|
|
258
|
-
return [browser, () => browser.deleteSession()]
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
module.exports = {
|
|
262
|
-
isDriver,
|
|
263
|
-
isElement,
|
|
264
|
-
isSelector,
|
|
265
|
-
transformElement,
|
|
266
|
-
transformSelector,
|
|
267
|
-
extractSelector,
|
|
268
|
-
isEqualElements,
|
|
269
|
-
executeScript,
|
|
270
|
-
mainContext,
|
|
271
|
-
parentContext,
|
|
272
|
-
childContext,
|
|
273
|
-
findElement,
|
|
274
|
-
findElements,
|
|
275
|
-
getElementRegion,
|
|
276
|
-
getElementAttribute,
|
|
277
|
-
getWindowSize,
|
|
278
|
-
setWindowSize,
|
|
279
|
-
getOrientation,
|
|
280
|
-
getDriverInfo,
|
|
281
|
-
takeScreenshot,
|
|
282
|
-
visit,
|
|
283
|
-
click,
|
|
284
|
-
performAction,
|
|
285
|
-
getElementText,
|
|
286
|
-
|
|
287
|
-
build,
|
|
288
|
-
}
|