Skip to content

Commit

Permalink
fix: update xpath selector selection logic yet again
Browse files Browse the repository at this point in the history
  • Loading branch information
jlipps committed Oct 19, 2023
1 parent b1c919a commit eb01e75
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 20 deletions.
23 changes: 21 additions & 2 deletions app/renderer/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ const MAYBE_UNIQUE_XPATH_ATTRIBUTES = [
'label',
'text',
'value',
'class',
'type',
];

const UNIQUE_CLASS_CHAIN_ATTRIBUTES = [
Expand Down Expand Up @@ -139,7 +137,25 @@ function getUniqueXPath(doc, domNode, attrs) {
let uniqueXpath, semiUniqueXpath;
const tagForXpath = domNode.tagName || '*';
const isPairs = attrs.length > 0 && _.isArray(attrs[0]);
const isNodeName = attrs.length === 0;

// If we're looking for a unique //<nodetype>, return it only if it's actually unique. No
// semi-uniqueness here!
if (isNodeName) {
let xpath = `//${domNode.tagName}`;
const [isUnique] = determineXpathUniqueness(xpath, doc, domNode);
if (isUnique) {
// even if this node name is unique, if it's the root node, we don't want to refer to it using
// '//' but rather '/'
if (!(domNode.parentNode && domNode.parentNode.tagName)) {
xpath = `/${domNode.tagName}`;
}
return [xpath, true];
}
return [];
}

// Otherwise go through our various attributes to look for uniqueness
for (const attrName of attrs) {
let xpath;
if (isPairs) {
Expand Down Expand Up @@ -209,6 +225,9 @@ export function getOptimalXPath (doc, domNode) {
// of these that's unique in conjunction with another attribute, but if not, that's OK.
// Better than a hierarchical query.
MAYBE_UNIQUE_XPATH_ATTRIBUTES,

// BASE CASE #5: Look to see if the node type is unique in the document
[],
];

// It's possible that in all of these cases we don't find a truly unique selector. But
Expand Down
39 changes: 21 additions & 18 deletions test/unit/util-specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ describe('util.js', function () {
width: '1024',
height: '768',
},
xpath: '//XCUIElementTypeApplication[@name="🦋"]/XCUIElementTypeWindow',
xpath: '//XCUIElementTypeWindow',
path: '0.0',
classChain: '**/XCUIElementTypeWindow',
predicateString: 'type == "XCUIElementTypeWindow"',
Expand Down Expand Up @@ -142,7 +142,7 @@ describe('util.js', function () {
bounds: '[0,0][1080,2028]',
displayed: 'true'
},
xpath: '/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout',
xpath: '//android.widget.LinearLayout',
path: '0.0'
}
],
Expand All @@ -165,7 +165,7 @@ describe('util.js', function () {
bounds: '[0,0][1080,2028]',
displayed: 'true'
},
xpath: '/hierarchy/android.widget.FrameLayout',
xpath: '//android.widget.FrameLayout',
path: '0'
}],
attributes: {
Expand Down Expand Up @@ -236,8 +236,8 @@ describe('util.js', function () {
height: '30'
},
xpath: '//XCUIElementTypeStaticText[@name="Login"]',
classChain: '**/XCUIElementTypeStaticText[`label == "Login"`]',
predicateString: 'label == "Login" AND name == "Login" AND value == "Login"',
classChain: '**/XCUIElementTypeStaticText[`name == "Login"`]',
predicateString: 'name == "Login" AND label == "Login" AND value == "Login"',
path: '0.0.0.0.0.0.0'
}
],
Expand All @@ -254,8 +254,8 @@ describe('util.js', function () {
height: '40'
},
xpath: '//XCUIElementTypeOther[@name="Login"]',
classChain: '**/XCUIElementTypeOther[`label == "Login"`][2]',
predicateString: 'label == "Login" AND name == "Login" AND type == "XCUIElementTypeOther"',
classChain: '**/XCUIElementTypeOther[`name == "Login"`]',
predicateString: 'name == "Login" AND label == "Login" AND type == "XCUIElementTypeOther"',
path: '0.0.0.0.0.0'
}
],
Expand All @@ -272,8 +272,8 @@ describe('util.js', function () {
height: '40'
},
xpath: '//XCUIElementTypeOther[@name="button-login-container"]',
classChain: '**/XCUIElementTypeOther[`label == "Login"`][1]',
predicateString: 'label == "Login" AND name == "button-login-container"',
classChain: '**/XCUIElementTypeOther[`name == "button-login-container"`]',
predicateString: 'name == "button-login-container"',
path: '0.0.0.0.0'
}
],
Expand All @@ -290,7 +290,7 @@ describe('util.js', function () {
height: '802'
},
xpath: '(//XCUIElementTypeOther[@name="Appium Inspector"])[2]',
classChain: '**/XCUIElementTypeOther[`label == "Appium Inspector"`][2]',
classChain: '**/XCUIElementTypeOther[`name == "Appium Inspector"`][2]',
predicateString: '',
path: '0.0.0.0'
},
Expand All @@ -314,8 +314,8 @@ describe('util.js', function () {
height: '50'
},
xpath: '//XCUIElementTypeButton[@name="Login"]',
classChain: '**/XCUIElementTypeButton[`label == "Login"`]',
predicateString: 'label == "Login" AND name == "Login" AND value == "1"',
classChain: '**/XCUIElementTypeButton[`name == "Login"`]',
predicateString: 'name == "Login" AND label == "Login" AND value == "1"',
path: '0.0.0.1.0.0'
}
],
Expand All @@ -332,7 +332,7 @@ describe('util.js', function () {
height: '94'
},
xpath: '(//XCUIElementTypeOther[@name="Home WebView Login Forms Swipe"])[2]',
classChain: '**/XCUIElementTypeOther[`label == "Home WebView Login Forms Swipe"`][2]',
classChain: '**/XCUIElementTypeOther[`name == "Home WebView Login Forms Swipe"`][2]',
predicateString: '',
path: '0.0.0.1.0'
}
Expand All @@ -350,7 +350,7 @@ describe('util.js', function () {
height: '94'
},
xpath: '(//XCUIElementTypeOther[@name="Home WebView Login Forms Swipe"])[1]',
classChain: '**/XCUIElementTypeOther[`label == "Home WebView Login Forms Swipe"`][1]',
classChain: '**/XCUIElementTypeOther[`name == "Home WebView Login Forms Swipe"`][1]',
predicateString: '',
path: '0.0.0.1'
}
Expand All @@ -368,7 +368,7 @@ describe('util.js', function () {
height: '896'
},
xpath: '(//XCUIElementTypeOther[@name="Appium Inspector"])[1]',
classChain: '**/XCUIElementTypeOther[`label == "Appium Inspector"`][1]',
classChain: '**/XCUIElementTypeOther[`name == "Appium Inspector"`][1]',
predicateString: '',
path: '0.0.0'
}
Expand Down Expand Up @@ -490,6 +490,9 @@ describe('util.js', function () {
it('should set first child node to relative xpath with tagname if the child node has no siblings', function () {
doc = new DOMParser().parseFromString(`<xml>
<child-node non-unique-attr='hello'>Hello</child-node>
<other-node>
<child-node></child-node>
</other-node>
</xml>`);
testXPath(doc, doc.getElementsByTagName('child-node')[0], '/xml/child-node');
});
Expand Down Expand Up @@ -528,10 +531,10 @@ describe('util.js', function () {
<other-child-node>asdfasdf</other-child-node>
<child-node>Bar</child-node>
</xml>`);
testXPath(doc, doc.getElementsByTagName('child')[0], '/xml/child');
testXPath(doc, doc.getElementsByTagName('child')[0], '//child');
testXPath(doc, doc.getElementsByTagName('child-node')[0], '/xml/child-node[1]');
testXPath(doc, doc.getElementsByTagName('child-node')[1], '/xml/child-node[2]');
testXPath(doc, doc.getElementsByTagName('other-child-node')[0], '/xml/other-child-node');
testXPath(doc, doc.getElementsByTagName('other-child-node')[0], '//other-child-node');
});
});
describe('on XML with height = 3', function () {
Expand Down Expand Up @@ -589,7 +592,7 @@ describe('util.js', function () {
testXPath(doc, grandchildren[3], '(//child[@id="foo"])[4]/grandchild');

const greatgrandchildren = doc.getElementsByTagName('great-grand-child');
testXPath(doc, greatgrandchildren[0], '(//child[@id="foo"])[5]/great-grand-child');
testXPath(doc, greatgrandchildren[0], '//great-grand-child');

const children = doc.getElementsByTagName('child');
testXPath(doc, children[0], '(//child[@id="foo"])[1]');
Expand Down

0 comments on commit eb01e75

Please sign in to comment.