@applitools/screenshoter 3.3.2 → 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.
@@ -9,13 +9,13 @@
9
9
  }
10
10
  },
11
11
  "node_modules/@applitools/screenshoter": {
12
- "version": "3.3.1",
12
+ "version": "3.3.2",
13
13
  "resolved": "file:../dry-run.tgz",
14
- "integrity": "sha512-Nn4m2tV2g1+ECbNzbJ4NJzvpx02lYAZ6MM4HXuT16ShCZPXi2fCuvnJEZX3D5j/EkZjEB6yFZWXrKT70m2Djew==",
14
+ "integrity": "sha512-5epXLfRKO+64jIbxTYwUMUui0xGPQmwccz5aMlLg2V1gtgyrcZ689cn7n8jDBWPCt1Ds0MNEZCimcDjGiAXb4Q==",
15
15
  "license": "SEE LICENSE IN LICENSE",
16
16
  "dependencies": {
17
- "@applitools/snippets": "2.1.10",
18
- "@applitools/utils": "1.2.4",
17
+ "@applitools/snippets": "2.1.11",
18
+ "@applitools/utils": "1.2.5",
19
19
  "png-async": "0.9.4"
20
20
  },
21
21
  "engines": {
@@ -23,17 +23,17 @@
23
23
  }
24
24
  },
25
25
  "node_modules/@applitools/snippets": {
26
- "version": "2.1.10",
27
- "resolved": "https://registry.npmjs.org/@applitools/snippets/-/snippets-2.1.10.tgz",
28
- "integrity": "sha512-LGjtd8IZOwbhETqKRDX9CX85+BskkFUkuMuLvriSDEEQiSm0MpFbbcJVMjYaVL2qobaJbY+3WvK0g7+gkZuuqA==",
26
+ "version": "2.1.11",
27
+ "resolved": "https://registry.npmjs.org/@applitools/snippets/-/snippets-2.1.11.tgz",
28
+ "integrity": "sha512-uNx2sqFACva5Lt23NvYjnxkbUoyAmoCN8dVtAFOhL2a0HyxzYKP5z0tCT/JK8QqM3gkSzeQ0rT0FdxQ9UAl7Og==",
29
29
  "engines": {
30
30
  "node": ">=8.9.0"
31
31
  }
32
32
  },
33
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==",
34
+ "version": "1.2.5",
35
+ "resolved": "https://registry.npmjs.org/@applitools/utils/-/utils-1.2.5.tgz",
36
+ "integrity": "sha512-nETSgqGeCk5yqjFaQ7x1KURf+t5IxsY2RoeFB5w1+6lHprmHdEithMcN0tiJWeqi14QBJdUkXCzCJrMW5RcDFg==",
37
37
  "engines": {
38
38
  "node": ">= 8.9.0"
39
39
  }
