forked from appium/appium-xcuitest-driver
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfind-e2e-specs.js
473 lines (410 loc) · 16.5 KB
/
find-e2e-specs.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
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
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import B from 'bluebird';
import _ from 'lodash';
import {retryInterval} from 'asyncbox';
import {
extractCapabilityValue,
amendCapabilities,
UICATALOG_CAPS,
UICATALOG_BUNDLE_ID,
PLATFORM_VERSION,
} from '../desired';
import {PREDICATE_SEARCH, CLASS_CHAIN_SEARCH} from '../helpers/element';
import {initSession, deleteSession, getUsePrebuiltWDACaps, MOCHA_TIMEOUT} from '../helpers/session';
import {util} from 'appium/support';
chai.should();
chai.use(chaiAsPromised);
const TEST_PAUSE_DURATION = 500;
const PV_ABOVE_13 = util.compareVersions(PLATFORM_VERSION, '>=', '13.0');
// there are some differences in the apps
const FIRST_ELEMENT = PV_ABOVE_13 ? 'Activity Indicators' : 'Action Sheets';
const APP_TITLE = PV_ABOVE_13 ? 'UIKitCatalog' : 'UICatalog';
describe('XCUITestDriver - find -', function () {
this.timeout(MOCHA_TIMEOUT);
let driver;
before(async function () {
const caps = amendCapabilities(UICATALOG_CAPS, await getUsePrebuiltWDACaps());
driver = await initSession(caps);
});
after(async function () {
try {
await driver.terminateApp(UICATALOG_BUNDLE_ID);
} catch (e) {
// eslint-disable-next-line no-console
console.error(e);
}
await deleteSession();
});
// establish that the basic things work as we imagine
describe('basics', function () {
let el1;
before(async function () {
el1 = await driver.$('~Buttons');
el1.elementId.should.exist;
});
it('should find an element within descendants', async function () {
let el2 = await el1.$('XCUIElementTypeStaticText');
(await el2.getAttribute('name')).should.contain('Buttons');
});
it('should not find an element not within itself', async function () {
const el2 = await el1.$('class name', 'XCUIElementTypeNavigationBar');
el2.error.error.should.equal('no such element');
});
it.skip('should find some elements within itself', async function () {
let els = await el1.$$('XCUIElementTypeStaticText');
els.should.have.length(2);
});
it('should not find elements not within itself', async function () {
let els = await el1.$$('XCUIElementTypeNavigationBar');
els.should.have.length(0);
});
});
// make sure that elements are mixed up
describe.skip('no mix up', function () {
after(async function () {
await driver.back();
});
it('should not allow found elements to be mixed up', async function () {
let table = await driver.$('XCUIElementTypeTable');
let el1 = await table.$('XCUIElementTypeStaticText');
let el1Name = await el1.getAttribute('name');
await el1.click();
// we need a hard pause, because if we haven't shifted views yet
// we will have the previous elements, so the get command will be fulfilled.
await B.delay(1000);
await driver.setImplicitTimeout(5000);
table = await driver.$('XCUIElementTypeTable');
let el2 = await driver.$('XCUIElementTypeStaticText');
let el2Name = await el2.getAttribute('name');
el1.should.not.equal(el2);
el1Name.should.not.equal(el2Name);
// el1 is gone, so it doesn't have a name anymore
(await el1.getAttribute('name')).should.equal('');
});
});
describe('by id', function () {
it('should find a single element by id', async function () {
let el = await driver.$('~Alert Views');
el.elementId.should.exist;
});
it('should find a single element by id wrapped in array for multi', async function () {
let els = await driver.$$('~Alert Views');
els.should.have.length(1);
});
it('should first attempt to match accessibility id', async function () {
let el = await driver.$('~Alert Views');
(await el.getAttribute('label')).should.equal('Alert Views');
});
it('should attempt to match by string if no accessibility id matches', async function () {
let el = await driver.$('~Alert Views');
(await el.getAttribute('label')).should.equal('Alert Views');
});
it.skip('should use a localized string if the id is a localization key', async function () {
let el = await driver.$('#main.button.computeSum');
(await el.getAttribute('label')).should.equal('Compute Sum');
});
it.skip('should be able to return multiple matches', async function () {
let els = await driver.$$('#Cell');
els.length.should.be.greaterThan(1);
});
});
describe('by xpath', function () {
describe('individual calls', function () {
before(async function () {
// before anything, try to go back
// otherwise the tests will fail erroneously
await driver.back();
// and make sure we are at the top of the page
try {
await driver.execute('mobile: scroll', {direction: 'up'});
} catch (ign) {}
});
beforeEach(async function () {
// go into the right page
await retryInterval(10, 500, async () => {
let el = await driver.$('~Buttons');
await el.click();
(await driver.$$('~Button')).should.have.length.at.least(1);
});
});
afterEach(async function () {
await driver.back();
});
it('should respect implicit wait', async function () {
await driver.setImplicitTimeout(5000);
let begin = Date.now();
const el = await driver.$('//something_not_there');
el.error.error.should.equal('no such element');
(Date.now() - begin).should.be.above(5000);
});
it.skip('should return the last button', async function () {
let el = await driver.$('//XCUIElementTypeButton[last()]');
(await el.getAttribute('name')).should.equal('Button'); // this is the name of the last button
});
it('should return a single element', async function () {
let el = await driver.$('//XCUIElementTypeButton');
(await el.getAttribute('name')).should.equal(APP_TITLE);
});
it('should return multiple elements', async function () {
let els = await driver.$$('//XCUIElementTypeButton');
els.should.have.length.above(4);
});
it('should filter by name', async function () {
let el = await driver.$(`//XCUIElementTypeButton[@name='X Button']`);
(await el.getAttribute('name')).should.equal('X Button');
});
it('should know how to restrict root-level elements', async function () {
const el = await driver.$('/XCUIElementTypeButton');
el.error.error.should.equal('no such element');
});
it('should search an extended path by child', async function () {
// pause a moment or the next command gets stuck getting the xpath :(
await B.delay(TEST_PAUSE_DURATION);
let el;
try {
el = await driver.$('//XCUIElementTypeNavigationBar/XCUIElementTypeStaticText');
} catch (err) {
el = await driver.$('//XCUIElementTypeNavigationBar/XCUIElementTypeOther');
}
(await el.getAttribute('name')).should.equal('Buttons');
});
it('should search an extended path by descendant', async function () {
let els = await driver.$$('//XCUIElementTypeTable//XCUIElementTypeButton');
let texts = await B.all(_.map(els, (el) => el.getAttribute('name')));
texts.should.not.include('UICatalog');
texts.should.not.include('UIKitCatalog');
texts.should.include('X Button');
});
it.skip('should filter by indices', async function () {
let el = await driver.$('//XCUIElementTypeTable[1]//XCUIElementTypeButton[4]');
(await el.getAttribute('name')).should.equal('X Button');
});
it('should filter by partial text', async function () {
let el = await driver.$(
`//XCUIElementTypeTable//XCUIElementTypeButton[contains(@name, 'X')]`,
);
(await el.getAttribute('name')).should.equal('X Button');
});
});
describe.skip('multiple calls', function () {
let runs = 5;
before(async function () {
// go into the right page
let el = await driver.$('~Buttons');
await el.click();
});
after(async function () {
await driver.back();
});
let test = function (path, minLength) {
return function () {
it('should not crash', async function () {
let els = await driver.$$(path);
els.should.have.length.above(minLength);
});
};
};
describe.skip('finding specific path', function () {
for (let n = 0; n < runs; n++) {
describe(
`test ${n + 1}`,
test('//XCUIElementTypeApplication[0]/XCUIElementTypeWindow[0]', 17),
);
}
});
describe('finding //*', function () {
for (let n = 0; n < runs; n++) {
describe(`test ${n + 1}`, test('//*', 52));
}
});
});
});
describe('by accessibility id', function () {
afterEach(async function () {
await driver.back();
});
it('should find one element', async function () {
let el1 = await driver.$('~Alert Views');
await el1.click();
let el2 = await driver.$('~Okay / Cancel');
(await el2.getAttribute('name')).should.equal('Okay / Cancel');
});
it.skip('should find several elements', async function () {
let el1 = await driver.$('~Alert Views');
await el1.click();
let els = await driver.$$('~Okay / Cancel');
els.should.have.length(2);
});
it('should find an element beneath another element', async function () {
let el1 = await driver.$('XCUIElementTypeTable');
let el2 = await el1.$('~Alert Views');
el2.elementId.should.exist;
});
});
describe('by class name', function () {
afterEach(async function () {
await driver.back();
});
it('should return all image elements with internally generated ids', async function () {
let el = await driver.$('~Image View');
await el.click();
let els = await driver.$$('XCUIElementTypeImage');
els.length.should.be.above(0);
for (const el of els) {
el.elementId.should.exist;
}
});
describe('textfield case', function () {
it('should find only one textfield', async function () {
// TODO: this works locally but fails in CI.
if (
process.env.CI &&
extractCapabilityValue(UICATALOG_CAPS, 'appium:platformVersion') === '10.3'
) {
return this.skip();
}
let el1 = await driver.$('~Alert Views');
await el1.click();
let el2 = await driver.$('~Okay / Cancel');
let els = await el2.$$('XCUIElementTypeStaticText');
els.should.have.length(1);
});
});
});
describe('duplicate text field', function () {
before(async function () {
try {
const el = await driver.$('~Text Fields');
await driver.execute('mobile: scroll', {element: el.elementId, toVisible: true});
} catch (ign) {}
});
afterEach(async function () {
await driver.back();
});
after(async function () {
// make sure we scroll back so as not to mess up subsequent tests
const el = await driver.$('~Alert Views');
await driver.execute('mobile: scroll', {element: el.elementId, toVisible: true});
});
it('should find only one element per text field', async function () {
await driver.$('~Text Fields').click();
let els = await driver.$$('XCUIElementTypeTextField');
els.should.have.length(PV_ABOVE_13 ? 5 : 4);
});
it('should find only one element per secure text field', async function () {
await driver.$('~Text Fields').click();
let els = await driver.$$('XCUIElementTypeSecureTextField');
els.should.have.length(1);
});
});
describe('by predicate string', function () {
before(async function () {
// if we don't pause, WDA freaks out sometimes, especially on fast systems
await B.delay(TEST_PAUSE_DURATION);
});
it('should find invisible elements', async function () {
const selector = 'visible = 0';
let els = await driver.$$(`${PREDICATE_SEARCH}:${selector}`);
els.should.have.length.above(0);
});
it('should find elements with widths above 0', async function () {
const selector = 'wdRect.width >= 0';
let els = await driver.$$(`${PREDICATE_SEARCH}:${selector}`);
els.should.have.length.above(0);
});
it('should find elements with widths between 100 and 200', async function () {
const selector = 'wdRect.width BETWEEN {100,200}';
let els = await driver.$$(`${PREDICATE_SEARCH}:${selector}`);
els.should.have.length.above(0);
});
it('should find elements that end in the word "View" in the name', async function () {
const selector = "wdName LIKE '* View'";
let els = await driver.$$(`${PREDICATE_SEARCH}:${selector}`);
els.should.have.length.above(1);
});
it('should find elements that have x and y coordinates greater than 0', async function () {
const selector = 'wdRect.x >= 0 AND wdRect.y >= 0';
let els = await driver.$$(`${PREDICATE_SEARCH}:${selector}`);
els.should.have.length.above(1);
});
});
describe('by class chain', function () {
before(async function () {
// if we don't pause, WDA freaks out sometimes, especially on fast systems
await B.delay(TEST_PAUSE_DURATION);
});
it('should find elements', async function () {
const selector = 'XCUIElementTypeWindow';
let els = await driver.$$(`${CLASS_CHAIN_SEARCH}:${selector}`);
els.should.have.length.above(0);
});
it('should find child elements', async function () {
const selector = 'XCUIElementTypeWindow/*';
let els = await driver.$$(`${CLASS_CHAIN_SEARCH}:${selector}`);
els.should.have.length.above(0);
});
it('should find elements with index', async function () {
const selector = 'XCUIElementTypeWindow[1]/*';
let els = await driver.$$(`${CLASS_CHAIN_SEARCH}:${selector}`);
els.should.have.length.above(0);
});
it('should find elements with negative index', async function () {
const selector = 'XCUIElementTypeWindow/*[-1]';
let els = await driver.$$(`${CLASS_CHAIN_SEARCH}:${selector}`);
els.should.have.length(1);
});
});
describe('by css selector', function () {
before(async function () {
// if we don't pause, WDA freaks out sometimes, especially on fast systems
await B.delay(TEST_PAUSE_DURATION);
});
it('should find cell types', async function () {
let cellEls = await driver.$$('cell');
cellEls.should.have.length.above(1);
});
it('should find elements', async function () {
let els = await driver.$$('window');
els.should.have.length.above(0);
});
it('should find child elements', async function () {
let els = await driver.$$('window > *');
els.should.have.length.above(0);
});
it('should find elements with index', async function () {
let els = await driver.$$('window:nth-child(1) > *');
els.should.have.length.above(0);
});
it('should find elements with negative index', async function () {
let els = await driver.$$('window > *:nth-child(-1)');
els.should.have.length(1);
});
it('should work with a nested CSS selector', async function () {
let imageViewButtons = await driver.$$('cell > staticText[value="Image View"]');
imageViewButtons.should.have.length(1);
});
});
describe('magic first visible child xpath', function () {
it('should find the first visible child of an element', async function () {
let el = await driver.$('XCUIElementTypeTable');
let child = await el.$('/*[@firstVisible="true"]');
await child.getAttribute('type').should.eventually.eql('XCUIElementTypeCell');
// do another call and double-check the different quote/spacing works
let grandchild = await child.$("/*[@firstVisible = 'true']");
const type = await grandchild.getAttribute('type');
if (type === 'XCUIElementTypeStaticText') {
await grandchild.getAttribute('name').should.eventually.eql(FIRST_ELEMENT);
} else {
type.should.equal('XCUIElementTypeOther');
}
});
});
describe('magic scrollable descendents xpath', function () {
it('should find any scrollable elements', async function () {
let els = await driver.$$('//*[@scrollable="true"]');
els.should.have.length(1);
await els[0].getAttribute('type').should.eventually.eql('XCUIElementTypeTable');
});
});
});