appium-xcuitest-driver 10.13.1 → 10.13.2
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 +6 -0
- package/build/lib/commands/certificate.d.ts +14 -19
- package/build/lib/commands/certificate.d.ts.map +1 -1
- package/build/lib/commands/certificate.js +24 -31
- package/build/lib/commands/certificate.js.map +1 -1
- package/build/lib/commands/element.d.ts +83 -67
- package/build/lib/commands/element.d.ts.map +1 -1
- package/build/lib/commands/element.js +111 -134
- package/build/lib/commands/element.js.map +1 -1
- package/build/lib/commands/file-movement.d.ts +31 -42
- package/build/lib/commands/file-movement.d.ts.map +1 -1
- package/build/lib/commands/file-movement.js +146 -205
- package/build/lib/commands/file-movement.js.map +1 -1
- package/build/lib/commands/find.d.ts +20 -12
- package/build/lib/commands/find.d.ts.map +1 -1
- package/build/lib/commands/find.js +27 -65
- package/build/lib/commands/find.js.map +1 -1
- package/build/lib/commands/navigation.d.ts.map +1 -1
- package/build/lib/commands/navigation.js +12 -14
- package/build/lib/commands/navigation.js.map +1 -1
- package/lib/commands/{certificate.js → certificate.ts} +55 -50
- package/lib/commands/element.ts +419 -0
- package/lib/commands/{file-movement.js → file-movement.ts} +212 -235
- package/lib/commands/find.ts +277 -0
- package/lib/commands/navigation.js +20 -14
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/lib/commands/element.js +0 -423
- package/lib/commands/find.js +0 -205
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import {errors} from 'appium/driver';
|
|
3
|
+
import {util} from 'appium/support';
|
|
4
|
+
import type {Element, Position, Size, Rect} from '@appium/types';
|
|
5
|
+
import type {XCUITestDriver} from '../driver';
|
|
6
|
+
import type {AtomsElement} from './types';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Checks whether an element is displayed.
|
|
10
|
+
*
|
|
11
|
+
* @param el - Element or element ID
|
|
12
|
+
*/
|
|
13
|
+
export async function elementDisplayed(this: XCUITestDriver, el: Element | string): Promise<boolean> {
|
|
14
|
+
const elementId = util.unwrapElement(el);
|
|
15
|
+
if (this.isWebContext()) {
|
|
16
|
+
const atomsElement = this.getAtomsElement(elementId);
|
|
17
|
+
return await this.executeAtom('is_displayed', [atomsElement]) as boolean;
|
|
18
|
+
}
|
|
19
|
+
return await this.proxyCommand(`/element/${elementId}/displayed`, 'GET') as boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Checks whether an element is enabled.
|
|
24
|
+
*
|
|
25
|
+
* @param el - Element or element ID
|
|
26
|
+
*/
|
|
27
|
+
export async function elementEnabled(this: XCUITestDriver, el: Element | string): Promise<boolean> {
|
|
28
|
+
const elementId = util.unwrapElement(el);
|
|
29
|
+
if (this.isWebContext()) {
|
|
30
|
+
const atomsElement = this.getAtomsElement(elementId);
|
|
31
|
+
return await this.executeAtom('is_enabled', [atomsElement]) as boolean;
|
|
32
|
+
}
|
|
33
|
+
return await this.proxyCommand(`/element/${elementId}/enabled`, 'GET') as boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Checks whether an element is selected.
|
|
38
|
+
*
|
|
39
|
+
* @param el - Element or element ID
|
|
40
|
+
*/
|
|
41
|
+
export async function elementSelected(this: XCUITestDriver, el: Element | string): Promise<boolean> {
|
|
42
|
+
const elementId = util.unwrapElement(el);
|
|
43
|
+
if (this.isWebContext()) {
|
|
44
|
+
const atomsElement = this.getAtomsElement(elementId);
|
|
45
|
+
return await this.executeAtom('is_selected', [atomsElement]) as boolean;
|
|
46
|
+
}
|
|
47
|
+
return await this.proxyCommand(`/element/${elementId}/selected`, 'GET') as boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Gets the tag/name of an element.
|
|
52
|
+
*
|
|
53
|
+
* @param el - Element or element ID
|
|
54
|
+
*/
|
|
55
|
+
export async function getName(this: XCUITestDriver, el: Element | string): Promise<string> {
|
|
56
|
+
const elementId = util.unwrapElement(el);
|
|
57
|
+
if (this.isWebContext()) {
|
|
58
|
+
const atomsElement = this.getAtomsElement(elementId);
|
|
59
|
+
const script = 'return arguments[0].tagName.toLowerCase()';
|
|
60
|
+
return await this.executeAtom('execute_script', [script, [atomsElement]]) as string;
|
|
61
|
+
}
|
|
62
|
+
return await this.proxyCommand(`/element/${elementId}/name`, 'GET') as string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Gets a native attribute (non-web) from an element.
|
|
67
|
+
*
|
|
68
|
+
* @param attribute - Attribute name
|
|
69
|
+
* @param el - Element or element ID
|
|
70
|
+
*/
|
|
71
|
+
export async function getNativeAttribute(
|
|
72
|
+
this: XCUITestDriver,
|
|
73
|
+
attribute: string,
|
|
74
|
+
el: Element | string,
|
|
75
|
+
): Promise<string | null> {
|
|
76
|
+
if (attribute === 'contentSize') {
|
|
77
|
+
return await this.getContentSize(el);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const elementId = util.unwrapElement(el);
|
|
81
|
+
let value = await this.proxyCommand(`/element/${elementId}/attribute/${attribute}`, 'GET') as
|
|
82
|
+
| string
|
|
83
|
+
| number
|
|
84
|
+
| null
|
|
85
|
+
| undefined
|
|
86
|
+
| boolean;
|
|
87
|
+
if ([0, 1].includes(value as number)) {
|
|
88
|
+
value = !!value;
|
|
89
|
+
}
|
|
90
|
+
return _.isNull(value) || _.isString(value) ? value : JSON.stringify(value);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Gets an element attribute (web or native).
|
|
95
|
+
*
|
|
96
|
+
* @param attribute - Attribute name
|
|
97
|
+
* @param el - Element or element ID
|
|
98
|
+
*/
|
|
99
|
+
export async function getAttribute(
|
|
100
|
+
this: XCUITestDriver,
|
|
101
|
+
attribute: string,
|
|
102
|
+
el: Element | string,
|
|
103
|
+
): Promise<string | null> {
|
|
104
|
+
const elementId = util.unwrapElement(el);
|
|
105
|
+
if (!this.isWebContext()) {
|
|
106
|
+
return await this.getNativeAttribute(attribute, elementId);
|
|
107
|
+
}
|
|
108
|
+
const atomsElement = this.getAtomsElement(elementId);
|
|
109
|
+
return await this.executeAtom('get_attribute_value', [atomsElement, attribute]) as string | null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Gets an element property (web) or native attribute fallback.
|
|
114
|
+
*
|
|
115
|
+
* @param property - Property name
|
|
116
|
+
* @param el - Element or element ID
|
|
117
|
+
*/
|
|
118
|
+
export async function getProperty(
|
|
119
|
+
this: XCUITestDriver,
|
|
120
|
+
property: string,
|
|
121
|
+
el: Element | string,
|
|
122
|
+
): Promise<string | null> {
|
|
123
|
+
const elementId = util.unwrapElement(el);
|
|
124
|
+
if (!this.isWebContext()) {
|
|
125
|
+
return await this.getNativeAttribute(property, elementId);
|
|
126
|
+
}
|
|
127
|
+
const atomsElement = this.getAtomsElement(elementId);
|
|
128
|
+
return await this.executeAtom('get_attribute_value', [atomsElement, property]) as string | null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Gets the text content of an element.
|
|
133
|
+
*
|
|
134
|
+
* @param el - Element or element ID
|
|
135
|
+
*/
|
|
136
|
+
export async function getText(this: XCUITestDriver, el: Element | string): Promise<string> {
|
|
137
|
+
const elementId = util.unwrapElement(el);
|
|
138
|
+
if (!this.isWebContext()) {
|
|
139
|
+
return await this.proxyCommand(`/element/${elementId}/text`, 'GET') as string;
|
|
140
|
+
}
|
|
141
|
+
const atomsElement = this.getAtomsElement(elementId);
|
|
142
|
+
return await this.executeAtom('get_text', [atomsElement]) as string;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Gets the bounding rect of an element.
|
|
147
|
+
*
|
|
148
|
+
* @param el - Element or element ID
|
|
149
|
+
*/
|
|
150
|
+
export async function getElementRect(this: XCUITestDriver, el: Element | string): Promise<Rect> {
|
|
151
|
+
if (this.isWebContext()) {
|
|
152
|
+
const {x, y} = await this.getLocation(el);
|
|
153
|
+
const {width, height} = await this.getSize(el);
|
|
154
|
+
return {x, y, width, height};
|
|
155
|
+
}
|
|
156
|
+
const elementId = util.unwrapElement(el);
|
|
157
|
+
return await this.getNativeRect(elementId);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Gets the top-left location of an element.
|
|
162
|
+
*
|
|
163
|
+
* @param elementId - Element or element ID
|
|
164
|
+
*/
|
|
165
|
+
export async function getLocation(this: XCUITestDriver, elementId: Element | string): Promise<Position> {
|
|
166
|
+
const el = util.unwrapElement(elementId);
|
|
167
|
+
if (this.isWebContext()) {
|
|
168
|
+
const atomsElement = this.getAtomsElement(el);
|
|
169
|
+
const loc = await this.executeAtom('get_top_left_coordinates', [atomsElement]) as Position;
|
|
170
|
+
if (this.opts.absoluteWebLocations) {
|
|
171
|
+
const script =
|
|
172
|
+
'return [' +
|
|
173
|
+
'Math.max(window.pageXOffset,document.documentElement.scrollLeft,document.body.scrollLeft),' +
|
|
174
|
+
'Math.max(window.pageYOffset,document.documentElement.scrollTop,document.body.scrollTop)];';
|
|
175
|
+
const [xOffset, yOffset] = await this.execute(script) as [number, number];
|
|
176
|
+
loc.x += xOffset;
|
|
177
|
+
loc.y += yOffset;
|
|
178
|
+
}
|
|
179
|
+
return loc;
|
|
180
|
+
}
|
|
181
|
+
const rect = await this.getElementRect(el);
|
|
182
|
+
return {x: rect.x, y: rect.y};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Alias for getLocation.
|
|
187
|
+
*
|
|
188
|
+
* @param elementId - Element or element ID
|
|
189
|
+
*/
|
|
190
|
+
export async function getLocationInView(this: XCUITestDriver, elementId: Element | string): Promise<Position> {
|
|
191
|
+
return await this.getLocation(elementId);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Gets the size of an element.
|
|
196
|
+
*
|
|
197
|
+
* @param el - Element or element ID
|
|
198
|
+
*/
|
|
199
|
+
export async function getSize(this: XCUITestDriver, el: Element | string): Promise<Size> {
|
|
200
|
+
const elementId = util.unwrapElement(el);
|
|
201
|
+
if (this.isWebContext()) {
|
|
202
|
+
return await this.executeAtom('get_size', [this.getAtomsElement(elementId)]) as Size;
|
|
203
|
+
}
|
|
204
|
+
const rect = await this.getElementRect(elementId);
|
|
205
|
+
return {width: rect.width, height: rect.height};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Legacy alias for setValue; always types via keyboard.
|
|
210
|
+
*
|
|
211
|
+
* @param value - Value to set
|
|
212
|
+
* @param el - Element or element ID
|
|
213
|
+
*/
|
|
214
|
+
export async function setValueImmediate(
|
|
215
|
+
this: XCUITestDriver,
|
|
216
|
+
value: string | string[] | number,
|
|
217
|
+
el: Element | string,
|
|
218
|
+
): Promise<void> {
|
|
219
|
+
this.log.info(
|
|
220
|
+
'There is currently no way to bypass typing using XCUITest. Setting value through keyboard',
|
|
221
|
+
);
|
|
222
|
+
await this.setValue(value, el);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Sets an element value (native or web).
|
|
227
|
+
*
|
|
228
|
+
* @param value - Value to set
|
|
229
|
+
* @param el - Element or element ID
|
|
230
|
+
*/
|
|
231
|
+
export async function setValue(
|
|
232
|
+
this: XCUITestDriver,
|
|
233
|
+
value: string | string[] | number,
|
|
234
|
+
el: Element | string,
|
|
235
|
+
): Promise<void> {
|
|
236
|
+
const elementId = util.unwrapElement(el);
|
|
237
|
+
if (!this.isWebContext()) {
|
|
238
|
+
await this.proxyCommand(`/element/${elementId}/value`, 'POST', {
|
|
239
|
+
value: prepareInputValue(value),
|
|
240
|
+
});
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const atomsElement = this.getAtomsElement(elementId);
|
|
245
|
+
await this.executeAtom('click', [atomsElement]);
|
|
246
|
+
|
|
247
|
+
if (this.opts.sendKeyStrategy !== 'oneByOne') {
|
|
248
|
+
await this.setValueWithWebAtom(atomsElement, value);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
for (const char of prepareInputValue(value)) {
|
|
252
|
+
await this.setValueWithWebAtom(atomsElement, char);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Types text into a web element using atoms.
|
|
258
|
+
*
|
|
259
|
+
* @param atomsElement - Target atoms element
|
|
260
|
+
* @param value - Text to type
|
|
261
|
+
*/
|
|
262
|
+
export async function setValueWithWebAtom(
|
|
263
|
+
this: XCUITestDriver,
|
|
264
|
+
atomsElement: AtomsElement<string>,
|
|
265
|
+
value: string | string[] | number,
|
|
266
|
+
): Promise<void> {
|
|
267
|
+
await this.executeAtom('type', [atomsElement, value]);
|
|
268
|
+
|
|
269
|
+
if (this.opts.skipTriggerInputEventAfterSendkeys) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function triggerInputEvent(input: EventTarget & {_valueTracker?: any}) {
|
|
274
|
+
const lastValue = '';
|
|
275
|
+
const event = new Event('input', {bubbles: true});
|
|
276
|
+
const tracker = input._valueTracker;
|
|
277
|
+
if (tracker) {
|
|
278
|
+
tracker.setValue(lastValue);
|
|
279
|
+
}
|
|
280
|
+
input.dispatchEvent(event);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const scriptAsString = `return (${triggerInputEvent}).apply(null, arguments)`;
|
|
284
|
+
await this.executeAtom('execute_script', [scriptAsString, [atomsElement]]);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Sends raw key sequences via WDA.
|
|
289
|
+
*
|
|
290
|
+
* @param value - Keys to send
|
|
291
|
+
*/
|
|
292
|
+
export async function keys(this: XCUITestDriver, value: string[] | string | number): Promise<void> {
|
|
293
|
+
await this.proxyCommand('/wda/keys', 'POST', {
|
|
294
|
+
value: prepareInputValue(value),
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Clears the contents of an element.
|
|
300
|
+
*
|
|
301
|
+
* @param el - Element or element ID
|
|
302
|
+
*/
|
|
303
|
+
export async function clear(this: XCUITestDriver, el: Element | string): Promise<void> {
|
|
304
|
+
const elementId = util.unwrapElement(el);
|
|
305
|
+
if (this.isWebContext()) {
|
|
306
|
+
const atomsElement = this.getAtomsElement(elementId);
|
|
307
|
+
await this.executeAtom('clear', [atomsElement]);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
await this.proxyCommand(`/element/${elementId}/clear`, 'POST');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Gets content size for table/collection views (native only).
|
|
315
|
+
*
|
|
316
|
+
* @param el - Element or element ID
|
|
317
|
+
*/
|
|
318
|
+
export async function getContentSize(this: XCUITestDriver, el: Element | string): Promise<string> {
|
|
319
|
+
if (this.isWebContext()) {
|
|
320
|
+
throw new errors.NotYetImplementedError(
|
|
321
|
+
'Support for getContentSize for web context is not yet implemented. Please contact an Appium dev',
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const type = await this.getAttribute('type', el);
|
|
326
|
+
|
|
327
|
+
if (type !== 'XCUIElementTypeTable' && type !== 'XCUIElementTypeCollectionView') {
|
|
328
|
+
throw new Error(
|
|
329
|
+
`Can't get content size for type '${type}', only for tables and collection views`,
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
let locator = '*';
|
|
333
|
+
if (type === 'XCUIElementTypeTable') {
|
|
334
|
+
locator = 'XCUIElementTypeCell';
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
let contentHeight = 0;
|
|
338
|
+
const children = await this.findElOrEls('class chain', locator, true, el);
|
|
339
|
+
if (children.length === 1) {
|
|
340
|
+
const rect = await this.getElementRect(children[0]);
|
|
341
|
+
contentHeight = rect.height;
|
|
342
|
+
} else if (children.length) {
|
|
343
|
+
switch (type) {
|
|
344
|
+
case 'XCUIElementTypeTable': {
|
|
345
|
+
const firstRect = await this.getElementRect(_.head(children) as Element);
|
|
346
|
+
const lastRect = await this.getElementRect(_.last(children) as Element);
|
|
347
|
+
contentHeight = lastRect.y + lastRect.height - firstRect.y;
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
case 'XCUIElementTypeCollectionView': {
|
|
351
|
+
let elsInRow = 1;
|
|
352
|
+
const firstRect = await this.getElementRect(_.head(children) as Element);
|
|
353
|
+
const initialRects = [firstRect];
|
|
354
|
+
for (let i = 1; i < children.length; i++) {
|
|
355
|
+
const rect = await this.getElementRect(children[i] as Element);
|
|
356
|
+
initialRects.push(rect);
|
|
357
|
+
if (rect.y !== firstRect.y) {
|
|
358
|
+
elsInRow = i;
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
const spaceBetweenEls =
|
|
363
|
+
initialRects[elsInRow].y -
|
|
364
|
+
initialRects[elsInRow - 1].y -
|
|
365
|
+
initialRects[elsInRow - 1].height;
|
|
366
|
+
const numRows = Math.ceil(children.length / elsInRow);
|
|
367
|
+
contentHeight = numRows * firstRect.height + spaceBetweenEls * (numRows - 1);
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
default:
|
|
371
|
+
throw new Error(
|
|
372
|
+
`Programming error: type '${type}' was not valid but should have already been rejected`,
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
const size = await this.getSize(el);
|
|
377
|
+
const origin = await this.getLocationInView(el);
|
|
378
|
+
return JSON.stringify({
|
|
379
|
+
width: size.width,
|
|
380
|
+
height: size.height,
|
|
381
|
+
top: origin.y,
|
|
382
|
+
left: origin.x,
|
|
383
|
+
scrollableOffset: contentHeight,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Gets the native rect of an element (no web fallback).
|
|
389
|
+
*
|
|
390
|
+
* @param el - Element or element ID
|
|
391
|
+
*/
|
|
392
|
+
export async function getNativeRect(this: XCUITestDriver, el: Element | string): Promise<Rect> {
|
|
393
|
+
const elementId = util.unwrapElement(el);
|
|
394
|
+
return await this.proxyCommand(`/element/${elementId}/rect`, 'GET') as Rect;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function prepareInputValue(inp: string | string[] | number): string[] {
|
|
398
|
+
if (![_.isArray, _.isString, _.isFinite].some((f) => f(inp))) {
|
|
399
|
+
throw new Error(
|
|
400
|
+
`Only strings, numbers and arrays are supported as input arguments. ` +
|
|
401
|
+
`Received: ${JSON.stringify(inp)}`,
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (_.isArray(inp)) {
|
|
406
|
+
inp = inp.join('');
|
|
407
|
+
} else if (_.isFinite(inp)) {
|
|
408
|
+
inp = `${inp}`;
|
|
409
|
+
}
|
|
410
|
+
return [...String(inp)].map((k) => {
|
|
411
|
+
if (['\uE006', '\uE007'].includes(k)) {
|
|
412
|
+
return '\n';
|
|
413
|
+
}
|
|
414
|
+
if (['\uE003', '\ue017'].includes(k)) {
|
|
415
|
+
return '\b';
|
|
416
|
+
}
|
|
417
|
+
return k;
|
|
418
|
+
});
|
|
419
|
+
}
|