@@ -47,22 +47,22 @@
47
47
  "dependencies": {
48
48
  "@applitools/screenshoter": {
49
49
  "version": "file:../dry-run.tgz",
50
- "integrity": "sha512-Nn4m2tV2g1+ECbNzbJ4NJzvpx02lYAZ6MM4HXuT16ShCZPXi2fCuvnJEZX3D5j/EkZjEB6yFZWXrKT70m2Djew==",
50
+ "integrity": "sha512-5epXLfRKO+64jIbxTYwUMUui0xGPQmwccz5aMlLg2V1gtgyrcZ689cn7n8jDBWPCt1Ds0MNEZCimcDjGiAXb4Q==",
51
51
  "requires": {
52
- "@applitools/snippets": "2.1.10",
53
- "@applitools/utils": "1.2.4",
52
+ "@applitools/snippets": "2.1.11",
53
+ "@applitools/utils": "1.2.5",
54
54
  "png-async": "0.9.4"
55
55
  }
56
56
  },
57
57
  "@applitools/snippets": {
58
- "version": "2.1.10",
59
- "resolved": "https://registry.npmjs.org/@applitools/snippets/-/snippets-2.1.10.tgz",
60
- "integrity": "sha512-LGjtd8IZOwbhETqKRDX9CX85+BskkFUkuMuLvriSDEEQiSm0MpFbbcJVMjYaVL2qobaJbY+3WvK0g7+gkZuuqA=="
58
+ "version": "2.1.11",
59
+ "resolved": "https://registry.npmjs.org/@applitools/snippets/-/snippets-2.1.11.tgz",
60
+ "integrity": "sha512-uNx2sqFACva5Lt23NvYjnxkbUoyAmoCN8dVtAFOhL2a0HyxzYKP5z0tCT/JK8QqM3gkSzeQ0rT0FdxQ9UAl7Og=="
61
61
  },
62
62
  "@applitools/utils": {
63
- "version": "1.2.4",
64
- "resolved": "https://registry.npmjs.org/@applitools/utils/-/utils-1.2.4.tgz",
65
- "integrity": "sha512-w7ma6FFGyqhdP6LEcuHFWOcH7EzBjnoAX3UfbFWcTHA3QXnXPX37Y2ENYRodfwkorP1cUKyUHwNXJB/BMIj/hg=="
63
+ "version": "1.2.5",
64
+ "resolved": "https://registry.npmjs.org/@applitools/utils/-/utils-1.2.5.tgz",
65
+ "integrity": "sha512-nETSgqGeCk5yqjFaQ7x1KURf+t5IxsY2RoeFB5w1+6lHprmHdEithMcN0tiJWeqi14QBJdUkXCzCJrMW5RcDFg=="
66
66
  },
67
67
  "png-async": {
68
68
  "version": "0.9.4",
Binary file
package/CHANGELOG.md CHANGED
@@ -4,6 +4,14 @@
4
4
  ## Unreleased
5
5
 
6
6
 
7
+ ## 3.3.3 - 2021/12/22
8
+
9
+ - improve default rotation and scaling logic
10
+ - improve scroll into viewport algorithm
11
+ - fix lazy handling of image rotation
12
+ - updated to @applitools/snippets@2.1.11 (from 2.1.10)
13
+ - updated to @applitools/utils@1.2.5 (from 1.2.4)
14
+
7
15
  ## 3.3.2 - 2021/12/20
8
16
 
9
17
  - add basic support of webview screenshots on ios
@@ -0,0 +1,29 @@
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=30"
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=30"
20
+ ],
21
+ "volumes": [
22
+ "/dev/shm:/dev/shm"
23
+ ],
24
+ "ports": [
25
+ "4445:4444"
26
+ ]
27
+ }
28
+ }
29
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applitools/screenshoter",
3
- "version": "3.3.2",
3
+ "version": "3.3.3",
4
4
  "description": "Applitools universal screenshoter for web and native applications",
5
5
  "keywords": [
6
6
  "applitools",
@@ -49,14 +49,14 @@
49
49
  }
50
50
  },
51
51
  "dependencies": {
52
- "@applitools/snippets": "2.1.10",
53
- "@applitools/utils": "1.2.4",
52
+ "@applitools/snippets": "2.1.11",
53
+ "@applitools/utils": "1.2.5",
54
54
  "png-async": "0.9.4"
55
55
  },
56
56
  "devDependencies": {
57
- "@applitools/driver": "1.4.5",
57
+ "@applitools/driver": "1.4.6",
58
58
  "@applitools/sdk-release-kit": "0.13.4",
59
- "@applitools/spec-driver-webdriverio": "1.2.2",
59
+ "@applitools/spec-driver-webdriverio": "1.2.5",
60
60
  "@applitools/test-utils": "1.0.10",
61
61
  "chromedriver": "^95.0.0",
62
62
  "eslint": "^7.9.0",
package/src/image.js CHANGED
@@ -38,7 +38,9 @@ function makeImage(data) {
38
38
  return true
39
39
  },
40
40
  get size() {
41
- return utils.geometry.round(utils.geometry.scale(size, transforms.scale))
41
+ return utils.geometry.round(
42
+ utils.geometry.rotate(utils.geometry.scale(size, transforms.scale), transforms.rotate),
43
+ )
42
44
  },
43
45
  get transforms() {
44
46
  return {...transforms}
@@ -51,6 +53,12 @@ function makeImage(data) {
51
53
  },
52
54
  scale(ratio) {
53
55
  transforms.scale *= ratio
56
+ // size = utils.geometry.scale(size, ratio)
57
+ return this
58
+ },
59
+ rotate(degrees) {
60
+ transforms.rotate = (transforms.rotate + degrees) % 360
61
+ // size = utils.geometry.rotate(size, degrees)
54
62
  return this
55
63
  },
56
64
  crop(region) {
@@ -64,17 +72,12 @@ function makeImage(data) {
64
72
  } else {
65
73
  region = utils.geometry.scale(region, 1 / transforms.scale)
66
74
  }
67
- region = utils.geometry.rotate(region, transforms.rotate)
68
- transforms.crop = transforms.crop
75
+ region = utils.geometry.rotate(region, -transforms.rotate, utils.geometry.rotate(size, transforms.rotate))
76
+ region = transforms.crop
69
77
  ? utils.geometry.intersect(transforms.crop, utils.geometry.offset(region, transforms.crop))
70
78
  : utils.geometry.intersect({x: 0, y: 0, ...size}, region)
71
-
72
- size = utils.geometry.round(utils.geometry.size(transforms.crop))
73
-
74
- return this
75
- },
76
- rotate(degree) {
77
- transforms.rotate = (transforms.rotate + degree) % 360
79
+ transforms.crop = region
80
+ size = utils.geometry.size(transforms.crop)
78
81
  return this
79
82
  },
80
83
  copy(srcImage, offset) {
@@ -204,9 +207,9 @@ async function transform(image, transforms) {
204
207
  }
205
208
  }, image)
206
209
 
207
- image = transforms.rotate > 0 ? await rotate(image, transforms.rotate) : image
208
210
  image = transforms.crop ? await extract(image, transforms.crop) : image
209
211
  image = transforms.scale !== 1 ? await scale(image, transforms.scale) : image
212
+ image = transforms.rotate !== 0 ? await rotate(image, transforms.rotate) : image
210
213
  return image
211
214
  }
