@applitools/screenshoter 3.2.6 → 3.3.0

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.
Files changed (90) hide show
  1. package/.bongo/dry-run/package-lock.json +53 -10
  2. package/.bongo/dry-run/package.json +5 -0
  3. package/.bongo/dry-run.tgz +0 -0
  4. package/CHANGELOG.md +18 -0
  5. package/index.js +2 -1
  6. package/logs/screenshot_2021_12_16_19_06_06_332Z_full_app_failed_1639681566332.png +0 -0
  7. package/logs/screenshot_2021_12_16_19_15_59_935Z_full_app_failed_1639682159935.png +0 -0
  8. package/logs/screenshot_2021_12_16_19_33_20_679Z_full_app_failed_1639683200679.png +0 -0
  9. package/logs/screenshot_2021_12_16_19_37_22_120Z_ios_viewport_failed.png +0 -0
  10. package/logs/screenshot_2021_12_16_19_38_09_461Z_ios_full_page_failed.png +0 -0
  11. package/logs/screenshot_2021_12_16_19_59_45_182Z_ios_viewport_failed.png +0 -0
  12. package/package.json +11 -7
  13. package/src/find-image-pattern.js +10 -38
  14. package/src/image.js +107 -64
  15. package/src/take-screenshot.js +136 -160
  16. package/src/take-simple-screenshot.js +25 -0
  17. package/src/take-stitched-screenshot.js +34 -40
  18. package/src/take-viewport-screenshot.js +179 -16
  19. package/test/e2e/android.spec.js +77 -13
  20. package/test/e2e/external.spec.js +155 -0
  21. package/test/e2e/ios.spec.js +64 -11
  22. package/test/e2e/web-ios.spec.js +19 -6
  23. package/test/e2e/web.spec.js +33 -21
  24. package/test/fixtures/android/app-fully-non-scrollable.png +0 -0
  25. package/test/fixtures/android/app-fully-recycler.png +0 -0
  26. package/test/fixtures/android/app-fully-scroll-statusbar.png +0 -0
  27. package/test/fixtures/android/app-fully-scroll.png +0 -0
  28. package/test/fixtures/android/app-statusbar.png +0 -0
  29. package/test/fixtures/android/region.png +0 -0
  30. package/test/fixtures/android/x-app-fully-collapsing.png +0 -0
  31. package/test/fixtures/android/x-app-fully-recycler.png +0 -0
  32. package/test/fixtures/android/x-element-fully.png +0 -0
  33. package/test/fixtures/external/agl.png +0 -0
  34. package/test/fixtures/image/{house.combined-higher-wider.png → house.framed-higher-wider.png} +0 -0
  35. package/test/fixtures/image/{house.combined-higher.png → house.framed-higher.png} +0 -0
  36. package/test/fixtures/image/house.framed-shorter-thinner.png +0 -0
  37. package/test/fixtures/image/{house.combined-wider.png → house.framed-wider.png} +0 -0
  38. package/test/fixtures/ios/app-fully-collapsing.png +0 -0
  39. package/test/fixtures/ios/app-fully-collection.png +0 -0
  40. package/test/fixtures/ios/app-fully-overlapped-statusbar.png +0 -0
  41. package/test/fixtures/ios/app-fully-overlapped.png +0 -0
  42. package/test/fixtures/ios/app-fully-scroll-statusbar.png +0 -0
  43. package/test/fixtures/ios/app-fully-scroll.png +0 -0
  44. package/test/fixtures/ios/app-fully-superview.png +0 -0
  45. package/test/fixtures/ios/app-fully-table.png +0 -0
  46. package/test/fixtures/ios/app-statusbar.png +0 -0
  47. package/test/fixtures/ios/app.png +0 -0
  48. package/test/fixtures/ios/element-fully.png +0 -0
  49. package/test/fixtures/ios/element.png +0 -0
  50. package/test/fixtures/ios/region.png +0 -0
  51. package/test/fixtures/pattern/iPad_5th_landscape.png +0 -0
  52. package/test/fixtures/pattern/iPad_5th_portrait.png +0 -0
  53. package/test/fixtures/pattern/iPad_9th_landscape.png +0 -0
  54. package/test/fixtures/pattern/iPad_9th_portrait.png +0 -0
  55. package/test/fixtures/pattern/iPhone_11_landscape.png +0 -0
  56. package/test/fixtures/pattern/iPhone_11_portrait.png +0 -0
  57. package/test/fixtures/pattern/iPhone_13_landscape.png +0 -0
  58. package/test/fixtures/pattern/iPhone_13_portrait.png +0 -0
  59. package/test/fixtures/pattern/iPhone_SE_landscape.png +0 -0
  60. package/test/fixtures/pattern/iPhone_SE_portrait.png +0 -0
  61. package/test/fixtures/pattern/iPhone_XS_portrait_noviewport.png +0 -0
  62. package/test/fixtures/web/frame-fully.png +0 -0
  63. package/test/fixtures/web/frame.png +0 -0
  64. package/test/fixtures/web/inner-element-fully.png +0 -0
  65. package/test/fixtures/web/inner-element.png +0 -0
  66. package/test/fixtures/web/inner-region-fully.png +0 -0
  67. package/test/fixtures/web/inner-region.png +0 -0
  68. package/test/fixtures/web/page-fully.png +0 -0
  69. package/test/fixtures/web/page.png +0 -0
  70. package/test/fixtures/web/region-fully.png +0 -0
  71. package/test/fixtures/web/region.png +0 -0
  72. package/test/fixtures/web-ios/page-fully.png +0 -0
  73. package/test/fixtures/web-ios/page.png +0 -0
  74. package/test/it/find-pattern.spec.js +16 -11
  75. package/test/it/image.spec.js +42 -15
  76. package/logs/screenshot_2021_10_11_13_22_59_543Z_viewport_failed_1633958579543.png +0 -0
  77. package/logs/screenshot_2021_10_11_13_28_41_375Z_viewport_failed_1633958921375.png +0 -0
  78. package/logs/screenshot_2021_10_11_13_36_46_414Z_viewport_failed_1633959406414.png +0 -0
  79. package/logs/screenshot_2021_10_11_13_54_21_340Z_viewport_failed_1633960461340.png +0 -0
  80. package/logs/screenshot_2021_10_11_14_32_39_581Z_full_app_failed_1633962759581.png +0 -0
  81. package/src/calculate-screenshot-regions.js +0 -31
  82. package/src/screenshoter.js +0 -158
  83. package/test/fixtures/pattern/iPad_Air_portrait.png +0 -0
  84. package/test/fixtures/pattern/iPhone_5S_landscape.png +0 -0
  85. package/test/fixtures/pattern/iPhone_XR_perfecto_landscape.png +0 -0
  86. package/test/fixtures/pattern/iPhone_XS_Max_perfecto_landscape.png +0 -0
  87. package/test/fixtures/pattern/iPhone_XS_landscape.png +0 -0
  88. package/test/fixtures/pattern/iPhone_XS_portrait.png +0 -0
  89. package/test/fixtures/pattern/iPhone_X_perfecto_portrait.png +0 -0
  90. package/test/util/spec-driver.js +0 -288
