axe-core
Version:
Accessibility engine for automated Web UI testing
85 lines (77 loc) • 2.34 kB
JavaScript
/*global axe */
/*eslint max-statements: ["error", 36], complexity: ["error", 22]*/
function getXPathArray(node, path) {
var sibling, count;
// Gets an XPath for an element which describes its hierarchical location.
if(!node) {
return [];
}
if (!path && node.nodeType === 9) {
// special case for when we are called and give the document itself as the starting node
path = [{
str: 'html'
}];
return path;
}
path = path || [];
if (node.parentNode && node.parentNode !== node) {
path = getXPathArray(node.parentNode, path);
}
if (node.previousSibling) {
count = 1;
sibling = node.previousSibling;
do {
if(sibling.nodeType === 1 && sibling.nodeName === node.nodeName) {
count++;
}
sibling = sibling.previousSibling;
}
while (sibling);
if(count === 1) {
count = null;
}
} else if (node.nextSibling) {
sibling = node.nextSibling;
do {
if(sibling.nodeType === 1 && sibling.nodeName === node.nodeName) {
count = 1;
sibling = null;
} else {
count = null;
sibling = sibling.previousSibling;
}
} while (sibling);
}
if (node.nodeType === 1) {
var element = {};
element.str = node.nodeName.toLowerCase();
// add the id and the count so we can construct robust versions of the xpath
var id = node.getAttribute && axe.utils.escapeSelector(node.getAttribute('id'));
if (id && node.ownerDocument.querySelectorAll('#' + id).length === 1) {
element.id = node.getAttribute('id');
}
if (count > 1) {
element.count = count;
}
path.push(element);
}
return path;
}
// Robust is intended to allow xpaths to be robust to changes in the HTML structure of the page
// This means always use the id when present
// Non robust means always use the count (i.e. the exact position of the element)
// Ironically this is a bit of a misnomer because in very, very dynamic pages (e.g. where ids are generated on the fly)
// the non-ribust Xpaths will work whereas the robust ones will not work
function xpathToString(xpathArray) {
return xpathArray.reduce((str, elm) => {
if(elm.id) {
return `/${elm.str}[@id='${elm.id}']`;
} else {
return str + `/${elm.str}` + (elm.count > 0 ? `[${elm.count}]` : '');
}
}, '');
}
axe.utils.getXpath = function getXpath(node) {
var xpathArray = getXPathArray(node);
return xpathToString(xpathArray);
};