@applitools/screenshoter 3.2.5 → 3.2.9

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 (57) hide show
  1. package/.bongo/dry-run/package-lock.json +49 -6
  2. package/.bongo/dry-run/package.json +5 -0
  3. package/.bongo/dry-run.tgz +0 -0
  4. package/CHANGELOG.md +16 -0
  5. package/logs/screenshot_2021_11_14_12_35_00_342Z_full_frame_failed.png +0 -0
  6. package/logs/screenshot_2021_11_14_12_38_00_715Z_frame_failed.png +0 -0
  7. package/logs/screenshot_2021_11_14_12_38_03_866Z_frame_failed.png +0 -0
  8. package/logs/screenshot_2021_11_14_13_02_56_464Z_full_app_failed_1636894976464.png +0 -0
  9. package/logs/screenshot_2021_11_14_13_04_27_904Z_full_app_failed_1636895067904.png +0 -0
  10. package/logs/screenshot_2021_11_14_13_06_13_662Z_full_app_failed_1636895173662.png +0 -0
  11. package/logs/screenshot_2021_11_14_13_06_23_745Z_full_app_failed_1636895183745.png +0 -0
  12. package/logs/screenshot_2021_11_14_13_18_31_571Z_full_app_failed_1636895911571.png +0 -0
  13. package/logs/screenshot_2021_11_14_13_25_54_557Z_viewport_failed_1636896354557.png +0 -0
  14. package/logs/screenshot_2021_11_14_13_29_32_326Z_viewport_failed_1636896572326.png +0 -0
  15. package/logs/screenshot_2021_11_14_13_34_22_483Z_viewport_failed_1636896862483.png +0 -0
  16. package/logs/screenshot_2021_11_14_13_37_25_734Z_viewport_failed_1636897045734.png +0 -0
  17. package/logs/screenshot_2021_11_14_13_42_25_024Z_viewport_failed_1636897345024.png +0 -0
  18. package/logs/screenshot_2021_11_14_13_57_24_366Z_full_app_failed_1636898244366.png +0 -0
  19. package/logs/screenshot_2021_11_14_14_20_42_951Z_full_app_failed_1636899642951.png +0 -0
  20. package/logs/screenshot_2021_11_14_14_31_07_853Z_full_app_failed_1636900267853.png +0 -0
  21. package/logs/screenshot_2021_11_14_14_32_07_195Z_full_app_failed_1636900327195.png +0 -0
  22. package/logs/screenshot_2021_11_14_14_42_16_716Z_full_app_failed_1636900936716.png +0 -0
  23. package/logs/screenshot_2021_11_14_14_47_37_646Z_full_app_failed_1636901257646.png +0 -0
  24. package/logs/screenshot_2021_11_14_14_54_18_522Z_full_app_failed_1636901658522.png +0 -0
  25. package/logs/screenshot_2021_11_14_14_55_36_756Z_full_app_failed_1636901736756.png +0 -0
  26. package/logs/screenshot_2021_11_14_15_00_26_000Z_full_app_failed_1636902026000.png +0 -0
  27. package/logs/screenshot_2021_11_14_15_04_13_598Z_full_app_failed_1636902253598.png +0 -0
  28. package/logs/screenshot_2021_11_14_15_07_37_914Z_full_app_failed_1636902457914.png +0 -0
  29. package/logs/screenshot_2021_11_14_15_12_20_039Z_full_app_failed_1636902740039.png +0 -0
  30. package/logs/screenshot_2021_11_14_15_15_44_401Z_full_app_failed_1636902944401.png +0 -0
  31. package/logs/screenshot_2021_11_14_15_26_23_318Z_viewport_failed_1636903583318.png +0 -0
  32. package/package.json +10 -6
  33. package/src/image.js +4 -1
  34. package/src/screenshoter.js +15 -11
  35. package/src/take-screenshot.js +1 -1
  36. package/src/take-stitched-screenshot.js +20 -6
  37. package/test/e2e/android.spec.js +44 -4
  38. package/test/e2e/external.spec.js +84 -0
  39. package/test/e2e/ios.spec.js +17 -2
  40. package/test/e2e/web-ios.spec.js +17 -2
  41. package/test/e2e/web.spec.js +13 -6
  42. package/test/fixtures/android/app-fully-non-scrollable.png +0 -0
  43. package/test/fixtures/android/region.png +0 -0
  44. package/test/fixtures/external/agl.png +0 -0
  45. package/test/fixtures/ios/app-fully-table.png +0 -0
  46. package/test/fixtures/web/frame-fully.png +0 -0
  47. package/test/fixtures/web/frame.png +0 -0
  48. package/test/fixtures/web/inner-element-fully.png +0 -0
  49. package/test/fixtures/web/inner-element.png +0 -0
  50. package/test/fixtures/web/inner-region-fully.png +0 -0
  51. package/test/fixtures/web/inner-region.png +0 -0
  52. package/test/fixtures/web/page-fully.png +0 -0
  53. package/test/fixtures/web/page.png +0 -0
  54. package/test/fixtures/web/region-fully.png +0 -0
  55. package/test/fixtures/web/region.png +0 -0
  56. package/docker-compose.yaml +0 -21
  57. package/test/util/spec-driver.js +0 -288
