@xenon-device-management/xenon 1.17.5 → 1.18.0

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/lib/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xenon-device-management/xenon",
3
- "version": "1.17.5",
3
+ "version": "1.18.0",
4
4
  "description": "Xenon - Intelligent Mobile Infrastructure. A self-healing device orchestration platform for Appium.",
5
5
  "main": "./lib/src/index.js",
6
6
  "exports": {
@@ -432,28 +432,46 @@ let InspectorService = class InspectorService {
432
432
  score: 95,
433
433
  });
434
434
  }
435
+ // On iOS `node.text` is `label || value`, so every locator that selects on
436
+ // it — predicate and xpath alike — has to name whichever of the two
437
+ // actually supplied it. Hardcoding `label` sends a page indicator, whose
438
+ // text lives in `value`, to a predicate that matches nothing.
439
+ const iosTextAttr = asText(node.attributes.label) === node.text ? 'label' : 'value';
435
440
  // 3. Platform Specific - Performance Tier
436
441
  if (platform === 'ios') {
437
442
  // iOS Predicate String - High Performance
438
- const predicate = `type == "${node.type}" AND label == "${node.text || node.label || ''}"`;
443
+ const predicateValue = node.text || node.label || '';
439
444
  if (node.text || node.label) {
440
445
  suggestions.push({
441
446
  strategy: '-ios predicate string',
442
- value: predicate,
443
- unique: true,
447
+ value: `type == "${node.type}" AND ${iosTextAttr} == "${predicateValue}"`,
448
+ // A compound predicate is at least as selective as its label term, so
449
+ // a unique label makes it unique. The converse does not hold — two
450
+ // elements sharing a label may differ by type — which makes this
451
+ // conservative rather than exact, and that is the right direction:
452
+ // it was previously hardcoded true, and a predicate badged unique
453
+ // was measured against a real device matching two elements.
454
+ unique: this.isUnique(uniqueness, 'text', predicateValue),
444
455
  score: 90,
445
456
  });
446
457
  }
447
458
  // iOS Class Chain - High Precision
448
- const classChain = `**/${node.type}[` +
449
- (node.attributes.name
450
- ? `name == "${node.attributes.name}"`
451
- : `label == "${node.text || ''}"`) +
452
- ']';
459
+ //
460
+ // The predicate MUST be wrapped in backticks. Without them Appium
461
+ // resolves nothing at all: measured against a real iPhone,
462
+ // `**/XCUIElementTypePageIndicator[name == "Page control"]` found 0
463
+ // elements and the same chain with backticks found 1. Every class chain
464
+ // this service produced was unusable, and each was badged unique — 280
465
+ // of them in a single snapshot of a home screen.
466
+ const chainValue = node.attributes.name ? String(node.attributes.name) : node.text || '';
467
+ const chainTerm = node.attributes.name ? 'name' : 'label';
453
468
  suggestions.push({
454
469
  strategy: '-ios class chain',
455
- value: classChain,
456
- unique: true,
470
+ value: `**/${node.type}[\`${chainTerm} == "${chainValue}"\`]`,
471
+ // Same index the accessibility-id suggestion uses, and the same value:
472
+ // a node's identity there is `name || label`, which is exactly what
473
+ // the chain selects on.
474
+ unique: this.isUnique(uniqueness, 'accessibility id', chainValue),
457
475
  score: 85,
458
476
  });
459
477
  }
@@ -495,9 +513,15 @@ let InspectorService = class InspectorService {
495
513
  });
496
514
  }
497
515
  else if (node.text) {
516
+ // `@text` is an Android attribute. An XCUIElement has name, label and
517
+ // value and no text at all, so every iOS suggestion in this shape
518
+ // resolved to nothing: measured on a real iPhone,
519
+ // `//XCUIElementTypePageIndicator[@text="Page control"]` found 0 and the
520
+ // same path with `@label` found 1.
521
+ const textAttr = platform === 'ios' ? iosTextAttr : 'text';
498
522
  suggestions.push({
499
523
  strategy: 'xpath',
500
- value: `//${tagName}[@text="${node.text}"]`,
524
+ value: `//${tagName}[@${textAttr}="${node.text}"]`,
501
525
  unique: this.isUnique(uniqueness, 'text', node.text),
502
526
  score: 60,
503
527
  });
@@ -197,6 +197,61 @@ describe('InspectorService hierarchy parsing', () => {
197
197
  (0, chai_1.expect)(root.name).to.equal('1');
198
198
  });
199
199
  });
