// ==UserScript==
// @name Xpath Locator
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Capture and test Xpath locators
// @author Maged Ahmed
// @include *
// @grant none
// @noframes
// @license MIT
// ==/UserScript==
let highlightElementCount = 0;
let currentElement;
let highlightInterval;
let elements = [];
let elementIndex = 0;
const MENU = `
<br/>
<br/>
<fieldset>
<legend>Find by xpath:</legend>
<p><label>
<textarea id="xpValue" placeholder="xpath: //*" rows="10" maxlength="9999"></textarea>
</label></p>
<p>
<button id="xpElementFind" class="xpMenuBtn"><i class="fa fa-search"></i></button>
<button id="xpElementPrev" class="xpMenuBtn"><i class="fa fa-arrow-left"></i></button>
<button id="xpElementNext" class="xpMenuBtn"><i class="fa fa-arrow-right"></i></button>
<button id="xpElementClear" class="xpMenuBtn"><i class="fa fa-close"></i></button>
<button id="xpElementCopy" class="xpMenuBtn"><i class="fa fa-copy"></i></button>
<span id="xpElementsCount"></span>
</p>
</fieldset>
<fieldset>
<legend>Capture xpath:</legend>
<p>
<label for="xpId">Add id</label>
<input id="xpId" type="checkbox" checked="checked"/>
</p>
<p>
<label for="xpName">Add name</label>
<input id="xpName" type="checkbox" checked="checked"/>
</p>
<p>
<label for="xpClass">Add class</label>
<input id="xpClass" type="checkbox"/>
</p>
<p>
<label for="xpType">Add type</label>
<input id="xpType" type="checkbox"/>
</p>
<p><label for="xpText">Add text</label>
<input id="xpText" type="checkbox"/>
</p>
<p>
<label for="xpIndex">Add index</label>
<input id="xpIndex" type="checkbox"/>
</p>
<p>
<label for="xpLength">Xpath Length</label>
<input type="number" id="xpLength" value="1" min="1" max="100">
</p>
<p>
<button id="xpCapture"><i class="fa fa-location-arrow"></i> Capture</button>
<button id="xpUpdate"><i class="fa fa-refresh"></i> Update</button>
</p>
</fieldset>
<fieldset class="xpElementData">
<legend>Element Properties</legend>
<table>
<col style="width:50px">
<tr>
<td>Tag</td>
<td><div id="xpElementTag" style="max-height:100px; overflow:scroll"></div></td>
</tr>
<tr>
<td>Id</td>
<td><div id="xpElementId" style="max-height:100px; overflow:scroll"></div></td>
</tr>
<tr>
<td>Name</td>
<td><div id="xpElementName" style="max-height:100px; overflow:scroll"></div></td>
</tr>
<tr>
<td>Class</td>
<td><div id="xpElementClass" style="max-height:100px; overflow:scroll"></div></td>
</tr>
<tr>
<td>Type</td>
<td><div id="xpElementType" style="max-height:100px; overflow:scroll"></div></td>
</tr>
<tr>
<td>Text</td>
<td><div id="xpElementText" style="max-height:100px; overflow:scroll"></div></td>
</tr>
<tr>
<td>Xpath</td>
<td><div id="xpElementXpath" style="max-height:100px; overflow:scroll"></div></td>
</tr>
</table>
</fieldset>
`;
const SCRIPT_CSS = `
.selectedElement {
color: lime !important;
background-color: yellow !important;
outline-offset: -2px !important;
outline: 4px solid lime !important;
}
.capturedElement {
color: aqua !important;
background-color: yellow !important;
outline-offset: -2px !important;
outline: 4px solid aqua !important;
}
.xpMenu p,
.xpMenu label,
.xpMenu button,
.xpMenu a,
.xpMenu fieldset,
.xpMenu div,
.xpMenu span,
.xpMenuLink a {
font: 16px arial, sans-serif;
margin: 0;
padding: .5 1em;
min-height: 1;
border: 1px solid transparent;
box-shadow: none;
outline: none;
color: white;
}
.xpMenuLink a:hover {
text-decoration: none;
left: 0;
color: white;
}
.xpMenuLink a {
position: fixed;
left: -100px;
transition: 0.3s;
padding: 5px;
width: 100px;
text-decoration: none;
color: CornflowerBlue;
border-radius: 0 20px 20px 0;
z-index: 999999;
top: 0px ;
background-color: CornflowerBlue;
white-space: nowrap;
box-sizing: initial;
}
a.xpMenuLinkOpened{
left: 0;
color: white;
}
.xpMenu {
height: 100%;
width: 0;
position: fixed;
top: 0;
left: 0;
background-color: CornflowerBlue;
overflow-x: hidden;
transition: 0.5s;
z-index: 999998;
}
.xpMenu a {
text-decoration: none;
font-size: 25px;
color: black;
display: block;
transition: 0.3s;
}
.xpMenu a:hover {
text-decoration: none;
color: white;
}
.xpMenu .xpMenuClosebtn {
color: DIMGRAY;
position: absolute;
top: 0;
right: 25px;
font-size: 36px;
margin-left: 50px;
cursor: pointer;
}
.xpMenu button {
font-size: 14px !important;
appearance: button !important;
cursor: pointer !important;
background-color: lightblue !important;
}
.xpMenu .xpMenuBtn {
color: white !important;
background-color: CornflowerBlue !important;
border: 2px solid #6495ED !important;
border-radius: 10px !important;
}
.xpMenu textarea {
width: 100%;
box-sizing: border-box;
resize: vertical ;
color: black;
}
.xpMenu fieldset {
display: block;
margin-inline-start: 2px;
margin-inline-end: 2px;
padding-block-start: 0.35em;
padding-inline-start: 0.75em;
padding-inline-end: 0.75em;
padding-block-end: 0.625em;
min-inline-size: min-content;
border-width: 2px;
border-style: groove;
border-color: threedface;
border-image: initial;
}
.xpMenu table {
font: 14px arial, sans-serif;
width: 100%;
height: 100%;
table-layout:fixed;
}
.xpMenu td div {
font: 14px arial, sans-serif;
}
.xpMenu td {
text-align: left;
border: 1px solid white;
word-wrap: break-word;
white-space: pre-wrap;
}
`;
function openNav() {
// const currentPosition = window.scrollY;
// window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
// setTimeout(() => { window.scrollTo(0,currentPosition); }, Math.floor(document.body.scrollHeight/25));
document.getElementById("xpMenuLinkOpen").classList.add("xpMenuLinkOpened");
document.getElementById("xpMenu").style.width = "300px";
document.body.style.marginLeft = "300px";
document.querySelectorAll('*').forEach(function(node) {
if (window.getComputedStyle(node, null).getPropertyValue('position') === 'fixed' &&
window.getComputedStyle(node, null).getPropertyValue('left') === "0px" &&
(node.id !== 'xpMenuLinkOpen' && node.id !== 'xpMenu')) {
node.style.marginLeft = "300px";
}
});
}
function closeNav() {
document.getElementById("xpMenuLinkOpen").classList.remove("xpMenuLinkOpened");
document.getElementById("xpMenu").style.width = "0";
document.body.style.marginLeft = "0";
document.querySelectorAll('*').forEach(function(node) {
if (window.getComputedStyle(node, null).getPropertyValue('position') === 'fixed') {
node.style.marginLeft = "0";
}
});
clearAll();
}
function getElementsByXpath(path) {
const result = document.evaluate(path, document, null, XPathResult.ANY_TYPE, null);
let node, nodes = [];
while ((node = result.iterateNext())) {
nodes.push(node);
}
return nodes;
}
function unHighlightElement() {
if (currentElement instanceof HTMLElement) {
clearInterval(highlightInterval);
currentElement.classList.remove("capturedElement");
currentElement.classList.remove("selectedElement");
highlightInterval = null;
}
}
function highlightElement() {
currentElement.classList.toggle("selectedElement");
highlightElementCount++;
if (highlightElementCount === 5) {
clearInterval(highlightInterval);
highlightElementCount = 0;
highlightInterval = null;
}
}
function findElements() {
clearElements();
unsetCapture();
const xpath = document.getElementById("xpValue").value;
elements = getElementsByXpath(xpath);
elementIndex = 0;
selectElement()
}
function nextElement() {
if (elementIndex < elements.length - 1) {
elementIndex++;
selectElement()
}
}
function prevElement() {
if (elementIndex > 0) {
elementIndex--;
selectElement()
}
}
function selectElement() {
unHighlightElement();
if (elements.length > 0 && (currentElement = elements[elementIndex]) && currentElement instanceof HTMLElement) {
currentElement.scrollIntoView({
behavior: "smooth",
block: "center",
inline: "center"
});
document.getElementById("xpElementsCount").textContent = "[" + (elementIndex + 1) + " / " + elements.length + "]";
displayElementInfo();
highlightInterval = setInterval(function() {
highlightElement();
}, 100);
} else {
document.getElementById("xpElementsCount").textContent = '';
}
}
function displayElementInfo() {
document.getElementById("xpElementTag").textContent = currentElement.tagName;
document.getElementById("xpElementId").textContent = currentElement.id;
document.getElementById("xpElementName").textContent = currentElement.name;
document.getElementById("xpElementClass").textContent = currentElement.className;
document.getElementById("xpElementType").textContent = currentElement.type;
document.getElementById("xpElementText").textContent = currentElement.textContent;
document.getElementById("xpElementXpath").textContent = getXPath(currentElement, true, true, true, false, false, true, 5);
}
function clearElements() {
unHighlightElement();
document.getElementById("xpElementsCount").textContent = '';
elements = [];
elementIndex = 0;
}
function clearAll() {
document.getElementById("xpValue").value = '';
clearElements();
unsetCapture();
}
function unsetCapture() {
unHighlightElement();
document.onmousemove = null;
document.oncontextmenu = null;
}
function captureElement() {
if (currentElement) {
const xpId = document.getElementById("xpId").checked;
const xpName = document.getElementById("xpName").checked;
const xpClass = document.getElementById("xpClass").checked;
const xpType = document.getElementById("xpType").checked;
const xpText = document.getElementById("xpText").checked;
const xpIndex = document.getElementById("xpIndex").checked;
const xpLength = document.getElementById("xpLength").value;
document.getElementById("xpValue").value = getXPath(currentElement, xpId, xpName, xpClass, xpType, xpText, xpIndex, xpLength);
findElements();
}
}
function setCapture() {
clearElements();
unsetCapture();
document.onmousemove = function(event) {
if (currentElement) {
currentElement.classList.remove("capturedElement");
}
const target = event.target;
target.classList.add("capturedElement");
currentElement = target;
};
document.oncontextmenu = function() {
unsetCapture();
captureElement();
return false
};
}
function copyXpath() {
document.getElementById("xpValue").select();
document.execCommand("copy");
}
function getXPath(element, getId, getName, getClass, getType, getText, getIndex, maxCount) {
let uiElementText;
let xpath = '';
let count = 0;
while (element) {
let pathIndex = "";
if (getIndex) {
try {
let index = 0;
for (let sibling = element.previousSibling; sibling; sibling = sibling.previousSibling) {
if (sibling.nodeType === Node.DOCUMENT_TYPE_NODE) {
continue;
}
if (sibling.nodeName === element.nodeName) {
++index;
}
}
pathIndex = (index ? "[" + (index + 1) + "]" : "[1]");
} catch (err) {
continue;
}
}
let nodeXpath = '';
try {
if (element.id && getId) {
nodeXpath += '@id=\"' + element.id + '\"';
}
} catch (err) {
}
try {
if (element.name && getName) {
if (nodeXpath !== '') {
nodeXpath += ' and ';
}
nodeXpath += '@name=\"' + element.name + '\"';
}
} catch (err) {
}
try {
if (element.hasAttribute("type") && typeof element.type !== 'undefined' && getType) {
if (nodeXpath !== '') {
nodeXpath += ' and ';
}
nodeXpath += '@type=\"' + element.type + '\"';
}
} catch (err) {
}
try {
if (element.className && nodeXpath === '' && getClass) {
if (nodeXpath !== '') {
nodeXpath += ' and ';
}
nodeXpath += '@class=\"' + element.className + '\"';
}
} catch (err) {
}
try {
if (element.textContent && element.textContent.length < 50 && element.textContent === element.innerHTML && getText) {
uiElementText = element.textContent;
try {
uiElementText = uiElementText.trim();
} catch (err) {
uiElementText = uiElementText.replace(/^\s+|\s+$/g, '');
}
if (nodeXpath !== '') {
nodeXpath += ' and ';
}
if (element.textContent === uiElementText && element.textContent.length > 0) {
nodeXpath += 'text()=\"' + element.textContent + '\"';
} else {
nodeXpath += 'normalize-space() = \"' + uiElementText + '\"';
}
} else if (element.text && element.text.length < 50 && element.text === element.innerHTML && getText) {
uiElementText = element.text;
try {
uiElementText = uiElementText.trim();
} catch (err) {
uiElementText = uiElementText.replace(/^\s+|\s+$/g, '');
}
if (uiElementText.length > 0) {
if (nodeXpath !== '') {
nodeXpath += ' and ';
}
if (element.text === uiElementText) {
nodeXpath += 'contains(text(),\'' + uiElementText + '\')';
} else {
nodeXpath += 'contains(normalize-space(),\'' + uiElementText + '\')';
}
}
} else if (element.innerText && element.innerText.length < 50 && element.innerText === element.innerHTML && getText) {
uiElementText = element.innerText;
try {
uiElementText = uiElementText.trim();
} catch (err) {
uiElementText = uiElementText.replace(/^\s+|\s+$/g, '');
}
if (uiElementText.length > 0) {
if (nodeXpath !== '') {
nodeXpath += ' and ';
}
if (element.innerText === uiElementText) {
nodeXpath += 'contains(text(),\'' + uiElementText + '\')';
} else {
nodeXpath += 'contains(normalize-space(),\'' + uiElementText + '\')';
}
}
} else if (element.nodeName.toLocaleLowerCase() === "a" || count === 0) {
uiElementText = element.textContent;
try {
uiElementText = uiElementText.trim().substring(0, 20);
uiElementText = uiElementText.replace("'", "') and contains (.,'");
} catch (err) {
uiElementText = uiElementText.replace(/^\s+|\s+$/g, '');
uiElementText = uiElementText.replace("'", "') and contains (.,'");
}
if (uiElementText.length > 0) {
if (nodeXpath !== '') {
nodeXpath += ' and ';
}
if (element.textContent === uiElementText) {
nodeXpath += 'contains(normalize-space(),\'' + uiElementText + '\')';
} else {
nodeXpath += 'contains(.,\'' + uiElementText + '\')';
}
}
}
} catch (err) {
}
/** Getting the Element's Tag Name
**/
const currentElementTagName = element.nodeName.toLocaleLowerCase();
/** Building Xpath for the current Element Node
**/
if (nodeXpath === '') {
xpath = '/' + currentElementTagName + pathIndex + xpath;
} else {
xpath = '/' + currentElementTagName + pathIndex + '[' + nodeXpath + ']' + xpath;
}
/** Switching focus to parent node
**/
element = element.parentElement;
/** Incrementing the element counter and breaking the loop in case we reach the maximum number of elements defined by the user
**/
count++;
if (count >= maxCount) {
break;
}
}
window.captured = null;
return '/' + xpath;
}
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css';
document.head.appendChild(link);
const s = document.createElement('style');
s.innerHTML = SCRIPT_CSS;
document.head.appendChild(s);
const xpMenuDiv = document.createElement("div");
xpMenuDiv.id = "xpMenuLink";
xpMenuDiv.className = "xpMenuLink";
document.body.appendChild(xpMenuDiv);
const xpMenuLink = document.createElement("a");
xpMenuLink.id = "xpMenuLinkOpen";
xpMenuLink.innerHTML = "Xpath Locator";
xpMenuLink.onclick = openNav;
xpMenuDiv.appendChild(xpMenuLink);
const xpMenu = document.createElement("div");
xpMenu.className = "xpMenu";
xpMenu.id = "xpMenu";
xpMenu.innerHTML = MENU;
document.body.appendChild(xpMenu);
const xpCloseMenu = document.createElement("div");
xpCloseMenu.id = "xpMenuClosebtn";
xpCloseMenu.className = "xpMenuClosebtn";
xpCloseMenu.innerHTML = "×";
xpCloseMenu.onclick = closeNav;
xpCloseMenu.href = "#";
xpMenu.appendChild(xpCloseMenu);
document.getElementById("xpElementFind").onclick = findElements;
document.getElementById("xpElementNext").onclick = nextElement;
document.getElementById("xpElementPrev").onclick = prevElement;
document.getElementById("xpElementClear").onclick = clearAll;
document.getElementById("xpCapture").onclick = setCapture;
document.getElementById("xpUpdate").onclick = captureElement;
document.getElementById("xpElementCopy").onclick = copyXpath;