@applitools/screenshoter 3.2.9 → 3.3.3

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 (97) hide show
  1. package/.bongo/dry-run/package-lock.json +19 -19
  2. package/.bongo/dry-run.tgz +0 -0
  3. package/CHANGELOG.md +23 -0
  4. package/docker-compose.yaml +29 -0
  5. package/index.js +2 -1
  6. package/package.json +6 -6
  7. package/src/find-image-pattern.js +11 -38
  8. package/src/image.js +116 -73
  9. package/src/scroll-into-viewport.js +16 -7
  10. package/src/take-screenshot.js +136 -160
  11. package/src/take-simple-screenshot.js +25 -0
  12. package/src/take-stitched-screenshot.js +32 -52
  13. package/src/take-viewport-screenshot.js +157 -16
  14. package/test/e2e/android.spec.js +84 -11
  15. package/test/e2e/external.spec.js +81 -10
  16. package/test/e2e/ios.spec.js +129 -13
  17. package/test/e2e/web-ios.spec.js +44 -11
  18. package/test/e2e/web.spec.js +20 -15
  19. package/test/fixtures/android/app-fully-non-scrollable.png +0 -0
  20. package/test/fixtures/android/app-fully-recycler.png +0 -0
  21. package/test/fixtures/android/app-fully-scroll-statusbar.png +0 -0
  22. package/test/fixtures/android/app-fully-scroll.png +0 -0
  23. package/test/fixtures/android/app-statusbar.png +0 -0
  24. package/test/fixtures/android/x-app-fully-collapsing.png +0 -0
  25. package/test/fixtures/android/x-app-fully-recycler.png +0 -0
  26. package/test/fixtures/android/x-element-fully.png +0 -0
  27. package/test/fixtures/image/{house.combined-higher-wider.png → house.framed-higher-wider.png} +0 -0
  28. package/test/fixtures/image/{house.combined-higher.png → house.framed-higher.png} +0 -0
  29. package/test/fixtures/image/house.framed-shorter-thinner.png +0 -0
  30. package/test/fixtures/image/{house.combined-wider.png → house.framed-wider.png} +0 -0
  31. package/test/fixtures/ios/app-fully-collapsing.png +0 -0
  32. package/test/fixtures/ios/app-fully-collection.png +0 -0
  33. package/test/fixtures/ios/app-fully-overlapped-statusbar.png +0 -0
  34. package/test/fixtures/ios/app-fully-overlapped.png +0 -0
  35. package/test/fixtures/ios/app-fully-scroll-statusbar.png +0 -0
  36. package/test/fixtures/ios/app-fully-scroll.png +0 -0
  37. package/test/fixtures/ios/app-fully-superview.png +0 -0
  38. package/test/fixtures/ios/app-fully-table.png +0 -0
  39. package/test/fixtures/ios/app-statusbar.png +0 -0
  40. package/test/fixtures/ios/app.png +0 -0
  41. package/test/fixtures/ios/element-fully.png +0 -0
  42. package/test/fixtures/ios/element.png +0 -0
  43. package/test/fixtures/ios/region.png +0 -0
  44. package/test/fixtures/ios/webview-fully.png +0 -0
  45. package/test/fixtures/ios/webview.png +0 -0
  46. package/test/fixtures/pattern/iPad_5th_landscape.png +0 -0
  47. package/test/fixtures/pattern/iPad_5th_portrait.png +0 -0
  48. package/test/fixtures/pattern/iPad_9th_landscape.png +0 -0
  49. package/test/fixtures/pattern/iPad_9th_portrait.png +0 -0
  50. package/test/fixtures/pattern/iPhone_11_landscape.png +0 -0
  51. package/test/fixtures/pattern/iPhone_11_portrait.png +0 -0
  52. package/test/fixtures/pattern/iPhone_13_landscape.png +0 -0
  53. package/test/fixtures/pattern/iPhone_13_portrait.png +0 -0
  54. package/test/fixtures/pattern/iPhone_SE_landscape.png +0 -0
  55. package/test/fixtures/pattern/iPhone_SE_portrait.png +0 -0
  56. package/test/fixtures/pattern/iPhone_XS_portrait_noviewport.png +0 -0
  57. package/test/fixtures/web-ios/page-fully-landscape.png +0 -0
  58. package/test/fixtures/web-ios/page-fully.png +0 -0
  59. package/test/fixtures/web-ios/page-landscape.png +0 -0
  60. package/test/it/find-pattern.spec.js +16 -11
  61. package/test/it/image.spec.js +42 -15
  62. package/logs/screenshot_2021_11_14_12_35_00_342Z_full_frame_failed.png +0 -0
  63. package/logs/screenshot_2021_11_14_12_38_00_715Z_frame_failed.png +0 -0
  64. package/logs/screenshot_2021_11_14_12_38_03_866Z_frame_failed.png +0 -0
  65. package/logs/screenshot_2021_11_14_13_02_56_464Z_full_app_failed_1636894976464.png +0 -0
  66. package/logs/screenshot_2021_11_14_13_04_27_904Z_full_app_failed_1636895067904.png +0 -0
  67. package/logs/screenshot_2021_11_14_13_06_13_662Z_full_app_failed_1636895173662.png +0 -0
  68. package/logs/screenshot_2021_11_14_13_06_23_745Z_full_app_failed_1636895183745.png +0 -0
  69. package/logs/screenshot_2021_11_14_13_18_31_571Z_full_app_failed_1636895911571.png +0 -0
  70. package/logs/screenshot_2021_11_14_13_25_54_557Z_viewport_failed_1636896354557.png +0 -0
  71. package/logs/screenshot_2021_11_14_13_29_32_326Z_viewport_failed_1636896572326.png +0 -0
  72. package/logs/screenshot_2021_11_14_13_34_22_483Z_viewport_failed_1636896862483.png +0 -0
  73. package/logs/screenshot_2021_11_14_13_37_25_734Z_viewport_failed_1636897045734.png +0 -0
  74. package/logs/screenshot_2021_11_14_13_42_25_024Z_viewport_failed_1636897345024.png +0 -0
  75. package/logs/screenshot_2021_11_14_13_57_24_366Z_full_app_failed_1636898244366.png +0 -0
  76. package/logs/screenshot_2021_11_14_14_20_42_951Z_full_app_failed_1636899642951.png +0 -0
  77. package/logs/screenshot_2021_11_14_14_31_07_853Z_full_app_failed_1636900267853.png +0 -0
  78. package/logs/screenshot_2021_11_14_14_32_07_195Z_full_app_failed_1636900327195.png +0 -0
  79. package/logs/screenshot_2021_11_14_14_42_16_716Z_full_app_failed_1636900936716.png +0 -0
  80. package/logs/screenshot_2021_11_14_14_47_37_646Z_full_app_failed_1636901257646.png +0 -0
  81. package/logs/screenshot_2021_11_14_14_54_18_522Z_full_app_failed_1636901658522.png +0 -0
  82. package/logs/screenshot_2021_11_14_14_55_36_756Z_full_app_failed_1636901736756.png +0 -0
  83. package/logs/screenshot_2021_11_14_15_00_26_000Z_full_app_failed_1636902026000.png +0 -0
  84. package/logs/screenshot_2021_11_14_15_04_13_598Z_full_app_failed_1636902253598.png +0 -0
  85. package/logs/screenshot_2021_11_14_15_07_37_914Z_full_app_failed_1636902457914.png +0 -0
  86. package/logs/screenshot_2021_11_14_15_12_20_039Z_full_app_failed_1636902740039.png +0 -0
  87. package/logs/screenshot_2021_11_14_15_15_44_401Z_full_app_failed_1636902944401.png +0 -0
  88. package/logs/screenshot_2021_11_14_15_26_23_318Z_viewport_failed_1636903583318.png +0 -0
  89. package/src/calculate-screenshot-regions.js +0 -31
  90. package/src/screenshoter.js +0 -159
  91. package/test/fixtures/pattern/iPad_Air_portrait.png +0 -0
  92. package/test/fixtures/pattern/iPhone_5S_landscape.png +0 -0
  93. package/test/fixtures/pattern/iPhone_XR_perfecto_landscape.png +0 -0
  94. package/test/fixtures/pattern/iPhone_XS_Max_perfecto_landscape.png +0 -0
  95. package/test/fixtures/pattern/iPhone_XS_landscape.png +0 -0
  96. package/test/fixtures/pattern/iPhone_XS_portrait.png +0 -0
  97. package/test/fixtures/pattern/iPhone_X_perfecto_portrait.png +0 -0