@@ -1,25 +1,188 @@
1
1
  const utils = require('@applitools/utils')
2
- const makeTakeScreenshot = require('./take-screenshot')
2
+ const snippets = require('@applitools/snippets')
3
+ const findImagePattern = require('./find-image-pattern')
4
+ const makeImage = require('./image')
3
5
 
4
- async function takeViewportScreenshot({context, region, withStatusBar, wait, stabilization, debug = {}, logger}) {
5
- logger.verbose('Taking image of...')
6
+ function makeTakeViewportScreenshot(options) {
7
+ const {driver} = options
8
+ if (driver.isNative) {
9
+ return makeTakeNativeScreenshot(options)
10
+ } else if (driver.browserName === 'Firefox') {
11
+ try {
12
+ const browserVersion = Number.parseInt(driver.browserVersion, 10)
13
+ if (browserVersion >= 48 && browserVersion <= 72) {
14
+ // firefox between versions 48 and 72 takes current frame screenshot only
15
+ return makeTakeMainContextScreenshot(options)
16
+ }
17
+ } catch (ignored) {}
18
+ } else if (driver.browserName === 'Safari') {
19
+ if (driver.isIOS) {
20
+ // safari on ios takes screenshot with browser and os interfaces
21
+ return makeTakeMarkedScreenshot(options)
22
+ } else if (driver.browserVersion === '11') {
23
+ // safari 11 on macs takes full page screenshot
24
+ return makeTakeSafari11Screenshot(options)
25
+ }
26
+ }
27
+
28
+ return makeTakeDefaultScreenshot(options)
29
+ }
30
+
31
+ function makeTakeDefaultScreenshot({driver, stabilization = {}, debug, logger}) {
32
+ const calculateScaleRatio = makeCalculateScaleRatio({driver})
33
+ return async function takeScreenshot({name} = {}) {
34
+ logger.verbose('Taking screenshot...')
35
+ const image = makeImage(await driver.takeScreenshot())
36
+ await image.debug({...debug, name, suffix: 'original'})
37
+
38
+ if (stabilization.scale) image.scale(stabilization.scale)
39
+ else image.scale(await calculateScaleRatio(image.width))
40
+
41
+ if (stabilization.rotate) image.crop(stabilization.rotate)
42
+
43
+ if (stabilization.crop) image.crop(stabilization.crop)
44
+
45
+ return image
46
+ }
47
+ }
48
+
49
+ function makeTakeMainContextScreenshot({driver, stabilization = {}, debug, logger}) {
50
+ const calculateScaleRatio = makeCalculateScaleRatio({driver})
51
+ return async function takeScreenshot({name} = {}) {
52
+ logger.verbose('Taking screenshot...')
53
+ const originalContext = driver.currentContext
54
+ await driver.mainContext.focus()
55
+ const image = makeImage(await driver.takeScreenshot())
56
+ await originalContext.focus()
57
+ await image.debug({...debug, name, suffix: 'original'})
58
+
59
+ if (stabilization.scale) image.scale(stabilization.scale)
60
+ else image.scale(await calculateScaleRatio(image.width))
6
61
 
7
- const driver = context.driver
8
- const takeScreenshot = makeTakeScreenshot({logger, driver, stabilization, debug})
62
+ if (stabilization.rotate) image.rotate(stabilization.rotate)
63
+
64
+ if (stabilization.crop) image.crop(stabilization.crop)
65
+
66
+ return image
67
+ }
68
+ }
69
+
70
+ function makeTakeSafari11Screenshot({driver, stabilization = {}, debug, logger}) {
71
+ const calculateScaleRatio = makeCalculateScaleRatio({driver})
72
+ let viewportSize
73
+
74
+ return async function takeScreenshot({name} = {}) {
75
+ logger.verbose('Taking safari 11 driver screenshot...')
76
+ const image = makeImage(await driver.takeScreenshot())
77
+ await image.debug({...debug, name, suffix: 'original'})
78
+
79
+ if (stabilization.scale) image.scale(stabilization.scale)
80
+ else image.scale(await calculateScaleRatio(image.width))
81
+
82
+ if (stabilization.rotate) image.rotate(stabilization.rotate)
83
+
84
+ if (stabilization.crop) image.crop(stabilization.crop)
85
+ else {
86
+ if (!viewportSize) viewportSize = await driver.getViewportSize()
87
+ const viewportLocation = await driver.mainContext.execute(snippets.getElementScrollOffset, [])
88
+ image.crop(utils.geometry.region(viewportLocation, viewportSize))
89
+ }
90
+
91
+ return image
92
+ }
93
+ }
9
94
 
10
- await utils.general.sleep(wait)
95
+ function makeTakeMarkedScreenshot({driver, stabilization = {}, debug, logger}) {
96
+ const calculateScaleRatio = makeCalculateScaleRatio({driver})
97
+ let viewportRegion
98
+
99
+ return async function takeScreenshot({name} = {}) {
100
+ logger.verbose('Taking viewport screenshot (using markers)...')
101
+ const image = makeImage(await driver.takeScreenshot())
102
+ await image.debug({...debug, name, suffix: 'original'})
103
+
104
+ if (stabilization.scale) image.scale(stabilization.scale)
105
+ else image.scale(await calculateScaleRatio(image.width))
106
+
107
+ if (stabilization.rotate) image.rotate(stabilization.rotate)
108
+
109
+ if (stabilization.crop) image.crop(stabilization.crop)
110
+ else {
111
+ if (!viewportRegion) viewportRegion = await getViewportRegion()
112
+ if (viewportRegion) image.crop(viewportRegion)
113
+ }
114
+
115
+ await image.debug({...debug, name, suffix: 'viewport'})
116
+
117
+ return image
118
+ }
119
+
120
+ async function getViewportRegion() {
121
+ const marker = await driver.mainContext.execute(snippets.addPageMarker)
122
+ try {
123
+ const image = makeImage(await driver.takeScreenshot())
124
+
125
+ if (stabilization.rotate) await image.rotate(stabilization.rotate)
126
+
127
+ await image.debug({...debug, name: 'marker'})
128
+
129
+ const markerLocation = findImagePattern(await image.toObject(), {...marker, pixelRatio: driver.pixelRatio})
130
+ if (!markerLocation) return null
131
+
132
+ const viewportSize = await driver.getViewportSize()
133
+
134
+ return utils.geometry.region(utils.geometry.scale(markerLocation, 1 / driver.pixelRatio), viewportSize)
135
+ } finally {
136
+ await driver.mainContext.execute(snippets.cleanupPageMarker)
137
+ }
138
+ }
139
+ }
140
+
141
+ function makeTakeNativeScreenshot({driver, stabilization = {}, debug, logger}) {
142
+ return async function takeScreenshot({name, withStatusBar} = {}) {
143
+ logger.verbose('Taking native driver screenshot...')
144
+ const image = makeImage(await driver.takeScreenshot())
145
+ await image.debug({...debug, name, suffix: 'original'})
146
+
147
+ if (stabilization.scale) image.scale(stabilization.scale)
148
+ else image.scale(1 / driver.pixelRatio)
149
+
150
+ if (stabilization.rotate) image.rotate(stabilization.rotate)
151
+
152
+ if (stabilization.crop) image.crop(stabilization.crop)
153
+ else {
154
+ const viewportSize = await driver.getViewportSize()
155
+ const cropRegion = withStatusBar
156
+ ? {x: 0, y: 0, width: viewportSize.width, height: viewportSize.height + driver.statusBarHeight}
157
+ : {top: driver.statusBarHeight, bottom: driver.navigationBarHeight, left: 0, right: 0}
158
+ image.crop(cropRegion)
159
+ }
160
+
161
+ await image.debug({...debug, name, suffix: `viewport${withStatusBar ? '-with-statusbar' : ''}`})
162
+
163
+ return image
164
+ }
165
+ }
11
166
 
12
- const image = await takeScreenshot({withStatusBar})
167
+ function makeCalculateScaleRatio({driver}) {
168
+ let viewportWidth, contentWidth
169
+ const VIEWPORT_THRESHOLD = 1
170
+ const CONTENT_THRESHOLD = 10
171
+ return async function calculateScaleRatio(imageWidth) {
172
+ if (!viewportWidth) viewportWidth = await driver.getViewportSize().then(size => size.width)
173
+ if (!contentWidth) contentWidth = await driver.mainContext.getContentSize().then(size => size.width)
174
+ // If the image's width is the same as the viewport's width or the
175
+ // top level context's width, no scaling is necessary.
176
+ if (
177
+ (imageWidth >= viewportWidth - VIEWPORT_THRESHOLD && imageWidth <= viewportWidth + VIEWPORT_THRESHOLD) ||
178
+ (imageWidth >= contentWidth - CONTENT_THRESHOLD && imageWidth <= contentWidth + CONTENT_THRESHOLD)
179
+ ) {
180
+ return 1
181
+ }
13
182
 
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)}
183
+ const scaledImageWidth = Math.round(imageWidth / driver.pixelRatio)
184
+ return viewportWidth / scaledImageWidth / driver.pixelRatio
22
185
  }
