appium-xcuitest-driver 10.13.0 → 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.
Files changed (33) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/build/lib/commands/certificate.d.ts +14 -19
  3. package/build/lib/commands/certificate.d.ts.map +1 -1
  4. package/build/lib/commands/certificate.js +24 -31
  5. package/build/lib/commands/certificate.js.map +1 -1
  6. package/build/lib/commands/element.d.ts +83 -67
  7. package/build/lib/commands/element.d.ts.map +1 -1
  8. package/build/lib/commands/element.js +111 -134
  9. package/build/lib/commands/element.js.map +1 -1
  10. package/build/lib/commands/file-movement.d.ts +31 -42
  11. package/build/lib/commands/file-movement.d.ts.map +1 -1
  12. package/build/lib/commands/file-movement.js +146 -205
  13. package/build/lib/commands/file-movement.js.map +1 -1
  14. package/build/lib/commands/find.d.ts +20 -12
  15. package/build/lib/commands/find.d.ts.map +1 -1
  16. package/build/lib/commands/find.js +27 -65
  17. package/build/lib/commands/find.js.map +1 -1
  18. package/build/lib/commands/navigation.d.ts.map +1 -1
  19. package/build/lib/commands/navigation.js +12 -14
  20. package/build/lib/commands/navigation.js.map +1 -1
  21. package/build/lib/commands/web.d.ts.map +1 -1
  22. package/build/lib/commands/web.js +10 -1
  23. package/build/lib/commands/web.js.map +1 -1
  24. package/lib/commands/{certificate.js → certificate.ts} +55 -50
  25. package/lib/commands/element.ts +419 -0
  26. package/lib/commands/{file-movement.js → file-movement.ts} +212 -235
  27. package/lib/commands/find.ts +277 -0
  28. package/lib/commands/navigation.js +20 -14
  29. package/lib/commands/web.ts +9 -1
  30. package/npm-shrinkwrap.json +2 -2
  31. package/package.json +1 -1
  32. package/lib/commands/element.js +0 -423
  33. package/lib/commands/find.js +0 -205