@@ -78,42 +78,69 @@ describe('image', () => {
78
78
  assert.strictEqual(actual.height, 50000)
79
79
  })
80
80
 
81
- it('should replace region in image with a higher and wider image', async () => {
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.combine(image, image, {x: 200, y: 200, width: 100, height: 100})
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.combined-higher-wider.png').toObject()
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 replace region in image with a higher image', async () => {
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.combine(image, image, {x: 200, y: 200, width: 200, height: 100})
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.combined-higher.png').toObject()
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 replace region in image with a higher image', async () => {
107
+ it('should frame image in a wider region', async () => {
108
108
  const image = await makeImage('./test/fixtures/image/house.png')
109
- const srcImage = makeImage({
110
- width: 200,
111
- height: 200,
112
- data: Buffer.alloc(200 * 200 * 4, Buffer.from([0, 0, 0xff, 0xff])),
113
- })
114
- const combinedImage = await srcImage.combine(image, image, {x: 200, y: 200, width: 100, height: 200})
115
- const actual = await combinedImage.toObject()
116
- const expected = await makeImage('./test/fixtures/image/house.combined-wider.png').toObject()
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
  })
@@ -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
@@ -1,159 +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
- scrollingMode = driver.isNative ? 'scroll' : scrollingMode
31
-
32
- const activeContext = driver.currentContext
33
- const context =
34
- frames.length > 0
35
- ? await activeContext.context(frames.reduce((parent, frame) => ({...frame, parent}), null))
36
- : activeContext
37
-
38
- // traverse from main context to target context to hide scrollbars and preserve context state (scroll/translate position)
39
- for (const nextContext of context.path) {
40
- const scrollingElement = await nextContext.getScrollingElement()
41
- // unlike web apps, native apps do not always have scrolling element
42
- if (scrollingElement) {
43
- if (driver.isWeb && hideScrollbars) await scrollingElement.hideScrollbars()
44
- await scrollingElement.preserveState()
45
- }
46
- }
47
-
48
- // blur active element in target context
49
- const activeElement = driver.isWeb && hideCaret ? await context.blurElement() : null
50
-
51
- const target = await getTarget({window, context, region, fully, scrollingMode, logger})
52
-
53
- if (driver.isWeb && hideScrollbars) await target.scroller.hideScrollbars()
54
-
55
- try {
56
- if (!window) await scrollIntoViewport({...target, logger})
57
-
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})
62
-
63
- if (hooks && hooks.afterScreenshot) {
64
- // imitate image-like state for the hook
65
- if (window && fully && target.scroller) {
66
- await target.scroller.moveTo({x: 0, y: 0}, await driver.mainContext.getScrollingElement())
67
- }
68
- await hooks.afterScreenshot({driver, scroller: target.scroller, screenshot})
69
- }
70
-
71
- return screenshot
72
- } finally {
73
- if (target.scroller) {
74
- await target.scroller.restoreScrollbars()
75
- }
76
-
77
- // if there was active element and we have blurred it, then restore focus
78
- if (activeElement) await context.focusElement(activeElement)
79
-
80
- // traverse from target context to the main context to restore scrollbars and context states
81
- for (const prevContext of context.path.reverse()) {
82
- const scrollingElement = await prevContext.getScrollingElement()
83
- if (scrollingElement) {
84
- if (driver.isWeb && hideScrollbars) await scrollingElement.restoreScrollbars()
85
- await scrollingElement.restoreState()
86
- }
87
- }
88
-
89
- // restore focus on original active context
90
- await activeContext.focus()
91
- }
92
- }
93
-
94
- async function getTarget({window, context, region, fully, scrollingMode, logger}) {
95
- if (window) {
96
- // window/app
97
- const scrollingElement = await context.main.getScrollingElement()
98
- return {
99
- context: context.main,
100
- scroller: scrollingElement ? makeScroller({element: scrollingElement, scrollingMode, logger}) : null,
101
- }
102
- } else if (region) {
103
- if (utils.types.has(region, ['x', 'y', 'width', 'height'])) {
104
- // region by coordinates
105
- const scrollingElement = await context.getScrollingElement()
106
- return {
107
- context,
108
- region,
109
- scroller: scrollingElement ? makeScroller({element: scrollingElement, scrollingMode, logger}) : null,
110
- }
111
- } else {
112
- // region by element or selector
113
- const element = await context.element(region)
114
- if (!element) throw new Error('Element not found!')
115
-
116
- const elementContext = element.context
117
-
118
- if (fully) {
119
- const isScrollable = await element.isScrollable()
120
- // if element is scrollable, then take screenshot of the full element content, otherwise take screenshot of full element
121
- const region = isScrollable ? null : await element.getRegion()
122
- const scrollingElement = isScrollable ? element : await elementContext.getScrollingElement()
123
- // css stitching could be applied only to root element of its context
124
- scrollingMode = scrollingMode === 'css' && !(await scrollingElement.isRoot()) ? 'mixed' : scrollingMode
125
- return {
126
- context: elementContext,
127
- region,
128
- scroller: scrollingElement ? makeScroller({element: scrollingElement, scrollingMode, logger}) : null,
129
- }
130
- } else {
131
- const scrollingElement = await context.getScrollingElement()
132
- return {
133
- context: elementContext,
134
- region: await element.getRegion(),
135
- scroller: scrollingElement ? makeScroller({element: scrollingElement, scrollingMode, logger}) : null,
136
- }
137
- }
138
- }
139
- } else if (!context.isMain) {
140
- // context
141
- if (fully) {
142
- const scrollingElement = await context.getScrollingElement()
143
- return {
144
- context,
145
- scroller: scrollingElement ? makeScroller({logger, element: scrollingElement, scrollingMode}) : null,
146
- }
147
- } else {
148
- const scrollingElement = await context.parent.getScrollingElement()
149
- const element = await context.getContextElement()
150
- return {
151
- context: context.parent,
152
- region: await element.getRegion(), // IMHO we should use CLIENT (without borders) region here
153
- scroller: scrollingElement ? makeScroller({logger, element: scrollingElement, scrollingMode}) : null,
154
- }
155
- }
156
- }
157
- }
158
-
159
- module.exports = screenshoter