23
186
  }
24
187
 
25
- module.exports = takeViewportScreenshot
188
+ module.exports = makeTakeViewportScreenshot
@@ -1,9 +1,52 @@
1
1
  const assert = require('assert')
2
2
  const pixelmatch = require('pixelmatch')
3
3
  const {Driver} = require('@applitools/driver')
4
- const spec = require('../util/spec-driver')
4
+ const spec = require('@applitools/spec-driver-webdriverio')
5
5
  const makeImage = require('../../src/image')
6
- const screenshoter = require('../../index')
6
+ const takeScreenshot = require('../../index')
7
+
8
+ const env = {
9
+ android: {
10
+ url: 'https://ondemand.saucelabs.com/wd/hub',
11
+ capabilities: {
12
+ name: 'Android Screenshoter Test',
13
+ browserName: '',
14
+ platformName: 'Android',
15
+ platformVersion: '7.0',
16
+ appiumVersion: '1.20.2',
17
+ deviceName: 'Samsung Galaxy S8 FHD GoogleAPI Emulator',
18
+ automationName: 'uiautomator2',
19
+ app: 'https://applitools.jfrog.io/artifactory/Examples/android/1.3/app-debug.apk',
20
+ username: process.env.SAUCE_USERNAME,
21
+ accessKey: process.env.SAUCE_ACCESS_KEY,
22
+ },
23
+ },
24
+ androidx: {
25
+ url: 'https://ondemand.saucelabs.com/wd/hub',
26
+ capabilities: {
27
+ name: 'AndroidX Screenshoter Test',
28
+ browserName: '',
29
+ platformName: 'Android',
30
+ platformVersion: '10.0',
31
+ appiumVersion: '1.20.2',
32
+ deviceName: 'Google Pixel 3a XL GoogleAPI Emulator',
33
+ automationName: 'uiautomator2',
34
+ app: 'https://applitools.jfrog.io/artifactory/Examples/androidx/1.3.1/app_androidx.apk',
35
+ username: process.env.SAUCE_USERNAME,
36
+ accessKey: process.env.SAUCE_ACCESS_KEY,
37
+ },
38
+
39
+ // url: 'http://0.0.0.0:4723/wd/hub',
40
+ // capabilities: {
41
+ // deviceName: 'Google Pixel 3a XL',
42
+ // platformName: 'Android',
43
+ // platformVersion: '10.0',
44
+ // automationName: 'uiautomator2',
45
+ // avd: 'Pixel_3a_XL',
46
+ // app: 'https://applitools.jfrog.io/artifactory/Examples/androidx/1.3.3/app_androidx.apk',
47
+ // },
48
+ },
49
+ }
7
50
 