212
215
 
@@ -296,7 +299,7 @@ async function rotate(image, degrees) {
296
299
  for (let srcY = 0, dstX = 0; srcY < image.height; ++srcY, ++dstX) {
297
300
  for (let srcX = 0, dstY = image.width - 1; srcX < image.width; ++srcX, --dstY) {
298
301
  const pixel = image.data.readUInt32BE((srcY * image.width + srcX) * 4)
299
- dstImage.data.writeUInt32BE(pixel, (srcX * dstImage.width + dstY) * 4)
302
+ dstImage.data.writeUInt32BE(pixel, (dstY * dstImage.width + dstX) * 4)
300
303
  }
301
304
  }
302
305
  } else {
@@ -15,14 +15,23 @@ async function scrollIntoViewport({context, scroller, region, logger}) {
15
15
  let remainingOffset = {x: elementContextRegion.x, y: elementContextRegion.y}
16
16
  while (currentContext) {
17
17
  const scrollingElement = await currentContext.getScrollingElement()
18
- const scrollingElementOffset = scrollingElement
19
- ? utils.geometry.location(await scrollingElement.getClientRegion())
20
- : {x: 0, y: 0}
18
+ if (!scrollingElement) {
19
+ currentContext = currentContext.parent
20
+ continue
21
+ }
21
22
 
22
- const actualOffset = await scroller.moveTo(
23
- utils.geometry.offsetNegative(remainingOffset, scrollingElementOffset),
24
- scrollingElement,
25
- )
23
+ const scrollableRegion = await scrollingElement.getClientRegion()
24
+ const requiredOffset = {
25
+ x: Math.max(
26
+ remainingOffset.x - (scrollableRegion.x + Math.max(scrollableRegion.width - elementContextRegion.width, 0)),
27
+ 0,
28
+ ),
29
+ y: Math.max(
30
+ remainingOffset.y - (scrollableRegion.y + Math.max(scrollableRegion.height - elementContextRegion.height, 0)),
31
+ 0,
32
+ ),
33
+ }
34
+ const actualOffset = await scroller.moveTo(requiredOffset, scrollingElement)
26
35
 
27
36
  remainingOffset = utils.geometry.offset(
28
37
  utils.geometry.offsetNegative(remainingOffset, actualOffset),
@@ -27,14 +27,13 @@ function makeTakeViewportScreenshot(options) {
27
27
  }
28
28
 
29
29
  function makeTakeDefaultScreenshot({driver, stabilization = {}, debug, logger}) {
30
- const calculateScaleRatio = makeCalculateScaleRatio({driver})
31
30
  return async function takeScreenshot({name} = {}) {
32
31
  logger.verbose('Taking screenshot...')
33
32
  const image = makeImage(await driver.takeScreenshot())
34
33
  await image.debug({...debug, name, suffix: 'original'})
35
34
 
36
35
  if (stabilization.scale) image.scale(stabilization.scale)
37
- else image.scale(await calculateScaleRatio(image.width))
36
+ else image.scale(1 / driver.pixelRatio)
38
37
 
39
38
  if (stabilization.rotate) image.crop(stabilization.rotate)
40
39
 
@@ -45,7 +44,6 @@ function makeTakeDefaultScreenshot({driver, stabilization = {}, debug, logger})
45
44
  }
46
45
 
47
46
  function makeTakeMainContextScreenshot({driver, stabilization = {}, debug, logger}) {
48
- const calculateScaleRatio = makeCalculateScaleRatio({driver})
49
47
  return async function takeScreenshot({name} = {}) {
50
48
  logger.verbose('Taking screenshot...')
51
49
  const originalContext = driver.currentContext
@@ -55,7 +53,7 @@ function makeTakeMainContextScreenshot({driver, stabilization = {}, debug, logge
55
53
  await image.debug({...debug, name, suffix: 'original'})
56
54
 
57
55
  if (stabilization.scale) image.scale(stabilization.scale)
58
- else image.scale(await calculateScaleRatio(image.width))
56
+ else image.scale(1 / driver.pixelRatio)
59
57
 
60
58
  if (stabilization.rotate) image.rotate(stabilization.rotate)
61
59
 
@@ -66,7 +64,6 @@ function makeTakeMainContextScreenshot({driver, stabilization = {}, debug, logge
66
64
  }
67
65
 
68
66
  function makeTakeSafari11Screenshot({driver, stabilization = {}, debug, logger}) {
69
- const calculateScaleRatio = makeCalculateScaleRatio({driver})
70
67
  let viewportSize
71
68
 
72
69
  return async function takeScreenshot({name} = {}) {
@@ -75,7 +72,7 @@ function makeTakeSafari11Screenshot({driver, stabilization = {}, debug, logger})
75
72
  await image.debug({...debug, name, suffix: 'original'})
76
73
 
77
74
  if (stabilization.scale) image.scale(stabilization.scale)
78
- else image.scale(await calculateScaleRatio(image.width))
75
+ else image.scale(1 / driver.pixelRatio)
79
76
 
80
77
  if (stabilization.rotate) image.rotate(stabilization.rotate)
81
78
 
@@ -91,7 +88,6 @@ function makeTakeSafari11Screenshot({driver, stabilization = {}, debug, logger})
91
88
  }
92
89
 
93
90
  function makeTakeMarkedScreenshot({driver, stabilization = {}, debug, logger}) {
94
- const calculateScaleRatio = makeCalculateScaleRatio({driver})
95
91
  let viewportRegion
96
92
 
97
93
  return async function takeScreenshot({name} = {}) {
@@ -100,9 +96,10 @@ function makeTakeMarkedScreenshot({driver, stabilization = {}, debug, logger}) {
100
96
  await image.debug({...debug, name, suffix: 'original'})
101
97
 
102
98
  if (stabilization.scale) image.scale(stabilization.scale)
103
- else image.scale(await calculateScaleRatio(image.width))
99
+ else image.scale(1 / driver.pixelRatio)
104
100
 
105
101
  if (stabilization.rotate) image.rotate(stabilization.rotate)
102
+ else if (driver.orientation === 'landscape' && image.width < image.height) image.rotate(-90)
106
103
 
107
104
  if (stabilization.crop) image.crop(stabilization.crop)
108
105
  else {
@@ -122,7 +119,8 @@ function makeTakeMarkedScreenshot({driver, stabilization = {}, debug, logger}) {
122
119
  try {
123
120
  const image = makeImage(await driver.takeScreenshot())
124
121
 
125
- if (stabilization.rotate) await image.rotate(stabilization.rotate)
122
+ if (stabilization.rotate) image.rotate(stabilization.rotate)
123
+ else if (driver.orientation === 'landscape' && image.width < image.height) image.rotate(-90)
126
124
 
127
125
  await image.debug({...debug, name: 'marker'})
128
126
 
@@ -148,6 +146,7 @@ function makeTakeNativeScreenshot({driver, stabilization = {}, debug, logger}) {
148
146
  else image.scale(1 / driver.pixelRatio)
149
147
 
150
148
  if (stabilization.rotate) image.rotate(stabilization.rotate)
149
+ else if (driver.orientation === 'landscape' && image.width < image.height) image.rotate(-90)
151
150
 
152
151
  if (stabilization.crop) image.crop(stabilization.crop)
153
152
  else {
@@ -164,25 +163,4 @@ function makeTakeNativeScreenshot({driver, stabilization = {}, debug, logger}) {
164
163
  }
165
164
  }
166
165
 
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
- }
182
-
183
- const scaledImageWidth = Math.round(imageWidth / driver.pixelRatio)
184
- return viewportWidth / scaledImageWidth / driver.pixelRatio
185
- }
186
- }
187
-
188
166
  module.exports = makeTakeViewportScreenshot
@@ -240,7 +240,6 @@ describe('screenshoter', () => {
240
240
 
241
241
  const button = await driver.element(buttonSelector)
242
242
  await button.click()
243
- console.log(await driver.target.getContexts())
244
243
  await driver.target.switchContext('WEBVIEW')
245
244
 
246
245
  await driver.init()
@@ -203,6 +203,7 @@ describe('screenshoter ios', () => {
203
203
  await driver.target.getContexts()
204
204
  await utils.general.sleep(500)
205
205
  const [, webview] = await driver.target.getContexts()
206
+ console.log(webview)
206
207
  await driver.target.switchContext(webview)
207
208
 
208
209
  await driver.init()
@@ -18,6 +18,19 @@ const env = {
18
18
  username: process.env.SAUCE_USERNAME,
19
19
  accessKey: process.env.SAUCE_ACCESS_KEY,
20
20
  },
21
+
22
+ // url: 'http://0.0.0.0:4723/wd/hub',
23
+ // capabilities: {
24
+ // name: 'iOS Web Screenshoter Test',
25
+ // deviceName: 'iPhone 11 Pro',
26
+ // browserName: 'safari',
27
+ // platformName: 'iOS',
28
+ // platformVersion: '14.5',
29
+ // appiumVersion: '1.20.1',
30
+ // automationName: 'XCUITest',
31
+ // username: process.env.SAUCE_USERNAME,
32
+ // accessKey: process.env.SAUCE_ACCESS_KEY,
33
+ // },
21
34
  }
22
35
 
23
36
  describe('screenshoter web ios', () => {
@@ -39,18 +52,33 @@ describe('screenshoter web ios', () => {
39
52
  })
40
53
 
41
54
  it('take viewport screenshot', () => {
42
- return viewport()
55
+ return viewport({orientation: 'portrait'})
56
+ })
57
+
58
+ it('take viewport screenshot with landscape orientation', () => {
59
+ return viewport({orientation: 'landscape'})
43
60
  })
44
61
 
45
62
  it('take full page screenshot', () => {
46
- return fullPage()
63
+ return fullPage({orientation: 'portrait'})
47
64
  })
48
65
 
49
- async function viewport(options) {
66
+ it('take full page screenshot with landscape orientation', () => {
67
+ return fullPage({orientation: 'landscape'})
68
+ })
69
+
70
+ async function viewport({orientation = 'portrait', ...options} = {}) {
71
+ const expectedPath = `./test/fixtures/web-ios/page${orientation === 'landscape' ? '-landscape' : ''}.png`
72
+
73
+ await driver.target.setOrientation(orientation.toUpperCase())
74
+ await driver.visit(await driver.getUrl())
75
+
76
+ await driver.init()
77
+
50
78
  const screenshot = await takeScreenshot({logger, driver, ...options})
51
79
  try {
52
80
  const actual = await screenshot.image.toObject()
53
- const expected = await makeImage('./test/fixtures/web-ios/page.png').toObject()
81
+ const expected = await makeImage(expectedPath).toObject()
54
82
  assert.strictEqual(pixelmatch(actual.data, expected.data, null, expected.width, expected.height), 0)
55
83
  } catch (err) {
56
84
  await screenshot.image.debug({path: './logs', name: 'ios_viewport_failed'})
@@ -58,11 +86,18 @@ describe('screenshoter web ios', () => {
58
86
  }
59
87
  }
60
88
 
61
- async function fullPage(options) {
89
+ async function fullPage({orientation = 'portrait', ...options} = {}) {
90
+ const expectedPath = `./test/fixtures/web-ios/page-fully${orientation === 'landscape' ? '-landscape' : ''}.png`
91
+
92
+ await driver.target.setOrientation(orientation.toUpperCase())
93
+ await driver.visit(await driver.getUrl())
94
+
95
+ await driver.init()
96
+
62
97
  const screenshot = await takeScreenshot({logger, driver, fully: true, ...options})
63
98
  try {
64
99
  const actual = await screenshot.image.toObject()
65
- const expected = await makeImage('./test/fixtures/web-ios/page-fully.png').toObject()
100
+ const expected = await makeImage(expectedPath).toObject()
66
101
  assert.strictEqual(pixelmatch(actual.data, expected.data, null, expected.width, expected.height), 0)
67
102
  } catch (err) {
68
103
  await screenshot.image.debug({path: './logs', name: 'ios_full_page_failed'})