@@ -1,423 +0,0 @@
1
- import _ from 'lodash';
2
- import {errors} from 'appium/driver';
3
- import {util} from 'appium/support';
4
-
5
- /**
6
- * @this {XCUITestDriver}
7
- */
8
- export async function elementDisplayed(el) {
9
- el = util.unwrapElement(el);
10
- if (this.isWebContext()) {
11
- const atomsElement = this.getAtomsElement(el);
12
- return await this.executeAtom('is_displayed', [atomsElement]);
13
- }
14
-
15
- return await this.proxyCommand(`/element/${el}/displayed`, 'GET');
16
- }
17
-
18
- /**
19
- * @this {XCUITestDriver}
20
- */
21
- export async function elementEnabled(el) {
22
- el = util.unwrapElement(el);
23
- if (this.isWebContext()) {
24
- const atomsElement = this.getAtomsElement(el);
25
- return await this.executeAtom('is_enabled', [atomsElement]);
26
- }
27
-
28
- return await this.proxyCommand(`/element/${el}/enabled`, 'GET');
29
- }
30
-
31
- /**
32
- * @this {XCUITestDriver}
33
- */
34
- export async function elementSelected(el) {
35
- el = util.unwrapElement(el);
36
- if (this.isWebContext()) {
37
- const atomsElement = this.getAtomsElement(el);
38
- return await this.executeAtom('is_selected', [atomsElement]);
39
- }
40
-
41
- return await this.proxyCommand(`/element/${el}/selected`, 'GET');
42
- }
43
-
44
- /**
45
- * @this {XCUITestDriver}
46
- */
47
- export async function getName(el) {
48
- el = util.unwrapElement(el);
49
- if (this.isWebContext()) {
50
- const atomsElement = this.getAtomsElement(el);
51
- const script = 'return arguments[0].tagName.toLowerCase()';
52
- return await this.executeAtom('execute_script', [script, [atomsElement]]);
53
- }
54
-
55
- return await this.proxyCommand(`/element/${el}/name`, 'GET');
56
- }
57
-
58
- /**
59
- * @this {XCUITestDriver}
60
- */
61
- export async function getNativeAttribute(attribute, el) {
62
- if (attribute === 'contentSize') {
63
- // don't proxy requests for the content size of a scrollable element
64
- return await this.getContentSize(el);
65
- }
66
-
67
- el = util.unwrapElement(el);
68
-
69
- // otherwise let WDA handle attribute requests
70
- let value = /** @type {string|number|null|undefined|boolean} */ (
71
- await this.proxyCommand(`/element/${el}/attribute/${attribute}`, 'GET')
72
- );
73
- // Transform the result for the case when WDA returns an integer representation for a boolean value
74
- if ([0, 1].includes(/** @type {number} */ (value))) {
75
- value = !!value;
76
- }
77
- // The returned value must be of type string according to https://www.w3.org/TR/webdriver/#get-element-attribute
78
- return _.isNull(value) || _.isString(value) ? value : JSON.stringify(value);
79
- }
80
-
81
- /**
82
- * @this {XCUITestDriver}
83
- */
84
- export async function getAttribute(attribute, el) {
85
- el = util.unwrapElement(el);
86
- if (!this.isWebContext()) {
87
- return await this.getNativeAttribute(attribute, el);
88
- }
89
- const atomsElement = this.getAtomsElement(el);
90
- return await this.executeAtom('get_attribute_value', [atomsElement, attribute]);
91
- }
92
-
93
- /**
94
- * @this {XCUITestDriver}
95
- */
96
- export async function getProperty(property, el) {
97
- el = util.unwrapElement(el);
98
- if (!this.isWebContext()) {
99
- return await this.getNativeAttribute(property, el);
100
- }
101
- const atomsElement = this.getAtomsElement(el);
102
- return await this.executeAtom('get_attribute_value', [atomsElement, property]);
103
- }
104
-
105
- /**
106
- * @this {XCUITestDriver}
107
- */
108
- export async function getText(el) {
109
- el = util.unwrapElement(el);
110
- if (!this.isWebContext()) {
111
- return await this.proxyCommand(`/element/${el}/text`, 'GET');
112
- }
113
- let atomsElement = this.getAtomsElement(el);
114
- return await this.executeAtom('get_text', [atomsElement]);
115
- }
116
-
117
- /**
118
- * @this {XCUITestDriver}
119
- * @returns {Promise<import('@appium/types').Rect>}
120
- */
121
- export async function getElementRect(el) {
122
- if (this.isWebContext()) {
123
- // Mobile safari doesn't support rect
124
- const {x, y} = await this.getLocation(el);
125
- const {width, height} = await this.getSize(el);
126
- return {x, y, width, height};
127
- }
128
-
129
- el = util.unwrapElement(el);
130
- return await this.getNativeRect(el);
131
- }
132
-
133
- /**
134
- * Get the position of an element on screen
135
- *
136
- * @param {string|Element} elementId - the element ID
137
- * @returns {Promise<Position>} The position of the element
138
- * @deprecated Use {@linkcode XCUITestDriver.getElementRect} instead
139
- * @this {XCUITestDriver}
140
- */
141
- export async function getLocation(elementId) {
142
- const el = util.unwrapElement(elementId);
143
- if (this.isWebContext()) {
144
- const atomsElement = this.getAtomsElement(el);
145
- let loc = await this.executeAtom('get_top_left_coordinates', [atomsElement]);
146
- if (this.opts.absoluteWebLocations) {
147
- const script =
148
- 'return [' +
149
- 'Math.max(window.pageXOffset,document.documentElement.scrollLeft,document.body.scrollLeft),' +
150
- 'Math.max(window.pageYOffset,document.documentElement.scrollTop,document.body.scrollTop)];';
151
- const [xOffset, yOffset] = /** @type {[number, number]} */ (await this.execute(script));
152
- loc.x += xOffset;
153
- loc.y += yOffset;
154
- }
155
- return loc;
156
- }
157
-
158
- const rect = await this.getElementRect(el);
159
- return {x: rect.x, y: rect.y};
160
- }
161
-
162
- /**
163
- * Alias for {@linkcode XCUITestDriver.getLocation}
164
- * @param {string|Element} elementId - the element ID
165
- * @returns {Promise<Position>} The position of the element
166
- * @deprecated Use {@linkcode XCUITestDriver.getElementRect} instead
167
- * @this {XCUITestDriver}
168
- */
169
- export async function getLocationInView(elementId) {
170
- return await this.getLocation(elementId);
171
- }
172
-
173
- /**
174
- * Get the size of an element
175
- * @param {string|Element} el - the element ID
176
- * @returns {Promise<Size>} The position of the element
177
- * @this {XCUITestDriver}
178
- */
179
- export async function getSize(el) {
180
- el = util.unwrapElement(el);
181
- if (this.isWebContext()) {
182
- return await this.executeAtom('get_size', [this.getAtomsElement(el)]);
183
- }
184
-
185
- const rect = await this.getElementRect(el);
186
- return {width: rect.width, height: rect.height};
187
- }
188
-
189
- /**
190
- * Alias for {@linkcode setValue}
191
- *
192
- * @param {string} value - the value to set
193
- * @param {string} el - the element to set the value of
194
- * @deprecated
195
- * @this {XCUITestDriver}
196
- */
197
- export async function setValueImmediate(value, el) {
198
- // WDA does not provide no way to set the value directly
199
- this.log.info(
200
- 'There is currently no way to bypass typing using XCUITest. Setting value through keyboard',
201
- );
202
- await this.setValue(value, el);
203
- }
204
-
205
- /**
206
- * @this {XCUITestDriver}
207
- */
208
- export async function setValue(value, el) {
209
- el = util.unwrapElement(el);
210
- if (!this.isWebContext()) {
211
- await this.proxyCommand(`/element/${el}/value`, 'POST', {
212
- value: prepareInputValue(value),
213
- });
214
- return;
215
- }
216
-
217
- const atomsElement = this.getAtomsElement(el);
218
- await this.executeAtom('click', [atomsElement]);
219
-
220
- if (this.opts.sendKeyStrategy !== 'oneByOne') {
221
- await this.setValueWithWebAtom(atomsElement, value);
222
- return;
223
- }
224
- for (const char of prepareInputValue(value)) {
225
- await this.setValueWithWebAtom(atomsElement, char);
226
- }
227
- }
228
-
229
- /**
230
- * Set value with Atom for Web. This method calls `type` atom only.
231
- * Expected to be called as part of {@linkcode setValue}.
232
- * @this {XCUITestDriver}
233
- * @param {import('./types').AtomsElement<string>} atomsElement A target element to type the given value.
234
- * @param {string|string[]} value The actual text to type.
235
- */
236
- export async function setValueWithWebAtom(atomsElement, value) {
237
- await this.executeAtom('type', [atomsElement, value]);
238
-
239
- if (this.opts.skipTriggerInputEventAfterSendkeys) {
240
- return;
241
- }
242
-
243
- function triggerInputEvent(/** @type {EventTarget & {_valueTracker?: any}} */input) {
244
- let lastValue = '';
245
- let event = new Event('input', { bubbles: true });
246
- let tracker = input._valueTracker;
247
- if (tracker) {
248
- tracker.setValue(lastValue);
249
- }
250
- input.dispatchEvent(event);
251
- }
252
-
253
- const scriptAsString = `return (${triggerInputEvent}).apply(null, arguments)`;
254
- await this.executeAtom('execute_script', [scriptAsString, [atomsElement]]);
255
- }
256
-
257
- /**
258
- * Send keys to the app
259
- * @param {string[]} value - Array of keys to send
260
- * @this {XCUITestDriver}
261
- * @deprecated Use {@linkcode XCUITestDriver.setValue} instead
262
- */
263
- export async function keys(value) {
264
- await this.proxyCommand('/wda/keys', 'POST', {
265
- value: prepareInputValue(value),
266
- });
267
- }
268
-
269
- /**
270
- * @this {XCUITestDriver}
271
- */
272
- export async function clear(el) {
273
- el = util.unwrapElement(el);
274
- if (this.isWebContext()) {
275
- const atomsElement = this.getAtomsElement(el);
276
- await this.executeAtom('clear', [atomsElement]);
277
- return;
278
- }
279
- await this.proxyCommand(`/element/${el}/clear`, 'POST');
280
- }
281
-
282
- /**
283
- * @this {XCUITestDriver}
284
- */
285
- export async function getContentSize(el) {
286
- if (this.isWebContext()) {
287
- throw new errors.NotYetImplementedError(
288
- 'Support for getContentSize for web context is not yet implemented. Please contact an Appium dev',
289
- );
290
- }
291
-
292
- const type = await this.getAttribute('type', el);
293
-
294
- if (type !== 'XCUIElementTypeTable' && type !== 'XCUIElementTypeCollectionView') {
295
- throw new Error(
296
- `Can't get content size for type '${type}', only for ` + `tables and collection views`,
297
- );
298
- }
299
- let locator = '*';
300
- if (type === 'XCUIElementTypeTable') {
301
- // only find table cells, not just any children
302
- locator = 'XCUIElementTypeCell';
303
- }
304
-
305
- let contentHeight = 0;
306
- const children = await this.findElOrEls(`class chain`, locator, true, el);
307
- if (children.length === 1) {
308
- // if we know there's only one element, we can optimize to make just one
309
- // call to WDA
310
- const rect = await this.getElementRect(_.head(children));
311
- contentHeight = rect.height;
312
- } else if (children.length) {
313
- // otherwise if we have multiple elements, logic differs based on element
314
- // type
315
- switch (type) {
316
- case 'XCUIElementTypeTable': {
317
- const firstRect = await this.getElementRect(_.head(children));
318
- const lastRect = await this.getElementRect(_.last(children));
319
- contentHeight = lastRect.y + lastRect.height - firstRect.y;
320
- break;
321
- }
322
- case 'XCUIElementTypeCollectionView': {
323
- let elsInRow = 1; // we know there must be at least one element in the row
324
- let firstRect = await this.getElementRect(_.head(children));
325
- let initialRects = [firstRect];
326
- for (let i = 1; i < children.length; i++) {
327
- const rect = await this.getElementRect(children[i]);
328
- initialRects.push(rect);
329
- if (rect.y !== firstRect.y) {
330
- elsInRow = i;
331
- break;
332
- }
333
- }
334
- const spaceBetweenEls =
335
- initialRects[elsInRow].y -
336
- initialRects[elsInRow - 1].y -
337
- initialRects[elsInRow - 1].height;
338
- const numRows = Math.ceil(children.length / elsInRow);
339
-
340
- // assume all cells are the same height
341
- contentHeight = numRows * firstRect.height + spaceBetweenEls * (numRows - 1);
342
- break;
343
- }
344
- default:
345
- throw new Error(
346
- `Programming error: type '${type}' was not ` +
347
- `valid but should have already been rejected`,
348
- );
349
- }
350
- }
351
- const size = await this.getSize(el);
352
- const origin = await this.getLocationInView(el);
353
- // attributes have to be strings, so stringify this up
354
- return JSON.stringify({
355
- width: size.width,
356
- height: size.height,
357
- top: origin.y,
358
- left: origin.x,
359
- scrollableOffset: contentHeight,
360
- });
361
- }
362
-
363
- /**
364
- * @this {XCUITestDriver}
365
- * @returns {Promise<Rect>}
366
- */
367
- export async function getNativeRect(el) {
368
- return /** @type {Rect} */ (await this.proxyCommand(`/element/${el}/rect`, 'GET'));
369
- }
370
-
371
-
372
- /**
373
- * Prepares the input value to be passed as an argument to WDA.
374
- *
375
- * @param {string|string[]|number} inp The actual text to type.
376
- * @example
377
- * ```js
378
- * // Acceptable values of `inp`:
379
- * ['some text']
380
- * ['s', 'o', 'm', 'e', ' ', 't', 'e', 'x', 't']
381
- * 'some text'
382
- * 1234
383
- * ```
384
- * @throws {Error} If the value is not acceptable for input
385
- * @returns {string[]} The preprocessed value
386
- */
387
- function prepareInputValue(inp) {
388
- if (![_.isArray, _.isString, _.isFinite].some((f) => f(inp))) {
389
- throw new Error(
390
- `Only strings, numbers and arrays are supported as input arguments. ` +
391
- `Received: ${JSON.stringify(inp)}`,
392
- );
393
- }
394
-
395
- // make it into a string, so then we assure
396
- // the array items are single characters
397
- if (_.isArray(inp)) {
398
- inp = inp.join('');
399
- } else if (_.isFinite(inp)) {
400
- inp = `${inp}`;
401
- }
402
- // The `split` method must not be used on the string
403
- // to properly handle all Unicode code points
404
- return [...String(inp)].map((k) => {
405
- if (['\uE006', '\uE007'].includes(k)) {
406
- // RETURN or ENTER
407
- return '\n';
408
- }
409
- if (['\uE003', '\ue017'].includes(k)) {
410
- // BACKSPACE or DELETE
411
- return '\b';
412
- }
413
- return k;
414
- });
415
- }
416
-
417
- /**
418
- * @typedef {import('../driver').XCUITestDriver} XCUITestDriver
419
- * @typedef {import('@appium/types').Element} Element
420
- * @typedef {import('@appium/types').Position} Position
421
- * @typedef {import('@appium/types').Size} Size
422
- * @typedef {import('@appium/types').Rect} Rect
423
- */
@@ -1,205 +0,0 @@
1
- import _ from 'lodash';
2
- import {CssConverter} from '../css-converter';
3
- import {errors} from 'appium/driver';
4
- import {util} from 'appium/support';
5
-
6
- /**
7
- * @this {XCUITestDriver}
8
- */
9
- export async function findElOrEls(strategy, selector, mult, context) {
10
- if (this.isWebview()) {
11
- return await this.findWebElementOrElements(strategy, selector, mult, context);
12
- } else {
13
- return await this.findNativeElementOrElements(strategy, selector, mult, context);
14
- }
15
- }
16
-
17
- /**
18
- * @this {XCUITestDriver}
19
- * @privateRemarks I'm not sure what these objects are; they aren't `Element`.
20
- * @returns {Promise<any|any[]|undefined>}
21
- */
22
- export async function findNativeElementOrElements(strategy, selector, mult, context) {
23
- const initSelector = selector;
24
- let rewroteSelector = false;
25
- if (strategy === '-ios predicate string') {
26
- // WebDriverAgent uses 'predicate string'
27
- strategy = 'predicate string';
28
- } else if (strategy === '-ios class chain') {
29
- // WebDriverAgent uses 'class chain'
30
- strategy = WDA_CLASS_CHAIN_STRATEGY;
31
- } else if (strategy === 'css selector') {
32
- strategy = WDA_CLASS_CHAIN_STRATEGY;
33
- selector = CssConverter.toIosClassChainSelector(selector);
34
- }
35
-
36
- if (strategy === 'class name') {
37
- // XCUITest classes have `XCUIElementType` prepended
38
- // first check if there is the old `UIA` prefix
39
- if (selector.startsWith('UIA')) {
40
- selector = selector.substring(3);
41
- }
42
- // now check if we need to add `XCUIElementType`
43
- if (!selector.startsWith('XCUIElementType')) {
44
- selector = stripViewFromSelector(`XCUIElementType${selector}`);
45
- rewroteSelector = true;
46
- }
47
- }
48
-
49
- if (strategy === 'xpath' && MAGIC_FIRST_VIS_CHILD_SEL.test(selector)) {
50
- return await this.getFirstVisibleChild(mult, context);
51
- } else if (strategy === 'xpath' && MAGIC_SCROLLABLE_SEL.test(selector)) {
52
- [strategy, selector] = rewriteMagicScrollable(mult, this.log);
53
- } else if (strategy === 'xpath') {
54
- // Replace UIA if it comes after a forward slash or is at the beginning of the string
55
- selector = selector.replace(/(^|\/)(UIA)([^[/]+)/g, (str, g1, g2, g3) => {
56
- rewroteSelector = true;
57
- return g1 + stripViewFromSelector(`XCUIElementType${g3}`);
58
- });
59
- }
60
-
61
- if (rewroteSelector) {
62
- this.log.info(
63
- `Rewrote incoming selector from '${initSelector}' to ` +
64
- `'${selector}' to match XCUI type. You should consider ` +
65
- `updating your tests to use the new selectors directly`,
66
- );
67
- }
68
-
69
- return await this.doNativeFind(strategy, selector, mult, context);
70
- }
71
-
72
- /**
73
- * @this {XCUITestDriver}
74
- */
75
- export async function doNativeFind(strategy, selector, mult, context) {
76
- context = util.unwrapElement(context);
77
-
78
- let endpoint = `/element${context ? `/${context}/element` : ''}${mult ? 's' : ''}`;
79
-
80
- let body = {
81
- using: strategy,
82
- value: selector,
83
- };
84
-
85
- /** @type {import('./proxy-helper').AllowedHttpMethod} */
86
- const method = 'POST';
87
-
88
- // This is either an array is mult === true
89
- // or an object if mult === false
90
- /** @type {Element[]|undefined} */
91
- let els;
92
- try {
93
- await this.implicitWaitForCondition(async () => {
94
- try {
95
- els = /** @type {Element[]|undefined} */ (
96
- await this.proxyCommand(endpoint, method, body)
97
- );
98
- } catch {
99
- els = [];
100
- }
101
- // we succeed if we get some elements
102
- return !_.isEmpty(els);
103
- });
104
- } catch (err) {
105
- if (err.message?.match(/Condition unmet/)) {
106
- // condition was not met setting res to empty array
107
- els = [];
108
- } else {
109
- throw err;
110
- }
111
- }
112
- if (mult) {
113
- return els;
114
- }
115
- if (_.isEmpty(els)) {
116
- throw new errors.NoSuchElementError();
117
- }
118
- return els;
119
- }
120
-
121
- /**
122
- * @this {XCUITestDriver}
123
- */
124
- export async function getFirstVisibleChild(mult, context) {
125
- this.log.info(`Getting first visible child`);
126
- if (mult) {
127
- throw new Error('Cannot get multiple first visible children!');
128
- }
129
- if (!context) {
130
- throw new Error('Cannot get first visible child without a context element');
131
- }
132
- let index = 1;
133
- // loop through children via class-chain finds, until we run out of children
134
- // or we find a visible one. This loop looks infinite but its not, because at
135
- // some point the call to doNativeFind will throw with an Element Not Found
136
- // error, when the index gets higher than the number of child elements. This
137
- // is what we want because that error will halt the loop and make it all the
138
- // way to the client.
139
- while (true) {
140
- const strategy = WDA_CLASS_CHAIN_STRATEGY;
141
- const selector = `*[${index}]`;
142
- const nthChild = await this.doNativeFind(strategy, selector, false, context);
143
- const visible = await this.getAttribute('visible', nthChild);
144
- if (visible === 'true') {
145
- this.log.info(`Found first visible child at position ${index}`);
146
- return nthChild;
147
- }
148
- index++;
149
- }
150
- }
151
-
152
- // we override the xpath search for this first-visible-child selector, which
153
- // looks like /*[@firstVisible="true"]
154
- const MAGIC_FIRST_VIS_CHILD_SEL = /\/\*\[@firstVisible\s*=\s*('|")true\1\]/;
155
-
156
- // we likewise override xpath search to provide a shortcut for finding all
157
- // scrollable elements
158
- const MAGIC_SCROLLABLE_SEL = /\/\/\*\[@scrollable\s*=\s*('|")true\1\]/;
159
-
160
- const WDA_CLASS_CHAIN_STRATEGY = 'class chain';
161
-
162
- // Check if the word 'View' is appended to selector and if it is, strip it out
163
- function stripViewFromSelector(selector) {
164
- // Don't strip it out if it's one of these 4 element types
165
- // (see https://github.com/facebook/WebDriverAgent/blob/master/WebDriverAgentLib/Utilities/FBElementTypeTransformer.m for reference)
166
- const keepView = [
167
- 'XCUIElementTypeScrollView',
168
- 'XCUIElementTypeCollectionView',
169
- 'XCUIElementTypeTextView',
170
- 'XCUIElementTypeWebView',
171
- ].includes(selector);
172
-
173
- if (!keepView && selector.indexOf('View') === selector.length - 4) {
174
- return selector.substr(0, selector.length - 4);
175
- } else {
176
- return selector;
177
- }
178
- }
179
-
180
- /**
181
- *
182
- * @param {boolean} mult
183
- * @param {import('@appium/types').AppiumLogger|null} log
184
- * @returns {[string, string]}
185
- */
186
- function rewriteMagicScrollable(mult, log = null) {
187
- const pred = ['ScrollView', 'Table', 'CollectionView', 'WebView']
188
- .map((t) => `type == "XCUIElementType${t}"`)
189
- .join(' OR ');
190
- const strategy = WDA_CLASS_CHAIN_STRATEGY;
191
- let selector = '**/*[`' + pred + '`]';
192
- if (!mult) {
193
- selector += '[1]';
194
- }
195
- log?.info(
196
- 'Rewrote request for scrollable descendants to class chain ' +
197
- `format with selector '${selector}'`,
198
- );
199
- return [strategy, selector];
200
- }
201
-
202
- /**
203
- * @typedef {import('../driver').XCUITestDriver} XCUITestDriver
204
- * @typedef {import('@appium/types').Element} Element
205
- */