@@ -1,13 +1,56 @@
1
1
  {
2
+ "name": "dry-run",
3
+ "lockfileVersion": 2,
2
4
  "requires": true,
3
- "lockfileVersion": 1,
5
+ "packages": {
6
+ "": {
7
+ "dependencies": {
8
+ "@applitools/screenshoter": "file:../dry-run.tgz"
9
+ }
10
+ },
11
+ "node_modules/@applitools/screenshoter": {
12
+ "version": "3.2.8",
13
+ "resolved": "file:../dry-run.tgz",
14
+ "integrity": "sha512-GjN+h/vj5/lKFjJPm302jD0xSFIOoSG7gPU/ogEUcCS9l5FDcMW27PRMA/xGlolKrabmCgDHJqVbAXE1WCgjyQ==",
15
+ "license": "SEE LICENSE IN LICENSE",
16
+ "dependencies": {
17
+ "@applitools/snippets": "2.1.7",
18
+ "@applitools/utils": "1.2.4",
19
+ "png-async": "0.9.4"
20
+ },
21
+ "engines": {
22
+ "node": ">= 8.9.0"
23
+ }
24
+ },
25
+ "node_modules/@applitools/snippets": {
26
+ "version": "2.1.7",
27
+ "resolved": "https://registry.npmjs.org/@applitools/snippets/-/snippets-2.1.7.tgz",
28
+ "integrity": "sha512-Tr4Gj7Qov/oPy+8WI4oVmmubxqpOzr8P3Wjzpl6rA57xKLg6/TiIg5oZNb4+jEmO2ShjNYLaEwRWHl7kPgb4fw==",
29
+ "engines": {
30
+ "node": ">=8.9.0"
31
+ }
32
+ },
33
+ "node_modules/@applitools/utils": {
34
+ "version": "1.2.4",
35
+ "resolved": "https://registry.npmjs.org/@applitools/utils/-/utils-1.2.4.tgz",
36
+ "integrity": "sha512-w7ma6FFGyqhdP6LEcuHFWOcH7EzBjnoAX3UfbFWcTHA3QXnXPX37Y2ENYRodfwkorP1cUKyUHwNXJB/BMIj/hg==",
37
+ "engines": {
38
+ "node": ">= 8.9.0"
39
+ }
40
+ },
41
+ "node_modules/png-async": {
42
+ "version": "0.9.4",
43
+ "resolved": "https://registry.npmjs.org/png-async/-/png-async-0.9.4.tgz",
44
+ "integrity": "sha512-B//AXX9TkneKfgtOpT1mdUnnhk2BImGD+a98vImsMU8uo1dBeHyW/kM2erWZ/CsYteTPU/xKG+t6T62heHkC3A=="
45
+ }
46
+ },
4
47
  "dependencies": {
5
48
  "@applitools/screenshoter": {
6
49
  "version": "file:../dry-run.tgz",
7
- "integrity": "sha512-4dg3I0S18BIPhwzVzASacB3gOR5nKVzybivZJRsyJ2l/BQVY2g3FtV4oZp1RjkCty/FIQpv9E/BPrF/yE7Dylg==",
50
+ "integrity": "sha512-GjN+h/vj5/lKFjJPm302jD0xSFIOoSG7gPU/ogEUcCS9l5FDcMW27PRMA/xGlolKrabmCgDHJqVbAXE1WCgjyQ==",
8
51
  "requires": {
9
52
  "@applitools/snippets": "2.1.7",
10
- "@applitools/utils": "1.2.3",
53
+ "@applitools/utils": "1.2.4",
11
54
  "png-async": "0.9.4"
12
55
  }
13
56
  },
@@ -17,9 +60,9 @@
17
60
  "integrity": "sha512-Tr4Gj7Qov/oPy+8WI4oVmmubxqpOzr8P3Wjzpl6rA57xKLg6/TiIg5oZNb4+jEmO2ShjNYLaEwRWHl7kPgb4fw=="
18
61
  },
19
62
  "@applitools/utils": {
20
- "version": "1.2.3",
21
- "resolved": "https://registry.npmjs.org/@applitools/utils/-/utils-1.2.3.tgz",
22
- "integrity": "sha512-MZXsrzeHTvjFLzpfyKRDUmZWzNxH3gWd3reqYf+1kYimALKB3CO82VDNmkaGJykrRbxEP03Yqha7fHJj9eKslQ=="
63
+ "version": "1.2.4",
64
+ "resolved": "https://registry.npmjs.org/@applitools/utils/-/utils-1.2.4.tgz",
65
+ "integrity": "sha512-w7ma6FFGyqhdP6LEcuHFWOcH7EzBjnoAX3UfbFWcTHA3QXnXPX37Y2ENYRodfwkorP1cUKyUHwNXJB/BMIj/hg=="
23
66
  },
24
67
  "png-async": {
25
68
  "version": "0.9.4",
@@ -0,0 +1,5 @@
1
+ {
2
+ "dependencies": {
3
+ "@applitools/screenshoter": "file:../dry-run.tgz"
4
+ }
5
+ }
Binary file
package/CHANGELOG.md CHANGED
@@ -4,6 +4,22 @@
4
4
  ## Unreleased
5
5
 
6
6
 
7
+ ## 3.2.9 - 2021/11/14
8
+
9
+ - add support of scrollable elements that change its size during scrolling
10
+
11
+ ## 3.2.8 - 2021/10/30
12
+
13
+ - updated to @applitools/utils@1.2.4 (from 1.2.3)
14
+
15
+ ## 3.2.7 - 2021/10/13
16
+
17
+ - handle a case when scrolling element does not exist
18
+
19
+ ## 3.2.6 - 2021/10/12
20
+
21
+ - handle a case when scrolling element does not exist
22
+
7
23
  ## 3.2.5 - 2021/10/5
8
24
 
9
25
  - fix issue with fractional image size after scaling
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applitools/screenshoter",
3
- "version": "3.2.5",
3
+ "version": "3.2.9",
4
4
  "description": "Applitools universal screenshoter for web and native applications",
5
5
  "keywords": [
6
6
  "applitools",
@@ -17,7 +17,8 @@
17
17
  },
18
18
  "repository": {
19
19
  "type": "git",
20
- "url": "git://github.com/applitools/eyes.sdk.javascript1.git"
20
+ "url": "git://github.com/applitools/eyes.sdk.javascript1.git",
21
+ "directory": "packages/screenshoter"
21
22
  },
22
23
  "license": "SEE LICENSE IN LICENSE",
23
24
  "author": {
@@ -49,12 +50,15 @@
49
50
  },
50
51
  "dependencies": {
51
52
  "@applitools/snippets": "2.1.7",
52
- "@applitools/utils": "1.2.3",
53
+ "@applitools/utils": "1.2.4",
53
54
  "png-async": "0.9.4"
54
55
  },
55
56
  "devDependencies": {
56
- "@applitools/driver": "1.2.5",
57
- "@applitools/sdk-release-kit": "0.13.3",
57
+ "@applitools/driver": "1.3.2",
58
+ "@applitools/sdk-release-kit": "0.13.4",
59
+ "@applitools/spec-driver-webdriverio": "1.2.0",
60
+ "@applitools/test-utils": "1.0.9",
61
+ "chromedriver": "^95.0.0",
58
62
  "eslint": "^7.9.0",
59
63
  "eslint-plugin-mocha-no-only": "^1.1.1",
60
64
  "eslint-plugin-node": "^11.1.0",
@@ -63,7 +67,7 @@
63
67
  "mocha": "^8.2.1",
64
68
  "pixelmatch": "^5.2.1",
65
69
  "prettier": "1.19.0",
66
- "webdriverio": "^6.10.5"
70
+ "webdriverio": "^7.16.7"
67
71
  },
68
72
  "engines": {
69
73
  "node": ">= 8.9.0"
package/src/image.js CHANGED
@@ -289,7 +289,10 @@ async function copy(dstImage, srcImage, offset) {
289
289
  }
290
290
 
291
291
  async function combine(firstImage, lastImage, srcImage, region) {
292
- region = utils.geometry.intersect({x: 0, y: 0, width: firstImage.width, height: firstImage.height}, region)
292
+ region = utils.geometry.intersect(
293
+ {x: 0, y: 0, width: firstImage.width, height: firstImage.height},
294
+ utils.geometry.round(region),
295
+ )
293
296
 
294
297
  if (region.x === 0 && region.y === 0 && region.width >= firstImage.width && region.height >= firstImage.height) {
295
298
  return srcImage
@@ -27,6 +27,7 @@ async function screenshoter({
27
27
  framed = framed && fully && window
28
28
  // screenshots with status bar could be taken only when screenshot of app or framed app fully was requested
29
29
  withStatusBar = withStatusBar && driver.isNative && window && (!fully || framed)
30
+ scrollingMode = driver.isNative ? 'scroll' : scrollingMode
30
31
 
31
32
  const activeContext = driver.currentContext
32
33
  const context =
@@ -54,13 +55,14 @@ async function screenshoter({
54
55
  try {
55
56
  if (!window) await scrollIntoViewport({...target, logger})
56
57
 
57
- const screenshot = fully
58
- ? await takeStitchedScreenshot({...target, withStatusBar, overlap, framed, wait, stabilization, debug, logger})
59
- : await takeViewportScreenshot({...target, withStatusBar, wait, stabilization, debug, logger})
58
+ const screenshot =
59
+ fully && target.scroller
60
+ ? await takeStitchedScreenshot({...target, withStatusBar, overlap, framed, wait, stabilization, debug, logger})
61
+ : await takeViewportScreenshot({...target, withStatusBar, wait, stabilization, debug, logger})
60
62
 
61
63
  if (hooks && hooks.afterScreenshot) {
62
64
  // imitate image-like state for the hook
63
- if (window && fully) {
65
+ if (window && fully && target.scroller) {
64
66
  await target.scroller.moveTo({x: 0, y: 0}, await driver.mainContext.getScrollingElement())
65
67
  }
66
68
  await hooks.afterScreenshot({driver, scroller: target.scroller, screenshot})
@@ -68,7 +70,9 @@ async function screenshoter({
68
70
 
69
71
  return screenshot
70
72
  } finally {
71
- await target.scroller.restoreScrollbars()
73
+ if (target.scroller) {
74
+ await target.scroller.restoreScrollbars()
75
+ }
72
76
 
73
77
  // if there was active element and we have blurred it, then restore focus
74
78
  if (activeElement) await context.focusElement(activeElement)
@@ -93,7 +97,7 @@ async function getTarget({window, context, region, fully, scrollingMode, logger}
93
97
  const scrollingElement = await context.main.getScrollingElement()
94
98
  return {
95
99
  context: context.main,
96
- scroller: makeScroller({element: scrollingElement, scrollingMode, logger}),
100
+ scroller: scrollingElement ? makeScroller({element: scrollingElement, scrollingMode, logger}) : null,
97
101
  }
98
102
  } else if (region) {
99
103
  if (utils.types.has(region, ['x', 'y', 'width', 'height'])) {
@@ -102,7 +106,7 @@ async function getTarget({window, context, region, fully, scrollingMode, logger}
102
106
  return {
103
107
  context,
104
108
  region,
105
- scroller: makeScroller({element: scrollingElement, scrollingMode, logger}),
109
+ scroller: scrollingElement ? makeScroller({element: scrollingElement, scrollingMode, logger}) : null,
106
110
  }
107
111
  } else {
108
112
  // region by element or selector
@@ -121,14 +125,14 @@ async function getTarget({window, context, region, fully, scrollingMode, logger}
121
125
  return {
122
126
  context: elementContext,
123
127
  region,
124
- scroller: makeScroller({element: scrollingElement, scrollingMode, logger}),
128
+ scroller: scrollingElement ? makeScroller({element: scrollingElement, scrollingMode, logger}) : null,
125
129
  }
126
130
  } else {
127
131
  const scrollingElement = await context.getScrollingElement()
128
132
  return {
129
133
  context: elementContext,
130
134
  region: await element.getRegion(),
131
- scroller: makeScroller({element: scrollingElement, scrollingMode, logger}),
135
+ scroller: scrollingElement ? makeScroller({element: scrollingElement, scrollingMode, logger}) : null,
132
136
  }
133
137
  }
134
138
  }
@@ -138,7 +142,7 @@ async function getTarget({window, context, region, fully, scrollingMode, logger}
138
142
  const scrollingElement = await context.getScrollingElement()
139
143
  return {
140
144
  context,
141
- scroller: makeScroller({logger, element: scrollingElement, scrollingMode}),
145
+ scroller: scrollingElement ? makeScroller({logger, element: scrollingElement, scrollingMode}) : null,
142
146
  }
143
147
  } else {
144
148
  const scrollingElement = await context.parent.getScrollingElement()
@@ -146,7 +150,7 @@ async function getTarget({window, context, region, fully, scrollingMode, logger}
146
150
  return {
147
151
  context: context.parent,
148
152
  region: await element.getRegion(), // IMHO we should use CLIENT (without borders) region here
149
- scroller: makeScroller({logger, element: scrollingElement, scrollingMode}),
153
+ scroller: scrollingElement ? makeScroller({logger, element: scrollingElement, scrollingMode}) : null,
150
154
  }
151
155
  }
152
156
  }
@@ -150,7 +150,7 @@ function makeTakeNativeScreenshot({driver, stabilization = {}, debug, logger}) {
150
150
  const viewportSize = await driver.getViewportSize()
151
151
  const cropRegion = withStatusBar
152
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}
153
+ : {top: driver.statusBarHeight, bottom: driver.navigationBarHeight, left: 0, right: 0}
154
154
  image.crop(cropRegion)
155
155
  await image.debug({...debug, name, suffix: `viewport${withStatusBar ? '-with-statusbar' : ''}`})
156
156
  }
@@ -26,6 +26,8 @@ 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
32
  let image = await takeScreenshot({name: 'initial', withStatusBar})
31
33
  const firstImage = framed ? makeImage(image) : null
@@ -39,13 +41,13 @@ async function takeStitchedScreenshot({
39
41
 
40
42
  // TODO the solution should not check driver specifics,
41
43
  // in this case target region coordinate should be already related to the scrolling element of the context
42
- const cropRegion = driver.isNative ? targetRegion : await driver.getRegionInViewport(context, targetRegion)
44
+ let cropRegion = driver.isNative ? targetRegion : await driver.getRegionInViewport(context, targetRegion)
43
45
 
44
46
  logger.verbose('cropping...')
45
47
  image.crop(withStatusBar ? utils.geometry.offset(cropRegion, {x: 0, y: driver.statusBarHeight}) : cropRegion)
46
48
  await image.debug({...debug, name: 'initial', suffix: 'region'})
47
49
 
48
- const contentRegion = utils.geometry.region({x: 0, y: 0}, await scroller.getContentSize())
50
+ const contentRegion = utils.geometry.region({x: 0, y: 0}, contentSize)
49
51
  logger.verbose(`Scroller size: ${contentRegion}`)
50
52
 
51
53
  if (region) region = utils.geometry.intersect(region, contentRegion)
@@ -84,11 +86,21 @@ async function takeStitchedScreenshot({
84
86
  ),
85
87
  compensateOffset,
86
88
  )
89
+
90
+ // TODO come up with generic solution
91
+ // The problem is that web default scrolling element treated differently than normal scrollable elements on the web and native
92
+ const cropRegionOffset = driver.isNative
93
+ ? utils.geometry.offsetNegative(
94
+ utils.geometry.location(await scroller.getClientRegion()),
95
+ utils.geometry.location(cropRegion),
96
+ )
97
+ : {x: 0, y: 0}
98
+
87
99
  const cropPartRegion = {
88
- x: cropRegion.x + remainingOffset.x,
89
- y: cropRegion.y + remainingOffset.y,
90
- width: partRegion.width,
91
- height: partRegion.height,
100
+ x: cropRegion.x + remainingOffset.x + cropRegionOffset.x,
101
+ y: cropRegion.y + remainingOffset.y + cropRegionOffset.y,
102
+ width: partRegion.width - cropRegionOffset.x,
103
+ height: partRegion.height - cropRegionOffset.y,
92
104
  }
93
105
  logger.verbose(`Actual offset is ${actualOffset}, remaining offset is ${remainingOffset}`)
94
106
 
@@ -105,6 +117,8 @@ async function takeStitchedScreenshot({
105
117
  await image.debug({...debug, name: partName, suffix: 'region'})
106
118
 
107
119
  const pasteOffset = utils.geometry.offsetNegative(utils.geometry.location(partRegion), initialOffset)
120
+ pasteOffset.y += cropRegionOffset.y
121
+ pasteOffset.x += cropRegionOffset.x
108
122
  await stitchedImage.copy(image, pasteOffset)
109
123
 
110
124
  stitchedSize = {width: pasteOffset.x + image.width, height: pasteOffset.y + image.height}
@@ -1,10 +1,43 @@
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
6
  const screenshoter = require('../../index')
7
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.2.0/app_androidx.apk',
35
+ username: process.env.SAUCE_USERNAME,
36
+ accessKey: process.env.SAUCE_ACCESS_KEY,
37
+ },
38
+ },
39
+ }
40
+
8
41
  describe('screenshoter', () => {
9
42
  const logger = {log: () => {}, warn: () => {}, error: () => {}, verbose: () => {}}
10
43
  let driver, browser, destroyBrowser
@@ -20,7 +53,7 @@ describe('screenshoter', () => {
20
53
 
21
54
  describe('android app', () => {
22
55
  before(async () => {
23
- ;[browser, destroyBrowser] = await spec.build({type: 'android'})
56
+ ;[browser, destroyBrowser] = await spec.build(env.android)
24
57
  })
25
58
 
26
59
  after(async () => {
@@ -53,8 +86,12 @@ describe('screenshoter', () => {
53
86
  return fullApp({type: 'recycler'})
54
87
  })
55
88
 
89
+ it('take full app screenshot (non-scrollable)', () => {
90
+ return fullApp({type: 'non-scrollable'})
91
+ })
92
+
56
93
  it('take region screenshot', () => {
57
- region()
94
+ return region()
58
95
  })
59
96
 
60
97
  it.skip('take full region screenshot', () => {
@@ -68,7 +105,7 @@ describe('screenshoter', () => {
68
105
 
69
106
  describe('androidx app', () => {
70
107
  before(async () => {
71
- ;[browser, destroyBrowser] = await spec.build({type: 'androidx'})
108
+ ;[browser, destroyBrowser] = await spec.build(env.androidx)
72
109
  })
73
110
 
74
111
  after(async () => {
@@ -114,6 +151,9 @@ describe('screenshoter', () => {
114
151
  buttonSelector = {type: 'id', selector: 'btn_recycler_view'}
115
152
  expectedPath = `./test/fixtures/android/app-fully-recycler${options.withStatusBar ? '-statusbar' : ''}.png`
116
153
  }
154
+ } else if (type === 'non-scrollable') {
155
+ buttonSelector = {type: 'id', selector: 'btn_edit_text'}
156
+ expectedPath = `./test/fixtures/android/app-fully-non-scrollable${options.withStatusBar ? '-statusbar' : ''}.png`
117
157
  } else {
118
158
  buttonSelector = {type: 'id', selector: 'btn_scroll_view_footer_header'}
119
159
  expectedPath = `./test/fixtures/android/app-fully-scroll${options.withStatusBar ? '-statusbar' : ''}.png`
@@ -0,0 +1,84 @@
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 screenshoter = 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 - 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': 'android_agl_app',
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(8000)
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(8000)
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
+
56
+ const skipBtn = await browser.$('//android.widget.Button[@text="SKIP"]')
57
+ await skipBtn.click()
58
+ await utils.general.sleep(5000)
59
+ const finishBtn = await browser.$('//android.widget.Button[@text="FINISH"]')
60
+ await finishBtn.click()
61
+ await utils.general.sleep(15000)
62
+
63
+ // const billingBtn = await browser.$('//android.widget.FrameLayout[@content-desc="Billing"]')
64
+ // await billingBtn.click()
65
+ // await utils.general.sleep(25000)
66
+
67
+ const screenshot = await screenshoter({
68
+ logger,
69
+ driver,
70
+ fully: true,
71
+ framed: true,
72
+ stabilization: {crop: {top: 53, bottom: 16, left: 0, right: 0}},
73
+ debug: {path: './'},
74
+ })
75
+ try {
76
+ const actual = await screenshot.image.toObject()
77
+ const expected = await makeImage(expectedPath).toObject()
78
+ assert.strictEqual(pixelmatch(actual.data, expected.data, null, expected.width, expected.height), 0)
79
+ } catch (err) {
80
+ await screenshot.image.debug({path: './logs', name: 'viewport_failed', suffix: Date.now()})
81
+ throw err
82
+ }
83
+ })
84
+ })
@@ -1,10 +1,25 @@
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
6
  const screenshoter = require('../../index')
7
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.5/app/IOSTestApp-1.5.zip',
18
+ username: process.env.SAUCE_USERNAME,
19
+ accessKey: process.env.SAUCE_ACCESS_KEY,
20
+ },
21
+ }
22
+
8
23
  describe('screenshoter ios', () => {
9
24
  const logger = {log: () => {}, warn: () => {}, error: () => {}, verbose: () => {}}
10
25
  let driver, browser, destroyBrowser
@@ -25,7 +40,7 @@ describe('screenshoter ios', () => {
25
40
  }
26
41
 
27
42
  before(async () => {
28
- ;[browser, destroyBrowser] = await spec.build({type: 'ios'})
43
+ ;[browser, destroyBrowser] = await spec.build(env)
29
44
  })
30
45
 
31
46
  after(async () => {
@@ -1,10 +1,25 @@
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 screenshoter = require('../../index')
6
6
  const makeImage = require('../../src/image')
7
7
 
8
+ const env = {
9
+ url: 'https://ondemand.saucelabs.com/wd/hub',
10
+ capabilities: {
11
+ name: 'iOS Web Screenshoter Test',
12
+ deviceName: 'iPhone 11 Pro Simulator',
13
+ browserName: 'safari',
14
+ platformName: 'iOS',
15
+ platformVersion: '14.5',
16
+ appiumVersion: '1.20.1',
17
+ automationName: 'XCUITest',
18
+ username: process.env.SAUCE_USERNAME,
19
+ accessKey: process.env.SAUCE_ACCESS_KEY,
20
+ },
21
+ }
22
+
8
23
  // TODO add tests for page without viewport meta tag
9
24
 
10
25
  describe('screenshoter web ios', () => {
@@ -12,7 +27,7 @@ describe('screenshoter web ios', () => {
12
27
  let driver, browser, destroyBrowser
13
28
 
14
29
  before(async () => {
15
- ;[browser, destroyBrowser] = await spec.build({type: 'web-ios'})
30
+ ;[browser, destroyBrowser] = await spec.build(env)
16
31
  })
17
32
 
18
33
  after(async () => {
@@ -1,10 +1,17 @@
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 screenshoter = require('../../index')
6
6
  const makeImage = require('../../src/image')
7
7
 
8
+ const env = {
9
+ url: 'http://localhost:4444/wd/hub',
10
+ capabilities: {
11
+ browserName: 'chrome',
12
+ },
13
+ }
14
+
8
15
  // TODO add overflowed regions tests
9
16
 
10
17
  describe('screenshoter web', () => {
@@ -12,7 +19,7 @@ describe('screenshoter web', () => {
12
19
  let driver, browser, destroyBrowser
13
20
 
14
21
  before(async () => {
15
- ;[browser, destroyBrowser] = await spec.build({type: 'web'})
22
+ ;[browser, destroyBrowser] = await spec.build(env)
16
23
  })
17
24
 
18
25
  after(async () => {
@@ -37,10 +44,10 @@ describe('screenshoter web', () => {
37
44
  })
38
45
 
39
46
  it('take frame screenshot with "scroll" scrolling', () => {
40
- frame({scrollingMode: 'scroll'})
47
+ return frame({scrollingMode: 'scroll'})
41
48
  })
42
49
  it('take frame screenshot with "css" scrolling', () => {
43
- frame({scrollingMode: 'css'})
50
+ return frame({scrollingMode: 'css'})
44
51
  })
45
52
 
46
53
  it('take full frame screenshot with "scroll" scrolling', () => {
@@ -51,10 +58,10 @@ describe('screenshoter web', () => {
51
58
  })
52
59
 
53
60
  it('take region screenshot with "scroll" scrolling', () => {
54
- region({scrollingMode: 'scroll'})
61
+ return region({scrollingMode: 'scroll'})
55
62
  })
56
63
  it('take region screenshot with "css" scrolling', () => {
57
- region({scrollingMode: 'css'})
64
+ return region({scrollingMode: 'css'})
58
65
  })
59
66
 
60
67
  it('take full region screenshot with "scroll" scrolling', () => {
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -1,21 +0,0 @@
1
- {
2
- "version": "3.4",
3
- "services": {
4
- "chrome": {
5
- "image": "selenium/standalone-chrome",
6
- "volumes": [
7
- "/dev/shm:/dev/shm"
8
- ],
9
- "network_mode": "host"
10
- },
11
- "firefox": {
12
- "image": "selenium/standalone-firefox",
13
- "volumes": [
14
- "/dev/shm:/dev/shm"
15
- ],
16
- "ports": [
17
- "4445:4444"
18
- ]
19
- }
20
- }
21
- }
@@ -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
- }