appium-xcuitest-driver 10.3.0 → 10.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 +12 -0
- package/build/lib/commands/active-app-info.d.ts +9 -0
- package/build/lib/commands/active-app-info.d.ts.map +1 -0
- package/build/lib/commands/active-app-info.js +14 -0
- package/build/lib/commands/active-app-info.js.map +1 -0
- package/build/lib/commands/alert.d.ts +42 -45
- package/build/lib/commands/alert.d.ts.map +1 -1
- package/build/lib/commands/alert.js +66 -62
- package/build/lib/commands/alert.js.map +1 -1
- package/build/lib/commands/app-management.d.ts +150 -153
- package/build/lib/commands/app-management.d.ts.map +1 -1
- package/build/lib/commands/app-management.js +300 -286
- package/build/lib/commands/app-management.js.map +1 -1
- package/build/lib/commands/app-strings.d.ts +14 -17
- package/build/lib/commands/app-strings.d.ts.map +1 -1
- package/build/lib/commands/app-strings.js +23 -24
- package/build/lib/commands/app-strings.js.map +1 -1
- package/build/lib/commands/appearance.d.ts +19 -22
- package/build/lib/commands/appearance.d.ts.map +1 -1
- package/build/lib/commands/appearance.js +56 -56
- package/build/lib/commands/appearance.js.map +1 -1
- package/build/lib/commands/audit.d.ts +22 -17
- package/build/lib/commands/audit.d.ts.map +1 -1
- package/build/lib/commands/audit.js +17 -18
- package/build/lib/commands/audit.js.map +1 -1
- package/build/lib/commands/battery.d.ts +11 -14
- package/build/lib/commands/battery.d.ts.map +1 -1
- package/build/lib/commands/battery.js +36 -37
- package/build/lib/commands/battery.js.map +1 -1
- package/build/lib/commands/biometric.d.ts +30 -33
- package/build/lib/commands/biometric.d.ts.map +1 -1
- package/build/lib/commands/biometric.js +42 -41
- package/build/lib/commands/biometric.js.map +1 -1
- package/build/lib/commands/certificate.d.ts +48 -45
- package/build/lib/commands/certificate.d.ts.map +1 -1
- package/build/lib/commands/certificate.js +218 -205
- package/build/lib/commands/certificate.js.map +1 -1
- package/build/lib/commands/clipboard.d.ts +19 -22
- package/build/lib/commands/clipboard.d.ts.map +1 -1
- package/build/lib/commands/clipboard.js +30 -30
- package/build/lib/commands/clipboard.js.map +1 -1
- package/build/lib/commands/condition.d.ts +49 -26
- package/build/lib/commands/condition.d.ts.map +1 -1
- package/build/lib/commands/condition.js +87 -86
- package/build/lib/commands/condition.js.map +1 -1
- package/build/lib/commands/content-size.d.ts +26 -29
- package/build/lib/commands/content-size.d.ts.map +1 -1
- package/build/lib/commands/content-size.js +36 -36
- package/build/lib/commands/content-size.js.map +1 -1
- package/build/lib/commands/context.d.ts +161 -108
- package/build/lib/commands/context.d.ts.map +1 -1
- package/build/lib/commands/context.js +530 -517
- package/build/lib/commands/context.js.map +1 -1
- package/build/lib/commands/deviceInfo.d.ts +9 -12
- package/build/lib/commands/deviceInfo.d.ts.map +1 -1
- package/build/lib/commands/deviceInfo.js +17 -18
- package/build/lib/commands/deviceInfo.js.map +1 -1
- package/build/lib/commands/element.d.ts +102 -105
- package/build/lib/commands/element.d.ts.map +1 -1
- package/build/lib/commands/element.js +337 -323
- package/build/lib/commands/element.js.map +1 -1
- package/build/lib/commands/execute.d.ts +24 -19
- package/build/lib/commands/execute.d.ts.map +1 -1
- package/build/lib/commands/execute.js +63 -62
- package/build/lib/commands/execute.js.map +1 -1
- package/build/lib/commands/file-movement.d.ts +77 -80
- package/build/lib/commands/file-movement.d.ts.map +1 -1
- package/build/lib/commands/file-movement.js +130 -124
- package/build/lib/commands/file-movement.js.map +1 -1
- package/build/lib/commands/find.d.ts +18 -21
- package/build/lib/commands/find.d.ts.map +1 -1
- package/build/lib/commands/find.js +158 -156
- package/build/lib/commands/find.js.map +1 -1
- package/build/lib/commands/general.d.ts +124 -116
- package/build/lib/commands/general.d.ts.map +1 -1
- package/build/lib/commands/general.js +248 -232
- package/build/lib/commands/general.js.map +1 -1
- package/build/lib/commands/geolocation.d.ts +43 -46
- package/build/lib/commands/geolocation.d.ts.map +1 -1
- package/build/lib/commands/geolocation.js +10 -11
- package/build/lib/commands/geolocation.js.map +1 -1
- package/build/lib/commands/gesture.d.ts +273 -276
- package/build/lib/commands/gesture.d.ts.map +1 -1
- package/build/lib/commands/gesture.js +506 -492
- package/build/lib/commands/gesture.js.map +1 -1
- package/build/lib/commands/increase-contrast.d.ts +20 -23
- package/build/lib/commands/increase-contrast.d.ts.map +1 -1
- package/build/lib/commands/increase-contrast.js +30 -30
- package/build/lib/commands/increase-contrast.js.map +1 -1
- package/build/lib/commands/iohid.d.ts +1370 -1373
- package/build/lib/commands/iohid.d.ts.map +1 -1
- package/build/lib/commands/iohid.js +30 -31
- package/build/lib/commands/iohid.js.map +1 -1
- package/build/lib/commands/keyboard.d.ts +29 -32
- package/build/lib/commands/keyboard.d.ts.map +1 -1
- package/build/lib/commands/keyboard.js +53 -51
- package/build/lib/commands/keyboard.js.map +1 -1
- package/build/lib/commands/keychains.d.ts +9 -12
- package/build/lib/commands/keychains.d.ts.map +1 -1
- package/build/lib/commands/keychains.js +13 -14
- package/build/lib/commands/keychains.js.map +1 -1
- package/build/lib/commands/localization.d.ts +16 -19
- package/build/lib/commands/localization.d.ts.map +1 -1
- package/build/lib/commands/localization.js +25 -26
- package/build/lib/commands/localization.js.map +1 -1
- package/build/lib/commands/location.d.ts +36 -39
- package/build/lib/commands/location.d.ts.map +1 -1
- package/build/lib/commands/location.js +99 -98
- package/build/lib/commands/location.js.map +1 -1
- package/build/lib/commands/lock.d.ts +21 -24
- package/build/lib/commands/lock.d.ts.map +1 -1
- package/build/lib/commands/lock.js +39 -38
- package/build/lib/commands/lock.js.map +1 -1
- package/build/lib/commands/log.d.ts +43 -37
- package/build/lib/commands/log.d.ts.map +1 -1
- package/build/lib/commands/log.js +174 -171
- package/build/lib/commands/log.js.map +1 -1
- package/build/lib/commands/memory.d.ts +9 -12
- package/build/lib/commands/memory.d.ts.map +1 -1
- package/build/lib/commands/memory.js +37 -38
- package/build/lib/commands/memory.js.map +1 -1
- package/build/lib/commands/navigation.d.ts +30 -33
- package/build/lib/commands/navigation.d.ts.map +1 -1
- package/build/lib/commands/navigation.js +92 -92
- package/build/lib/commands/navigation.js.map +1 -1
- package/build/lib/commands/notifications.d.ts +26 -29
- package/build/lib/commands/notifications.d.ts.map +1 -1
- package/build/lib/commands/notifications.js +53 -53
- package/build/lib/commands/notifications.js.map +1 -1
- package/build/lib/commands/pasteboard.d.ts +21 -24
- package/build/lib/commands/pasteboard.d.ts.map +1 -1
- package/build/lib/commands/pasteboard.js +37 -37
- package/build/lib/commands/pasteboard.js.map +1 -1
- package/build/lib/commands/pcap.d.ts +39 -26
- package/build/lib/commands/pcap.d.ts.map +1 -1
- package/build/lib/commands/pcap.js +81 -81
- package/build/lib/commands/pcap.js.map +1 -1
- package/build/lib/commands/performance.d.ts +63 -44
- package/build/lib/commands/performance.d.ts.map +1 -1
- package/build/lib/commands/performance.js +105 -105
- package/build/lib/commands/performance.js.map +1 -1
- package/build/lib/commands/permissions.d.ts +33 -36
- package/build/lib/commands/permissions.d.ts.map +1 -1
- package/build/lib/commands/permissions.js +66 -65
- package/build/lib/commands/permissions.js.map +1 -1
- package/build/lib/commands/proxy-helper.d.ts +12 -15
- package/build/lib/commands/proxy-helper.d.ts.map +1 -1
- package/build/lib/commands/proxy-helper.js +53 -54
- package/build/lib/commands/proxy-helper.js.map +1 -1
- package/build/lib/commands/record-audio.d.ts +49 -29
- package/build/lib/commands/record-audio.d.ts.map +1 -1
- package/build/lib/commands/record-audio.js +100 -104
- package/build/lib/commands/record-audio.js.map +1 -1
- package/build/lib/commands/recordscreen.d.ts +54 -18
- package/build/lib/commands/recordscreen.d.ts.map +1 -1
- package/build/lib/commands/recordscreen.js +127 -129
- package/build/lib/commands/recordscreen.js.map +1 -1
- package/build/lib/commands/screenshots.d.ts +14 -17
- package/build/lib/commands/screenshots.d.ts.map +1 -1
- package/build/lib/commands/screenshots.js +108 -107
- package/build/lib/commands/screenshots.js.map +1 -1
- package/build/lib/commands/simctl.d.ts +11 -14
- package/build/lib/commands/simctl.d.ts.map +1 -1
- package/build/lib/commands/simctl.js +23 -26
- package/build/lib/commands/simctl.js.map +1 -1
- package/build/lib/commands/source.d.ts +14 -17
- package/build/lib/commands/source.d.ts.map +1 -1
- package/build/lib/commands/source.js +40 -43
- package/build/lib/commands/source.js.map +1 -1
- package/build/lib/commands/timeouts.d.ts +44 -33
- package/build/lib/commands/timeouts.d.ts.map +1 -1
- package/build/lib/commands/timeouts.js +65 -63
- package/build/lib/commands/timeouts.js.map +1 -1
- package/build/lib/commands/web.d.ts +275 -197
- package/build/lib/commands/web.d.ts.map +1 -1
- package/build/lib/commands/web.js +866 -785
- package/build/lib/commands/web.js.map +1 -1
- package/build/lib/commands/xctest-record-screen.d.ts +63 -66
- package/build/lib/commands/xctest-record-screen.d.ts.map +1 -1
- package/build/lib/commands/xctest-record-screen.js +103 -102
- package/build/lib/commands/xctest-record-screen.js.map +1 -1
- package/build/lib/commands/xctest.d.ts +55 -51
- package/build/lib/commands/xctest.d.ts.map +1 -1
- package/build/lib/commands/xctest.js +116 -117
- package/build/lib/commands/xctest.js.map +1 -1
- package/build/lib/driver.d.ts +277 -1597
- package/build/lib/driver.d.ts.map +1 -1
- package/build/lib/driver.js +318 -235
- package/build/lib/driver.js.map +1 -1
- package/build/lib/execute-method-map.d.ts.map +1 -1
- package/build/lib/execute-method-map.js +9 -0
- package/build/lib/execute-method-map.js.map +1 -1
- package/lib/commands/active-app-info.js +12 -0
- package/lib/commands/alert.js +68 -65
- package/lib/commands/app-management.js +308 -301
- package/lib/commands/app-strings.js +24 -26
- package/lib/commands/appearance.js +54 -56
- package/lib/commands/audit.js +18 -20
- package/lib/commands/battery.js +35 -37
- package/lib/commands/biometric.js +44 -46
- package/lib/commands/certificate.js +226 -215
- package/lib/commands/clipboard.js +30 -32
- package/lib/commands/condition.js +98 -100
- package/lib/commands/content-size.js +36 -38
- package/lib/commands/context.js +495 -490
- package/lib/commands/deviceInfo.js +19 -20
- package/lib/commands/element.js +367 -357
- package/lib/commands/execute.js +72 -72
- package/lib/commands/file-movement.js +132 -134
- package/lib/commands/find.js +160 -159
- package/lib/commands/general.js +238 -231
- package/lib/commands/geolocation.js +6 -14
- package/lib/commands/gesture.js +525 -515
- package/lib/commands/increase-contrast.js +30 -32
- package/lib/commands/iohid.js +32 -34
- package/lib/commands/keyboard.js +49 -51
- package/lib/commands/keychains.js +12 -14
- package/lib/commands/localization.js +24 -26
- package/lib/commands/location.js +102 -104
- package/lib/commands/lock.js +38 -38
- package/lib/commands/log.js +197 -198
- package/lib/commands/memory.js +40 -42
- package/lib/commands/navigation.js +96 -100
- package/lib/commands/notifications.js +57 -59
- package/lib/commands/pasteboard.js +37 -39
- package/lib/commands/pcap.js +84 -86
- package/lib/commands/performance.js +132 -133
- package/lib/commands/permissions.js +67 -69
- package/lib/commands/proxy-helper.js +60 -61
- package/lib/commands/record-audio.js +115 -120
- package/lib/commands/recordscreen.js +145 -149
- package/lib/commands/screenshots.js +116 -116
- package/lib/commands/simctl.js +25 -29
- package/lib/commands/source.js +42 -46
- package/lib/commands/timeouts.js +59 -63
- package/lib/commands/web.js +932 -859
- package/lib/commands/xctest-record-screen.js +103 -105
- package/lib/commands/xctest.js +134 -139
- package/lib/driver.js +286 -235
- package/lib/execute-method-map.ts +9 -0
- package/npm-shrinkwrap.json +8 -8
- package/package.json +1 -1
- package/build/lib/commands/activeAppInfo.d.ts +0 -12
- package/build/lib/commands/activeAppInfo.d.ts.map +0 -1
- package/build/lib/commands/activeAppInfo.js +0 -15
- package/build/lib/commands/activeAppInfo.js.map +0 -1
- package/build/lib/commands/index.d.ts +0 -96
- package/build/lib/commands/index.d.ts.map +0 -1
- package/build/lib/commands/index.js +0 -100
- package/build/lib/commands/index.js.map +0 -1
- package/build/lib/cookies.d.ts +0 -15
- package/build/lib/cookies.d.ts.map +0 -1
- package/build/lib/cookies.js +0 -84
- package/build/lib/cookies.js.map +0 -1
- package/lib/commands/activeAppInfo.js +0 -14
- package/lib/commands/index.js +0 -95
- package/lib/cookies.js +0 -92
|
@@ -36,12 +36,44 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.setFrame = setFrame;
|
|
40
|
+
exports.getCssProperty = getCssProperty;
|
|
41
|
+
exports.submit = submit;
|
|
42
|
+
exports.refresh = refresh;
|
|
43
|
+
exports.getUrl = getUrl;
|
|
44
|
+
exports.title = title;
|
|
45
|
+
exports.getCookies = getCookies;
|
|
46
|
+
exports.setCookie = setCookie;
|
|
47
|
+
exports.deleteCookie = deleteCookie;
|
|
48
|
+
exports.deleteCookies = deleteCookies;
|
|
49
|
+
exports.cacheWebElement = cacheWebElement;
|
|
50
|
+
exports.cacheWebElements = cacheWebElements;
|
|
51
|
+
exports.executeAtom = executeAtom;
|
|
52
|
+
exports.executeAtomAsync = executeAtomAsync;
|
|
53
|
+
exports.getAtomsElement = getAtomsElement;
|
|
54
|
+
exports.convertElementsForAtoms = convertElementsForAtoms;
|
|
55
|
+
exports.getElementId = getElementId;
|
|
56
|
+
exports.hasElementId = hasElementId;
|
|
57
|
+
exports.findWebElementOrElements = findWebElementOrElements;
|
|
58
|
+
exports.clickWebCoords = clickWebCoords;
|
|
59
|
+
exports.getSafariIsIphone = getSafariIsIphone;
|
|
60
|
+
exports.getSafariDeviceSize = getSafariDeviceSize;
|
|
61
|
+
exports.getSafariIsNotched = getSafariIsNotched;
|
|
62
|
+
exports.getExtraTranslateWebCoordsOffset = getExtraTranslateWebCoordsOffset;
|
|
63
|
+
exports.getExtraNativeWebTapOffset = getExtraNativeWebTapOffset;
|
|
64
|
+
exports.nativeWebTap = nativeWebTap;
|
|
65
|
+
exports.translateWebCoords = translateWebCoords;
|
|
66
|
+
exports.checkForAlert = checkForAlert;
|
|
67
|
+
exports.waitForAtom = waitForAtom;
|
|
68
|
+
exports.mobileWebNav = mobileWebNav;
|
|
69
|
+
exports.getWdaLocalhostRoot = getWdaLocalhostRoot;
|
|
70
|
+
exports.mobileCalibrateWebToRealCoordinatesTranslation = mobileCalibrateWebToRealCoordinatesTranslation;
|
|
71
|
+
exports.mobileUpdateSafariPreferences = mobileUpdateSafariPreferences;
|
|
39
72
|
const driver_1 = require("appium/driver");
|
|
40
73
|
const support_1 = require("appium/support");
|
|
41
74
|
const asyncbox_1 = require("asyncbox");
|
|
42
75
|
const bluebird_1 = __importStar(require("bluebird"));
|
|
43
76
|
const lodash_1 = __importDefault(require("lodash"));
|
|
44
|
-
const cookieUtils = __importStar(require("../cookies"));
|
|
45
77
|
const IPHONE_TOP_BAR_HEIGHT = 71;
|
|
46
78
|
const IPHONE_SCROLLED_TOP_BAR_HEIGHT = 41;
|
|
47
79
|
const IPHONE_X_SCROLLED_OFFSET = 55;
|
|
@@ -82,840 +114,794 @@ const TAB_BAR_POSITION_BOTTOM = 'bottom';
|
|
|
82
114
|
const TAB_BAR_POSSITIONS = [TAB_BAR_POSITION_TOP, TAB_BAR_POSITION_BOTTOM];
|
|
83
115
|
/**
|
|
84
116
|
* @this {XCUITestDriver}
|
|
85
|
-
* @
|
|
86
|
-
* @
|
|
117
|
+
* @group Mobile Web Only
|
|
118
|
+
* @param {number|string|null} frame
|
|
119
|
+
* @returns {Promise<void>}
|
|
87
120
|
*/
|
|
88
|
-
async function
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const el2 = els[1];
|
|
109
|
-
const rect2 = /** @type {import('@appium/types').Rect} */ (await this.proxyCommand(`/element/${support_1.util.unwrapElement(el2)}/rect`, 'GET'));
|
|
110
|
-
if (rect.x !== rect2.x || rect.y !== rect2.y
|
|
111
|
-
|| rect.width !== rect2.width || rect.height !== rect2.height) {
|
|
112
|
-
// These 2 native elements are not referring to the same web element
|
|
113
|
-
return false;
|
|
114
|
-
}
|
|
121
|
+
async function setFrame(frame) {
|
|
122
|
+
if (!this.isWebContext()) {
|
|
123
|
+
throw new driver_1.errors.NotImplementedError();
|
|
124
|
+
}
|
|
125
|
+
if (lodash_1.default.isNull(frame)) {
|
|
126
|
+
this.curWebFrames = [];
|
|
127
|
+
this.log.debug('Leaving web frame and going back to default content');
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (hasElementId(frame)) {
|
|
131
|
+
const atomsElement = this.getAtomsElement(frame);
|
|
132
|
+
const value = await this.executeAtom('get_frame_window', [atomsElement]);
|
|
133
|
+
this.log.debug(`Entering new web frame: '${value.WINDOW}'`);
|
|
134
|
+
this.curWebFrames.unshift(value.WINDOW);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
const atom = lodash_1.default.isNumber(frame) ? 'frame_by_index' : 'frame_by_id_or_name';
|
|
138
|
+
const value = await this.executeAtom(atom, [frame]);
|
|
139
|
+
if (lodash_1.default.isNull(value) || lodash_1.default.isUndefined(value.WINDOW)) {
|
|
140
|
+
throw new driver_1.errors.NoSuchFrameError();
|
|
115
141
|
}
|
|
116
|
-
|
|
117
|
-
|
|
142
|
+
this.log.debug(`Entering new web frame: '${value.WINDOW}'`);
|
|
143
|
+
this.curWebFrames.unshift(value.WINDOW);
|
|
118
144
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* @this {XCUITestDriver}
|
|
148
|
+
* @group Mobile Web Only
|
|
149
|
+
* @param {string} propertyName
|
|
150
|
+
* @param {Element | string} el
|
|
151
|
+
* @returns {Promise<string>}
|
|
152
|
+
*/
|
|
153
|
+
async function getCssProperty(propertyName, el) {
|
|
154
|
+
if (!this.isWebContext()) {
|
|
155
|
+
throw new driver_1.errors.NotImplementedError();
|
|
123
156
|
}
|
|
124
|
-
|
|
157
|
+
const atomsElement = this.getAtomsElement(el);
|
|
158
|
+
return await this.executeAtom('get_value_of_css_property', [atomsElement, propertyName]);
|
|
125
159
|
}
|
|
126
160
|
/**
|
|
127
|
-
*
|
|
128
|
-
*
|
|
161
|
+
* Submit the form an element is in
|
|
162
|
+
*
|
|
163
|
+
* @param {string|Element} el - the element ID
|
|
164
|
+
* @group Mobile Web Only
|
|
165
|
+
* @this {XCUITestDriver}
|
|
129
166
|
*/
|
|
130
|
-
function
|
|
131
|
-
if (!
|
|
132
|
-
|
|
167
|
+
async function submit(el) {
|
|
168
|
+
if (!this.isWebContext()) {
|
|
169
|
+
throw new driver_1.errors.NotImplementedError();
|
|
133
170
|
}
|
|
134
|
-
|
|
135
|
-
|
|
171
|
+
const atomsElement = this.getAtomsElement(el);
|
|
172
|
+
await this.executeAtom('submit', [atomsElement]);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* @this {XCUITestDriver}
|
|
176
|
+
* @group Mobile Web Only
|
|
177
|
+
*/
|
|
178
|
+
async function refresh() {
|
|
179
|
+
if (!this.isWebContext()) {
|
|
180
|
+
throw new driver_1.errors.NotImplementedError();
|
|
136
181
|
}
|
|
137
|
-
|
|
138
|
-
|
|
182
|
+
await ( /** @type {RemoteDebugger} */(this.remote)).execute('window.location.reload()');
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* @this {XCUITestDriver}
|
|
186
|
+
* @group Mobile Web Only
|
|
187
|
+
* @returns {Promise<string>}
|
|
188
|
+
*/
|
|
189
|
+
async function getUrl() {
|
|
190
|
+
if (!this.isWebContext()) {
|
|
191
|
+
throw new driver_1.errors.NotImplementedError();
|
|
139
192
|
}
|
|
140
|
-
return
|
|
193
|
+
return await ( /** @type {RemoteDebugger} */(this.remote)).execute('window.location.href');
|
|
141
194
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
this.curWebFrames.unshift(value.WINDOW);
|
|
170
|
-
}
|
|
171
|
-
},
|
|
172
|
-
/**
|
|
173
|
-
* @this {XCUITestDriver}
|
|
174
|
-
* @group Mobile Web Only
|
|
175
|
-
*/
|
|
176
|
-
async getCssProperty(propertyName, el) {
|
|
177
|
-
if (!this.isWebContext()) {
|
|
178
|
-
throw new driver_1.errors.NotImplementedError();
|
|
179
|
-
}
|
|
180
|
-
const atomsElement = this.getAtomsElement(el);
|
|
181
|
-
return await this.executeAtom('get_value_of_css_property', [atomsElement, propertyName]);
|
|
182
|
-
},
|
|
183
|
-
/**
|
|
184
|
-
* Submit the form an element is in
|
|
185
|
-
*
|
|
186
|
-
* @param {string|Element} el - the element ID
|
|
187
|
-
* @group Mobile Web Only
|
|
188
|
-
* @this {XCUITestDriver}
|
|
189
|
-
*/
|
|
190
|
-
async submit(el) {
|
|
191
|
-
if (!this.isWebContext()) {
|
|
192
|
-
throw new driver_1.errors.NotImplementedError();
|
|
193
|
-
}
|
|
194
|
-
const atomsElement = this.getAtomsElement(el);
|
|
195
|
-
await this.executeAtom('submit', [atomsElement]);
|
|
196
|
-
},
|
|
197
|
-
/**
|
|
198
|
-
* @this {XCUITestDriver}
|
|
199
|
-
* @group Mobile Web Only
|
|
200
|
-
*/
|
|
201
|
-
async refresh() {
|
|
202
|
-
if (!this.isWebContext()) {
|
|
203
|
-
throw new driver_1.errors.NotImplementedError();
|
|
204
|
-
}
|
|
205
|
-
await ( /** @type {RemoteDebugger} */(this.remote)).execute('window.location.reload()');
|
|
206
|
-
},
|
|
207
|
-
/**
|
|
208
|
-
* @this {XCUITestDriver}
|
|
209
|
-
* @group Mobile Web Only
|
|
210
|
-
*/
|
|
211
|
-
async getUrl() {
|
|
212
|
-
if (!this.isWebContext()) {
|
|
213
|
-
throw new driver_1.errors.NotImplementedError();
|
|
214
|
-
}
|
|
215
|
-
return await ( /** @type {RemoteDebugger} */(this.remote)).execute('window.location.href');
|
|
216
|
-
},
|
|
217
|
-
/**
|
|
218
|
-
* @this {XCUITestDriver}
|
|
219
|
-
* @group Mobile Web Only
|
|
220
|
-
*/
|
|
221
|
-
async title() {
|
|
222
|
-
if (!this.isWebContext()) {
|
|
223
|
-
throw new driver_1.errors.NotImplementedError();
|
|
224
|
-
}
|
|
225
|
-
return await ( /** @type {RemoteDebugger} */(this.remote)).execute('window.document.title');
|
|
226
|
-
},
|
|
227
|
-
/**
|
|
228
|
-
* @this {XCUITestDriver}
|
|
229
|
-
* @group Mobile Web Only
|
|
230
|
-
*/
|
|
231
|
-
async getCookies() {
|
|
232
|
-
if (!this.isWebContext()) {
|
|
233
|
-
throw new driver_1.errors.NotImplementedError();
|
|
234
|
-
}
|
|
235
|
-
// get the cookies from the remote debugger, or an empty object
|
|
236
|
-
const { cookies } = await ( /** @type {RemoteDebugger} */(this.remote)).getCookies();
|
|
237
|
-
// the value is URI encoded, so decode it safely
|
|
238
|
-
return cookies.map((cookie) => {
|
|
239
|
-
if (!lodash_1.default.isEmpty(cookie.value)) {
|
|
240
|
-
try {
|
|
241
|
-
cookie.value = decodeURI(cookie.value);
|
|
242
|
-
}
|
|
243
|
-
catch (error) {
|
|
244
|
-
this.log.debug(`Cookie ${cookie.name} was not decoded successfully. Cookie value: ${cookie.value}`);
|
|
245
|
-
this.log.warn(error);
|
|
246
|
-
// Keep the original value
|
|
247
|
-
}
|
|
195
|
+
/**
|
|
196
|
+
* @this {XCUITestDriver}
|
|
197
|
+
* @group Mobile Web Only
|
|
198
|
+
* @returns {Promise<string>}
|
|
199
|
+
*/
|
|
200
|
+
async function title() {
|
|
201
|
+
if (!this.isWebContext()) {
|
|
202
|
+
throw new driver_1.errors.NotImplementedError();
|
|
203
|
+
}
|
|
204
|
+
return await ( /** @type {RemoteDebugger} */(this.remote)).execute('window.document.title');
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* @this {XCUITestDriver}
|
|
208
|
+
* @group Mobile Web Only
|
|
209
|
+
* @returns {Promise<import('@appium/types').Cookie[]>}
|
|
210
|
+
*/
|
|
211
|
+
async function getCookies() {
|
|
212
|
+
if (!this.isWebContext()) {
|
|
213
|
+
throw new driver_1.errors.NotImplementedError();
|
|
214
|
+
}
|
|
215
|
+
// get the cookies from the remote debugger, or an empty object
|
|
216
|
+
const { cookies } = await ( /** @type {RemoteDebugger} */(this.remote)).getCookies();
|
|
217
|
+
// the value is URI encoded, so decode it safely
|
|
218
|
+
return cookies.map((cookie) => {
|
|
219
|
+
if (!lodash_1.default.isEmpty(cookie.value)) {
|
|
220
|
+
try {
|
|
221
|
+
cookie.value = decodeURI(cookie.value);
|
|
248
222
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
* @this {XCUITestDriver}
|
|
254
|
-
* @group Mobile Web Only
|
|
255
|
-
*/
|
|
256
|
-
async setCookie(cookie) {
|
|
257
|
-
if (!this.isWebContext()) {
|
|
258
|
-
throw new driver_1.errors.NotImplementedError();
|
|
259
|
-
}
|
|
260
|
-
cookie = lodash_1.default.clone(cookie);
|
|
261
|
-
// if `path` field is not specified, Safari will not update cookies as expected; eg issue #1708
|
|
262
|
-
if (!cookie.path) {
|
|
263
|
-
cookie.path = '/';
|
|
264
|
-
}
|
|
265
|
-
const jsCookie = cookieUtils.createJSCookie(cookie.name, cookie.value, {
|
|
266
|
-
expires: lodash_1.default.isNumber(cookie.expiry)
|
|
267
|
-
? new Date(cookie.expiry * 1000).toUTCString()
|
|
268
|
-
: cookie.expiry,
|
|
269
|
-
path: cookie.path,
|
|
270
|
-
domain: cookie.domain,
|
|
271
|
-
httpOnly: cookie.httpOnly,
|
|
272
|
-
secure: cookie.secure,
|
|
273
|
-
});
|
|
274
|
-
let script = `document.cookie = ${JSON.stringify(jsCookie)}`;
|
|
275
|
-
await this.executeAtom('execute_script', [script, []]);
|
|
276
|
-
},
|
|
277
|
-
/**
|
|
278
|
-
* @this {XCUITestDriver}
|
|
279
|
-
* @group Mobile Web Only
|
|
280
|
-
*/
|
|
281
|
-
async deleteCookie(cookieName) {
|
|
282
|
-
if (!this.isWebContext()) {
|
|
283
|
-
throw new driver_1.errors.NotImplementedError();
|
|
284
|
-
}
|
|
285
|
-
const cookies = await this.getCookies();
|
|
286
|
-
const cookie = cookies.find(({ name }) => name === cookieName);
|
|
287
|
-
if (!cookie) {
|
|
288
|
-
this.log.debug(`Cookie '${cookieName}' not found. Ignoring.`);
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
await this._deleteCookie(cookie);
|
|
292
|
-
},
|
|
293
|
-
/**
|
|
294
|
-
* @this {XCUITestDriver}
|
|
295
|
-
* @group Mobile Web Only
|
|
296
|
-
*/
|
|
297
|
-
async deleteCookies() {
|
|
298
|
-
if (!this.isWebContext()) {
|
|
299
|
-
throw new driver_1.errors.NotImplementedError();
|
|
300
|
-
}
|
|
301
|
-
const cookies = await this.getCookies();
|
|
302
|
-
await bluebird_1.default.all(cookies.map((cookie) => this._deleteCookie(cookie)));
|
|
303
|
-
},
|
|
304
|
-
};
|
|
305
|
-
const helpers = {
|
|
306
|
-
/**
|
|
307
|
-
* @this {XCUITestDriver}
|
|
308
|
-
*/
|
|
309
|
-
async _deleteCookie(cookie) {
|
|
310
|
-
const url = `http${cookie.secure ? 's' : ''}://${cookie.domain}${cookie.path}`;
|
|
311
|
-
return await ( /** @type {RemoteDebugger} */(this.remote)).deleteCookie(cookie.name, url);
|
|
312
|
-
},
|
|
313
|
-
/**
|
|
314
|
-
* @this {XCUITestDriver}
|
|
315
|
-
*/
|
|
316
|
-
cacheWebElement(el) {
|
|
317
|
-
if (!lodash_1.default.isPlainObject(el)) {
|
|
318
|
-
return el;
|
|
319
|
-
}
|
|
320
|
-
const elId = support_1.util.unwrapElement(el);
|
|
321
|
-
if (!isValidElementIdentifier(elId)) {
|
|
322
|
-
return el;
|
|
323
|
-
}
|
|
324
|
-
// In newer debugger releases element identifiers look like `:wdc:1628151649325`
|
|
325
|
-
// We assume it is safe to use these to identify cached elements
|
|
326
|
-
const cacheId = lodash_1.default.includes(elId, ':') ? elId : support_1.util.uuidV4();
|
|
327
|
-
this.webElementsCache.set(cacheId, elId);
|
|
328
|
-
return support_1.util.wrapElement(cacheId);
|
|
329
|
-
},
|
|
330
|
-
/**
|
|
331
|
-
* @this {XCUITestDriver}
|
|
332
|
-
*/
|
|
333
|
-
cacheWebElements(response) {
|
|
334
|
-
const toCached = (v) => (lodash_1.default.isArray(v) || lodash_1.default.isPlainObject(v) ? this.cacheWebElements(v) : v);
|
|
335
|
-
if (lodash_1.default.isArray(response)) {
|
|
336
|
-
return response.map(toCached);
|
|
337
|
-
}
|
|
338
|
-
else if (lodash_1.default.isPlainObject(response)) {
|
|
339
|
-
const result = { ...response, ...this.cacheWebElement(response) };
|
|
340
|
-
return lodash_1.default.toPairs(result).reduce((acc, [key, value]) => {
|
|
341
|
-
acc[key] = toCached(value);
|
|
342
|
-
return acc;
|
|
343
|
-
}, {});
|
|
344
|
-
}
|
|
345
|
-
return response;
|
|
346
|
-
},
|
|
347
|
-
/**
|
|
348
|
-
* @param {string} atom
|
|
349
|
-
* @param {unknown[]} args
|
|
350
|
-
* @returns {Promise<any>}
|
|
351
|
-
* @privateRemarks This should return `Promise<T>` where `T` extends `unknown`, but that's going to cause a lot of things to break.
|
|
352
|
-
* @this {XCUITestDriver}
|
|
353
|
-
*/
|
|
354
|
-
async executeAtom(atom, args, alwaysDefaultFrame = false) {
|
|
355
|
-
let frames = alwaysDefaultFrame === true ? [] : this.curWebFrames;
|
|
356
|
-
let promise = ( /** @type {RemoteDebugger} */(this.remote)).executeAtom(atom, args, frames);
|
|
357
|
-
return await this.waitForAtom(promise);
|
|
358
|
-
},
|
|
359
|
-
/**
|
|
360
|
-
* @this {XCUITestDriver}
|
|
361
|
-
* @param {string} atom
|
|
362
|
-
* @param {any[]} args
|
|
363
|
-
*/
|
|
364
|
-
async executeAtomAsync(atom, args) {
|
|
365
|
-
// save the resolve and reject methods of the promise to be waited for
|
|
366
|
-
let promise = new bluebird_1.default((resolve, reject) => {
|
|
367
|
-
this.asyncPromise = { resolve, reject };
|
|
368
|
-
});
|
|
369
|
-
await ( /** @type {RemoteDebugger} */(this.remote)).executeAtomAsync(atom, args, this.curWebFrames);
|
|
370
|
-
return await this.waitForAtom(promise);
|
|
371
|
-
},
|
|
372
|
-
/**
|
|
373
|
-
* @template {string} S
|
|
374
|
-
* @param {S|Element<S>} elOrId
|
|
375
|
-
* @returns {import('./types').AtomsElement<S>}
|
|
376
|
-
* @this {XCUITestDriver}
|
|
377
|
-
*/
|
|
378
|
-
getAtomsElement(elOrId) {
|
|
379
|
-
const elId = support_1.util.unwrapElement(elOrId);
|
|
380
|
-
if (!this.webElementsCache?.has(elId)) {
|
|
381
|
-
throw new driver_1.errors.StaleElementReferenceError();
|
|
382
|
-
}
|
|
383
|
-
return { ELEMENT: this.webElementsCache.get(elId) };
|
|
384
|
-
},
|
|
385
|
-
/**
|
|
386
|
-
* @param {readonly any[]} [args]
|
|
387
|
-
* @this {XCUITestDriver}
|
|
388
|
-
*/
|
|
389
|
-
convertElementsForAtoms(args = []) {
|
|
390
|
-
return args.map((arg) => {
|
|
391
|
-
if (helpers.hasElementId(arg)) {
|
|
392
|
-
try {
|
|
393
|
-
return this.getAtomsElement(arg);
|
|
394
|
-
}
|
|
395
|
-
catch (err) {
|
|
396
|
-
if (!(0, driver_1.isErrorType)(err, driver_1.errors.StaleElementReferenceError)) {
|
|
397
|
-
throw err;
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
return arg;
|
|
223
|
+
catch (error) {
|
|
224
|
+
this.log.debug(`Cookie ${cookie.name} was not decoded successfully. Cookie value: ${cookie.value}`);
|
|
225
|
+
this.log.warn(error);
|
|
226
|
+
// Keep the original value
|
|
401
227
|
}
|
|
402
|
-
return lodash_1.default.isArray(arg) ? this.convertElementsForAtoms(arg) : arg;
|
|
403
|
-
});
|
|
404
|
-
},
|
|
405
|
-
getElementId(element) {
|
|
406
|
-
return element.ELEMENT || element[W3C_WEB_ELEMENT_IDENTIFIER];
|
|
407
|
-
},
|
|
408
|
-
/**
|
|
409
|
-
* @param {any} element
|
|
410
|
-
* @returns {element is Element}
|
|
411
|
-
*/
|
|
412
|
-
hasElementId(element) {
|
|
413
|
-
return (support_1.util.hasValue(element) &&
|
|
414
|
-
(support_1.util.hasValue(element.ELEMENT) || support_1.util.hasValue(element[W3C_WEB_ELEMENT_IDENTIFIER])));
|
|
415
|
-
},
|
|
416
|
-
};
|
|
417
|
-
const extensions = {
|
|
418
|
-
/**
|
|
419
|
-
* @this {XCUITestDriver}
|
|
420
|
-
*/
|
|
421
|
-
async findWebElementOrElements(strategy, selector, many, ctx) {
|
|
422
|
-
const contextElement = lodash_1.default.isNil(ctx) ? null : this.getAtomsElement(ctx);
|
|
423
|
-
const atomName = many ? 'find_elements' : 'find_element_fragment';
|
|
424
|
-
let element;
|
|
425
|
-
let doFind = async () => {
|
|
426
|
-
element = await this.executeAtom(atomName, [strategy, selector, contextElement]);
|
|
427
|
-
return !lodash_1.default.isNull(element);
|
|
428
|
-
};
|
|
429
|
-
try {
|
|
430
|
-
await this.implicitWaitForCondition(doFind);
|
|
431
228
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
229
|
+
return cookie;
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* @this {XCUITestDriver}
|
|
234
|
+
* @group Mobile Web Only
|
|
235
|
+
* @param {import('@appium/types').Cookie} cookie
|
|
236
|
+
* @returns {Promise<void>}
|
|
237
|
+
*/
|
|
238
|
+
async function setCookie(cookie) {
|
|
239
|
+
if (!this.isWebContext()) {
|
|
240
|
+
throw new driver_1.errors.NotImplementedError();
|
|
241
|
+
}
|
|
242
|
+
const clonedCookie = lodash_1.default.clone(cookie);
|
|
243
|
+
// if `path` field is not specified, Safari will not update cookies as expected; eg issue #1708
|
|
244
|
+
if (!clonedCookie.path) {
|
|
245
|
+
clonedCookie.path = '/';
|
|
246
|
+
}
|
|
247
|
+
const jsCookie = createJSCookie(clonedCookie.name, clonedCookie.value, {
|
|
248
|
+
expires: lodash_1.default.isNumber(clonedCookie.expiry)
|
|
249
|
+
? new Date(clonedCookie.expiry * 1000).toUTCString()
|
|
250
|
+
: clonedCookie.expiry,
|
|
251
|
+
path: clonedCookie.path,
|
|
252
|
+
domain: clonedCookie.domain,
|
|
253
|
+
httpOnly: clonedCookie.httpOnly,
|
|
254
|
+
secure: clonedCookie.secure,
|
|
255
|
+
});
|
|
256
|
+
const script = `document.cookie = ${JSON.stringify(jsCookie)}`;
|
|
257
|
+
await this.executeAtom('execute_script', [script, []]);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* @this {XCUITestDriver}
|
|
261
|
+
* @param {string} cookieName
|
|
262
|
+
* @returns {Promise<void>}
|
|
263
|
+
* @group Mobile Web Only
|
|
264
|
+
*/
|
|
265
|
+
async function deleteCookie(cookieName) {
|
|
266
|
+
if (!this.isWebContext()) {
|
|
267
|
+
throw new driver_1.errors.NotImplementedError();
|
|
268
|
+
}
|
|
269
|
+
const cookies = await this.getCookies();
|
|
270
|
+
const cookie = cookies.find(({ name }) => name === cookieName);
|
|
271
|
+
if (!cookie) {
|
|
272
|
+
this.log.debug(`Cookie '${cookieName}' not found. Ignoring.`);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
await _deleteCookie.bind(this)(cookie);
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* @this {XCUITestDriver}
|
|
279
|
+
* @group Mobile Web Only
|
|
280
|
+
* @returns {Promise<void>}
|
|
281
|
+
*/
|
|
282
|
+
async function deleteCookies() {
|
|
283
|
+
if (!this.isWebContext()) {
|
|
284
|
+
throw new driver_1.errors.NotImplementedError();
|
|
285
|
+
}
|
|
286
|
+
const cookies = await this.getCookies();
|
|
287
|
+
await bluebird_1.default.all(cookies.map((cookie) => _deleteCookie.bind(this)(cookie)));
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* @this {XCUITestDriver}
|
|
291
|
+
* @param {Element | string} el
|
|
292
|
+
* @returns {Element | string}
|
|
293
|
+
*/
|
|
294
|
+
function cacheWebElement(el) {
|
|
295
|
+
if (!lodash_1.default.isPlainObject(el)) {
|
|
296
|
+
return el;
|
|
297
|
+
}
|
|
298
|
+
const elId = support_1.util.unwrapElement(el);
|
|
299
|
+
if (!isValidElementIdentifier(elId)) {
|
|
300
|
+
return el;
|
|
301
|
+
}
|
|
302
|
+
// In newer debugger releases element identifiers look like `:wdc:1628151649325`
|
|
303
|
+
// We assume it is safe to use these to identify cached elements
|
|
304
|
+
const cacheId = lodash_1.default.includes(elId, ':') ? elId : support_1.util.uuidV4();
|
|
305
|
+
this.webElementsCache.set(cacheId, elId);
|
|
306
|
+
return support_1.util.wrapElement(cacheId);
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* @this {XCUITestDriver}
|
|
310
|
+
* @param {any} response
|
|
311
|
+
* @returns {any}
|
|
312
|
+
*/
|
|
313
|
+
function cacheWebElements(response) {
|
|
314
|
+
const toCached = (/** @type {any} */ v) => (lodash_1.default.isArray(v) || lodash_1.default.isPlainObject(v)) ? this.cacheWebElements(v) : v;
|
|
315
|
+
if (lodash_1.default.isArray(response)) {
|
|
316
|
+
return response.map(toCached);
|
|
317
|
+
}
|
|
318
|
+
else if (lodash_1.default.isPlainObject(response)) {
|
|
319
|
+
const result = { ...response, ...( /** @type {Element} */(this.cacheWebElement(response))) };
|
|
320
|
+
return lodash_1.default.toPairs(result).reduce((acc, [key, value]) => {
|
|
321
|
+
acc[key] = toCached(value);
|
|
322
|
+
return acc;
|
|
323
|
+
}, {});
|
|
324
|
+
}
|
|
325
|
+
return response;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* @param {string} atom
|
|
329
|
+
* @param {unknown[]} args
|
|
330
|
+
* @returns {Promise<any>}
|
|
331
|
+
* @privateRemarks This should return `Promise<T>` where `T` extends `unknown`, but that's going to cause a lot of things to break.
|
|
332
|
+
* @this {XCUITestDriver}
|
|
333
|
+
*/
|
|
334
|
+
async function executeAtom(atom, args, alwaysDefaultFrame = false) {
|
|
335
|
+
let frames = alwaysDefaultFrame === true ? [] : this.curWebFrames;
|
|
336
|
+
let promise = ( /** @type {RemoteDebugger} */(this.remote)).executeAtom(atom, args, frames);
|
|
337
|
+
return await this.waitForAtom(promise);
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* @this {XCUITestDriver}
|
|
341
|
+
* @param {string} atom
|
|
342
|
+
* @param {any[]} args
|
|
343
|
+
*/
|
|
344
|
+
async function executeAtomAsync(atom, args) {
|
|
345
|
+
// save the resolve and reject methods of the promise to be waited for
|
|
346
|
+
let promise = new bluebird_1.default((resolve, reject) => {
|
|
347
|
+
this.asyncPromise = { resolve, reject };
|
|
348
|
+
});
|
|
349
|
+
await ( /** @type {RemoteDebugger} */(this.remote)).executeAtomAsync(atom, args, this.curWebFrames);
|
|
350
|
+
return await this.waitForAtom(promise);
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* @template {string} S
|
|
354
|
+
* @param {S|Element<S>} elOrId
|
|
355
|
+
* @returns {import('./types').AtomsElement<S>}
|
|
356
|
+
* @this {XCUITestDriver}
|
|
357
|
+
*/
|
|
358
|
+
function getAtomsElement(elOrId) {
|
|
359
|
+
const elId = support_1.util.unwrapElement(elOrId);
|
|
360
|
+
if (!this.webElementsCache?.has(elId)) {
|
|
361
|
+
throw new driver_1.errors.StaleElementReferenceError();
|
|
362
|
+
}
|
|
363
|
+
return { ELEMENT: this.webElementsCache.get(elId) };
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* @param {readonly any[]} [args]
|
|
367
|
+
* @this {XCUITestDriver}
|
|
368
|
+
*/
|
|
369
|
+
function convertElementsForAtoms(args = []) {
|
|
370
|
+
return args.map((arg) => {
|
|
371
|
+
if (hasElementId(arg)) {
|
|
372
|
+
try {
|
|
373
|
+
return this.getAtomsElement(arg);
|
|
436
374
|
}
|
|
437
|
-
|
|
438
|
-
|
|
375
|
+
catch (err) {
|
|
376
|
+
if (!(0, driver_1.isErrorType)(err, driver_1.errors.StaleElementReferenceError)) {
|
|
377
|
+
throw err;
|
|
378
|
+
}
|
|
439
379
|
}
|
|
380
|
+
return arg;
|
|
440
381
|
}
|
|
441
|
-
|
|
442
|
-
|
|
382
|
+
return lodash_1.default.isArray(arg) ? this.convertElementsForAtoms(arg) : arg;
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
*
|
|
387
|
+
* @param {any} element
|
|
388
|
+
* @returns {string | undefined}
|
|
389
|
+
*/
|
|
390
|
+
function getElementId(element) {
|
|
391
|
+
return element?.ELEMENT || element?.[W3C_WEB_ELEMENT_IDENTIFIER];
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* @param {any} element
|
|
395
|
+
* @returns {element is Element}
|
|
396
|
+
*/
|
|
397
|
+
function hasElementId(element) {
|
|
398
|
+
return (support_1.util.hasValue(element) &&
|
|
399
|
+
(support_1.util.hasValue(element.ELEMENT) || support_1.util.hasValue(element[W3C_WEB_ELEMENT_IDENTIFIER])));
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* @this {XCUITestDriver}
|
|
403
|
+
* @param {string} strategy
|
|
404
|
+
* @param {string} selector
|
|
405
|
+
* @param {boolean} [many]
|
|
406
|
+
* @param {Element | string | null} [ctx]
|
|
407
|
+
* @returns {Promise<Element | Element[]>}
|
|
408
|
+
*/
|
|
409
|
+
async function findWebElementOrElements(strategy, selector, many, ctx) {
|
|
410
|
+
const contextElement = lodash_1.default.isNil(ctx) ? null : this.getAtomsElement(ctx);
|
|
411
|
+
const atomName = many ? 'find_elements' : 'find_element_fragment';
|
|
412
|
+
let element;
|
|
413
|
+
const doFind = async () => {
|
|
414
|
+
element = await this.executeAtom(atomName, [strategy, selector, contextElement]);
|
|
415
|
+
return !lodash_1.default.isNull(element);
|
|
416
|
+
};
|
|
417
|
+
try {
|
|
418
|
+
await this.implicitWaitForCondition(doFind);
|
|
419
|
+
}
|
|
420
|
+
catch (err) {
|
|
421
|
+
if (err.message && lodash_1.default.isFunction(err.message.match) && err.message.match(/Condition unmet/)) {
|
|
422
|
+
// condition was not met setting res to empty array
|
|
423
|
+
element = [];
|
|
443
424
|
}
|
|
444
|
-
|
|
445
|
-
throw
|
|
425
|
+
else {
|
|
426
|
+
throw err;
|
|
446
427
|
}
|
|
428
|
+
}
|
|
429
|
+
if (many) {
|
|
447
430
|
return this.cacheWebElements(element);
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
431
|
+
}
|
|
432
|
+
if (lodash_1.default.isEmpty(element)) {
|
|
433
|
+
throw new driver_1.errors.NoSuchElementError();
|
|
434
|
+
}
|
|
435
|
+
return this.cacheWebElements(element);
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* @this {XCUITestDriver}
|
|
439
|
+
* @param {number} x
|
|
440
|
+
* @param {number} y
|
|
441
|
+
*/
|
|
442
|
+
async function clickWebCoords(x, y) {
|
|
443
|
+
const { x: translatedX, y: translatedY } = await this.translateWebCoords(x, y);
|
|
444
|
+
await this.mobileTap(translatedX, translatedY);
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* @this {XCUITestDriver}
|
|
448
|
+
* @returns {Promise<boolean>}
|
|
449
|
+
*/
|
|
450
|
+
async function getSafariIsIphone() {
|
|
451
|
+
if (lodash_1.default.isBoolean(this._isSafariIphone)) {
|
|
452
|
+
return this._isSafariIphone;
|
|
453
|
+
}
|
|
454
|
+
try {
|
|
455
|
+
const userAgent = /** @type {string} */ (await this.execute('return navigator.userAgent'));
|
|
456
|
+
this._isSafariIphone = userAgent.toLowerCase().includes('iphone');
|
|
457
|
+
}
|
|
458
|
+
catch (err) {
|
|
459
|
+
this.log.warn(`Unable to find device type from useragent. Assuming iPhone`);
|
|
460
|
+
this.log.debug(`Error: ${err.message}`);
|
|
461
|
+
}
|
|
462
|
+
return this._isSafariIphone ?? true;
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* @this {XCUITestDriver}
|
|
466
|
+
* @returns {Promise<import('@appium/types').Size>}
|
|
467
|
+
*/
|
|
468
|
+
async function getSafariDeviceSize() {
|
|
469
|
+
const script = 'return {height: window.screen.availHeight * window.devicePixelRatio, width: window.screen.availWidth * window.devicePixelRatio};';
|
|
470
|
+
const { width, height } = /** @type {import('@appium/types').Size} */ (await this.execute(script));
|
|
471
|
+
const [normHeight, normWidth] = height > width ? [height, width] : [width, height];
|
|
472
|
+
return {
|
|
473
|
+
width: normWidth,
|
|
474
|
+
height: normHeight,
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* @this {XCUITestDriver}
|
|
479
|
+
* @returns {Promise<boolean>}
|
|
480
|
+
*/
|
|
481
|
+
async function getSafariIsNotched() {
|
|
482
|
+
if (lodash_1.default.isBoolean(this._isSafariNotched)) {
|
|
483
|
+
return this._isSafariNotched;
|
|
484
|
+
}
|
|
485
|
+
try {
|
|
486
|
+
const { width, height } = await this.getSafariDeviceSize();
|
|
487
|
+
for (const device of NOTCHED_DEVICE_SIZES) {
|
|
488
|
+
if (device.w === width && device.h === height) {
|
|
489
|
+
this._isSafariNotched = true;
|
|
503
490
|
}
|
|
504
491
|
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
492
|
+
}
|
|
493
|
+
catch (err) {
|
|
494
|
+
this.log.warn(`Unable to find device type from dimensions. Assuming the device is not notched`);
|
|
495
|
+
this.log.debug(`Error: ${err.message}`);
|
|
496
|
+
}
|
|
497
|
+
return this._isSafariNotched ?? false;
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* @this {XCUITestDriver}
|
|
501
|
+
*/
|
|
502
|
+
async function getExtraTranslateWebCoordsOffset(wvPos, realDims) {
|
|
503
|
+
let topOffset = 0;
|
|
504
|
+
let bottomOffset = 0;
|
|
505
|
+
const isIphone = await this.getSafariIsIphone();
|
|
506
|
+
// No need to check whether the Smart App Banner or Tab Bar is visible or not
|
|
507
|
+
// if already defined by nativeWebTapTabBarVisibility or nativeWebTapSmartAppBannerVisibility in settings.
|
|
508
|
+
const { nativeWebTapTabBarVisibility, nativeWebTapSmartAppBannerVisibility, safariTabBarPosition = support_1.util.compareVersions(/** @type {string} */ (this.opts.platformVersion), '>=', '15.0') &&
|
|
509
|
+
isIphone
|
|
510
|
+
? TAB_BAR_POSITION_BOTTOM
|
|
511
|
+
: TAB_BAR_POSITION_TOP, } = this.settings.getSettings();
|
|
512
|
+
let tabBarVisibility = lodash_1.default.lowerCase(String(nativeWebTapTabBarVisibility));
|
|
513
|
+
let bannerVisibility = lodash_1.default.lowerCase(String(nativeWebTapSmartAppBannerVisibility));
|
|
514
|
+
const tabBarPosition = lodash_1.default.lowerCase(String(safariTabBarPosition));
|
|
515
|
+
if (!VISIBILITIES.includes(tabBarVisibility)) {
|
|
516
|
+
tabBarVisibility = DETECT;
|
|
517
|
+
}
|
|
518
|
+
if (!VISIBILITIES.includes(bannerVisibility)) {
|
|
519
|
+
bannerVisibility = DETECT;
|
|
520
|
+
}
|
|
521
|
+
if (!TAB_BAR_POSSITIONS.includes(tabBarPosition)) {
|
|
522
|
+
throw new driver_1.errors.InvalidArgumentError(`${safariTabBarPosition} is invalid as Safari tab bar position. Available positions are ${TAB_BAR_POSSITIONS}.`);
|
|
523
|
+
}
|
|
524
|
+
const isNotched = isIphone && (await this.getSafariIsNotched());
|
|
525
|
+
const orientation = realDims.h > realDims.w ? 'PORTRAIT' : 'LANDSCAPE';
|
|
526
|
+
const notchOffset = isNotched
|
|
527
|
+
? support_1.util.compareVersions(/** @type {string} */ (this.opts.platformVersion), '=', '13.0')
|
|
528
|
+
? IPHONE_X_NOTCH_OFFSET_IOS_13
|
|
529
|
+
: IPHONE_X_NOTCH_OFFSET_IOS
|
|
530
|
+
: 0;
|
|
531
|
+
const isScrolled = await this.execute('return document.documentElement.scrollTop > 0');
|
|
532
|
+
if (isScrolled) {
|
|
533
|
+
topOffset = IPHONE_SCROLLED_TOP_BAR_HEIGHT + notchOffset;
|
|
534
|
+
if (isNotched) {
|
|
535
|
+
topOffset -= IPHONE_X_SCROLLED_OFFSET;
|
|
532
536
|
}
|
|
533
|
-
|
|
534
|
-
|
|
537
|
+
// If the iPhone is landscape then there is no top bar
|
|
538
|
+
if (orientation === 'LANDSCAPE' && isIphone) {
|
|
539
|
+
topOffset = 0;
|
|
535
540
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
topOffset = IPHONE_SCROLLED_TOP_BAR_HEIGHT + notchOffset;
|
|
546
|
-
if (isNotched) {
|
|
547
|
-
topOffset -= IPHONE_X_SCROLLED_OFFSET;
|
|
541
|
+
}
|
|
542
|
+
else {
|
|
543
|
+
topOffset = tabBarPosition === TAB_BAR_POSITION_BOTTOM ? 0 : IPHONE_TOP_BAR_HEIGHT;
|
|
544
|
+
topOffset += notchOffset;
|
|
545
|
+
this.log.debug(`tabBarPosition and topOffset: ${tabBarPosition}, ${topOffset}`);
|
|
546
|
+
if (isIphone) {
|
|
547
|
+
if (orientation === 'PORTRAIT') {
|
|
548
|
+
// The bottom bar is only visible when portrait
|
|
549
|
+
bottomOffset = IPHONE_BOTTOM_BAR_OFFSET;
|
|
548
550
|
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
topOffset = 0;
|
|
551
|
+
else {
|
|
552
|
+
topOffset = IPHONE_LANDSCAPE_TOP_BAR_HEIGHT;
|
|
552
553
|
}
|
|
553
554
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
this.log.debug(`tabBarPosition and topOffset: ${tabBarPosition}, ${topOffset}`);
|
|
558
|
-
if (isIphone) {
|
|
559
|
-
if (orientation === 'PORTRAIT') {
|
|
560
|
-
// The bottom bar is only visible when portrait
|
|
561
|
-
bottomOffset = IPHONE_BOTTOM_BAR_OFFSET;
|
|
562
|
-
}
|
|
563
|
-
else {
|
|
564
|
-
topOffset = IPHONE_LANDSCAPE_TOP_BAR_HEIGHT;
|
|
565
|
-
}
|
|
555
|
+
if (orientation === 'LANDSCAPE' || !isIphone) {
|
|
556
|
+
if (tabBarVisibility === VISIBLE) {
|
|
557
|
+
topOffset += TAB_BAR_OFFSET;
|
|
566
558
|
}
|
|
567
|
-
if (
|
|
568
|
-
if
|
|
559
|
+
else if (tabBarVisibility === DETECT) {
|
|
560
|
+
// Tabs only appear if the device is landscape or if it's an iPad so we only check visibility in this case
|
|
561
|
+
// Assume that each tab bar is a WebView
|
|
562
|
+
const contextsAndViews = await this.getContextsAndViews();
|
|
563
|
+
const tabs = contextsAndViews.filter((ctx) => ctx.id.startsWith('WEBVIEW_'));
|
|
564
|
+
if (tabs.length > 1) {
|
|
565
|
+
this.log.debug(`Found ${tabs.length} tabs. Assuming the tab bar is visible`);
|
|
569
566
|
topOffset += TAB_BAR_OFFSET;
|
|
570
567
|
}
|
|
571
|
-
else if (tabBarVisibility === DETECT) {
|
|
572
|
-
// Tabs only appear if the device is landscape or if it's an iPad so we only check visibility in this case
|
|
573
|
-
// Assume that each tab bar is a WebView
|
|
574
|
-
const contextsAndViews = await this.getContextsAndViews();
|
|
575
|
-
const tabs = contextsAndViews.filter((ctx) => ctx.id.startsWith('WEBVIEW_'));
|
|
576
|
-
if (tabs.length > 1) {
|
|
577
|
-
this.log.debug(`Found ${tabs.length} tabs. Assuming the tab bar is visible`);
|
|
578
|
-
topOffset += TAB_BAR_OFFSET;
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
568
|
}
|
|
582
569
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
570
|
+
}
|
|
571
|
+
topOffset += await this.getExtraNativeWebTapOffset(isIphone, bannerVisibility);
|
|
572
|
+
wvPos.y += topOffset;
|
|
573
|
+
realDims.h -= topOffset + bottomOffset;
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* @this {XCUITestDriver}
|
|
577
|
+
* @param {boolean} isIphone
|
|
578
|
+
* @param {string} bannerVisibility
|
|
579
|
+
* @returns {Promise<number>}
|
|
580
|
+
*/
|
|
581
|
+
async function getExtraNativeWebTapOffset(isIphone, bannerVisibility) {
|
|
582
|
+
let offset = 0;
|
|
583
|
+
if (bannerVisibility === VISIBLE) {
|
|
584
|
+
offset += isIphone
|
|
585
|
+
? IPHONE_WEB_COORD_SMART_APP_BANNER_OFFSET
|
|
586
|
+
: IPAD_WEB_COORD_SMART_APP_BANNER_OFFSET;
|
|
587
|
+
}
|
|
588
|
+
else if (bannerVisibility === DETECT) {
|
|
589
|
+
// try to see if there is an Smart App Banner
|
|
590
|
+
const banners = /** @type {import('@appium/types').Element[]} */ (await this.findNativeElementOrElements('accessibility id', 'Close app download offer', true));
|
|
591
|
+
if (banners?.length) {
|
|
596
592
|
offset += isIphone
|
|
597
593
|
? IPHONE_WEB_COORD_SMART_APP_BANNER_OFFSET
|
|
598
594
|
: IPAD_WEB_COORD_SMART_APP_BANNER_OFFSET;
|
|
599
595
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
*/
|
|
640
|
-
async translateWebCoords(x, y) {
|
|
641
|
-
this.log.debug(`Translating web coordinates (${JSON.stringify({ x, y })}) to native coordinates`);
|
|
642
|
-
if (this.webviewCalibrationResult) {
|
|
643
|
-
this.log.debug(`Will use the recent calibration result: ${JSON.stringify(this.webviewCalibrationResult)}`);
|
|
644
|
-
const { offsetX, offsetY, pixelRatioX, pixelRatioY } = this.webviewCalibrationResult;
|
|
645
|
-
const cmd = '(function () {return {innerWidth: window.innerWidth, innerHeight: window.innerHeight, ' +
|
|
646
|
-
'outerWidth: window.outerWidth, outerHeight: window.outerHeight}; })()';
|
|
647
|
-
const wvDims = await ( /** @type {RemoteDebugger} */(this.remote)).execute(cmd);
|
|
648
|
-
// https://tripleodeon.com/2011/12/first-understand-your-screen/
|
|
649
|
-
const shouldApplyPixelRatio = wvDims.innerWidth > wvDims.outerWidth
|
|
650
|
-
|| wvDims.innerHeight > wvDims.outerHeight;
|
|
651
|
-
return {
|
|
652
|
-
x: offsetX + x * (shouldApplyPixelRatio ? pixelRatioX : 1),
|
|
653
|
-
y: offsetY + y * (shouldApplyPixelRatio ? pixelRatioY : 1),
|
|
654
|
-
};
|
|
655
|
-
}
|
|
656
|
-
else {
|
|
657
|
-
this.log.debug(`Using the legacy algorithm for coordinates translation. ` +
|
|
658
|
-
`Invoke 'mobile: calibrateWebToRealCoordinatesTranslation' to change that.`);
|
|
659
|
-
}
|
|
660
|
-
// absolutize web coords
|
|
661
|
-
/** @type {import('@appium/types').Element|undefined|string} */
|
|
662
|
-
let webview;
|
|
663
|
-
try {
|
|
664
|
-
webview = /** @type {import('@appium/types').Element|undefined} */ (await (0, asyncbox_1.retryInterval)(5, 100, async () => await this.findNativeElementOrElements('class name', 'XCUIElementTypeWebView', false)));
|
|
665
|
-
}
|
|
666
|
-
catch { }
|
|
667
|
-
if (!webview) {
|
|
668
|
-
throw new Error(`No WebView found. Unable to translate web coordinates for native web tap.`);
|
|
669
|
-
}
|
|
670
|
-
webview = support_1.util.unwrapElement(webview);
|
|
671
|
-
const rect = /** @type {Rect} */ (await this.proxyCommand(`/element/${webview}/rect`, 'GET'));
|
|
672
|
-
const wvPos = { x: rect.x, y: rect.y };
|
|
673
|
-
const realDims = { w: rect.width, h: rect.height };
|
|
674
|
-
const cmd = '(function () { return {w: window.innerWidth, h: window.innerHeight}; })()';
|
|
596
|
+
}
|
|
597
|
+
this.log.debug(`Additional native web tap offset computed: ${offset}`);
|
|
598
|
+
return offset;
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* @this {XCUITestDriver}
|
|
602
|
+
* @param {any} el
|
|
603
|
+
* @returns {Promise<void>}
|
|
604
|
+
*/
|
|
605
|
+
async function nativeWebTap(el) {
|
|
606
|
+
const atomsElement = this.getAtomsElement(el);
|
|
607
|
+
// if strict native tap, do not try to do it with WDA directly
|
|
608
|
+
if (!(this.settings.getSettings()).nativeWebTapStrict &&
|
|
609
|
+
(await tapWebElementNatively.bind(this)(atomsElement))) {
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
this.log.warn('Unable to do simple native web tap. Attempting to convert coordinates');
|
|
613
|
+
const [size, coordinates] =
|
|
614
|
+
/** @type {[import('@appium/types').Size, import('@appium/types').Position]} */ (await bluebird_1.default.Promise.all([
|
|
615
|
+
this.executeAtom('get_size', [atomsElement]),
|
|
616
|
+
this.executeAtom('get_top_left_coordinates', [atomsElement]),
|
|
617
|
+
]));
|
|
618
|
+
const { width, height } = size;
|
|
619
|
+
const { x, y } = coordinates;
|
|
620
|
+
await this.clickWebCoords(x + width / 2, y + height / 2);
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* @this {XCUITestDriver}
|
|
624
|
+
* @param {number} x
|
|
625
|
+
* @param {number} y
|
|
626
|
+
* @returns {Promise<import('@appium/types').Position>}
|
|
627
|
+
*/
|
|
628
|
+
async function translateWebCoords(x, y) {
|
|
629
|
+
this.log.debug(`Translating web coordinates (${JSON.stringify({ x, y })}) to native coordinates`);
|
|
630
|
+
if (this.webviewCalibrationResult) {
|
|
631
|
+
this.log.debug(`Will use the recent calibration result: ${JSON.stringify(this.webviewCalibrationResult)}`);
|
|
632
|
+
const { offsetX, offsetY, pixelRatioX, pixelRatioY } = this.webviewCalibrationResult;
|
|
633
|
+
const cmd = '(function () {return {innerWidth: window.innerWidth, innerHeight: window.innerHeight, ' +
|
|
634
|
+
'outerWidth: window.outerWidth, outerHeight: window.outerHeight}; })()';
|
|
675
635
|
const wvDims = await ( /** @type {RemoteDebugger} */(this.remote)).execute(cmd);
|
|
676
|
-
//
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
636
|
+
// https://tripleodeon.com/2011/12/first-understand-your-screen/
|
|
637
|
+
const shouldApplyPixelRatio = wvDims.innerWidth > wvDims.outerWidth
|
|
638
|
+
|| wvDims.innerHeight > wvDims.outerHeight;
|
|
639
|
+
return {
|
|
640
|
+
x: offsetX + x * (shouldApplyPixelRatio ? pixelRatioX : 1),
|
|
641
|
+
y: offsetY + y * (shouldApplyPixelRatio ? pixelRatioY : 1),
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
else {
|
|
645
|
+
this.log.debug(`Using the legacy algorithm for coordinates translation. ` +
|
|
646
|
+
`Invoke 'mobile: calibrateWebToRealCoordinatesTranslation' to change that.`);
|
|
647
|
+
}
|
|
648
|
+
// absolutize web coords
|
|
649
|
+
/** @type {import('@appium/types').Element|undefined|string} */
|
|
650
|
+
let webview;
|
|
651
|
+
try {
|
|
652
|
+
webview = /** @type {import('@appium/types').Element|undefined} */ (await (0, asyncbox_1.retryInterval)(5, 100, async () => await this.findNativeElementOrElements('class name', 'XCUIElementTypeWebView', false)));
|
|
653
|
+
}
|
|
654
|
+
catch { }
|
|
655
|
+
if (!webview) {
|
|
656
|
+
throw new Error(`No WebView found. Unable to translate web coordinates for native web tap.`);
|
|
657
|
+
}
|
|
658
|
+
webview = support_1.util.unwrapElement(webview);
|
|
659
|
+
const rect = /** @type {Rect} */ (await this.proxyCommand(`/element/${webview}/rect`, 'GET'));
|
|
660
|
+
const wvPos = { x: rect.x, y: rect.y };
|
|
661
|
+
const realDims = { w: rect.width, h: rect.height };
|
|
662
|
+
const cmd = '(function () { return {w: window.innerWidth, h: window.innerHeight}; })()';
|
|
663
|
+
const wvDims = await ( /** @type {RemoteDebugger} */(this.remote)).execute(cmd);
|
|
664
|
+
// keep track of implicit wait, and set locally to 0
|
|
665
|
+
// https://github.com/appium/appium/issues/14988
|
|
666
|
+
const implicitWaitMs = this.implicitWaitMs;
|
|
667
|
+
this.setImplicitWait(0);
|
|
668
|
+
try {
|
|
669
|
+
await this.getExtraTranslateWebCoordsOffset(wvPos, realDims);
|
|
670
|
+
}
|
|
671
|
+
finally {
|
|
672
|
+
this.setImplicitWait(implicitWaitMs);
|
|
673
|
+
}
|
|
674
|
+
if (!wvDims || !realDims || !wvPos) {
|
|
675
|
+
throw new Error(`Web coordinates ${JSON.stringify({ x, y })} cannot be translated into real coordinates. ` +
|
|
676
|
+
`Try to invoke 'mobile: calibrateWebToRealCoordinatesTranslation' or consider translating the ` +
|
|
677
|
+
`coordinates from the client code.`);
|
|
678
|
+
}
|
|
679
|
+
const xRatio = realDims.w / wvDims.w;
|
|
680
|
+
const yRatio = realDims.h / wvDims.h;
|
|
681
|
+
const newCoords = {
|
|
682
|
+
x: wvPos.x + Math.round(xRatio * x),
|
|
683
|
+
y: wvPos.y + Math.round(yRatio * y),
|
|
684
|
+
};
|
|
685
|
+
// additional logging for coordinates, since it is sometimes broken
|
|
686
|
+
// see https://github.com/appium/appium/issues/9159
|
|
687
|
+
this.log.debug(`Converted coordinates: ${JSON.stringify(newCoords)}`);
|
|
688
|
+
this.log.debug(` rect: ${JSON.stringify(rect)}`);
|
|
689
|
+
this.log.debug(` wvPos: ${JSON.stringify(wvPos)}`);
|
|
690
|
+
this.log.debug(` realDims: ${JSON.stringify(realDims)}`);
|
|
691
|
+
this.log.debug(` wvDims: ${JSON.stringify(wvDims)}`);
|
|
692
|
+
this.log.debug(` xRatio: ${JSON.stringify(xRatio)}`);
|
|
693
|
+
this.log.debug(` yRatio: ${JSON.stringify(yRatio)}`);
|
|
694
|
+
this.log.debug(`Converted web coords ${JSON.stringify({ x, y })} into real coords ${JSON.stringify(newCoords)}`);
|
|
695
|
+
return newCoords;
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* @this {XCUITestDriver}
|
|
699
|
+
* @returns {Promise<boolean>}
|
|
700
|
+
*/
|
|
701
|
+
async function checkForAlert() {
|
|
702
|
+
return lodash_1.default.isString(await this.getAlertText());
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* @param {Promise<any>} promise
|
|
706
|
+
* @this {XCUITestDriver}
|
|
707
|
+
*/
|
|
708
|
+
async function waitForAtom(promise) {
|
|
709
|
+
const timer = new support_1.timing.Timer().start();
|
|
710
|
+
const atomWaitTimeoutMs = lodash_1.default.isNumber(this.opts.webviewAtomWaitTimeout) && this.opts.webviewAtomWaitTimeout > 0
|
|
711
|
+
? this.opts.webviewAtomWaitTimeout
|
|
712
|
+
: ATOM_WAIT_TIMEOUT_MS;
|
|
713
|
+
// need to check for alert while the atom is being executed.
|
|
714
|
+
// so notify ourselves when it happens
|
|
715
|
+
const timedAtomPromise = bluebird_1.default.resolve(promise).timeout(atomWaitTimeoutMs);
|
|
716
|
+
const handlePromiseError = async (p) => {
|
|
680
717
|
try {
|
|
681
|
-
await
|
|
718
|
+
return await p;
|
|
682
719
|
}
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
`coordinates from the client code.`);
|
|
690
|
-
}
|
|
691
|
-
const xRatio = realDims.w / wvDims.w;
|
|
692
|
-
const yRatio = realDims.h / wvDims.h;
|
|
693
|
-
const newCoords = {
|
|
694
|
-
x: wvPos.x + Math.round(xRatio * x),
|
|
695
|
-
y: wvPos.y + Math.round(yRatio * y),
|
|
696
|
-
};
|
|
697
|
-
// additional logging for coordinates, since it is sometimes broken
|
|
698
|
-
// see https://github.com/appium/appium/issues/9159
|
|
699
|
-
this.log.debug(`Converted coordinates: ${JSON.stringify(newCoords)}`);
|
|
700
|
-
this.log.debug(` rect: ${JSON.stringify(rect)}`);
|
|
701
|
-
this.log.debug(` wvPos: ${JSON.stringify(wvPos)}`);
|
|
702
|
-
this.log.debug(` realDims: ${JSON.stringify(realDims)}`);
|
|
703
|
-
this.log.debug(` wvDims: ${JSON.stringify(wvDims)}`);
|
|
704
|
-
this.log.debug(` xRatio: ${JSON.stringify(xRatio)}`);
|
|
705
|
-
this.log.debug(` yRatio: ${JSON.stringify(yRatio)}`);
|
|
706
|
-
this.log.debug(`Converted web coords ${JSON.stringify({ x, y })} into real coords ${JSON.stringify(newCoords)}`);
|
|
707
|
-
return newCoords;
|
|
708
|
-
},
|
|
709
|
-
/**
|
|
710
|
-
* @this {XCUITestDriver}
|
|
711
|
-
* @returns {Promise<boolean>}
|
|
712
|
-
*/
|
|
713
|
-
async checkForAlert() {
|
|
714
|
-
return lodash_1.default.isString(await this.getAlertText());
|
|
715
|
-
},
|
|
716
|
-
/**
|
|
717
|
-
* @param {Promise<any>} promise
|
|
718
|
-
* @this {XCUITestDriver}
|
|
719
|
-
*/
|
|
720
|
-
async waitForAtom(promise) {
|
|
721
|
-
const timer = new support_1.timing.Timer().start();
|
|
722
|
-
const atomWaitTimeoutMs = lodash_1.default.isNumber(this.opts.webviewAtomWaitTimeout) && this.opts.webviewAtomWaitTimeout > 0
|
|
723
|
-
? this.opts.webviewAtomWaitTimeout
|
|
724
|
-
: ATOM_WAIT_TIMEOUT_MS;
|
|
725
|
-
// need to check for alert while the atom is being executed.
|
|
726
|
-
// so notify ourselves when it happens
|
|
727
|
-
const timedAtomPromise = bluebird_1.default.resolve(promise).timeout(atomWaitTimeoutMs);
|
|
728
|
-
const handlePromiseError = async (p) => {
|
|
729
|
-
try {
|
|
730
|
-
return await p;
|
|
731
|
-
}
|
|
732
|
-
catch (err) {
|
|
733
|
-
const originalError = err instanceof bluebird_1.AggregateError ? err[0] : err;
|
|
734
|
-
this.log.debug(`Error received while executing atom: ${originalError.message}`);
|
|
735
|
-
throw (originalError instanceof bluebird_1.TimeoutError
|
|
736
|
-
? (await generateAtomTimeoutError.bind(this)(timer))
|
|
737
|
-
: originalError);
|
|
738
|
-
}
|
|
739
|
-
};
|
|
740
|
-
// if the atom promise is fulfilled within ATOM_INITIAL_WAIT_MS
|
|
741
|
-
// then we don't need to check for an alert presence
|
|
742
|
-
await handlePromiseError(bluebird_1.default.any([bluebird_1.default.delay(ATOM_INITIAL_WAIT_MS), timedAtomPromise]));
|
|
743
|
-
if (timedAtomPromise.isFulfilled()) {
|
|
744
|
-
return await timedAtomPromise;
|
|
720
|
+
catch (err) {
|
|
721
|
+
const originalError = err instanceof bluebird_1.AggregateError ? err[0] : err;
|
|
722
|
+
this.log.debug(`Error received while executing atom: ${originalError.message}`);
|
|
723
|
+
throw (originalError instanceof bluebird_1.TimeoutError
|
|
724
|
+
? (await generateAtomTimeoutError.bind(this)(timer))
|
|
725
|
+
: originalError);
|
|
745
726
|
}
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
727
|
+
};
|
|
728
|
+
// if the atom promise is fulfilled within ATOM_INITIAL_WAIT_MS
|
|
729
|
+
// then we don't need to check for an alert presence
|
|
730
|
+
await handlePromiseError(bluebird_1.default.any([bluebird_1.default.delay(ATOM_INITIAL_WAIT_MS), timedAtomPromise]));
|
|
731
|
+
if (timedAtomPromise.isFulfilled()) {
|
|
732
|
+
return await timedAtomPromise;
|
|
733
|
+
}
|
|
734
|
+
// ...otherwise make sure there is no unexpected alert covering the element
|
|
735
|
+
this._waitingAtoms.count++;
|
|
736
|
+
let onAlertCallback;
|
|
737
|
+
let onAppCrashCallback;
|
|
738
|
+
try {
|
|
739
|
+
// only restart the monitor if it is not running already
|
|
740
|
+
if (this._waitingAtoms.alertMonitor.isResolved()) {
|
|
741
|
+
this._waitingAtoms.alertMonitor = bluebird_1.default.resolve((async () => {
|
|
742
|
+
while (this._waitingAtoms.count > 0) {
|
|
743
|
+
try {
|
|
744
|
+
if (await this.checkForAlert()) {
|
|
745
|
+
this._waitingAtoms.alertNotifier.emit(ON_OBSTRUCTING_ALERT_EVENT);
|
|
759
746
|
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
747
|
+
}
|
|
748
|
+
catch (err) {
|
|
749
|
+
if ((0, driver_1.isErrorType)(err, driver_1.errors.InvalidElementStateError)) {
|
|
750
|
+
this._waitingAtoms.alertNotifier.emit(ON_APP_CRASH_EVENT, err);
|
|
764
751
|
}
|
|
765
|
-
await bluebird_1.default.delay(OBSTRUCTING_ALERT_PRESENCE_CHECK_INTERVAL_MS);
|
|
766
752
|
}
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
onAlertCallback = () => reject(new driver_1.errors.UnexpectedAlertOpenError());
|
|
771
|
-
onAppCrashCallback = reject;
|
|
772
|
-
this._waitingAtoms.alertNotifier.once(ON_OBSTRUCTING_ALERT_EVENT, onAlertCallback);
|
|
773
|
-
this._waitingAtoms.alertNotifier.once(ON_APP_CRASH_EVENT, onAppCrashCallback);
|
|
774
|
-
handlePromiseError(timedAtomPromise)
|
|
775
|
-
.then(resolve)
|
|
776
|
-
.catch(reject);
|
|
777
|
-
});
|
|
753
|
+
await bluebird_1.default.delay(OBSTRUCTING_ALERT_PRESENCE_CHECK_INTERVAL_MS);
|
|
754
|
+
}
|
|
755
|
+
})());
|
|
778
756
|
}
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
757
|
+
return await new bluebird_1.default((resolve, reject) => {
|
|
758
|
+
onAlertCallback = () => reject(new driver_1.errors.UnexpectedAlertOpenError());
|
|
759
|
+
onAppCrashCallback = reject;
|
|
760
|
+
this._waitingAtoms.alertNotifier.once(ON_OBSTRUCTING_ALERT_EVENT, onAlertCallback);
|
|
761
|
+
this._waitingAtoms.alertNotifier.once(ON_APP_CRASH_EVENT, onAppCrashCallback);
|
|
762
|
+
handlePromiseError(timedAtomPromise)
|
|
763
|
+
.then(resolve)
|
|
764
|
+
.catch(reject);
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
finally {
|
|
768
|
+
if (onAlertCallback) {
|
|
769
|
+
this._waitingAtoms.alertNotifier.removeListener(ON_OBSTRUCTING_ALERT_EVENT, onAlertCallback);
|
|
787
770
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
* @param {string} navType
|
|
791
|
-
* @this {XCUITestDriver}
|
|
792
|
-
*/
|
|
793
|
-
async mobileWebNav(navType) {
|
|
794
|
-
( /** @type {RemoteDebugger} */(this.remote)).allowNavigationWithoutReload = true;
|
|
795
|
-
try {
|
|
796
|
-
await this.executeAtom('execute_script', [`history.${navType}();`, null]);
|
|
771
|
+
if (onAppCrashCallback) {
|
|
772
|
+
this._waitingAtoms.alertNotifier.removeListener(ON_APP_CRASH_EVENT, onAppCrashCallback);
|
|
797
773
|
}
|
|
798
|
-
|
|
799
|
-
|
|
774
|
+
this._waitingAtoms.count--;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* @param {string} navType
|
|
779
|
+
* @this {XCUITestDriver}
|
|
780
|
+
*/
|
|
781
|
+
async function mobileWebNav(navType) {
|
|
782
|
+
( /** @type {RemoteDebugger} */(this.remote)).allowNavigationWithoutReload = true;
|
|
783
|
+
try {
|
|
784
|
+
await this.executeAtom('execute_script', [`history.${navType}();`, null]);
|
|
785
|
+
}
|
|
786
|
+
finally {
|
|
787
|
+
( /** @type {RemoteDebugger} */(this.remote)).allowNavigationWithoutReload = false;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* @this {XCUITestDriver}
|
|
792
|
+
* @returns {string} The base url which could be used to access WDA HTTP endpoints
|
|
793
|
+
* FROM THE SAME DEVICE where WDA is running
|
|
794
|
+
*/
|
|
795
|
+
function getWdaLocalhostRoot() {
|
|
796
|
+
const remotePort = ((this.isRealDevice() ? this.opts.wdaRemotePort : null)
|
|
797
|
+
?? this.wda?.url?.port
|
|
798
|
+
?? this.opts.wdaLocalPort)
|
|
799
|
+
|| 8100;
|
|
800
|
+
return `http://127.0.0.1:${remotePort}`;
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* Calibrates web to real coordinates translation.
|
|
804
|
+
* This API can only be called from Safari web context.
|
|
805
|
+
* It must load a custom page to the browser, and then restore
|
|
806
|
+
* the original one, so don't call it if you can potentially
|
|
807
|
+
* lose the current web app state.
|
|
808
|
+
* The outcome of this API is then used in nativeWebTap mode.
|
|
809
|
+
* The returned value could also be used to manually transform web coordinates
|
|
810
|
+
* to real devices ones in client scripts.
|
|
811
|
+
*
|
|
812
|
+
* @this {XCUITestDriver}
|
|
813
|
+
* @returns {Promise<import('../types').CalibrationData>}
|
|
814
|
+
*/
|
|
815
|
+
async function mobileCalibrateWebToRealCoordinatesTranslation() {
|
|
816
|
+
if (!this.isWebContext()) {
|
|
817
|
+
throw new driver_1.errors.NotImplementedError('This API can only be called from a web context');
|
|
818
|
+
}
|
|
819
|
+
const currentUrl = await this.getUrl();
|
|
820
|
+
await this.setUrl(`${this.getWdaLocalhostRoot()}/calibrate`);
|
|
821
|
+
const { width, height } = /** @type {import('@appium/types').Rect} */ (await this.proxyCommand('/window/rect', 'GET'));
|
|
822
|
+
const [centerX, centerY] = [width / 2, height / 2];
|
|
823
|
+
const errorPrefix = 'Cannot determine web view coordinates offset. Are you in Safari context?';
|
|
824
|
+
const performCalibrationTap = async (/** @type {number} */ tapX, /** @type {number} */ tapY) => {
|
|
825
|
+
await this.mobileTap(tapX, tapY);
|
|
826
|
+
/** @type {import('@appium/types').Position} */
|
|
827
|
+
let result;
|
|
828
|
+
try {
|
|
829
|
+
const title = await this.title();
|
|
830
|
+
this.log.debug(JSON.stringify(title));
|
|
831
|
+
result = lodash_1.default.isPlainObject(title) ? title : JSON.parse(title);
|
|
800
832
|
}
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
* @this {XCUITestDriver}
|
|
804
|
-
* @returns {string} The base url which could be used to access WDA HTTP endpoints
|
|
805
|
-
* FROM THE SAME DEVICE where WDA is running
|
|
806
|
-
*/
|
|
807
|
-
getWdaLocalhostRoot() {
|
|
808
|
-
const remotePort = ((this.isRealDevice() ? this.opts.wdaRemotePort : null)
|
|
809
|
-
?? this.wda?.url?.port
|
|
810
|
-
?? this.opts.wdaLocalPort)
|
|
811
|
-
|| 8100;
|
|
812
|
-
return `http://127.0.0.1:${remotePort}`;
|
|
813
|
-
},
|
|
814
|
-
/**
|
|
815
|
-
* Calibrates web to real coordinates translation.
|
|
816
|
-
* This API can only be called from Safari web context.
|
|
817
|
-
* It must load a custom page to the browser, and then restore
|
|
818
|
-
* the original one, so don't call it if you can potentially
|
|
819
|
-
* lose the current web app state.
|
|
820
|
-
* The outcome of this API is then used in nativeWebTap mode.
|
|
821
|
-
* The returned value could also be used to manually transform web coordinates
|
|
822
|
-
* to real devices ones in client scripts.
|
|
823
|
-
*
|
|
824
|
-
* @this {XCUITestDriver}
|
|
825
|
-
* @returns {Promise<import('../types').CalibrationData>}
|
|
826
|
-
*/
|
|
827
|
-
async mobileCalibrateWebToRealCoordinatesTranslation() {
|
|
828
|
-
if (!this.isWebContext()) {
|
|
829
|
-
throw new driver_1.errors.NotImplementedError('This API can only be called from a web context');
|
|
833
|
+
catch (e) {
|
|
834
|
+
throw new Error(`${errorPrefix} Original error: ${e.message}`);
|
|
830
835
|
}
|
|
831
|
-
const
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
const [centerX, centerY] = [width / 2, height / 2];
|
|
835
|
-
const errorPrefix = 'Cannot determine web view coordinates offset. Are you in Safari context?';
|
|
836
|
-
const performCalibrationTap = async (/** @type {number} */ tapX, /** @type {number} */ tapY) => {
|
|
837
|
-
await this.mobileTap(tapX, tapY);
|
|
838
|
-
/** @type {import('@appium/types').Position} */
|
|
839
|
-
let result;
|
|
840
|
-
try {
|
|
841
|
-
const title = await this.title();
|
|
842
|
-
this.log.debug(JSON.stringify(title));
|
|
843
|
-
result = lodash_1.default.isPlainObject(title) ? title : JSON.parse(title);
|
|
844
|
-
}
|
|
845
|
-
catch (e) {
|
|
846
|
-
throw new Error(`${errorPrefix} Original error: ${e.message}`);
|
|
847
|
-
}
|
|
848
|
-
const { x, y } = result;
|
|
849
|
-
if (!lodash_1.default.isInteger(x) || !lodash_1.default.isInteger(y)) {
|
|
850
|
-
throw new Error(errorPrefix);
|
|
851
|
-
}
|
|
852
|
-
return result;
|
|
853
|
-
};
|
|
854
|
-
await (0, asyncbox_1.retryInterval)(6, 500, async () => {
|
|
855
|
-
const { x: x0, y: y0 } = await performCalibrationTap(centerX - CALIBRATION_TAP_DELTA_PX, centerY - CALIBRATION_TAP_DELTA_PX);
|
|
856
|
-
const { x: x1, y: y1 } = await performCalibrationTap(centerX + CALIBRATION_TAP_DELTA_PX, centerY + CALIBRATION_TAP_DELTA_PX);
|
|
857
|
-
const pixelRatioX = CALIBRATION_TAP_DELTA_PX * 2 / (x1 - x0);
|
|
858
|
-
const pixelRatioY = CALIBRATION_TAP_DELTA_PX * 2 / (y1 - y0);
|
|
859
|
-
this.webviewCalibrationResult = {
|
|
860
|
-
offsetX: centerX - CALIBRATION_TAP_DELTA_PX - x0 * pixelRatioX,
|
|
861
|
-
offsetY: centerY - CALIBRATION_TAP_DELTA_PX - y0 * pixelRatioY,
|
|
862
|
-
pixelRatioX,
|
|
863
|
-
pixelRatioY,
|
|
864
|
-
};
|
|
865
|
-
});
|
|
866
|
-
if (currentUrl) {
|
|
867
|
-
// restore the previous url
|
|
868
|
-
await this.setUrl(currentUrl);
|
|
836
|
+
const { x, y } = result;
|
|
837
|
+
if (!lodash_1.default.isInteger(x) || !lodash_1.default.isInteger(y)) {
|
|
838
|
+
throw new Error(errorPrefix);
|
|
869
839
|
}
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
840
|
+
return result;
|
|
841
|
+
};
|
|
842
|
+
await (0, asyncbox_1.retryInterval)(6, 500, async () => {
|
|
843
|
+
const { x: x0, y: y0 } = await performCalibrationTap(centerX - CALIBRATION_TAP_DELTA_PX, centerY - CALIBRATION_TAP_DELTA_PX);
|
|
844
|
+
const { x: x1, y: y1 } = await performCalibrationTap(centerX + CALIBRATION_TAP_DELTA_PX, centerY + CALIBRATION_TAP_DELTA_PX);
|
|
845
|
+
const pixelRatioX = CALIBRATION_TAP_DELTA_PX * 2 / (x1 - x0);
|
|
846
|
+
const pixelRatioY = CALIBRATION_TAP_DELTA_PX * 2 / (y1 - y0);
|
|
847
|
+
this.webviewCalibrationResult = {
|
|
848
|
+
offsetX: centerX - CALIBRATION_TAP_DELTA_PX - x0 * pixelRatioX,
|
|
849
|
+
offsetY: centerY - CALIBRATION_TAP_DELTA_PX - y0 * pixelRatioY,
|
|
850
|
+
pixelRatioX,
|
|
851
|
+
pixelRatioY,
|
|
875
852
|
};
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
853
|
+
});
|
|
854
|
+
if (currentUrl) {
|
|
855
|
+
// restore the previous url
|
|
856
|
+
await this.setUrl(currentUrl);
|
|
857
|
+
}
|
|
858
|
+
const result = /** @type {import('../types').CalibrationData} */ (this.webviewCalibrationResult);
|
|
859
|
+
return {
|
|
860
|
+
...result,
|
|
861
|
+
offsetX: Math.round(result.offsetX),
|
|
862
|
+
offsetY: Math.round(result.offsetY),
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* @typedef {Object} SafariOpts
|
|
867
|
+
* @property {object} preferences An object containing Safari settings to be updated.
|
|
868
|
+
* The list of available setting names and their values could be retrieved by
|
|
869
|
+
* changing the corresponding Safari settings in the UI and then inspecting
|
|
870
|
+
* 'Library/Preferences/com.apple.mobilesafari.plist' file inside of
|
|
871
|
+
* com.apple.mobilesafari app container.
|
|
872
|
+
* The full path to the Mobile Safari's container could be retrieved from
|
|
873
|
+
* `xcrun simctl get_app_container <sim_udid> com.apple.mobilesafari data`
|
|
874
|
+
* command output.
|
|
875
|
+
* Use the `xcrun simctl spawn <sim_udid> defaults read <path_to_plist>` command
|
|
876
|
+
* to print the plist content to the Terminal.
|
|
877
|
+
*/
|
|
878
|
+
/**
|
|
879
|
+
* Updates Mobile Safari preferences on an iOS Simulator
|
|
880
|
+
*
|
|
881
|
+
* @param {import('@appium/types').StringRecord} preferences - An object containing Safari settings to be updated.
|
|
882
|
+
* The list of available setting names and their values can be retrieved by changing the
|
|
883
|
+
* corresponding Safari settings in the UI and then inspecting
|
|
884
|
+
* `Library/Preferences/com.apple.mobilesafari.plist` file inside of the `com.apple.mobilesafari`
|
|
885
|
+
* app container within the simulator filesystem. The full path to Mobile Safari's container can
|
|
886
|
+
* be retrieved by running `xcrun simctl get_app_container <sim_udid> com.apple.mobilesafari
|
|
887
|
+
* data`. Use the `xcrun simctl spawn <sim_udid> defaults read <path_to_plist>` command to print
|
|
888
|
+
* the plist content to the Terminal.
|
|
889
|
+
*
|
|
890
|
+
* @group Simulator Only
|
|
891
|
+
* @returns {Promise<void>}
|
|
892
|
+
* @throws {Error} if run on a real device or if the preferences argument is invalid
|
|
893
|
+
* @this {XCUITestDriver}
|
|
894
|
+
*/
|
|
895
|
+
async function mobileUpdateSafariPreferences(preferences) {
|
|
896
|
+
if (!this.isSimulator()) {
|
|
897
|
+
throw new Error('This extension is only available for Simulator');
|
|
898
|
+
}
|
|
899
|
+
if (!lodash_1.default.isPlainObject(preferences)) {
|
|
900
|
+
throw new driver_1.errors.InvalidArgumentError('"preferences" argument must be a valid object');
|
|
901
|
+
}
|
|
902
|
+
this.log.debug(`About to update Safari preferences: ${JSON.stringify(preferences)}`);
|
|
903
|
+
await /** @type {import('../driver').Simulator} */ (this.device).updateSafariSettings(preferences);
|
|
904
|
+
}
|
|
919
905
|
/**
|
|
920
906
|
* @this {XCUITestDriver}
|
|
921
907
|
* @param {timing.Timer} timer
|
|
@@ -934,6 +920,101 @@ async function generateAtomTimeoutError(timer) {
|
|
|
934
920
|
}
|
|
935
921
|
return new driver_1.errors.TimeoutError(message);
|
|
936
922
|
}
|
|
923
|
+
/**
|
|
924
|
+
* @this {XCUITestDriver}
|
|
925
|
+
* @param {any} atomsElement
|
|
926
|
+
* @returns {Promise<boolean>}
|
|
927
|
+
*/
|
|
928
|
+
async function tapWebElementNatively(atomsElement) {
|
|
929
|
+
// try to get the text of the element, which will be accessible in the
|
|
930
|
+
// native context
|
|
931
|
+
try {
|
|
932
|
+
const [text1, text2] = await bluebird_1.default.all([
|
|
933
|
+
this.executeAtom('get_text', [atomsElement]),
|
|
934
|
+
this.executeAtom('get_attribute_value', [atomsElement, 'value'])
|
|
935
|
+
]);
|
|
936
|
+
const text = text1 || text2;
|
|
937
|
+
if (!text) {
|
|
938
|
+
return false;
|
|
939
|
+
}
|
|
940
|
+
const els = await this.findNativeElementOrElements('accessibility id', text, true);
|
|
941
|
+
if (![1, 2].includes(els.length)) {
|
|
942
|
+
return false;
|
|
943
|
+
}
|
|
944
|
+
const el = els[0];
|
|
945
|
+
// use tap because on iOS 11.2 and below `nativeClick` crashes WDA
|
|
946
|
+
const rect = /** @type {import('@appium/types').Rect} */ (await this.proxyCommand(`/element/${support_1.util.unwrapElement(el)}/rect`, 'GET'));
|
|
947
|
+
if (els.length > 1) {
|
|
948
|
+
const el2 = els[1];
|
|
949
|
+
const rect2 = /** @type {import('@appium/types').Rect} */ (await this.proxyCommand(`/element/${support_1.util.unwrapElement(el2)}/rect`, 'GET'));
|
|
950
|
+
if (rect.x !== rect2.x || rect.y !== rect2.y
|
|
951
|
+
|| rect.width !== rect2.width || rect.height !== rect2.height) {
|
|
952
|
+
// These 2 native elements are not referring to the same web element
|
|
953
|
+
return false;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
await this.mobileTap(rect.x + rect.width / 2, rect.y + rect.height / 2);
|
|
957
|
+
return true;
|
|
958
|
+
}
|
|
959
|
+
catch (err) {
|
|
960
|
+
// any failure should fall through and trigger the more elaborate
|
|
961
|
+
// method of clicking
|
|
962
|
+
this.log.warn(`Error attempting to click: ${err.message}`);
|
|
963
|
+
}
|
|
964
|
+
return false;
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* @param {any} id
|
|
968
|
+
* @returns {boolean}
|
|
969
|
+
*/
|
|
970
|
+
function isValidElementIdentifier(id) {
|
|
971
|
+
if (!lodash_1.default.isString(id) && !lodash_1.default.isNumber(id)) {
|
|
972
|
+
return false;
|
|
973
|
+
}
|
|
974
|
+
if (lodash_1.default.isString(id) && lodash_1.default.isEmpty(id)) {
|
|
975
|
+
return false;
|
|
976
|
+
}
|
|
977
|
+
if (lodash_1.default.isNumber(id) && isNaN(id)) {
|
|
978
|
+
return false;
|
|
979
|
+
}
|
|
980
|
+
return true;
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* Creates a JavaScript Cookie
|
|
984
|
+
*
|
|
985
|
+
* @param {string} key
|
|
986
|
+
* @param {string} value
|
|
987
|
+
* @param {CookieOptions} [options={}]
|
|
988
|
+
* @returns {string}
|
|
989
|
+
*/
|
|
990
|
+
function createJSCookie(key, value, options = {}) {
|
|
991
|
+
return [
|
|
992
|
+
encodeURIComponent(key),
|
|
993
|
+
'=',
|
|
994
|
+
value,
|
|
995
|
+
options.expires ? `; expires=${options.expires}` : '',
|
|
996
|
+
options.path ? `; path=${options.path}` : '',
|
|
997
|
+
options.domain ? `; domain=${options.domain}` : '',
|
|
998
|
+
options.secure ? '; secure' : '',
|
|
999
|
+
].join('');
|
|
1000
|
+
}
|
|
1001
|
+
/**
|
|
1002
|
+
* @this {XCUITestDriver}
|
|
1003
|
+
* @param {import('@appium/types').Cookie} cookie
|
|
1004
|
+
* @returns {Promise<any>}
|
|
1005
|
+
*/
|
|
1006
|
+
async function _deleteCookie(cookie) {
|
|
1007
|
+
const url = `http${cookie.secure ? 's' : ''}://${cookie.domain}${cookie.path}`;
|
|
1008
|
+
return await ( /** @type {RemoteDebugger} */(this.remote)).deleteCookie(cookie.name, url);
|
|
1009
|
+
}
|
|
1010
|
+
/**
|
|
1011
|
+
* @typedef {Object} CookieOptions
|
|
1012
|
+
* @property {string} [expires]
|
|
1013
|
+
* @property {string} [path]
|
|
1014
|
+
* @property {string} [domain]
|
|
1015
|
+
* @property {boolean} [secure]
|
|
1016
|
+
* @property {boolean} [httpOnly]
|
|
1017
|
+
*/
|
|
937
1018
|
/**
|
|
938
1019
|
* @typedef {import('../driver').XCUITestDriver} XCUITestDriver
|
|
939
1020
|
* @typedef {import('@appium/types').Rect} Rect
|