Skip to content

Commit d3d1545

Browse files
authored
feat: improve suggested xpath generation logic (#2733)
* feat: adjjust parent node usage for xpath generation This is better if the parent can be uniquely identified, but potentially worse if not. * improve semi-unique xpath logic to use the result with smallest index * allow specifying scope for xpath uniqueness * WIP trying to use attributes instead of indices for target node * use attributes for target node when possible * adjust one test to its original behavior * revert custom scope in _determineXpathUniqueness since it is unused * add back accidentally removed test * add another test to mirror its single parent version * additional optimizations and 2 more tests * change tuples to objects * remove unused param in jsdoc * formatting & comments * reuse base class methods * few more minor adjustments * update tests again * use fully unique attributes whenever possible * clarify comment * fix formatting * address comments * add another safeguard * address comment * address comments
1 parent 3b92366 commit d3d1545

File tree

3 files changed

+518
-134
lines changed

3 files changed

+518
-134
lines changed

app/common/renderer/utils/locator-generation/base.js

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,35 +7,39 @@ export class LocatorGeneratorBase {
77
/**
88
* @param {Document} doc - the document containing the DOM
99
* @param {Node} domNode - the DOM node to generate locators for
10+
* @param {Node | null} [contextNode] - optional context node to scope locator evaluation
1011
*/
11-
constructor(doc, domNode) {
12+
constructor(doc, domNode, contextNode = null) {
1213
this._doc = doc;
1314
this._domNode = domNode;
15+
this._contextNode = contextNode;
16+
// We only use contextNode as the direct parent of domNode, so all axes will be '/'
17+
this._nodeAxis = this._contextNode ? '/' : '//';
1418
}
1519

1620
/**
1721
* Get sibling nodes with the same tag name
1822
*
23+
* @param {Node} targetNode - the node to find siblings for, defaults to the main DOM node if not provided
1924
* @returns {Node[]} array of sibling nodes with the same tag name
2025
*/
21-
_getSiblingsWithSameTag() {
22-
if (!this._domNode.parentNode) {
26+
_getSiblingsWithSameTag(targetNode = this._domNode) {
27+
if (!targetNode.parentNode) {
2328
return [];
2429
}
2530
return Array.prototype.slice
26-
.call(this._domNode.parentNode.childNodes, 0)
27-
.filter(
28-
(childNode) => childNode.nodeType === 1 && childNode.tagName === this._domNode.tagName,
29-
);
31+
.call(targetNode.parentNode.childNodes, 0)
32+
.filter((childNode) => childNode.nodeType === 1 && childNode.tagName === targetNode.tagName);
3033
}
3134

3235
/**
3336
* Check if a node is a valid element node
3437
*
38+
* @param {Node} targetNode - the node to check, defaults to the main DOM node if not provided
3539
* @returns {boolean} true if the node is a valid element
3640
*/
37-
_isValidElementNode() {
38-
return this._domNode.tagName && this._domNode.nodeType === 1;
41+
_isValidElementNode(targetNode = this._domNode) {
42+
return targetNode?.tagName && targetNode?.nodeType === 1;
3943
}
4044

4145
/**

0 commit comments

Comments
 (0)