8
51
  describe('screenshoter', () => {
9
52
  const logger = {log: () => {}, warn: () => {}, error: () => {}, verbose: () => {}}
@@ -20,7 +63,7 @@ describe('screenshoter', () => {
20
63
 
21
64
  describe('android app', () => {
22
65
  before(async () => {
23
- ;[browser, destroyBrowser] = await spec.build({type: 'android'})
66
+ ;[browser, destroyBrowser] = await spec.build(env.android)
24
67
  })
25
68
 
26
69
  after(async () => {
@@ -72,7 +115,7 @@ describe('screenshoter', () => {
72
115
 
73
116
  describe('androidx app', () => {
74
117
  before(async () => {
75
- ;[browser, destroyBrowser] = await spec.build({type: 'androidx'})
118
+ ;[browser, destroyBrowser] = await spec.build(env.androidx)
76
119
  })
77
120
 
78
121
  after(async () => {
@@ -89,6 +132,14 @@ describe('screenshoter', () => {
89
132
  return fullApp({type: 'recycler', x: true})
90
133
  })
91
134
 
135
+ it('take full app screenshot (collapsing layout)', () => {
136
+ return fullApp({type: 'collapsing', x: true})
137
+ })
138
+
139
+ it.skip('take full app screenshot (overlapped status bar)', () => {
140
+ return fullApp({type: 'overlapped', x: true})
141
+ })
142
+
92
143
  it('take full element screenshot', () => {
93
144
  return fullElement()
94
145
  })
@@ -97,7 +148,7 @@ describe('screenshoter', () => {
97
148
  async function app(options = {}) {
98
149
  const expectedPath = `./test/fixtures/android/app${options.withStatusBar ? '-statusbar' : ''}.png`
99
150
 
100
- const screenshot = await screenshoter({logger, driver, wait: 1500, ...options})
151
+ const screenshot = await takeScreenshot({logger, driver, wait: 1500, ...options})
101
152
  try {
102
153
  if (options.withStatusBar) await sanitizeStatusBar(screenshot.image)
103
154
  const actual = await screenshot.image.toObject()
@@ -109,8 +160,15 @@ describe('screenshoter', () => {
109
160
  }
110
161
  }
111
162
  async function fullApp({type, x, ...options} = {}) {
112
- let buttonSelector, expectedPath
113
- if (type === 'recycler') {
163
+ let buttonSelector, expectedPath, scrollingElementSelector
164
+ if (type === 'overlapped') {
165
+ buttonSelector = {type: 'id', selector: 'btn_recycler_view_under_status_bar_activity'}
166
+ expectedPath = `./test/fixtures/android/x-app-fully-overlapped${options.withStatusBar ? '-statusbar' : ''}.png`
167
+ } else if (type === 'collapsing') {
168
+ buttonSelector = {type: 'id', selector: 'btn_recycler_view_nested_collapsing'}
169
+ scrollingElementSelector = {type: 'id', selector: 'recyclerView'}
170
+ expectedPath = `./test/fixtures/android/x-app-fully-collapsing${options.withStatusBar ? '-statusbar' : ''}.png`
171
+ } else if (type === 'recycler') {
114
172
  if (x) {
115
173
  buttonSelector = {type: 'id', selector: 'btn_recycler_view_activity'}
116
174
  expectedPath = `./test/fixtures/android/x-app-fully-recycler${options.withStatusBar ? '-statusbar' : ''}.png`
@@ -119,7 +177,7 @@ describe('screenshoter', () => {
119
177
  expectedPath = `./test/fixtures/android/app-fully-recycler${options.withStatusBar ? '-statusbar' : ''}.png`
120
178
  }
121
179
  } else if (type === 'non-scrollable') {
122
- buttonSelector = {type: 'id', selector: 'btn_edit_text'}
180
+ buttonSelector = {type: 'id', selector: 'btn_activity_as_dialog'}
123
181
  expectedPath = `./test/fixtures/android/app-fully-non-scrollable${options.withStatusBar ? '-statusbar' : ''}.png`
124
182
  } else {
125
183
  buttonSelector = {type: 'id', selector: 'btn_scroll_view_footer_header'}
@@ -129,7 +187,13 @@ describe('screenshoter', () => {
129
187
  const button = await driver.element(buttonSelector)
130
188
  await button.click()
131
189
 
132
- const screenshot = await screenshoter({
190
+ await driver.init()
191
+
192
+ if (scrollingElementSelector) {
193
+ await driver.currentContext.setScrollingElement(scrollingElementSelector)
194
+ }
195
+
196
+ const screenshot = await takeScreenshot({
133
197
  logger,
134
198
  driver,
135
199
  fully: true,
@@ -149,7 +213,7 @@ describe('screenshoter', () => {
149
213
  }
150
214
  }
151
215
  async function region(options) {
152
- const screenshot = await screenshoter({
216
+ const screenshot = await takeScreenshot({
153
217
  logger,
154
218
  driver,
155
219
  region: {x: 30, y: 500, height: 100, width: 200},
@@ -166,7 +230,7 @@ describe('screenshoter', () => {
166
230
  }
167
231
  }
168
232
  async function fullRegion(options) {
169
- const screenshot = await screenshoter({
233
+ const screenshot = await takeScreenshot({
170
234
  logger,
171
235
  driver,
172
236
  region: {x: 30, y: 10, height: 700, width: 200},
@@ -184,7 +248,7 @@ describe('screenshoter', () => {
184
248
  }
185
249
  }
186
250
  async function element(options) {
187
- const screenshot = await screenshoter({
251
+ const screenshot = await takeScreenshot({
188
252
  logger,
189
253
  driver,
190
254
  region: {type: 'id', selector: 'btn_recycler_view'},
@@ -207,7 +271,7 @@ describe('screenshoter', () => {
207
271
  })
208
272
  await button.click()
209
273
 
210
- const screenshot = await screenshoter({
274
+ const screenshot = await takeScreenshot({
211
275
  logger,
212
276
  driver,
213
277
  region: {type: 'id', selector: 'recyclerView'},
@@ -0,0 +1,155 @@
1
+ const assert = require('assert')
2
+ const pixelmatch = require('pixelmatch')
3
+ const {Driver} = require('@applitools/driver')
4
+ const utils = require('@applitools/utils')
5
+ const spec = require('@applitools/spec-driver-webdriverio')
6
+ const makeImage = require('../../src/image')
7
+ const takeScreenshot = require('../../index')
8
+
9
+ describe.skip('external tests', () => {
10
+ const logger = {log: () => {}, warn: () => {}, error: () => {}, verbose: () => {}}
11
+ let browser, destroyBrowser
12
+
13
+ afterEach(async () => {
14
+ await destroyBrowser()
15
+ })
16
+
17
+ it('AGL Android - full app screenshot of the view with animated scroll', async () => {
18
+ const expectedPath = `./test/fixtures/external/agl.png`
19
+
20
+ ;[browser, destroyBrowser] = await spec.build({
21
+ url: 'https://hub.browserstack.com/wd/hub',
22
+ capabilities: {
23
+ platformName: 'android',
24
+ 'appium:platformVersion': '11.0',
25
+ 'appium:deviceName': 'Google Pixel 5',
26
+ 'appium:app': 'agl_app_android',
27
+ 'bstack:options': {
28
+ userName: process.env.BROWSERSTACK_USERNAME,
29
+ accessKey: process.env.BROWSERSTACK_ACCESS_KEY,
30
+ },
31
+ },
32
+ })
33
+
34
+ await browser.closeApp()
35
+ await browser.launchApp()
36
+ const driver = await new Driver({driver: browser, spec, logger}).init()
37
+
38
+ const saveBtn = await browser.$('//android.widget.Button')
39
+ await saveBtn.click()
40
+
41
+ await utils.general.sleep(8000)
42
+ const signinBtn = await browser.$('//android.widget.Button[@text="SIGN IN"]')
43
+ await signinBtn.click()
44
+ await utils.general.sleep(10000)
45
+ const emailInput = await browser.$('//android.widget.EditText')
46
+ await emailInput.setValue('daniel.martin@toro.com')
47
+ const nxtBtn = await browser.$('//android.widget.Button[@text="NEXT"]')
48
+ await nxtBtn.click()
49
+ await utils.general.sleep(10000)
50
+ const passwordInput = await browser.$('//android.widget.EditText[@password="true"]')
51
+ await passwordInput.setValue('Welcome@1')
52
+ const loginBtn = await browser.$('//android.widget.Button[@text="LOGIN"]')
53
+ await loginBtn.click()
54
+ await utils.general.sleep(18000)
55
+ const skipBtn = await browser.$('//android.widget.Button[@text="SKIP"]')
56
+ await skipBtn.click()
57
+ await utils.general.sleep(5000)
58
+ const finishBtn = await browser.$('//android.widget.Button[@text="FINISH"]')
59
+ await finishBtn.click()
60
+ await utils.general.sleep(15000)
61
+
62
+ await driver.init()
63
+
64
+ const screenshot = await takeScreenshot({
65
+ logger,
66
+ driver,
67
+ fully: true,
68
+ framed: true,
69
+ stabilization: {crop: {top: 53, bottom: 16, left: 0, right: 0}},
70
+ debug: {path: './'},
71
+ })
72
+ try {
73
+ const actual = await screenshot.image.toObject()
74
+ const expected = await makeImage(expectedPath).toObject()
75
+ assert.strictEqual(pixelmatch(actual.data, expected.data, null, expected.width, expected.height), 0)
76
+ } catch (err) {
77
+ await screenshot.image.debug({path: './logs', name: 'viewport_failed', suffix: Date.now()})
78
+ throw err
79
+ }
80
+ })
81
+
82
+ it('AGL iOS - full app screenshot of the view with animated scroll', async () => {
83
+ const expectedPath = `./test/fixtures/external/agl.png`
84
+ ;[browser, destroyBrowser] = await spec.build({
85
+ url: 'https://hub.browserstack.com/wd/hub',
86
+ capabilities: {
87
+ platformName: 'ios',
88
+ 'appium:platformVersion': '14',
89
+ 'appium:deviceName': 'iPhone 12',
90
+ 'appium:app': 'agl_app',
91
+ 'appium:processArguments': `{
92
+ "args": [
93
+ "-dev.environment", "qtrtest",
94
+ "-dev.clear.keychain", "YES",
95
+ "-dev.keychain.shared", "NO",
96
+ "-user.lastQuickTourVersionCompleted", "0"
97
+ ],
98
+ "env": {
99
+ "DYLD_INSERT_LIBRARIES": "@executable_path/Frameworks/EyesiOSHelper.xcframework/ios-arm64/EyesiOSHelper.framework/EyesiOSHelper"
100
+ }
101
+ }`,
102
+ 'bstack:options': {
103
+ userName: process.env.BROWSERSTACK_USERNAME,
104
+ accessKey: process.env.BROWSERSTACK_ACCESS_KEY,
105
+ },
106
+ },
107
+ })
108
+
109
+ await browser.closeApp()
110
+ await browser.launchApp()
111
+ const driver = await new Driver({driver: browser, spec, logger}).init()
112
+
113
+ await browser.acceptAlert()
114
+
115
+ await utils.general.sleep(8000)
116
+ const signinBtn = await browser.$('//XCUIElementTypeButton[@name="SIGN IN"]')
117
+ await signinBtn.click()
118
+ await utils.general.sleep(8000)
119
+ const emailInput = await browser.$('//XCUIElementTypeTextField')
120
+ await emailInput.setValue('daniel.martin@toro.com')
121
+ const nxtBtn = await browser.$('//XCUIElementTypeButton[@name="NEXT"]')
122
+ await nxtBtn.click()
123
+ await utils.general.sleep(8000)
124
+ const passwordInput = await browser.$('//XCUIElementTypeSecureTextField')
125
+ await passwordInput.setValue('Welcome@1')
126
+ const loginBtn = await browser.$('//XCUIElementTypeButton[@name="LOGIN"]')
127
+ await loginBtn.click()
128
+ await utils.general.sleep(18000)
129
+ const skipBtn = await browser.$('//XCUIElementTypeButton[@name="Skip"]')
130
+ await skipBtn.click()
131
+ await utils.general.sleep(5000)
132
+ const finishBtn = await browser.$('//XCUIElementTypeButton[@name="Finish"]')
133
+ await finishBtn.click()
134
+ await utils.general.sleep(20000)
135
+
136
+ await driver.init()
137
+
138
+ const screenshot = await takeScreenshot({
139
+ logger,
140
+ driver,
141
+ fully: true,
142
+ framed: true,
143
+ wait: 1500,
144
+ // debug: {path: './'},
145
+ })
146
+ try {
147
+ const actual = await screenshot.image.toObject()
148
+ const expected = await makeImage(expectedPath).toObject()
149
+ assert.strictEqual(pixelmatch(actual.data, expected.data, null, expected.width, expected.height), 0)
150
+ } catch (err) {
151
+ await screenshot.image.debug({path: './logs', name: 'viewport_failed', suffix: Date.now()})
152
+ throw err
153
+ }
154
+ })
155
+ })
@@ -1,9 +1,32 @@
1
1
  const assert = require('assert')
2
2
  const pixelmatch = require('pixelmatch')
3
3
  const {Driver} = require('@applitools/driver')
4
- const spec = require('../util/spec-driver')
4
+ const spec = require('@applitools/spec-driver-webdriverio')
5
5
  const makeImage = require('../../src/image')
6
- const screenshoter = require('../../index')
6
+ const takeScreenshot = require('../../index')
7
+
8
+ const env = {
9
+ url: 'https://ondemand.saucelabs.com/wd/hub',
10
+ capabilities: {
11
+ name: 'iOS Screenshoter Test',
12
+ deviceName: 'iPhone 11 Pro Simulator',
13
+ platformName: 'iOS',
14
+ platformVersion: '13.4',
15
+ appiumVersion: '1.19.2',
16
+ automationName: 'XCUITest',
17
+ app: 'https://applitools.jfrog.io/artifactory/Examples/IOSTestApp/1.8/app/IOSTestApp.zip',
18
+ username: process.env.SAUCE_USERNAME,
19
+ accessKey: process.env.SAUCE_ACCESS_KEY,
20
+ },
21
+
22
+ // url: 'http://0.0.0.0:4723/wd/hub',
23
+ // capabilities: {
24
+ // deviceName: 'iPhone 11 Pro',
25
+ // platformName: 'iOS',
26
+ // platformVersion: '14.5',
27
+ // app: '/Users/kyrylo/Downloads/IOSTestApp.zip',
28
+ // },
29
+ }
7
30
 
8
31
  describe('screenshoter ios', () => {
9
32
  const logger = {log: () => {}, warn: () => {}, error: () => {}, verbose: () => {}}
@@ -25,7 +48,7 @@ describe('screenshoter ios', () => {
25
48
  }
26
49
 
27
50
  before(async () => {
28
- ;[browser, destroyBrowser] = await spec.build({type: 'ios'})
51
+ ;[browser, destroyBrowser] = await spec.build(env)
29
52
  })
30
53
 
31
54
  after(async () => {
@@ -62,6 +85,22 @@ describe('screenshoter ios', () => {
62
85
  return fullApp({type: 'collection'})
63
86
  })
64
87
 
88
+ it('take full app screenshot (table view with collapsing header)', () => {
89
+ return fullApp({type: 'collapsing'})
90
+ })
91
+
92
+ it('take full app screenshot (collection view with overlapped status bar)', () => {
93
+ return fullApp({type: 'overlapped'})
94
+ })
95
+
96
+ it('take full app screenshot with status bar (collection view with overlapped status bar)', () => {
97
+ return fullApp({type: 'overlapped', withStatusBar: true})
98
+ })
99
+
100
+ it('take full app screenshot (collection view with superview)', () => {
101
+ return fullApp({type: 'superview'})
102
+ })
103
+
65
104
  it('take region screenshot', () => {
66
105
  return region()
67
106
  })
@@ -81,7 +120,7 @@ describe('screenshoter ios', () => {
81
120
  async function app(options = {}) {
82
121
  const expectedPath = `./test/fixtures/ios/app${options.withStatusBar ? '-statusbar' : ''}.png`
83
122
 
84
- const screenshot = await screenshoter({logger, driver, ...options})
123
+ const screenshot = await takeScreenshot({logger, driver, ...options})
85
124
  try {
86
125
  if (options.withStatusBar) await sanitizeStatusBar(screenshot.image)
87
126
  const actual = await screenshot.image.toObject()
@@ -93,8 +132,19 @@ describe('screenshoter ios', () => {
93
132
  }
94
133
  }
95
134
  async function fullApp({type, ...options} = {}) {
96
- let buttonSelector, expectedPath
97
- if (type === 'collection') {
135
+ let buttonSelector, expectedPath, overlap
136
+ if (type === 'superview') {
137
+ overlap = {top: 200}
138
+ buttonSelector = {type: 'accessibility id', selector: 'Bottom to superview'}
139
+ expectedPath = `./test/fixtures/ios/app-fully-superview${options.withStatusBar ? '-statusbar' : ''}.png`
140
+ } else if (type === 'overlapped') {
141
+ overlap = {top: 200}
142
+ buttonSelector = {type: 'accessibility id', selector: 'Bottom to safe area'}
143
+ expectedPath = `./test/fixtures/ios/app-fully-overlapped${options.withStatusBar ? '-statusbar' : ''}.png`
144
+ } else if (type === 'collapsing') {
145
+ buttonSelector = {type: 'accessibility id', selector: 'Table view with stretchable header'}
146
+ expectedPath = `./test/fixtures/ios/app-fully-collapsing${options.withStatusBar ? '-statusbar' : ''}.png`
147
+ } else if (type === 'collection') {
98
148
  buttonSelector = {type: 'accessibility id', selector: 'Collection view'}
99
149
  expectedPath = `./test/fixtures/ios/app-fully-collection${options.withStatusBar ? '-statusbar' : ''}.png`
100
150
  } else if (type === 'table') {
@@ -108,13 +158,16 @@ describe('screenshoter ios', () => {
108
158
  const button = await driver.element(buttonSelector)
109
159
  await button.click()
110
160
 
111
- const screenshot = await screenshoter({
161
+ await driver.init()
162
+
163
+ const screenshot = await takeScreenshot({
112
164
  logger,
113
165
  driver,
114
166
  fully: true,
115
167
  framed: true,
116
168
  scrollingMode: 'scroll',
117
169
  wait: 1500,
170
+ overlap: {top: 10, bottom: 50, ...overlap},
118
171
  ...options,
119
172
  })
120
173
  try {
@@ -128,7 +181,7 @@ describe('screenshoter ios', () => {
128
181
  }
129
182
  }
130
183
  async function region(options) {
131
- const screenshot = await screenshoter({
184
+ const screenshot = await takeScreenshot({
132
185
  logger,
133
186
  driver,
134
187
  region: {x: 30, y: 500, height: 100, width: 200},
@@ -146,7 +199,7 @@ describe('screenshoter ios', () => {
146
199
  }
147
200
  }
148
201
  async function fullRegion(options) {
149
- const screenshot = await screenshoter({
202
+ const screenshot = await takeScreenshot({
150
203
  logger,
151
204
  driver,
152
205
  region: {x: 30, y: 10, height: 700, width: 200},
@@ -164,7 +217,7 @@ describe('screenshoter ios', () => {
164
217
  }
165
218
  }
166
219
  async function element(options) {
167
- const screenshot = await screenshoter({
220
+ const screenshot = await takeScreenshot({
168
221
  logger,
169
222
  driver,
170
223
  region: {type: 'accessibility id', selector: 'Table view'},
@@ -187,7 +240,7 @@ describe('screenshoter ios', () => {
187
240
  })
188
241
  await button.click()
189
242
 
190
- const screenshot = await screenshoter({
243
+ const screenshot = await takeScreenshot({
191
244
  logger,
192
245
  driver,
193
246
  region: {type: 'xpath', selector: '//XCUIElementTypeTable[1]'},