XPath selector is convenient in some situations, including a page that randomly generates a new id and class and many more. Let's see how we can get the full XPath position by using an element.
*we will use the child to parent architecture. *
Create a function that receive an element. We will do all work in this function
function getXPath(element) { // Selector let selector = ''; // ... Add code here }
Get tag name of current element
// Get element tag name const tagName = currentElement.tagName.toLowerCase();
Get parent element
// Get parent element const parentElement = currentElement.parentElement;
Is current element is the only child of parentElement
if true then add tag to selector
;
// Count children if (parentElement.childElementCount > 1) { } else { //* Current element has no siblings // Append tag to selector selector = `/${tagName}${selector}`; }
If not the only child get other child and filter elements from these elements, that has the same tagname of current tag
// Count children if (parentElement.childElementCount > 1) { // Get children of parent element const parentsChildren = [...parentElement.children]; // Count current tag let tag = []; parentsChildren.forEach(child => { if (child.tagName.toLowerCase() === tagName) tag.push(child) // Append to tag }) } else { //* Current element has no siblings // Append tag to selector selector = `/${tagName}${selector}`; }
Now check, is the current element only of type in parent. If so, add to selector /tagName
// Count children if (parentElement.childElementCount > 1) { // ... // Is only of type if (tag.length === 1) { // Append tag to selector selector = `/${tagName}${selector}`; } } else { //* Current element has no siblings // Append tag to selector selector = `/${tagName}${selector}`; }
If not then add tag position like /tagName[number]
// Count children if (parentElement.childElementCount > 1) { // ... // Is only of type if (tag.length === 1) { // Append tag to selector selector = `/${tagName}${selector}`; } else { // Get position of current element in tag const position = tag.indexOf(currentElement) + 1; // Append tag to selector selector = `/${tagName}[${position}]${selector}`; } } else { //* Current element has no siblings // Append tag to selector selector = `/${tagName}${selector}`; }
Now we need to repeate this method until we found html tag. Add a while loop. And some variable to help
function getXPath(element) { //... // Loop handler let foundRoot; // Element handler let currentElement = element; // Do action until we reach html element do { // Get element tag name const tagName = currentElement.tagName.toLowerCase(); // ... // Set parent element to current element currentElement = parentElement; // Is root foundRoot = parentElement.tagName.toLowerCase() === 'html'; // Finish selector if found root element if(foundRoot) selector = `/html${selector}`; } while (foundRoot === false);
And finally return selector
// Return selector return selector;
Here is the full code. element-to-path.js
/** * Get absolute xPath position from dom element * xPath position will does not contain any id, class or attribute, etc selector * Because, Some page use random id and class. This function should ignore that kind problem, so we're not using any selector * * @param {Element} element element to get position * @returns {String} xPath string */ function getXPath(element) { // Selector let selector = ''; // Loop handler let foundRoot; // Element handler let currentElement = element; // Do action until we reach html element do { // Get element tag name const tagName = currentElement.tagName.toLowerCase(); // Get parent element const parentElement = currentElement.parentElement; // Count children if (parentElement.childElementCount > 1) { // Get children of parent element const parentsChildren = [...parentElement.children]; // Count current tag let tag = []; parentsChildren.forEach(child => { if (child.tagName.toLowerCase() === tagName) tag.push(child) // Append to tag }) // Is only of type if (tag.length === 1) { // Append tag to selector selector = `/${tagName}${selector}`; } else { // Get position of current element in tag const position = tag.indexOf(currentElement) + 1; // Append tag to selector selector = `/${tagName}[${position}]${selector}`; } } else { //* Current element has no siblings // Append tag to selector selector = `/${tagName}${selector}`; } // Set parent element to current element currentElement = parentElement; // Is root foundRoot = parentElement.tagName.toLowerCase() === 'html'; // Finish selector if found root element if(foundRoot) selector = `/html${selector}`; } while (foundRoot === false); // Return selector return selector; }
Example
const button = document.querySelector("#main > div:nth-child(27) > a.w3-left.w3-btn"); getXPath(button)
returns
'/html/body/div[7]/div[1]/div[1]/div[4]/a[1]'
To learn more about Xpath https://www.w3schools.com/xml/xpath_intro.asp
Please give a feedback, what are you thinking about this? and do you had a better solution?
Top comments (0)