@applitools/driver 1.3.3 → 1.4.1
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/CHANGELOG.md +20 -0
- package/dist/capabilities.js +3 -1
- package/dist/debug/check-spec-driver.js +170 -0
- package/dist/debug/index.js +13 -0
- package/dist/driver.js +52 -14
- package/dist/element.js +103 -116
- package/dist/helper-android.js +68 -0
- package/dist/helper-ios.js +39 -0
- package/package.json +10 -3
- package/types/debug/check-spec-driver.d.ts +20 -0
- package/types/debug/index.d.ts +1 -0
- package/types/driver.d.ts +4 -0
- package/types/element.d.ts +2 -0
- package/types/fake/mock-driver.d.ts +1 -1
- package/types/helper-android.d.ts +23 -0
- package/types/helper-ios.d.ts +21 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,26 @@
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
5
|
|
|
6
|
+
## 1.4.1 - 2021/12/16
|
|
7
|
+
|
|
8
|
+
- updated to @applitools/snippets@2.1.8 (from 2.1.7)
|
|
9
|
+
|
|
10
|
+
## 1.4.0 - 2021/12/16
|
|
11
|
+
|
|
12
|
+
- improve native apps scrolling automation
|
|
13
|
+
- fix ios safe area related issues
|
|
14
|
+
- add helper library abstraction to cover appium edge cases
|
|
15
|
+
- made `setViewportSize` more reliable in worst case scenario and faster in best case scenario
|
|
16
|
+
- updated to @applitools/types@1.0.22 (from 1.0.21)
|
|
17
|
+
|
|
18
|
+
## 1.3.5 - 2021/11/23
|
|
19
|
+
|
|
20
|
+
- updated to @applitools/types@1.0.21 (from 1.0.20)
|
|
21
|
+
|
|
22
|
+
## 1.3.4 - 2021/11/18
|
|
23
|
+
|
|
24
|
+
- fix capabilities parsing for native apps
|
|
25
|
+
|
|
6
26
|
## 1.3.3 - 2021/11/14
|
|
7
27
|
|
|
8
28
|
- do not throw if `getCookies` method is missed in spec driver
|
package/dist/capabilities.js
CHANGED
|
@@ -4,7 +4,9 @@ exports.parseCapabilities = void 0;
|
|
|
4
4
|
function parseCapabilities(capabilities) {
|
|
5
5
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
6
6
|
const info = {
|
|
7
|
-
browserName:
|
|
7
|
+
browserName: !capabilities.app && !capabilities.bundleId
|
|
8
|
+
? ((_a = capabilities.browserName) !== null && _a !== void 0 ? _a : (_b = capabilities.desired) === null || _b === void 0 ? void 0 : _b.browserName) || undefined
|
|
9
|
+
: undefined,
|
|
8
10
|
browserVersion: ((_c = capabilities.browserVersion) !== null && _c !== void 0 ? _c : capabilities.version) || undefined,
|
|
9
11
|
platformName: ((_e = (_d = capabilities.platformName) !== null && _d !== void 0 ? _d : capabilities.platform) !== null && _e !== void 0 ? _e : (_f = capabilities.desired) === null || _f === void 0 ? void 0 : _f.platformName) || undefined,
|
|
10
12
|
platformVersion: capabilities.platformVersion || undefined,
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.checkSpecDriver = void 0;
|
|
7
|
+
const strict_1 = __importDefault(require("assert/strict"));
|
|
8
|
+
const snippets = require('@applitools/snippets');
|
|
9
|
+
async function checkSpecDriver(options) {
|
|
10
|
+
const { spec, driver } = options;
|
|
11
|
+
const context = extractContext(driver);
|
|
12
|
+
const tests = {
|
|
13
|
+
'execute script with echo snippet': async () => {
|
|
14
|
+
const arg = [1, '23', false, { a: 4, b: [5] }, null];
|
|
15
|
+
const result = await spec.executeScript(context, 'return arguments[0]', arg);
|
|
16
|
+
strict_1.default.deepEqual(result, arg, 'script returns an array of json data');
|
|
17
|
+
},
|
|
18
|
+
'execute script with functional script': async () => {
|
|
19
|
+
const arg = { a: 2, b: 1, c: 7 };
|
|
20
|
+
function script(arg) {
|
|
21
|
+
return arg;
|
|
22
|
+
}
|
|
23
|
+
const result = await spec.executeScript(context, script, arg);
|
|
24
|
+
strict_1.default.deepEqual(result, arg, 'script returns an array of json data from functional script');
|
|
25
|
+
},
|
|
26
|
+
'execute script with return value of dom element': async () => {
|
|
27
|
+
const element = await spec.executeScript(context, 'return document.documentElement');
|
|
28
|
+
const isHtmlElement = await spec.executeScript(context, 'return arguments[0] === document.documentElement', element);
|
|
29
|
+
strict_1.default.ok(isHtmlElement, 'script returns an element and could be executed with an element');
|
|
30
|
+
},
|
|
31
|
+
'execute script with nested element references': async () => {
|
|
32
|
+
const elements = await spec.executeScript(context, 'return [{html: document.documentElement, body: document.body}]');
|
|
33
|
+
const isElements = await spec.executeScript(context, 'return arguments[0][0].html === document.documentElement && arguments[0][0].html === document.documentElement', elements);
|
|
34
|
+
strict_1.default.ok(isElements, 'script returns elements inside nested structure and could be executed with a nested structure of elements');
|
|
35
|
+
},
|
|
36
|
+
'find element with string selector': async () => {
|
|
37
|
+
const selector = transformSelector('html>body>h1');
|
|
38
|
+
const element = await spec.findElement(context, selector);
|
|
39
|
+
const isWantedElement = await spec.executeScript(context, `return arguments[0] === document.querySelector("${selector}")`, element);
|
|
40
|
+
strict_1.default.ok(isWantedElement, `returns element by string selector - "${selector}"`);
|
|
41
|
+
},
|
|
42
|
+
'find element with spec selector': async () => {
|
|
43
|
+
const cssSelector = transformSelector({ type: 'css', selector: 'html>body>h1' });
|
|
44
|
+
const xpathSelector = transformSelector({ type: 'xpath', selector: '//html/body/h1' });
|
|
45
|
+
const verificationScript = `return arguments[0] === document.querySelector('html>body>h1')`;
|
|
46
|
+
const cssElement = await spec.findElement(context, cssSelector);
|
|
47
|
+
const isCssElement = await spec.executeScript(context, verificationScript, cssElement);
|
|
48
|
+
strict_1.default.ok(isCssElement, `returns element by spec selector - ${JSON.stringify(cssSelector)}`);
|
|
49
|
+
const xpathElement = await spec.findElement(context, xpathSelector);
|
|
50
|
+
const isXpathElement = await spec.executeScript(context, verificationScript, xpathElement);
|
|
51
|
+
strict_1.default.ok(isXpathElement, `returns element by spec selector - ${JSON.stringify(xpathSelector)}`);
|
|
52
|
+
},
|
|
53
|
+
'find element with unresolvable selector': async () => {
|
|
54
|
+
const selector = transformSelector('unresolvable_selector');
|
|
55
|
+
const element = await spec.findElement(context, selector);
|
|
56
|
+
strict_1.default.equal(element, null, `returns null by unresolvable selector - "${selector}"`);
|
|
57
|
+
},
|
|
58
|
+
'find elements with string selector': async () => {
|
|
59
|
+
const selector = transformSelector('html p');
|
|
60
|
+
const elements = await spec.findElements(context, selector);
|
|
61
|
+
const isExpectedElements = await spec.executeScript(context, `var expected = arguments[0]; return Array.prototype.every.call(document.querySelectorAll("${selector}"), function(element, index) { return element === expected[index] })`, elements);
|
|
62
|
+
strict_1.default.ok(isExpectedElements, `returns elements by string selector - "${selector}"`);
|
|
63
|
+
},
|
|
64
|
+
'find elements with spec selector': async () => {
|
|
65
|
+
const cssSelector = transformSelector({ type: 'css', selector: 'html p' });
|
|
66
|
+
const xpathSelector = transformSelector({ type: 'xpath', selector: '//html//p' });
|
|
67
|
+
const verificationScript = `var expected = arguments[0]; return Array.prototype.every.call(document.querySelectorAll('html p'), function(element, index) { return element === expected[index] })`;
|
|
68
|
+
const cssElements = await spec.findElements(context, cssSelector);
|
|
69
|
+
const isCssElements = await spec.executeScript(context, verificationScript, cssElements);
|
|
70
|
+
strict_1.default.ok(isCssElements, `returns elements by spec selector - ${JSON.stringify(cssSelector)}`);
|
|
71
|
+
const xpathElements = await spec.findElements(context, xpathSelector);
|
|
72
|
+
const isXpathElements = await spec.executeScript(context, verificationScript, xpathElements);
|
|
73
|
+
strict_1.default.ok(isXpathElements, `returns element by spec selector - ${JSON.stringify(xpathSelector)}`);
|
|
74
|
+
},
|
|
75
|
+
'find elements with unresolvable selector': async () => {
|
|
76
|
+
const selector = transformSelector('unresolvable_selector');
|
|
77
|
+
const element = await spec.findElements(context, selector);
|
|
78
|
+
strict_1.default.deepEqual(element, [], `returns empty array by unresolvable selector - "${selector}"`);
|
|
79
|
+
},
|
|
80
|
+
'child context': async () => {
|
|
81
|
+
const element = await spec.findElement(context, transformSelector('[name="frame1"]'));
|
|
82
|
+
const childContext = await spec.childContext(context, element);
|
|
83
|
+
const inFrame = await spec.executeScript(childContext, 'return window.frameElement.name === "frame1"');
|
|
84
|
+
strict_1.default.ok(inFrame, 'returns or switches to a child context');
|
|
85
|
+
strict_1.default.ok(typeof spec.mainContext === 'function', 'spec.mainContext also needs to be implemented in order to test spec.childContext');
|
|
86
|
+
await spec.mainContext(context);
|
|
87
|
+
},
|
|
88
|
+
'is equal elements': async () => {
|
|
89
|
+
if (!spec.isEqualElements)
|
|
90
|
+
return { skipped: true };
|
|
91
|
+
const htmlEl = await spec.findElement(context, transformSelector('html'));
|
|
92
|
+
const htmlEl2 = await spec.executeScript(context, 'return document.documentElement');
|
|
93
|
+
strict_1.default.ok(await spec.isEqualElements(context, htmlEl, htmlEl2), 'elements should be equal');
|
|
94
|
+
const bodyEl = await spec.executeScript(context, 'return document.body');
|
|
95
|
+
strict_1.default.ok(!(await spec.isEqualElements(context, htmlEl, bodyEl)), 'elements should not be equal');
|
|
96
|
+
strict_1.default.ok(!(await spec.isEqualElements(context, htmlEl, undefined)), 'isEqualElements should return false if one of the arguments is falsy');
|
|
97
|
+
strict_1.default.ok(!(await spec.isEqualElements(context, undefined, htmlEl)), 'isEqualElements should return false if one of the arguments is falsy');
|
|
98
|
+
},
|
|
99
|
+
'main context': async () => {
|
|
100
|
+
const mainDocument1 = await spec.findElement(context, transformSelector('html'));
|
|
101
|
+
const childContext1 = await spec.childContext(context, await spec.findElement(context, transformSelector('[name="frame1"]')));
|
|
102
|
+
const childContext2 = await spec.childContext(childContext1, await spec.findElement(childContext1, transformSelector('[name="frame1-1"]')));
|
|
103
|
+
const frameDocument = await spec.findElement(childContext2, transformSelector('html'));
|
|
104
|
+
strict_1.default.ok(!(await isEqualElements(childContext2, mainDocument1, frameDocument)));
|
|
105
|
+
const mainContext = await spec.mainContext(childContext2);
|
|
106
|
+
const mainDocument2 = await spec.findElement(mainContext, transformSelector('html'));
|
|
107
|
+
strict_1.default.ok(await isEqualElements(mainContext, mainDocument2, mainDocument1));
|
|
108
|
+
},
|
|
109
|
+
'parent context': async () => {
|
|
110
|
+
const parentContext1 = await spec.childContext(context, await spec.findElement(context, transformSelector('[name="frame1"]')));
|
|
111
|
+
const parentDocument1 = await spec.findElement(parentContext1, transformSelector('html'));
|
|
112
|
+
const frameContext = await spec.childContext(parentContext1, await spec.findElement(parentContext1, transformSelector('[name="frame1-1"]')));
|
|
113
|
+
const frameDocument = await spec.findElement(frameContext, transformSelector('html'));
|
|
114
|
+
strict_1.default.ok(!(await isEqualElements(frameContext, parentDocument1, frameDocument)));
|
|
115
|
+
const parentContext2 = await spec.parentContext(frameContext);
|
|
116
|
+
const parentDocument2 = await spec.findElement(parentContext2, transformSelector('html'));
|
|
117
|
+
strict_1.default.ok(await isEqualElements(parentContext2, parentDocument2, parentDocument1));
|
|
118
|
+
await spec.mainContext(context);
|
|
119
|
+
},
|
|
120
|
+
'get title': async () => {
|
|
121
|
+
const title = await spec.getTitle(driver);
|
|
122
|
+
strict_1.default.equal(title, 'Cross SDK test', 'returns title of the current page');
|
|
123
|
+
},
|
|
124
|
+
'get url': async () => {
|
|
125
|
+
const url = await spec.getUrl(driver);
|
|
126
|
+
strict_1.default.equal(url, 'https://applitools.github.io/demo/TestPages/FramesTestPage/', 'returns url of the current page');
|
|
127
|
+
},
|
|
128
|
+
'is driver': async () => {
|
|
129
|
+
strict_1.default.ok(await spec.isDriver(driver), 'driver should be considered a driver :)');
|
|
130
|
+
strict_1.default.ok(!(await spec.isDriver(undefined)), 'undefined should not be considered a driver');
|
|
131
|
+
strict_1.default.ok(!(await spec.isDriver(3)), 'number should not be considered a driver');
|
|
132
|
+
strict_1.default.ok(!(await spec.isDriver('str')), 'string should not be considered a driver');
|
|
133
|
+
},
|
|
134
|
+
'is element': async () => {
|
|
135
|
+
const el = await spec.findElement(context, transformSelector('html'));
|
|
136
|
+
strict_1.default.ok(await spec.isElement(el), 'element should be considered an element :)');
|
|
137
|
+
strict_1.default.ok(!(await spec.isElement(undefined)), 'undefined should not be considered an element');
|
|
138
|
+
strict_1.default.ok(!(await spec.isElement(3)), 'number should not be considered an element');
|
|
139
|
+
strict_1.default.ok(!(await spec.isElement('str')), 'str should not be considered an element');
|
|
140
|
+
},
|
|
141
|
+
// 'is selector': async () => {}, // hard to test this
|
|
142
|
+
// 'set window size': async () => {}, // hard to test this
|
|
143
|
+
// 'get window size': async () => {}, // hard to test this
|
|
144
|
+
};
|
|
145
|
+
const report = [];
|
|
146
|
+
await spec.visit(driver, 'https://applitools.github.io/demo/TestPages/FramesTestPage/');
|
|
147
|
+
for (const [test, check] of Object.entries(tests)) {
|
|
148
|
+
try {
|
|
149
|
+
const result = (await check()) || { success: true };
|
|
150
|
+
report.push(Object.assign({ test }, result));
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
report.push({ test, error: { message: error.message, expected: error.expected, actual: error.actual } });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return report;
|
|
157
|
+
function isEqualElements(context, element1, element2) {
|
|
158
|
+
var _a, _b;
|
|
159
|
+
return ((_b = (_a = spec.isEqualElements) === null || _a === void 0 ? void 0 : _a.call(spec, context, element1, element2)) !== null && _b !== void 0 ? _b : spec.executeScript(context, snippets.isEqualElements, [element1, element2]).catch(() => false));
|
|
160
|
+
}
|
|
161
|
+
function extractContext(driver) {
|
|
162
|
+
var _a, _b;
|
|
163
|
+
return (_b = (_a = spec.extractContext) === null || _a === void 0 ? void 0 : _a.call(spec, driver)) !== null && _b !== void 0 ? _b : driver;
|
|
164
|
+
}
|
|
165
|
+
function transformSelector(selector) {
|
|
166
|
+
var _a, _b;
|
|
167
|
+
return (_b = (_a = spec.transformSelector) === null || _a === void 0 ? void 0 : _a.call(spec, selector)) !== null && _b !== void 0 ? _b : selector;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
exports.checkSpecDriver = checkSpecDriver;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
5
|
+
}) : (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
o[k2] = m[k];
|
|
8
|
+
}));
|
|
9
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
10
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
__exportStar(require("./check-spec-driver"), exports);
|
package/dist/driver.js
CHANGED
|
@@ -22,6 +22,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
22
22
|
exports.Driver = void 0;
|
|
23
23
|
const utils = __importStar(require("@applitools/utils"));
|
|
24
24
|
const context_1 = require("./context");
|
|
25
|
+
const helper_ios_1 = require("./helper-ios");
|
|
26
|
+
const helper_android_1 = require("./helper-android");
|
|
25
27
|
const utils_1 = require("./utils");
|
|
26
28
|
const user_agent_1 = require("./user-agent");
|
|
27
29
|
const capabilities_1 = require("./capabilities");
|
|
@@ -59,6 +61,9 @@ class Driver {
|
|
|
59
61
|
get mainContext() {
|
|
60
62
|
return this._mainContext;
|
|
61
63
|
}
|
|
64
|
+
get helper() {
|
|
65
|
+
return this._helper;
|
|
66
|
+
}
|
|
62
67
|
get features() {
|
|
63
68
|
var _a;
|
|
64
69
|
return (_a = this._driverInfo) === null || _a === void 0 ? void 0 : _a.features;
|
|
@@ -140,7 +145,7 @@ class Driver {
|
|
|
140
145
|
const userAgentInfo = user_agent_1.parseUserAgent(this._driverInfo.userAgent);
|
|
141
146
|
this._driverInfo.browserName = (_g = userAgentInfo.browserName) !== null && _g !== void 0 ? _g : this._driverInfo.browserName;
|
|
142
147
|
this._driverInfo.browserVersion = (_h = userAgentInfo.browserVersion) !== null && _h !== void 0 ? _h : this._driverInfo.browserVersion;
|
|
143
|
-
if (
|
|
148
|
+
if (this._driverInfo.isMobile) {
|
|
144
149
|
(_j = (_u = this._driverInfo).platformName) !== null && _j !== void 0 ? _j : (_u.platformName = userAgentInfo.platformName);
|
|
145
150
|
(_k = (_v = this._driverInfo).platformVersion) !== null && _k !== void 0 ? _k : (_v.platformVersion = userAgentInfo.platformVersion);
|
|
146
151
|
}
|
|
@@ -153,24 +158,48 @@ class Driver {
|
|
|
153
158
|
(_p = (_x = this._driverInfo.features).allCookies) !== null && _p !== void 0 ? _p : (_x.allCookies = /chrome/i.test(this._driverInfo.browserName) && !this._driverInfo.isMobile);
|
|
154
159
|
}
|
|
155
160
|
else {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
this._driverInfo.statusBarHeight /= this.pixelRatio;
|
|
164
|
-
this._driverInfo.navigationBarHeight /= this.pixelRatio;
|
|
161
|
+
const barsHeight = await ((_r = (_q = this._spec).getBarsHeight) === null || _r === void 0 ? void 0 : _r.call(_q, this.target).catch(() => undefined));
|
|
162
|
+
const displaySize = await this.getDisplaySize();
|
|
163
|
+
// calculate status and navigation bars sizes
|
|
164
|
+
if (barsHeight) {
|
|
165
|
+
// when status bar is overlapping content on android it returns status bar height equal to viewport height
|
|
166
|
+
if (this.isAndroid && barsHeight.statusBarHeight / this.pixelRatio < displaySize.height) {
|
|
167
|
+
this._driverInfo.statusBarHeight = Math.max(this._driverInfo.statusBarHeight, barsHeight.statusBarHeight);
|
|
165
168
|
}
|
|
169
|
+
this._driverInfo.navigationBarHeight = Math.max(this._driverInfo.navigationBarHeight, barsHeight.navigationBarHeight);
|
|
170
|
+
}
|
|
171
|
+
if (this.isAndroid) {
|
|
172
|
+
this._driverInfo.statusBarHeight /= this.pixelRatio;
|
|
173
|
+
this._driverInfo.navigationBarHeight /= this.pixelRatio;
|
|
166
174
|
}
|
|
175
|
+
// calculate viewport size
|
|
167
176
|
if (!this._driverInfo.viewportSize) {
|
|
168
|
-
const displaySize = await this.getDisplaySize();
|
|
169
177
|
this._driverInfo.viewportSize = {
|
|
170
178
|
width: displaySize.width,
|
|
171
179
|
height: displaySize.height - this._driverInfo.statusBarHeight,
|
|
172
180
|
};
|
|
173
181
|
}
|
|
182
|
+
// calculate safe area
|
|
183
|
+
if (this.isIOS && !this._driverInfo.safeArea) {
|
|
184
|
+
this._driverInfo.safeArea = Object.assign({ x: 0, y: 0 }, displaySize);
|
|
185
|
+
const topElement = await this.element({ type: 'class name', selector: 'XCUIElementTypeNavigationBar' });
|
|
186
|
+
if (topElement) {
|
|
187
|
+
const topRegion = await this._spec.getElementRegion(this.target, topElement.target);
|
|
188
|
+
const topOffset = topRegion.y + topRegion.height;
|
|
189
|
+
this._driverInfo.safeArea.y = topOffset;
|
|
190
|
+
this._driverInfo.safeArea.height -= topOffset;
|
|
191
|
+
}
|
|
192
|
+
const bottomElement = await this.element({ type: 'class name', selector: 'XCUIElementTypeTabBar' });
|
|
193
|
+
if (bottomElement) {
|
|
194
|
+
const bottomRegion = await this._spec.getElementRegion(this.target, bottomElement.target);
|
|
195
|
+
const bottomOffset = bottomRegion.height;
|
|
196
|
+
this._driverInfo.safeArea.height -= bottomOffset;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// init helper lib
|
|
200
|
+
this._helper = this.isIOS
|
|
201
|
+
? await helper_ios_1.HelperIOS.make({ spec: this._spec, driver: this, logger: this._logger })
|
|
202
|
+
: await helper_android_1.HelperAndroid.make({ spec: this._spec, driver: this, logger: this._logger });
|
|
174
203
|
}
|
|
175
204
|
this._logger.log('Combined driver info', this._driverInfo);
|
|
176
205
|
return this;
|
|
@@ -339,7 +368,13 @@ class Driver {
|
|
|
339
368
|
if (this.isWeb || !utils.types.has(this._driverInfo, ['viewportSize', 'statusBarHeight']))
|
|
340
369
|
return region;
|
|
341
370
|
const scaledRegion = this.isAndroid ? utils.geometry.scale(region, 1 / this.pixelRatio) : region;
|
|
342
|
-
|
|
371
|
+
const safeRegion = this.isIOS ? utils.geometry.intersect(scaledRegion, this._driverInfo.safeArea) : scaledRegion;
|
|
372
|
+
const offsetRegion = utils.geometry.offsetNegative(safeRegion, { x: 0, y: this.statusBarHeight });
|
|
373
|
+
if (offsetRegion.y < 0) {
|
|
374
|
+
offsetRegion.height += offsetRegion.y;
|
|
375
|
+
offsetRegion.y = 0;
|
|
376
|
+
}
|
|
377
|
+
return offsetRegion;
|
|
343
378
|
}
|
|
344
379
|
async getRegionInViewport(context, region) {
|
|
345
380
|
await context.focus();
|
|
@@ -411,9 +446,12 @@ class Driver {
|
|
|
411
446
|
};
|
|
412
447
|
this._logger.log(`Attempt #${attempt} to set viewport size by setting window size to`, requiredWindowSize);
|
|
413
448
|
await this._spec.setWindowSize(this.target, requiredWindowSize);
|
|
414
|
-
|
|
415
|
-
currentWindowSize = requiredWindowSize;
|
|
449
|
+
const prevViewportSize = currentViewportSize;
|
|
416
450
|
currentViewportSize = await this.getViewportSize();
|
|
451
|
+
if (utils.geometry.equals(currentViewportSize, prevViewportSize)) {
|
|
452
|
+
currentViewportSize = await this.getViewportSize();
|
|
453
|
+
}
|
|
454
|
+
currentWindowSize = requiredWindowSize;
|
|
417
455
|
if (utils.geometry.equals(currentViewportSize, requiredViewportSize))
|
|
418
456
|
return;
|
|
419
457
|
this._logger.log(`Attempt #${attempt} to set viewport size failed. Current viewport:`, currentViewportSize);
|
package/dist/element.js
CHANGED
|
@@ -18,6 +18,17 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
18
18
|
__setModuleDefault(result, mod);
|
|
19
19
|
return result;
|
|
20
20
|
};
|
|
21
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
22
|
+
var t = {};
|
|
23
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
24
|
+
t[p] = s[p];
|
|
25
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
26
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
27
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
28
|
+
t[p[i]] = s[p[i]];
|
|
29
|
+
}
|
|
30
|
+
return t;
|
|
31
|
+
};
|
|
21
32
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
33
|
exports.Element = void 0;
|
|
23
34
|
const utils = __importStar(require("@applitools/utils"));
|
|
@@ -112,10 +123,7 @@ class Element {
|
|
|
112
123
|
return this.context.execute(snippets.getElementRect, [this, true]);
|
|
113
124
|
}
|
|
114
125
|
else {
|
|
115
|
-
this.
|
|
116
|
-
const region = await this._spec.getElementRegion(this.driver.target, this.target);
|
|
117
|
-
this._logger.log('Extracted native region', region);
|
|
118
|
-
return this.driver.normalizeRegion(region);
|
|
126
|
+
return this.getRegion();
|
|
119
127
|
}
|
|
120
128
|
});
|
|
121
129
|
this._logger.log('Extracted client region', region);
|
|
@@ -125,7 +133,7 @@ class Element {
|
|
|
125
133
|
if (this._state.contentSize)
|
|
126
134
|
return this._state.contentSize;
|
|
127
135
|
const size = await this.withRefresh(async () => {
|
|
128
|
-
var _a;
|
|
136
|
+
var _a, _b, _c;
|
|
129
137
|
if (this.driver.isWeb) {
|
|
130
138
|
this._logger.log('Extracting content size of web element with selector', this.selector);
|
|
131
139
|
return this.context.execute(snippets.getElementContentSize, [this]);
|
|
@@ -133,81 +141,37 @@ class Element {
|
|
|
133
141
|
else {
|
|
134
142
|
this._logger.log('Extracting content size of native element with selector', this.selector);
|
|
135
143
|
try {
|
|
144
|
+
const _d = await this.getAttribute('contentSize')
|
|
145
|
+
.then(data => {
|
|
146
|
+
const contentSize = JSON.parse(data);
|
|
147
|
+
return {
|
|
148
|
+
touchPadding: contentSize.touchPadding,
|
|
149
|
+
x: contentSize.left,
|
|
150
|
+
y: contentSize.top,
|
|
151
|
+
width: contentSize.width,
|
|
152
|
+
height: (this.driver.isAndroid ? contentSize.height : 0) + contentSize.scrollableOffset,
|
|
153
|
+
};
|
|
154
|
+
})
|
|
155
|
+
.catch(() => {
|
|
156
|
+
return this._spec.getElementRegion(this.driver.target, this.target);
|
|
157
|
+
}), { touchPadding } = _d, contentRegion = __rest(_d, ["touchPadding"]);
|
|
158
|
+
this._logger.log('Extracted native content size attribute', contentRegion);
|
|
159
|
+
const contentSize = await ((_a = this.driver.helper) === null || _a === void 0 ? void 0 : _a.getContentSize(this));
|
|
160
|
+
this._logger.log('Extracted native content size with helper library', contentSize);
|
|
161
|
+
this._state.contentSize = {
|
|
162
|
+
width: Math.max((_b = contentSize === null || contentSize === void 0 ? void 0 : contentSize.width) !== null && _b !== void 0 ? _b : 0, contentRegion.width),
|
|
163
|
+
height: Math.max((_c = contentSize === null || contentSize === void 0 ? void 0 : contentSize.height) !== null && _c !== void 0 ? _c : 0, contentRegion.height),
|
|
164
|
+
};
|
|
165
|
+
this._touchPadding = touchPadding !== null && touchPadding !== void 0 ? touchPadding : this._touchPadding;
|
|
136
166
|
if (this.driver.isAndroid) {
|
|
137
|
-
|
|
138
|
-
if ([
|
|
139
|
-
'android.widget.ListView',
|
|
140
|
-
'android.widget.GridView',
|
|
141
|
-
'android.support.v7.widget.RecyclerView',
|
|
142
|
-
// 'androidx.recyclerview.widget.RecyclerView',
|
|
143
|
-
'androidx.viewpager2.widget.ViewPager2',
|
|
144
|
-
].includes(className)) {
|
|
145
|
-
this._logger.log('Trying to extract content size using android helper library');
|
|
146
|
-
const helperElement = await this.driver.element({
|
|
147
|
-
type: '-android uiautomator',
|
|
148
|
-
selector: 'new UiSelector().description("EyesAppiumHelper")',
|
|
149
|
-
});
|
|
150
|
-
if (helperElement) {
|
|
151
|
-
const elementRegion = await this._spec.getElementRegion(this.driver.target, this.target);
|
|
152
|
-
await helperElement.click();
|
|
153
|
-
const info = await this._spec.getElementText(this.driver.target, helperElement.target);
|
|
154
|
-
this._state.contentSize = utils.geometry.scale({ width: elementRegion.width, height: Number(info) }, 1 / this.driver.pixelRatio);
|
|
155
|
-
}
|
|
156
|
-
else {
|
|
157
|
-
this._logger.log('Helper library for android was not detected');
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
else if (this.driver.isIOS) {
|
|
162
|
-
const type = await this.getAttribute('type');
|
|
163
|
-
if (type === 'XCUIElementTypeScrollView') {
|
|
164
|
-
const elementRegion = await this._spec.getElementRegion(this.driver.target, this.target);
|
|
165
|
-
const [childElement] = await this.driver.elements({
|
|
166
|
-
type: 'xpath',
|
|
167
|
-
selector: '//XCUIElementTypeScrollView[1]/*', // We cannot be sure that our element is the first one
|
|
168
|
-
});
|
|
169
|
-
const childElementRegion = await this._spec.getElementRegion(this.driver.target, childElement.target);
|
|
170
|
-
this._state.contentSize = {
|
|
171
|
-
width: elementRegion.width,
|
|
172
|
-
height: childElementRegion.y + childElementRegion.height - elementRegion.y,
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
else if (type === 'XCUIElementTypeCollectionView') {
|
|
176
|
-
this._logger.log('Trying to extract content size using ios helper library');
|
|
177
|
-
const helperElement = await this.driver.element({
|
|
178
|
-
type: 'name',
|
|
179
|
-
selector: 'applitools_grab_scrollable_data_button',
|
|
180
|
-
});
|
|
181
|
-
if (helperElement) {
|
|
182
|
-
const helperElementRegion = await this._spec.getElementRegion(this.driver.target, helperElement.target);
|
|
183
|
-
await this._spec.performAction(this.driver.target, [
|
|
184
|
-
{ action: 'tap', x: helperElementRegion.x, y: helperElementRegion.y },
|
|
185
|
-
{ action: 'wait', ms: 1000 },
|
|
186
|
-
{ action: 'release' },
|
|
187
|
-
]);
|
|
188
|
-
const infoElement = await this.driver.element({ type: 'name', selector: 'applitools_content_size_label' });
|
|
189
|
-
const info = await this._spec.getElementText(this.driver.target, infoElement.target);
|
|
190
|
-
if (info) {
|
|
191
|
-
const [_, width, height] = info.match(/\{(\d+),\s?(\d+)\}/);
|
|
192
|
-
this._state.contentSize = { width: Number(width), height: Number(height) };
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
else {
|
|
196
|
-
this._logger.log('Helper library for ios was not detected');
|
|
197
|
-
}
|
|
198
|
-
}
|
|
167
|
+
this._state.contentSize = utils.geometry.scale(this._state.contentSize, 1 / this.driver.pixelRatio);
|
|
199
168
|
}
|
|
200
|
-
if (
|
|
201
|
-
|
|
202
|
-
this._logger.log('Extracted native content size attribute', data);
|
|
203
|
-
this._state.contentSize = this.driver.isIOS
|
|
204
|
-
? { width: data.width, height: data.scrollableOffset }
|
|
205
|
-
: utils.geometry.scale({ width: data.width, height: data.height + data.scrollableOffset }, 1 / this.driver.pixelRatio);
|
|
206
|
-
this._touchPadding = (_a = data.touchPadding) !== null && _a !== void 0 ? _a : this._touchPadding;
|
|
169
|
+
if (contentRegion.y < this.driver.statusBarHeight) {
|
|
170
|
+
this._state.contentSize.height -= this.driver.statusBarHeight - contentRegion.y;
|
|
207
171
|
}
|
|
172
|
+
// android has a bug when after extracting 'contentSize' attribute the element is being scrolled by undetermined number of pixels
|
|
208
173
|
if (this.driver.isAndroid) {
|
|
209
174
|
this._logger.log('Stabilizing android scroll offset');
|
|
210
|
-
// android has a bug when after extracting 'contentSize' attribute the element is being scrolled by undetermined number of pixels
|
|
211
175
|
const originalScrollOffset = await this.getScrollOffset();
|
|
212
176
|
this._state.scrollOffset = { x: -1, y: -1 };
|
|
213
177
|
await this.scrollTo({ x: 0, y: 0 });
|
|
@@ -260,7 +224,7 @@ class Element {
|
|
|
260
224
|
if (this.driver.isWeb)
|
|
261
225
|
this._touchPadding = 0;
|
|
262
226
|
else if (this.driver.isIOS)
|
|
263
|
-
this._touchPadding =
|
|
227
|
+
this._touchPadding = 10;
|
|
264
228
|
else if (this.driver.isAndroid) {
|
|
265
229
|
const { touchPadding } = JSON.parse(await this.getAttribute('contentSize'));
|
|
266
230
|
this._touchPadding = touchPadding !== null && touchPadding !== void 0 ? touchPadding : 0;
|
|
@@ -268,6 +232,19 @@ class Element {
|
|
|
268
232
|
}
|
|
269
233
|
return this._touchPadding;
|
|
270
234
|
}
|
|
235
|
+
async getText() {
|
|
236
|
+
const text = await this.withRefresh(async () => {
|
|
237
|
+
if (this.driver.isWeb) {
|
|
238
|
+
return '';
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
this._logger.log('Extracting text of native element with selector', this.selector);
|
|
242
|
+
return this._spec.getElementText(this.driver.target, this.target);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
this._logger.log('Extracted element text', text);
|
|
246
|
+
return text;
|
|
247
|
+
}
|
|
271
248
|
async getAttribute(name) {
|
|
272
249
|
if (this.driver.isWeb) {
|
|
273
250
|
const properties = await this.context.execute(snippets.getElementProperties, [this, [name]]);
|
|
@@ -284,7 +261,7 @@ class Element {
|
|
|
284
261
|
}
|
|
285
262
|
async scrollTo(offset) {
|
|
286
263
|
return this.withRefresh(async () => {
|
|
287
|
-
offset =
|
|
264
|
+
offset = utils.geometry.round(offset);
|
|
288
265
|
if (this.driver.isWeb) {
|
|
289
266
|
let actualOffset = await this.context.execute(snippets.scrollTo, [this, offset]);
|
|
290
267
|
// iOS has an issue when scroll offset is read immediately after it is been set it will always return the exact value that was set
|
|
@@ -297,62 +274,69 @@ class Element {
|
|
|
297
274
|
if (utils.geometry.equals(offset, currentScrollOffset))
|
|
298
275
|
return currentScrollOffset;
|
|
299
276
|
const contentSize = await this.getContentSize();
|
|
300
|
-
const scrollableRegion = await this.
|
|
301
|
-
const
|
|
302
|
-
? utils.geometry.scale(scrollableRegion,
|
|
277
|
+
const scrollableRegion = await this.getClientRegion();
|
|
278
|
+
const effectiveRegion = this.driver.isAndroid
|
|
279
|
+
? utils.geometry.scale(scrollableRegion, this.driver.pixelRatio)
|
|
303
280
|
: scrollableRegion;
|
|
304
281
|
const maxOffset = {
|
|
305
|
-
x: Math.round(
|
|
306
|
-
y: Math.round(
|
|
282
|
+
x: Math.round(scrollableRegion.width * (contentSize.width / scrollableRegion.width - 1)),
|
|
283
|
+
y: Math.round(scrollableRegion.height * (contentSize.height / scrollableRegion.height - 1)),
|
|
307
284
|
};
|
|
308
|
-
|
|
309
|
-
let remainingOffset
|
|
310
|
-
|
|
311
|
-
requiredOffset
|
|
312
|
-
// if it has to be scrolled to the very beginning, then scroll maximum amount of pixels and a bit extra to be sure
|
|
313
|
-
remainingOffset = { x: -(maxOffset.x + 0), y: -(maxOffset.y + 0) };
|
|
314
|
-
}
|
|
315
|
-
else {
|
|
316
|
-
requiredOffset = { x: Math.min(offset.x, maxOffset.x), y: Math.min(offset.y, maxOffset.y) };
|
|
317
|
-
remainingOffset = utils.geometry.offsetNegative(requiredOffset, currentScrollOffset);
|
|
318
|
-
// if it has to be scrolled to the very end, then do a bit of extra scrolling to be sure
|
|
319
|
-
// if (requiredOffset.x === maxOffset.x) remainingOffset.x += 100
|
|
320
|
-
// if (requiredOffset.y === maxOffset.y) remainingOffset.y += 100
|
|
321
|
-
}
|
|
322
|
-
// if (requiredOffset.x === 0) remainingOffset.x -= 100
|
|
323
|
-
// if (requiredOffset.y === 0) remainingOffset.y -= 100
|
|
324
|
-
// if (requiredOffset.x === maxOffset.x) remainingOffset.x += 100
|
|
325
|
-
// if (requiredOffset.y === maxOffset.y) remainingOffset.y += 100
|
|
285
|
+
const requiredOffset = { x: Math.min(offset.x, maxOffset.x), y: Math.min(offset.y, maxOffset.y) };
|
|
286
|
+
let remainingOffset = offset.x === 0 && offset.y === 0
|
|
287
|
+
? { x: -maxOffset.x, y: -maxOffset.y } // if it has to be scrolled to the very beginning, then scroll maximum amount of pixels
|
|
288
|
+
: utils.geometry.offsetNegative(requiredOffset, currentScrollOffset);
|
|
326
289
|
if (this.driver.isAndroid) {
|
|
327
290
|
remainingOffset = utils.geometry.scale(remainingOffset, this.driver.pixelRatio);
|
|
328
291
|
}
|
|
329
|
-
const
|
|
330
|
-
const xPadding = Math.floor(
|
|
331
|
-
const yTrack = Math.floor(
|
|
332
|
-
const xLeft =
|
|
292
|
+
const touchPadding = await this.getTouchPadding();
|
|
293
|
+
const xPadding = Math.max(Math.floor(effectiveRegion.width * 0.1), touchPadding);
|
|
294
|
+
const yTrack = Math.floor(effectiveRegion.y + effectiveRegion.height / 2); // center
|
|
295
|
+
const xLeft = effectiveRegion.y + xPadding;
|
|
333
296
|
const xDirection = remainingOffset.y > 0 ? 'right' : 'left';
|
|
297
|
+
const xGap = xDirection === 'right' ? -touchPadding : touchPadding;
|
|
334
298
|
let xRemaining = Math.abs(remainingOffset.x);
|
|
335
299
|
while (xRemaining > 0) {
|
|
336
|
-
const xRight =
|
|
300
|
+
const xRight = effectiveRegion.x + Math.min(xRemaining + xPadding, effectiveRegion.width - xPadding);
|
|
337
301
|
const [xStart, xEnd] = xDirection === 'right' ? [xRight, xLeft] : [xLeft, xRight];
|
|
338
|
-
|
|
302
|
+
await this._spec.performAction(this.driver.target, [
|
|
303
|
+
{ action: 'press', y: yTrack, x: xStart },
|
|
304
|
+
{ action: 'wait', ms: 100 },
|
|
305
|
+
{ action: 'moveTo', y: yTrack, x: xStart + xGap },
|
|
306
|
+
{ action: 'wait', ms: 100 },
|
|
307
|
+
{ action: 'moveTo', y: yTrack, x: xEnd + xGap },
|
|
308
|
+
{ action: 'wait', ms: 100 },
|
|
309
|
+
{ action: 'moveTo', y: yTrack + 1, x: xEnd + xGap },
|
|
310
|
+
{ action: 'release' },
|
|
311
|
+
]);
|
|
339
312
|
xRemaining -= xRight - xLeft;
|
|
340
313
|
}
|
|
341
|
-
const yPadding = Math.floor(
|
|
342
|
-
const xTrack = Math.floor(
|
|
343
|
-
const
|
|
314
|
+
const yPadding = Math.max(Math.floor(effectiveRegion.height * 0.1), touchPadding);
|
|
315
|
+
const xTrack = Math.floor(effectiveRegion.x + 5); // a little bit off left border
|
|
316
|
+
const yBottom = effectiveRegion.y + effectiveRegion.height - yPadding;
|
|
344
317
|
const yDirection = remainingOffset.y > 0 ? 'down' : 'up';
|
|
345
|
-
|
|
318
|
+
const yGap = yDirection === 'down' ? -touchPadding : touchPadding;
|
|
319
|
+
let yRemaining = Math.abs(remainingOffset.y);
|
|
346
320
|
while (yRemaining > 0) {
|
|
347
|
-
const
|
|
321
|
+
const yTop = Math.max(yBottom - yRemaining, effectiveRegion.y + yPadding);
|
|
348
322
|
const [yStart, yEnd] = yDirection === 'down' ? [yBottom, yTop] : [yTop, yBottom];
|
|
349
|
-
|
|
323
|
+
await this._spec.performAction(this.driver.target, [
|
|
324
|
+
{ action: 'press', x: xTrack, y: yStart },
|
|
325
|
+
{ action: 'wait', ms: 100 },
|
|
326
|
+
{ action: 'moveTo', x: xTrack, y: yStart + yGap },
|
|
327
|
+
{ action: 'wait', ms: 100 },
|
|
328
|
+
{ action: 'moveTo', x: xTrack, y: yEnd + yGap },
|
|
329
|
+
{ action: 'wait', ms: 100 },
|
|
330
|
+
{ action: 'moveTo', x: xTrack + 1, y: yEnd + yGap },
|
|
331
|
+
{ action: 'release' },
|
|
332
|
+
]);
|
|
350
333
|
yRemaining -= yBottom - yTop;
|
|
351
334
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
335
|
+
const actualScrollableRegion = await this.getClientRegion();
|
|
336
|
+
this._state.scrollOffset = utils.geometry.offsetNegative(requiredOffset, {
|
|
337
|
+
x: scrollableRegion.x - actualScrollableRegion.x,
|
|
338
|
+
y: scrollableRegion.y - actualScrollableRegion.y,
|
|
339
|
+
});
|
|
356
340
|
return this._state.scrollOffset;
|
|
357
341
|
}
|
|
358
342
|
});
|
|
@@ -394,6 +378,9 @@ class Element {
|
|
|
394
378
|
async click() {
|
|
395
379
|
await this._spec.click(this.context.target, this.target);
|
|
396
380
|
}
|
|
381
|
+
async type(value) {
|
|
382
|
+
await this._spec.type(this.context.target, this.target, value);
|
|
383
|
+
}
|
|
397
384
|
async preserveState() {
|
|
398
385
|
if (this.driver.isNative)
|
|
399
386
|
return;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HelperAndroid = void 0;
|
|
4
|
+
class HelperAndroid {
|
|
5
|
+
constructor(options) {
|
|
6
|
+
this._spec = options.spec;
|
|
7
|
+
this._element = options.element;
|
|
8
|
+
this._legacy = options.legacy;
|
|
9
|
+
this._logger = options.logger;
|
|
10
|
+
}
|
|
11
|
+
static async make(options) {
|
|
12
|
+
const { spec, driver, logger } = options;
|
|
13
|
+
let legacy = false;
|
|
14
|
+
let element = await driver.element({
|
|
15
|
+
type: '-android uiautomator',
|
|
16
|
+
selector: 'new UiSelector().description("EyesAppiumHelperEDT")',
|
|
17
|
+
});
|
|
18
|
+
if (!element) {
|
|
19
|
+
legacy = true;
|
|
20
|
+
element = await driver.element({
|
|
21
|
+
type: '-android uiautomator',
|
|
22
|
+
selector: 'new UiSelector().description("EyesAppiumHelper")',
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
return element ? new HelperAndroid({ spec, element, legacy, logger }) : null;
|
|
26
|
+
}
|
|
27
|
+
async _getElementId(element) {
|
|
28
|
+
const resourceId = await element.getAttribute('resource-id');
|
|
29
|
+
if (!resourceId)
|
|
30
|
+
return null;
|
|
31
|
+
return resourceId.split('/')[1];
|
|
32
|
+
}
|
|
33
|
+
async getContentSize(element) {
|
|
34
|
+
let contentHeight;
|
|
35
|
+
if (this._legacy) {
|
|
36
|
+
await this._element.click();
|
|
37
|
+
contentHeight = await this._element.getText();
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
const elementId = this._getElementId(element);
|
|
41
|
+
if (!elementId)
|
|
42
|
+
return null;
|
|
43
|
+
await this._element.type(`offset;${elementId};0;0;0`);
|
|
44
|
+
await this._element.click();
|
|
45
|
+
contentHeight = await this._element.getText();
|
|
46
|
+
await this._element.type('');
|
|
47
|
+
}
|
|
48
|
+
const region = await this._spec.getElementRegion(this._element.driver.target, element.target);
|
|
49
|
+
return { width: region.width, height: Number(contentHeight) };
|
|
50
|
+
}
|
|
51
|
+
async getRegion(element) {
|
|
52
|
+
if (this._legacy)
|
|
53
|
+
return null;
|
|
54
|
+
const elementId = await this._getElementId(element);
|
|
55
|
+
if (!elementId)
|
|
56
|
+
return null;
|
|
57
|
+
await this._element.type(`getRect;${elementId};0;0`);
|
|
58
|
+
await this._element.click();
|
|
59
|
+
const regionString = await this._element.getText();
|
|
60
|
+
await this._element.type('');
|
|
61
|
+
const [, x, y, height, width] = regionString.match(/\[(-?\d+(?:\.\d+)?);(-?\d+(?:\.\d+)?);(-?\d+(?:\.\d+)?);(-?\d+(?:\.\d+)?)\]/);
|
|
62
|
+
const region = { x: Number(x), y: Number(y), width: Number(width), height: Number(height) };
|
|
63
|
+
if (Number.isNaN(region.x + region.y + region.width + region.height))
|
|
64
|
+
return null;
|
|
65
|
+
return region;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
exports.HelperAndroid = HelperAndroid;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HelperIOS = void 0;
|
|
4
|
+
class HelperIOS {
|
|
5
|
+
constructor(options) {
|
|
6
|
+
this._driver = options.driver;
|
|
7
|
+
this._element = options.element;
|
|
8
|
+
this._spec = options.spec;
|
|
9
|
+
this._logger = options.logger;
|
|
10
|
+
}
|
|
11
|
+
static async make(options) {
|
|
12
|
+
const { spec, driver, logger } = options;
|
|
13
|
+
const element = await driver.element({ type: 'name', selector: 'applitools_grab_scrollable_data_button' });
|
|
14
|
+
return element ? new HelperIOS({ driver, element, spec, logger }) : null;
|
|
15
|
+
}
|
|
16
|
+
async getContentSize(_element) {
|
|
17
|
+
await this._element.click();
|
|
18
|
+
const sizeLabel = await this._driver.element({ type: 'name', selector: 'applitools_content_size_label' });
|
|
19
|
+
const sizeString = await (sizeLabel === null || sizeLabel === void 0 ? void 0 : sizeLabel.getText());
|
|
20
|
+
if (!sizeString)
|
|
21
|
+
return null;
|
|
22
|
+
const [, width, height] = sizeString.match(/\{(-?\d+(?:\.\d+)?),\s?(-?\d+(?:\.\d+)?)\}/);
|
|
23
|
+
const contentSize = { width: Number(width), height: Number(height) };
|
|
24
|
+
if (Number.isNaN(contentSize.width + contentSize.height))
|
|
25
|
+
return null;
|
|
26
|
+
const paddingLabel = await this._driver.element({ type: 'name', selector: 'applitools_content_offset_label' });
|
|
27
|
+
const paddingString = await (paddingLabel === null || paddingLabel === void 0 ? void 0 : paddingLabel.getText());
|
|
28
|
+
if (!paddingString)
|
|
29
|
+
return contentSize;
|
|
30
|
+
const [, x, y] = paddingString.match(/\{(-?\d+(?:\.\d+)?),\s?(-?\d+(?:\.\d+)?)\}/);
|
|
31
|
+
const contentOffset = { x: Number(x), y: Number(y) };
|
|
32
|
+
if (!Number.isNaN(contentOffset.x))
|
|
33
|
+
contentSize.width -= contentOffset.x;
|
|
34
|
+
if (!Number.isNaN(contentOffset.y))
|
|
35
|
+
contentSize.height -= contentOffset.y;
|
|
36
|
+
return contentSize;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.HelperIOS = HelperIOS;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applitools/driver",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "Applitools universal framework wrapper",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"applitools",
|
|
@@ -34,12 +34,19 @@
|
|
|
34
34
|
"./fake": {
|
|
35
35
|
"default": "./dist/fake/index.js",
|
|
36
36
|
"types": "./types/fake/index.d.ts"
|
|
37
|
+
},
|
|
38
|
+
"./debug": {
|
|
39
|
+
"default": "./dist/debug/index.js",
|
|
40
|
+
"types": "./types/debug/index.d.ts"
|
|
37
41
|
}
|
|
38
42
|
},
|
|
39
43
|
"typesVersions": {
|
|
40
44
|
"*": {
|
|
41
45
|
"fake": [
|
|
42
46
|
"./types/fake/index.d.ts"
|
|
47
|
+
],
|
|
48
|
+
"debug": [
|
|
49
|
+
"./types/debug/index.d.ts"
|
|
43
50
|
]
|
|
44
51
|
}
|
|
45
52
|
},
|
|
@@ -62,8 +69,8 @@
|
|
|
62
69
|
}
|
|
63
70
|
},
|
|
64
71
|
"dependencies": {
|
|
65
|
-
"@applitools/snippets": "2.1.
|
|
66
|
-
"@applitools/types": "1.0.
|
|
72
|
+
"@applitools/snippets": "2.1.8",
|
|
73
|
+
"@applitools/types": "1.0.22",
|
|
67
74
|
"@applitools/utils": "1.2.4"
|
|
68
75
|
},
|
|
69
76
|
"devDependencies": {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { SpecDriver } from '@applitools/types';
|
|
2
|
+
export declare function checkSpecDriver<TDriver, TContext, TElement, TSelector>(options: {
|
|
3
|
+
spec: SpecDriver<TDriver, TContext, TElement, TSelector>;
|
|
4
|
+
driver: TDriver;
|
|
5
|
+
}): Promise<({
|
|
6
|
+
skipped: boolean;
|
|
7
|
+
test: string;
|
|
8
|
+
error?: undefined;
|
|
9
|
+
} | {
|
|
10
|
+
success: boolean;
|
|
11
|
+
test: string;
|
|
12
|
+
error?: undefined;
|
|
13
|
+
} | {
|
|
14
|
+
test: string;
|
|
15
|
+
error: {
|
|
16
|
+
message: any;
|
|
17
|
+
expected: any;
|
|
18
|
+
actual: any;
|
|
19
|
+
};
|
|
20
|
+
})[]>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './check-spec-driver';
|
package/types/driver.d.ts
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import type * as types from '@applitools/types';
|
|
3
3
|
import { Context, ContextReference } from './context';
|
|
4
4
|
import { Element } from './element';
|
|
5
|
+
import { HelperIOS } from './helper-ios';
|
|
6
|
+
import { HelperAndroid } from './helper-android';
|
|
5
7
|
export declare class Driver<TDriver, TContext, TElement, TSelector> {
|
|
6
8
|
private _target;
|
|
7
9
|
private _mainContext;
|
|
@@ -9,6 +11,7 @@ export declare class Driver<TDriver, TContext, TElement, TSelector> {
|
|
|
9
11
|
private _driverInfo;
|
|
10
12
|
private _logger;
|
|
11
13
|
private _utils;
|
|
14
|
+
private _helper?;
|
|
12
15
|
protected readonly _spec: types.SpecDriver<TDriver, TContext, TElement, TSelector>;
|
|
13
16
|
constructor(options: {
|
|
14
17
|
spec: types.SpecDriver<TDriver, TContext, TElement, TSelector>;
|
|
@@ -18,6 +21,7 @@ export declare class Driver<TDriver, TContext, TElement, TSelector> {
|
|
|
18
21
|
get target(): TDriver;
|
|
19
22
|
get currentContext(): Context<TDriver, TContext, TElement, TSelector>;
|
|
20
23
|
get mainContext(): Context<TDriver, TContext, TElement, TSelector>;
|
|
24
|
+
get helper(): HelperAndroid<TDriver, TContext, TElement, TSelector> | HelperIOS<TDriver, TContext, TElement, TSelector>;
|
|
21
25
|
get features(): {
|
|
22
26
|
shadowSelector?: boolean;
|
|
23
27
|
allCookies?: boolean;
|
package/types/element.d.ts
CHANGED
|
@@ -37,6 +37,7 @@ export declare class Element<TDriver, TContext, TElement, TSelector> {
|
|
|
37
37
|
isScrollable(): Promise<boolean>;
|
|
38
38
|
isRoot(): Promise<boolean>;
|
|
39
39
|
getTouchPadding(): Promise<number>;
|
|
40
|
+
getText(): Promise<string>;
|
|
40
41
|
getAttribute(name: string): Promise<string>;
|
|
41
42
|
setAttribute(name: string, value: string): Promise<void>;
|
|
42
43
|
scrollTo(offset: types.Location): Promise<types.Location>;
|
|
@@ -45,6 +46,7 @@ export declare class Element<TDriver, TContext, TElement, TSelector> {
|
|
|
45
46
|
getTranslateOffset(): Promise<types.Location>;
|
|
46
47
|
getInnerOffset(): Promise<types.Location>;
|
|
47
48
|
click(): Promise<void>;
|
|
49
|
+
type(value: string): Promise<void>;
|
|
48
50
|
preserveState(): Promise<ElementState>;
|
|
49
51
|
restoreState(state?: ElementState): Promise<void>;
|
|
50
52
|
hideScrollbars(): Promise<void>;
|
|
@@ -20,7 +20,7 @@ export declare class MockDriver {
|
|
|
20
20
|
browserVersion: any;
|
|
21
21
|
};
|
|
22
22
|
executeScript(script: any, args?: any[]): Promise<any>;
|
|
23
|
-
findElement(selector: any, rootElement
|
|
23
|
+
findElement(selector: any, rootElement?: any): Promise<any>;
|
|
24
24
|
findElements(selector: any, rootElement: any): Promise<any>;
|
|
25
25
|
switchToFrame(reference: any): Promise<this>;
|
|
26
26
|
switchToParentFrame(): Promise<this>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type * as types from '@applitools/types';
|
|
2
|
+
import type { Driver } from './driver';
|
|
3
|
+
import type { Element } from './element';
|
|
4
|
+
export declare class HelperAndroid<TDriver, TContext, TElement, TSelector> {
|
|
5
|
+
static make<TDriver, TContext, TElement, TSelector>(options: {
|
|
6
|
+
spec: types.SpecDriver<TDriver, TContext, TElement, TSelector>;
|
|
7
|
+
driver: Driver<TDriver, TContext, TElement, TSelector>;
|
|
8
|
+
logger: any;
|
|
9
|
+
}): Promise<HelperAndroid<TDriver, TContext, TElement, TSelector> | null>;
|
|
10
|
+
private readonly _spec;
|
|
11
|
+
private readonly _element;
|
|
12
|
+
private readonly _legacy;
|
|
13
|
+
private _logger;
|
|
14
|
+
constructor(options: {
|
|
15
|
+
spec: types.SpecDriver<TDriver, TContext, TElement, TSelector>;
|
|
16
|
+
element: Element<TDriver, TContext, TElement, TSelector>;
|
|
17
|
+
legacy: boolean;
|
|
18
|
+
logger?: any;
|
|
19
|
+
});
|
|
20
|
+
_getElementId(element: Element<TDriver, TContext, TElement, TSelector>): Promise<string>;
|
|
21
|
+
getContentSize(element: Element<TDriver, TContext, TElement, TSelector>): Promise<types.Size>;
|
|
22
|
+
getRegion(element: Element<TDriver, TContext, TElement, TSelector>): Promise<types.Region>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type * as types from '@applitools/types';
|
|
2
|
+
import type { Driver } from './driver';
|
|
3
|
+
import type { Element } from './element';
|
|
4
|
+
export declare class HelperIOS<TDriver, TContext, TElement, TSelector> {
|
|
5
|
+
static make<TDriver, TContext, TElement, TSelector>(options: {
|
|
6
|
+
spec: types.SpecDriver<TDriver, TContext, TElement, TSelector>;
|
|
7
|
+
driver: Driver<TDriver, TContext, TElement, TSelector>;
|
|
8
|
+
logger: any;
|
|
9
|
+
}): Promise<HelperIOS<TDriver, TContext, TElement, TSelector> | null>;
|
|
10
|
+
private readonly _driver;
|
|
11
|
+
private readonly _element;
|
|
12
|
+
private readonly _spec;
|
|
13
|
+
private _logger;
|
|
14
|
+
constructor(options: {
|
|
15
|
+
driver: Driver<TDriver, TContext, TElement, TSelector>;
|
|
16
|
+
element: Element<TDriver, TContext, TElement, TSelector>;
|
|
17
|
+
spec: types.SpecDriver<TDriver, TContext, TElement, TSelector>;
|
|
18
|
+
logger?: any;
|
|
19
|
+
});
|
|
20
|
+
getContentSize(_element: Element<TDriver, TContext, TElement, TSelector>): Promise<types.Size>;
|
|
21
|
+
}
|