@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.
- package/.bongo/dry-run/package-lock.json +19 -19
- package/.bongo/dry-run.tgz +0 -0
- package/CHANGELOG.md +23 -0
- package/docker-compose.yaml +29 -0
- package/index.js +2 -1
- package/package.json +6 -6
- package/src/find-image-pattern.js +11 -38
- package/src/image.js +116 -73
- package/src/scroll-into-viewport.js +16 -7
- package/src/take-screenshot.js +136 -160
- package/src/take-simple-screenshot.js +25 -0
- package/src/take-stitched-screenshot.js +32 -52
- package/src/take-viewport-screenshot.js +157 -16
- package/test/e2e/android.spec.js +84 -11
- package/test/e2e/external.spec.js +81 -10
- package/test/e2e/ios.spec.js +129 -13
- package/test/e2e/web-ios.spec.js +44 -11
- package/test/e2e/web.spec.js +20 -15
- package/test/fixtures/android/app-fully-non-scrollable.png +0 -0
- package/test/fixtures/android/app-fully-recycler.png +0 -0
- package/test/fixtures/android/app-fully-scroll-statusbar.png +0 -0
- package/test/fixtures/android/app-fully-scroll.png +0 -0
- package/test/fixtures/android/app-statusbar.png +0 -0
- package/test/fixtures/android/x-app-fully-collapsing.png +0 -0
- package/test/fixtures/android/x-app-fully-recycler.png +0 -0
- package/test/fixtures/android/x-element-fully.png +0 -0
- package/test/fixtures/image/{house.combined-higher-wider.png → house.framed-higher-wider.png} +0 -0
- package/test/fixtures/image/{house.combined-higher.png → house.framed-higher.png} +0 -0
- package/test/fixtures/image/house.framed-shorter-thinner.png +0 -0
- package/test/fixtures/image/{house.combined-wider.png → house.framed-wider.png} +0 -0
- package/test/fixtures/ios/app-fully-collapsing.png +0 -0
- package/test/fixtures/ios/app-fully-collection.png +0 -0
- package/test/fixtures/ios/app-fully-overlapped-statusbar.png +0 -0
- package/test/fixtures/ios/app-fully-overlapped.png +0 -0
- package/test/fixtures/ios/app-fully-scroll-statusbar.png +0 -0
- package/test/fixtures/ios/app-fully-scroll.png +0 -0
- package/test/fixtures/ios/app-fully-superview.png +0 -0
- package/test/fixtures/ios/app-fully-table.png +0 -0
- package/test/fixtures/ios/app-statusbar.png +0 -0
- package/test/fixtures/ios/app.png +0 -0
- package/test/fixtures/ios/element-fully.png +0 -0
- package/test/fixtures/ios/element.png +0 -0
- package/test/fixtures/ios/region.png +0 -0
- package/test/fixtures/ios/webview-fully.png +0 -0
- package/test/fixtures/ios/webview.png +0 -0
- package/test/fixtures/pattern/iPad_5th_landscape.png +0 -0
- package/test/fixtures/pattern/iPad_5th_portrait.png +0 -0
- package/test/fixtures/pattern/iPad_9th_landscape.png +0 -0
- package/test/fixtures/pattern/iPad_9th_portrait.png +0 -0
- package/test/fixtures/pattern/iPhone_11_landscape.png +0 -0
- package/test/fixtures/pattern/iPhone_11_portrait.png +0 -0
- package/test/fixtures/pattern/iPhone_13_landscape.png +0 -0
- package/test/fixtures/pattern/iPhone_13_portrait.png +0 -0
- package/test/fixtures/pattern/iPhone_SE_landscape.png +0 -0
- package/test/fixtures/pattern/iPhone_SE_portrait.png +0 -0
- package/test/fixtures/pattern/iPhone_XS_portrait_noviewport.png +0 -0
- package/test/fixtures/web-ios/page-fully-landscape.png +0 -0
- package/test/fixtures/web-ios/page-fully.png +0 -0
- package/test/fixtures/web-ios/page-landscape.png +0 -0
- package/test/it/find-pattern.spec.js +16 -11
- package/test/it/image.spec.js +42 -15
- package/logs/screenshot_2021_11_14_12_35_00_342Z_full_frame_failed.png +0 -0
- package/logs/screenshot_2021_11_14_12_38_00_715Z_frame_failed.png +0 -0
- package/logs/screenshot_2021_11_14_12_38_03_866Z_frame_failed.png +0 -0
- package/logs/screenshot_2021_11_14_13_02_56_464Z_full_app_failed_1636894976464.png +0 -0
- package/logs/screenshot_2021_11_14_13_04_27_904Z_full_app_failed_1636895067904.png +0 -0
- package/logs/screenshot_2021_11_14_13_06_13_662Z_full_app_failed_1636895173662.png +0 -0
- package/logs/screenshot_2021_11_14_13_06_23_745Z_full_app_failed_1636895183745.png +0 -0
- package/logs/screenshot_2021_11_14_13_18_31_571Z_full_app_failed_1636895911571.png +0 -0
- package/logs/screenshot_2021_11_14_13_25_54_557Z_viewport_failed_1636896354557.png +0 -0
- package/logs/screenshot_2021_11_14_13_29_32_326Z_viewport_failed_1636896572326.png +0 -0
- package/logs/screenshot_2021_11_14_13_34_22_483Z_viewport_failed_1636896862483.png +0 -0
- package/logs/screenshot_2021_11_14_13_37_25_734Z_viewport_failed_1636897045734.png +0 -0
- package/logs/screenshot_2021_11_14_13_42_25_024Z_viewport_failed_1636897345024.png +0 -0
- package/logs/screenshot_2021_11_14_13_57_24_366Z_full_app_failed_1636898244366.png +0 -0
- package/logs/screenshot_2021_11_14_14_20_42_951Z_full_app_failed_1636899642951.png +0 -0
- package/logs/screenshot_2021_11_14_14_31_07_853Z_full_app_failed_1636900267853.png +0 -0
- package/logs/screenshot_2021_11_14_14_32_07_195Z_full_app_failed_1636900327195.png +0 -0
- package/logs/screenshot_2021_11_14_14_42_16_716Z_full_app_failed_1636900936716.png +0 -0
- package/logs/screenshot_2021_11_14_14_47_37_646Z_full_app_failed_1636901257646.png +0 -0
- package/logs/screenshot_2021_11_14_14_54_18_522Z_full_app_failed_1636901658522.png +0 -0
- package/logs/screenshot_2021_11_14_14_55_36_756Z_full_app_failed_1636901736756.png +0 -0
- package/logs/screenshot_2021_11_14_15_00_26_000Z_full_app_failed_1636902026000.png +0 -0
- package/logs/screenshot_2021_11_14_15_04_13_598Z_full_app_failed_1636902253598.png +0 -0
- package/logs/screenshot_2021_11_14_15_07_37_914Z_full_app_failed_1636902457914.png +0 -0
- package/logs/screenshot_2021_11_14_15_12_20_039Z_full_app_failed_1636902740039.png +0 -0
- package/logs/screenshot_2021_11_14_15_15_44_401Z_full_app_failed_1636902944401.png +0 -0
- package/logs/screenshot_2021_11_14_15_26_23_318Z_viewport_failed_1636903583318.png +0 -0
- package/src/calculate-screenshot-regions.js +0 -31
- package/src/screenshoter.js +0 -159
- package/test/fixtures/pattern/iPad_Air_portrait.png +0 -0
- package/test/fixtures/pattern/iPhone_5S_landscape.png +0 -0
- package/test/fixtures/pattern/iPhone_XR_perfecto_landscape.png +0 -0
- package/test/fixtures/pattern/iPhone_XS_Max_perfecto_landscape.png +0 -0
- package/test/fixtures/pattern/iPhone_XS_landscape.png +0 -0
- package/test/fixtures/pattern/iPhone_XS_portrait.png +0 -0
- package/test/fixtures/pattern/iPhone_X_perfecto_portrait.png +0 -0
|
@@ -9,13 +9,13 @@
|
|
|
9
9
|
}
|
|
10
10
|
},
|
|
11
11
|
"node_modules/@applitools/screenshoter": {
|
|
12
|
-
"version": "3.2
|
|
12
|
+
"version": "3.3.2",
|
|
13
13
|
"resolved": "file:../dry-run.tgz",
|
|
14
|
-
"integrity": "sha512-
|
|
14
|
+
"integrity": "sha512-5epXLfRKO+64jIbxTYwUMUui0xGPQmwccz5aMlLg2V1gtgyrcZ689cn7n8jDBWPCt1Ds0MNEZCimcDjGiAXb4Q==",
|
|
15
15
|
"license": "SEE LICENSE IN LICENSE",
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@applitools/snippets": "2.1.
|
|
18
|
-
"@applitools/utils": "1.2.
|
|
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.
|
|
27
|
-
"resolved": "https://registry.npmjs.org/@applitools/snippets/-/snippets-2.1.
|
|
28
|
-
"integrity": "sha512-
|
|
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.
|
|
35
|
-
"resolved": "https://registry.npmjs.org/@applitools/utils/-/utils-1.2.
|
|
36
|
-
"integrity": "sha512-
|
|
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-
|
|
50
|
+
"integrity": "sha512-5epXLfRKO+64jIbxTYwUMUui0xGPQmwccz5aMlLg2V1gtgyrcZ689cn7n8jDBWPCt1Ds0MNEZCimcDjGiAXb4Q==",
|
|
51
51
|
"requires": {
|
|
52
|
-
"@applitools/snippets": "2.1.
|
|
53
|
-
"@applitools/utils": "1.2.
|
|
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.
|
|
59
|
-
"resolved": "https://registry.npmjs.org/@applitools/snippets/-/snippets-2.1.
|
|
60
|
-
"integrity": "sha512-
|
|
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.
|
|
64
|
-
"resolved": "https://registry.npmjs.org/@applitools/utils/-/utils-1.2.
|
|
65
|
-
"integrity": "sha512-
|
|
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",
|
package/.bongo/dry-run.tgz
CHANGED
|
Binary file
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,29 @@
|
|
|
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
|
+
|
|
15
|
+
## 3.3.2 - 2021/12/20
|
|
16
|
+
|
|
17
|
+
- add basic support of webview screenshots on ios
|
|
18
|
+
- updated to @applitools/snippets@2.1.10 (from 2.1.8)
|
|
19
|
+
|
|
20
|
+
## 3.3.1 - 2021/12/17
|
|
21
|
+
|
|
22
|
+
- no changes
|
|
23
|
+
|
|
24
|
+
## 3.3.0 - 2021/12/16
|
|
25
|
+
|
|
26
|
+
- fix ios web screenshots on pages without viewport meta tag
|
|
27
|
+
- improve native apps support
|
|
28
|
+
- updated to @applitools/snippets@2.1.8 (from 2.1.7)
|
|
29
|
+
|
|
7
30
|
## 3.2.9 - 2021/11/14
|
|
8
31
|
|
|
9
32
|
- add support of scrollable elements that change its size during scrolling
|
|
@@ -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/index.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
module.exports = require('./src/
|
|
1
|
+
module.exports = require('./src/take-screenshot')
|
|
2
|
+
exports.takeScreenshot = require('./src/take-screenshot')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applitools/screenshoter",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.3",
|
|
4
4
|
"description": "Applitools universal screenshoter for web and native applications",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"applitools",
|
|
@@ -49,15 +49,15 @@
|
|
|
49
49
|
}
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@applitools/snippets": "2.1.
|
|
53
|
-
"@applitools/utils": "1.2.
|
|
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.
|
|
57
|
+
"@applitools/driver": "1.4.6",
|
|
58
58
|
"@applitools/sdk-release-kit": "0.13.4",
|
|
59
|
-
"@applitools/spec-driver-webdriverio": "1.2.
|
|
60
|
-
"@applitools/test-utils": "1.0.
|
|
59
|
+
"@applitools/spec-driver-webdriverio": "1.2.5",
|
|
60
|
+
"@applitools/test-utils": "1.0.10",
|
|
61
61
|
"chromedriver": "^95.0.0",
|
|
62
62
|
"eslint": "^7.9.0",
|
|
63
63
|
"eslint-plugin-mocha-no-only": "^1.1.1",
|
|
@@ -1,60 +1,33 @@
|
|
|
1
1
|
function findImagePattern(image, pattern) {
|
|
2
2
|
for (let pixel = 0; pixel < image.width * image.height; ++pixel) {
|
|
3
3
|
if (isPattern(image, pixel, pattern)) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
y: Math.floor(pixel / image.width) - pattern.offset,
|
|
7
|
-
}
|
|
4
|
+
const patterOffset = pattern.offset * pattern.pixelRatio
|
|
5
|
+
return {x: (pixel % image.width) - patterOffset, y: Math.floor(pixel / image.width) - patterOffset}
|
|
8
6
|
}
|
|
9
7
|
}
|
|
10
8
|
return null
|
|
11
9
|
}
|
|
12
10
|
|
|
13
11
|
function isPattern(image, index, pattern) {
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const sideLength = pattern.size - round * 2
|
|
20
|
-
const stepsNumber = sideLength * channels - channels
|
|
21
|
-
const threshold = Math.min((roundNumber - round) * 10 + 10, 100)
|
|
22
|
-
for (let step = 0; step < stepsNumber; ++step) {
|
|
23
|
-
let pixelIndex = pixelOffset + round + round * image.width
|
|
24
|
-
|
|
25
|
-
if (step < sideLength) {
|
|
26
|
-
pixelIndex += step
|
|
27
|
-
} else if (step < sideLength * 2 - 1) {
|
|
28
|
-
pixelIndex += sideLength - 1 + ((step % sideLength) + 1) * image.width
|
|
29
|
-
} else if (step < sideLength * 3 - 2) {
|
|
30
|
-
pixelIndex += (sideLength - 1) * image.width + (sideLength - (step % sideLength) - 1)
|
|
31
|
-
} else {
|
|
32
|
-
pixelIndex += (step % sideLength) * image.width
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const pixelColor = pixelColorAt(image, pixelIndex, threshold)
|
|
36
|
-
if (pixelColor !== chunkColor) {
|
|
37
|
-
return false
|
|
38
|
-
}
|
|
39
|
-
}
|
|
12
|
+
const itemLength = pattern.size * pattern.pixelRatio
|
|
13
|
+
for (const [itemIndex, itemColor] of pattern.mask.entries()) {
|
|
14
|
+
for (let partOffset = itemIndex * itemLength; partOffset < (itemIndex + 1) * itemLength; ++partOffset) {
|
|
15
|
+
const pixelColor = pixelColorAt(image, index + partOffset)
|
|
16
|
+
if (pixelColor !== itemColor) return false
|
|
40
17
|
}
|
|
41
18
|
}
|
|
42
19
|
return true
|
|
43
20
|
}
|
|
44
21
|
|
|
45
|
-
function pixelColorAt(image, index
|
|
22
|
+
function pixelColorAt(image, index) {
|
|
46
23
|
const channels = 4
|
|
47
24
|
const r = image.data[index * channels]
|
|
48
25
|
const g = image.data[index * channels + 1]
|
|
49
26
|
const b = image.data[index * channels + 2]
|
|
50
|
-
const rgb = [r, g, b]
|
|
51
27
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
else if (rgb.every(sub => sub <= threshold)) return 0
|
|
56
|
-
// OTHER
|
|
57
|
-
else return -1
|
|
28
|
+
const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b
|
|
29
|
+
|
|
30
|
+
return luminance < 128 ? /* black */ 1 : /* white */ 0
|
|
58
31
|
}
|
|
59
32
|
|
|
60
33
|
module.exports = findImagePattern
|
package/src/image.js
CHANGED
|
@@ -6,7 +6,7 @@ const utils = require('@applitools/utils')
|
|
|
6
6
|
|
|
7
7
|
function makeImage(data) {
|
|
8
8
|
let image, size
|
|
9
|
-
let transforms = {rotate: 0, scale: 1, crop: null}
|
|
9
|
+
let transforms = {rotate: 0, scale: 1, crop: null, modifiers: []}
|
|
10
10
|
|
|
11
11
|
if (utils.types.isBase64(data)) {
|
|
12
12
|
const buffer = Buffer.from(data, 'base64')
|
|
@@ -20,39 +20,45 @@ function makeImage(data) {
|
|
|
20
20
|
image = fromBuffer(data)
|
|
21
21
|
size = extractPngSize(data)
|
|
22
22
|
} else if (data.isImage) {
|
|
23
|
-
image = data.toRaw()
|
|
24
|
-
size = data.size
|
|
25
23
|
transforms = data.transforms
|
|
24
|
+
image = data.toRaw()
|
|
25
|
+
size = utils.geometry.scale(data.size, 1 / transforms.scale)
|
|
26
26
|
} else if (utils.types.has(data, ['width', 'height'])) {
|
|
27
27
|
image = fromSize(data)
|
|
28
28
|
if (data.data) image.data = data.data
|
|
29
29
|
size = {width: data.width, height: data.height}
|
|
30
|
+
} else if (data.auto) {
|
|
31
|
+
size = {width: -1, height: -1}
|
|
30
32
|
} else {
|
|
31
33
|
throw new Error('Unable to create an image abstraction from unknown data')
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
if (!transforms.crop) {
|
|
35
|
-
transforms.crop = utils.geometry.region({x: 0, y: 0}, size)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
36
|
return {
|
|
39
37
|
get isImage() {
|
|
40
38
|
return true
|
|
41
39
|
},
|
|
42
40
|
get size() {
|
|
43
|
-
return utils.geometry.round(
|
|
41
|
+
return utils.geometry.round(
|
|
42
|
+
utils.geometry.rotate(utils.geometry.scale(size, transforms.scale), transforms.rotate),
|
|
43
|
+
)
|
|
44
44
|
},
|
|
45
45
|
get transforms() {
|
|
46
46
|
return {...transforms}
|
|
47
47
|
},
|
|
48
48
|
get width() {
|
|
49
|
-
return size.width
|
|
49
|
+
return this.size.width
|
|
50
50
|
},
|
|
51
51
|
get height() {
|
|
52
|
-
return size.height
|
|
52
|
+
return this.size.height
|
|
53
53
|
},
|
|
54
54
|
scale(ratio) {
|
|
55
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)
|
|
56
62
|
return this
|
|
57
63
|
},
|
|
58
64
|
crop(region) {
|
|
@@ -66,28 +72,50 @@ function makeImage(data) {
|
|
|
66
72
|
} else {
|
|
67
73
|
region = utils.geometry.scale(region, 1 / transforms.scale)
|
|
68
74
|
}
|
|
69
|
-
region = utils.geometry.rotate(region, transforms.rotate)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
75
|
+
region = utils.geometry.rotate(region, -transforms.rotate, utils.geometry.rotate(size, transforms.rotate))
|
|
76
|
+
region = transforms.crop
|
|
77
|
+
? utils.geometry.intersect(transforms.crop, utils.geometry.offset(region, transforms.crop))
|
|
78
|
+
: utils.geometry.intersect({x: 0, y: 0, ...size}, region)
|
|
79
|
+
transforms.crop = region
|
|
80
|
+
size = utils.geometry.size(transforms.crop)
|
|
74
81
|
return this
|
|
75
82
|
},
|
|
76
|
-
|
|
77
|
-
|
|
83
|
+
copy(srcImage, offset) {
|
|
84
|
+
const scale = srcImage.transforms.scale
|
|
85
|
+
if (!image) {
|
|
86
|
+
size = {
|
|
87
|
+
width: Math.max(Math.floor((offset.x + srcImage.width) / scale), size.width),
|
|
88
|
+
height: Math.max(Math.floor((offset.y + srcImage.height) / scale), size.height),
|
|
89
|
+
}
|
|
90
|
+
transforms.scale = Math.min(scale, transforms.scale)
|
|
91
|
+
}
|
|
92
|
+
transforms.modifiers.push({
|
|
93
|
+
type: 'copy',
|
|
94
|
+
image: srcImage.scale(scale === transforms.scale ? 1 / scale : scale / transforms.scale).toObject(),
|
|
95
|
+
offset: utils.geometry.scale(offset, 1 / transforms.scale),
|
|
96
|
+
})
|
|
97
|
+
|
|
78
98
|
return this
|
|
79
99
|
},
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
|
|
100
|
+
frame(topImage, bottomImage, region) {
|
|
101
|
+
const scale = topImage.transforms.scale
|
|
102
|
+
const prevSize = size
|
|
103
|
+
region = utils.geometry.scale(region, 1 / scale)
|
|
104
|
+
size = {
|
|
105
|
+
width: Math.floor(topImage.width / scale + Math.max(size.width - region.width, 0)),
|
|
106
|
+
height: Math.floor(topImage.height / scale + Math.max(size.height - region.height, 0)),
|
|
107
|
+
}
|
|
108
|
+
transforms.modifiers.push({
|
|
109
|
+
type: 'frame',
|
|
110
|
+
top: topImage.scale(scale === transforms.scale ? 1 / scale : scale / transforms.scale).toObject(),
|
|
111
|
+
bottom: bottomImage.scale(scale === transforms.scale ? 1 / scale : scale / transforms.scale).toObject(),
|
|
112
|
+
region,
|
|
113
|
+
})
|
|
114
|
+
transforms.added = {width: size.width - prevSize.width, height: size.height - prevSize.height}
|
|
83
115
|
return this
|
|
84
116
|
},
|
|
85
|
-
async
|
|
86
|
-
|
|
87
|
-
image = await combine(first, last, src, region)
|
|
88
|
-
size = {width: image.width, height: image.height}
|
|
89
|
-
transforms.crop = utils.geometry.region({x: 0, y: 0}, size)
|
|
90
|
-
return this
|
|
117
|
+
async toRaw() {
|
|
118
|
+
return image
|
|
91
119
|
},
|
|
92
120
|
async toBuffer() {
|
|
93
121
|
const image = await this.toObject()
|
|
@@ -99,19 +127,17 @@ function makeImage(data) {
|
|
|
99
127
|
async toFile(path) {
|
|
100
128
|
return toFile(await image, path)
|
|
101
129
|
},
|
|
102
|
-
async toRaw() {
|
|
103
|
-
return image
|
|
104
|
-
},
|
|
105
130
|
async toObject() {
|
|
106
|
-
image = await transform(await image, transforms)
|
|
107
|
-
transforms = {
|
|
131
|
+
image = await transform(image ? await image : size, transforms)
|
|
132
|
+
transforms = {crop: null, scale: 1, rotate: 0, modifiers: []}
|
|
108
133
|
return image
|
|
109
134
|
},
|
|
110
135
|
async debug(debug) {
|
|
111
136
|
if (!debug || !debug.path) return
|
|
112
137
|
const timestamp = new Date().toISOString().replace(/[-T:.]/g, '_')
|
|
113
138
|
const filename = ['screenshot', timestamp, debug.name, debug.suffix].filter(part => part).join('_') + '.png'
|
|
114
|
-
|
|
139
|
+
const transformedImage = await transform(image ? await image : size, transforms)
|
|
140
|
+
return toFile(transformedImage, path.join(debug.path, filename)).catch(() => null)
|
|
115
141
|
},
|
|
116
142
|
}
|
|
117
143
|
}
|
|
@@ -164,10 +190,27 @@ async function toFile(image, path) {
|
|
|
164
190
|
}
|
|
165
191
|
|
|
166
192
|
async function transform(image, transforms) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
193
|
+
if (!image.data) {
|
|
194
|
+
const size = transforms.added
|
|
195
|
+
? {width: image.width - transforms.added.width, height: image.height - transforms.added.height}
|
|
196
|
+
: image
|
|
197
|
+
image = new png.Image(size)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
image = await transforms.modifiers.reduce(async (image, modifier) => {
|
|
201
|
+
if (modifier.type === 'copy') {
|
|
202
|
+
return copy(await image, await modifier.image, modifier.offset)
|
|
203
|
+
} else if (modifier.type === 'frame') {
|
|
204
|
+
return frame(await modifier.top, await modifier.bottom, await image, modifier.region)
|
|
205
|
+
} else {
|
|
206
|
+
return image
|
|
207
|
+
}
|
|
208
|
+
}, image)
|
|
209
|
+
|
|
210
|
+
image = transforms.crop ? await extract(image, transforms.crop) : image
|
|
211
|
+
image = transforms.scale !== 1 ? await scale(image, transforms.scale) : image
|
|
212
|
+
image = transforms.rotate !== 0 ? await rotate(image, transforms.rotate) : image
|
|
213
|
+
return image
|
|
171
214
|
}
|
|
172
215
|
|
|
173
216
|
async function scale(image, scaleRatio) {
|
|
@@ -256,7 +299,7 @@ async function rotate(image, degrees) {
|
|
|
256
299
|
for (let srcY = 0, dstX = 0; srcY < image.height; ++srcY, ++dstX) {
|
|
257
300
|
for (let srcX = 0, dstY = image.width - 1; srcX < image.width; ++srcX, --dstY) {
|
|
258
301
|
const pixel = image.data.readUInt32BE((srcY * image.width + srcX) * 4)
|
|
259
|
-
dstImage.data.writeUInt32BE(pixel, (
|
|
302
|
+
dstImage.data.writeUInt32BE(pixel, (dstY * dstImage.width + dstX) * 4)
|
|
260
303
|
}
|
|
261
304
|
}
|
|
262
305
|
} else {
|
|
@@ -288,98 +331,98 @@ async function copy(dstImage, srcImage, offset) {
|
|
|
288
331
|
return dstImage
|
|
289
332
|
}
|
|
290
333
|
|
|
291
|
-
async function
|
|
334
|
+
async function frame(topImage, bottomImage, srcImage, region) {
|
|
292
335
|
region = utils.geometry.intersect(
|
|
293
|
-
{x: 0, y: 0, width:
|
|
336
|
+
{x: 0, y: 0, width: topImage.width, height: topImage.height},
|
|
294
337
|
utils.geometry.round(region),
|
|
295
338
|
)
|
|
296
339
|
|
|
297
|
-
if (region.x === 0 && region.y === 0 && region.width >=
|
|
340
|
+
if (region.x === 0 && region.y === 0 && region.width >= topImage.width && region.height >= topImage.height) {
|
|
298
341
|
return srcImage
|
|
299
342
|
}
|
|
300
343
|
|
|
301
|
-
if (region.width
|
|
302
|
-
await copy(
|
|
303
|
-
return
|
|
344
|
+
if (region.width >= srcImage.width && region.height >= srcImage.height) {
|
|
345
|
+
await copy(topImage, srcImage, {x: region.x, y: region.y})
|
|
346
|
+
return topImage
|
|
304
347
|
}
|
|
305
348
|
|
|
306
349
|
const dstImage = new png.Image({
|
|
307
|
-
width:
|
|
308
|
-
height:
|
|
350
|
+
width: topImage.width + Math.max(srcImage.width - region.width, 0),
|
|
351
|
+
height: topImage.height + Math.max(srcImage.height - region.height, 0),
|
|
309
352
|
})
|
|
310
353
|
|
|
311
354
|
if (region.width === srcImage.width) {
|
|
312
|
-
const
|
|
355
|
+
const topExtImage = await extract(topImage, {
|
|
313
356
|
x: 0,
|
|
314
357
|
y: 0,
|
|
315
|
-
width:
|
|
358
|
+
width: topImage.width,
|
|
316
359
|
height: region.y + region.height,
|
|
317
360
|
})
|
|
318
|
-
await copy(dstImage,
|
|
361
|
+
await copy(dstImage, topExtImage, {x: 0, y: 0})
|
|
319
362
|
} else if (region.height === srcImage.height) {
|
|
320
|
-
const
|
|
363
|
+
const leftExtImage = await extract(topImage, {
|
|
321
364
|
x: 0,
|
|
322
365
|
y: 0,
|
|
323
366
|
width: region.x + region.width,
|
|
324
|
-
height:
|
|
367
|
+
height: topImage.height,
|
|
325
368
|
})
|
|
326
|
-
await copy(dstImage,
|
|
369
|
+
await copy(dstImage, leftExtImage, {x: 0, y: 0})
|
|
327
370
|
} else {
|
|
328
|
-
const
|
|
371
|
+
const topLeftExtImage = await extract(topImage, {
|
|
329
372
|
x: 0,
|
|
330
373
|
y: 0,
|
|
331
374
|
width: region.x + region.width,
|
|
332
375
|
height: region.y + region.height,
|
|
333
376
|
})
|
|
334
|
-
await copy(dstImage,
|
|
377
|
+
await copy(dstImage, topLeftExtImage, {x: 0, y: 0})
|
|
335
378
|
|
|
336
|
-
const rightExtImage = await extract(
|
|
379
|
+
const rightExtImage = await extract(topImage, {
|
|
337
380
|
x: region.x + region.width,
|
|
338
381
|
y: 0,
|
|
339
|
-
width:
|
|
382
|
+
width: topImage.width - (region.x + region.width),
|
|
340
383
|
height: region.y,
|
|
341
384
|
})
|
|
342
385
|
await copy(dstImage, rightExtImage, {x: region.x + region.width, y: 0})
|
|
343
386
|
|
|
344
|
-
const bottomExtImage = await extract(
|
|
387
|
+
const bottomExtImage = await extract(topImage, {
|
|
345
388
|
x: 0,
|
|
346
389
|
y: region.y + region.height,
|
|
347
390
|
width: region.x,
|
|
348
|
-
height:
|
|
391
|
+
height: topImage.height - (region.y + region.height),
|
|
349
392
|
})
|
|
350
393
|
await copy(dstImage, bottomExtImage, {x: 0, y: region.y + region.height})
|
|
351
394
|
}
|
|
352
395
|
|
|
353
|
-
if (
|
|
396
|
+
if (bottomImage.height > region.y + region.height || bottomImage.width > region.x + region.width) {
|
|
354
397
|
// first image might be higher
|
|
355
|
-
const yDiff =
|
|
398
|
+
const yDiff = topImage.height - bottomImage.height
|
|
356
399
|
if (region.width === srcImage.width) {
|
|
357
|
-
const
|
|
400
|
+
const bottomExtImage = await extract(bottomImage, {
|
|
358
401
|
x: 0,
|
|
359
402
|
y: region.y - yDiff + region.height,
|
|
360
|
-
width:
|
|
361
|
-
height:
|
|
403
|
+
width: bottomImage.width,
|
|
404
|
+
height: bottomImage.height - (region.y - yDiff + region.height),
|
|
362
405
|
})
|
|
363
|
-
await copy(dstImage,
|
|
406
|
+
await copy(dstImage, bottomExtImage, {x: 0, y: region.y + Math.max(srcImage.height, region.height)})
|
|
364
407
|
} else if (region.height === srcImage.height) {
|
|
365
|
-
const
|
|
408
|
+
const rightExtImage = await extract(bottomImage, {
|
|
366
409
|
x: region.x + region.width,
|
|
367
410
|
y: 0,
|
|
368
|
-
width:
|
|
369
|
-
height:
|
|
411
|
+
width: bottomImage.width - (region.x + region.width),
|
|
412
|
+
height: bottomImage.height,
|
|
370
413
|
})
|
|
371
|
-
await copy(dstImage,
|
|
414
|
+
await copy(dstImage, rightExtImage, {x: region.x + Math.max(srcImage.width, region.width), y: 0})
|
|
372
415
|
} else {
|
|
373
|
-
const
|
|
416
|
+
const bottomRightExtImage = await extract(bottomImage, {
|
|
374
417
|
x: region.x,
|
|
375
418
|
y: region.y - yDiff,
|
|
376
|
-
width:
|
|
377
|
-
height:
|
|
419
|
+
width: bottomImage.width - region.x,
|
|
420
|
+
height: bottomImage.height - (region.y - yDiff),
|
|
378
421
|
})
|
|
379
422
|
|
|
380
|
-
await copy(dstImage,
|
|
381
|
-
x: region.x + srcImage.width - region.width,
|
|
382
|
-
y: region.y + srcImage.height - region.height,
|
|
423
|
+
await copy(dstImage, bottomRightExtImage, {
|
|
424
|
+
x: region.x + Math.max(srcImage.width - region.width, 0),
|
|
425
|
+
y: region.y + Math.max(srcImage.height - region.height, 0),
|
|
383
426
|
})
|
|
384
427
|
}
|
|
385
428
|
}
|
|
@@ -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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
if (!scrollingElement) {
|
|
19
|
+
currentContext = currentContext.parent
|
|
20
|
+
continue
|
|
21
|
+
}
|
|
21
22
|
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
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),
|