/*global axe */ 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); };