200
+ describe('iOS locator syntax', () => {
201
+ // Two StaticTexts share the label "Done" and neither has a name, so both a
202
+ // predicate and a class chain built from that label are genuinely
203
+ // ambiguous. The button carries a name and is genuinely unique. The text
204
+ // field has a value and no label.
205
+ const XML = `<AppiumAUT>
206
+ <XCUIElementTypeApplication type="XCUIElementTypeApplication" name="MyApp" label="MyApp" x="0" y="0" width="390" height="844">
207
+ <XCUIElementTypeButton type="XCUIElementTypeButton" name="done-btn" label="Save" x="0" y="0" width="10" height="10" />
208
+ <XCUIElementTypeStaticText type="XCUIElementTypeStaticText" label="Done" x="0" y="20" width="10" height="10" />
209
+ <XCUIElementTypeStaticText type="XCUIElementTypeStaticText" label="Done" x="0" y="30" width="10" height="10" />
210
+ <XCUIElementTypeTextField type="XCUIElementTypeTextField" value="typed text" x="0" y="40" width="10" height="10" />
211
+ </XCUIElementTypeApplication>
212
+ </AppiumAUT>`;
213
+ const root = () => parse(XML, 'ios');
214
+ const byType = (t) => find(root(), (n) => n.type === t)[0];
215
+ it('wraps the class chain predicate in backticks', () => {
216
+ // Measured on a real iPhone: without backticks Appium resolves 0
217
+ // elements, with them it resolves 1. Every chain this produced was
218
+ // unusable — 280 of them in one home-screen snapshot.
219
+ (0, chai_1.expect)(locator(byType('XCUIElementTypeButton'), '-ios class chain').value).to.equal('**/XCUIElementTypeButton[`name == "done-btn"`]');
220
+ });
221
+ it('selects on label in the class chain when there is no name', () => {
222
+ (0, chai_1.expect)(locator(byType('XCUIElementTypeStaticText'), '-ios class chain').value).to.equal('**/XCUIElementTypeStaticText[`label == "Done"`]');
223
+ });
224
+ it('uses an attribute an XCUIElement actually has in xpath', () => {
225
+ // `@text` is Android's. iOS has name, label and value — and `@text`
226
+ // resolved to nothing on a real device.
227
+ const xpath = locator(byType('XCUIElementTypeStaticText'), 'xpath').value;
228
+ (0, chai_1.expect)(xpath).to.equal('//XCUIElementTypeStaticText[@label="Done"]');
229
+ (0, chai_1.expect)(xpath).to.not.contain('@text');
230
+ });
231
+ it('names value rather than label when value is what supplied the text', () => {
232
+ (0, chai_1.expect)(locator(byType('XCUIElementTypeTextField'), 'xpath').value).to.equal('//XCUIElementTypeTextField[@value="typed text"]');
233
+ });
234
+ it('names the same attribute in the predicate as in the xpath', () => {
235
+ // A page indicator carries its text in `value` and has no label at all.
236
+ // The predicate hardcoded `label ==`, so it matched nothing — measured
237
+ // against a real iPhone as the last surviving miss after the other
238
+ // fixes landed.
239
+ const field = byType('XCUIElementTypeTextField');
240
+ (0, chai_1.expect)(locator(field, '-ios predicate string').value).to.equal('type == "XCUIElementTypeTextField" AND value == "typed text"');
241
+ (0, chai_1.expect)(locator(byType('XCUIElementTypeStaticText'), '-ios predicate string').value).to.equal('type == "XCUIElementTypeStaticText" AND label == "Done"');
242
+ });
243
+ it('stops claiming every predicate and class chain is unique', () => {
244
+ // Both were hardcoded true. On a real device a predicate badged unique
245
+ // matched two elements.
246
+ const shared = byType('XCUIElementTypeStaticText'); // label "Done", shared
247
+ (0, chai_1.expect)(locator(shared, '-ios predicate string').unique).to.equal(false);
248
+ (0, chai_1.expect)(locator(shared, '-ios class chain').unique).to.equal(false);
249
+ });
250
+ it('still reports a genuinely unique one as unique', () => {
251
+ const named = byType('XCUIElementTypeButton'); // name "done-btn", unique
252
+ (0, chai_1.expect)(locator(named, '-ios class chain').unique).to.equal(true);
253
+ });
254
+ });
200
255
  describe('iOS', () => {
201
256
  it('parses the XCUIElementType tree unchanged', () => {
202
257
  const root = parse(IOS_XML, 'ios');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xenon-device-management/xenon",
3
- "version": "1.17.5",
3
+ "version": "1.18.0",
4
4
  "description": "Xenon - Intelligent Mobile Infrastructure. A self-healing device orchestration platform for Appium.",
5
5
  "main": "./lib/src/index.js",
6
6
  "exports": {