XPath Extension for Chrome

Interactive XPath tool to test, evaluate, and highlight elements on any webpage

Size

16.3 KB

Version

1.0.1

Created

Oct 22, 2025

Updated

23 days ago

1// ==UserScript==
2// @name		XPath Extension for Chrome
3// @description		Interactive XPath tool to test, evaluate, and highlight elements on any webpage
4// @version		1.0.1
5// @match		*://*/*
6// @icon		https://www.gstatic.com/images/branding/searchlogo/ico/favicon.ico
7// ==/UserScript==
8(function() {
9    'use strict';
10
11    console.log('XPath Helper & Evaluator extension loaded');
12
13    // Add styles for the XPath panel and highlights
14    TM_addStyle(`
15        #xpath-helper-panel {
16            position: fixed;
17            top: 20px;
18            right: 20px;
19            width: 400px;
20            background: #ffffff;
21            border: 2px solid #333;
22            border-radius: 8px;
23            box-shadow: 0 4px 12px rgba(0,0,0,0.3);
24            z-index: 999999;
25            font-family: Arial, sans-serif;
26            color: #333;
27        }
28        
29        #xpath-helper-header {
30            background: #2c3e50;
31            color: white;
32            padding: 12px 15px;
33            border-radius: 6px 6px 0 0;
34            display: flex;
35            justify-content: space-between;
36            align-items: center;
37            cursor: move;
38        }
39        
40        #xpath-helper-header h3 {
41            margin: 0;
42            font-size: 16px;
43            font-weight: 600;
44        }
45        
46        #xpath-close-btn {
47            background: #e74c3c;
48            color: white;
49            border: none;
50            padding: 4px 10px;
51            border-radius: 4px;
52            cursor: pointer;
53            font-size: 14px;
54            font-weight: bold;
55        }
56        
57        #xpath-close-btn:hover {
58            background: #c0392b;
59        }
60        
61        #xpath-helper-content {
62            padding: 15px;
63        }
64        
65        .xpath-input-group {
66            margin-bottom: 15px;
67        }
68        
69        .xpath-input-group label {
70            display: block;
71            margin-bottom: 5px;
72            font-weight: 600;
73            font-size: 13px;
74            color: #2c3e50;
75        }
76        
77        #xpath-input {
78            width: 100%;
79            padding: 8px;
80            border: 2px solid #bdc3c7;
81            border-radius: 4px;
82            font-family: 'Courier New', monospace;
83            font-size: 13px;
84            box-sizing: border-box;
85        }
86        
87        #xpath-input:focus {
88            outline: none;
89            border-color: #3498db;
90        }
91        
92        .xpath-button-group {
93            display: flex;
94            gap: 8px;
95            margin-bottom: 15px;
96        }
97        
98        .xpath-btn {
99            flex: 1;
100            padding: 8px 12px;
101            border: none;
102            border-radius: 4px;
103            cursor: pointer;
104            font-size: 13px;
105            font-weight: 600;
106            transition: background 0.2s;
107        }
108        
109        #xpath-evaluate-btn {
110            background: #3498db;
111            color: white;
112        }
113        
114        #xpath-evaluate-btn:hover {
115            background: #2980b9;
116        }
117        
118        #xpath-clear-btn {
119            background: #95a5a6;
120            color: white;
121        }
122        
123        #xpath-clear-btn:hover {
124            background: #7f8c8d;
125        }
126        
127        #xpath-results {
128            background: #ecf0f1;
129            padding: 12px;
130            border-radius: 4px;
131            max-height: 300px;
132            overflow-y: auto;
133            font-size: 13px;
134        }
135        
136        .xpath-result-count {
137            font-weight: 600;
138            color: #27ae60;
139            margin-bottom: 10px;
140            padding-bottom: 8px;
141            border-bottom: 2px solid #bdc3c7;
142        }
143        
144        .xpath-result-item {
145            background: white;
146            padding: 8px;
147            margin-bottom: 8px;
148            border-radius: 4px;
149            border-left: 3px solid #3498db;
150            cursor: pointer;
151            transition: background 0.2s;
152        }
153        
154        .xpath-result-item:hover {
155            background: #d5dbdb;
156        }
157        
158        .xpath-result-tag {
159            font-weight: 600;
160            color: #e74c3c;
161            font-family: 'Courier New', monospace;
162        }
163        
164        .xpath-result-text {
165            color: #555;
166            margin-top: 4px;
167            font-size: 12px;
168            white-space: nowrap;
169            overflow: hidden;
170            text-overflow: ellipsis;
171        }
172        
173        .xpath-highlight {
174            outline: 3px solid #f39c12 !important;
175            outline-offset: 2px;
176            background-color: rgba(243, 156, 18, 0.2) !important;
177        }
178        
179        .xpath-error {
180            color: #e74c3c;
181            font-weight: 600;
182            padding: 10px;
183            background: #fadbd8;
184            border-radius: 4px;
185            border-left: 3px solid #e74c3c;
186        }
187        
188        #xpath-toggle-btn {
189            position: fixed;
190            bottom: 20px;
191            right: 20px;
192            background: #2c3e50;
193            color: white;
194            border: none;
195            padding: 12px 20px;
196            border-radius: 25px;
197            cursor: pointer;
198            font-size: 14px;
199            font-weight: 600;
200            box-shadow: 0 4px 12px rgba(0,0,0,0.3);
201            z-index: 999998;
202            transition: background 0.2s;
203        }
204        
205        #xpath-toggle-btn:hover {
206            background: #34495e;
207        }
208        
209        .xpath-inspector-mode {
210            cursor: crosshair !important;
211        }
212        
213        .xpath-inspector-highlight {
214            outline: 3px solid #e74c3c !important;
215            outline-offset: 2px;
216            background-color: rgba(231, 76, 60, 0.1) !important;
217        }
218        
219        #xpath-inspector-btn {
220            background: #9b59b6;
221            color: white;
222        }
223        
224        #xpath-inspector-btn:hover {
225            background: #8e44ad;
226        }
227        
228        #xpath-inspector-btn.active {
229            background: #e74c3c;
230        }
231    `);
232
233    let highlightedElements = [];
234    let inspectorMode = false;
235    let inspectorHighlight = null;
236
237    function init() {
238        createToggleButton();
239        createXPathPanel();
240        setupEventListeners();
241    }
242
243    function createToggleButton() {
244        const toggleBtn = document.createElement('button');
245        toggleBtn.id = 'xpath-toggle-btn';
246        toggleBtn.textContent = '🔍 XPath Helper';
247        toggleBtn.addEventListener('click', togglePanel);
248        document.body.appendChild(toggleBtn);
249        console.log('XPath toggle button created');
250    }
251
252    function createXPathPanel() {
253        const panel = document.createElement('div');
254        panel.id = 'xpath-helper-panel';
255        panel.style.display = 'none';
256        
257        panel.innerHTML = `
258            <div id="xpath-helper-header">
259                <h3>🔍 XPath Helper</h3>
260                <button id="xpath-close-btn"></button>
261            </div>
262            <div id="xpath-helper-content">
263                <div class="xpath-input-group">
264                    <label for="xpath-input">XPath Expression:</label>
265                    <input type="text" id="xpath-input" placeholder="//div[@class='example']" />
266                </div>
267                <div class="xpath-button-group">
268                    <button class="xpath-btn" id="xpath-evaluate-btn">Evaluate</button>
269                    <button class="xpath-btn" id="xpath-inspector-btn">Pick Element</button>
270                    <button class="xpath-btn" id="xpath-clear-btn">Clear</button>
271                </div>
272                <div id="xpath-results"></div>
273            </div>
274        `;
275        
276        document.body.appendChild(panel);
277        makeDraggable(panel);
278        console.log('XPath panel created');
279    }
280
281    function setupEventListeners() {
282        document.getElementById('xpath-close-btn').addEventListener('click', togglePanel);
283        document.getElementById('xpath-evaluate-btn').addEventListener('click', evaluateXPath);
284        document.getElementById('xpath-clear-btn').addEventListener('click', clearResults);
285        document.getElementById('xpath-inspector-btn').addEventListener('click', toggleInspector);
286        
287        // Allow Enter key to evaluate
288        document.getElementById('xpath-input').addEventListener('keypress', (e) => {
289            if (e.key === 'Enter') {
290                evaluateXPath();
291            }
292        });
293    }
294
295    function togglePanel() {
296        const panel = document.getElementById('xpath-helper-panel');
297        if (panel.style.display === 'none') {
298            panel.style.display = 'block';
299            console.log('XPath panel opened');
300        } else {
301            panel.style.display = 'none';
302            clearHighlights();
303            if (inspectorMode) {
304                toggleInspector();
305            }
306            console.log('XPath panel closed');
307        }
308    }
309
310    function evaluateXPath() {
311        const xpathInput = document.getElementById('xpath-input').value.trim();
312        const resultsDiv = document.getElementById('xpath-results');
313        
314        if (!xpathInput) {
315            resultsDiv.innerHTML = '<div class="xpath-error">Please enter an XPath expression</div>';
316            return;
317        }
318        
319        console.log('Evaluating XPath:', xpathInput);
320        clearHighlights();
321        
322        try {
323            const result = document.evaluate(
324                xpathInput,
325                document,
326                null,
327                XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
328                null
329            );
330            
331            const count = result.snapshotLength;
332            console.log('XPath results found:', count);
333            
334            if (count === 0) {
335                resultsDiv.innerHTML = '<div class="xpath-error">No elements found</div>';
336                return;
337            }
338            
339            let html = `<div class="xpath-result-count">Found ${count} element${count !== 1 ? 's' : ''}</div>`;
340            
341            for (let i = 0; i < count; i++) {
342                const element = result.snapshotItem(i);
343                highlightedElements.push(element);
344                element.classList.add('xpath-highlight');
345                
346                const tagName = element.tagName ? element.tagName.toLowerCase() : element.nodeName;
347                const textContent = element.textContent ? element.textContent.trim().substring(0, 50) : '';
348                const displayText = textContent ? `"${textContent}${textContent.length >= 50 ? '...' : ''}"` : '(no text)';
349                
350                html += `
351                    <div class="xpath-result-item" data-index="${i}">
352                        <div class="xpath-result-tag">&lt;${tagName}&gt;</div>
353                        <div class="xpath-result-text">${displayText}</div>
354                    </div>
355                `;
356            }
357            
358            resultsDiv.innerHTML = html;
359            
360            // Add click handlers to scroll to elements
361            resultsDiv.querySelectorAll('.xpath-result-item').forEach((item, index) => {
362                item.addEventListener('click', () => {
363                    const element = highlightedElements[index];
364                    element.scrollIntoView({ behavior: 'smooth', block: 'center' });
365                    
366                    // Flash effect
367                    element.style.transition = 'background-color 0.3s';
368                    const originalBg = element.style.backgroundColor;
369                    element.style.backgroundColor = 'rgba(243, 156, 18, 0.6)';
370                    setTimeout(() => {
371                        element.style.backgroundColor = originalBg;
372                    }, 600);
373                });
374            });
375            
376        } catch (error) {
377            console.error('XPath evaluation error:', error);
378            resultsDiv.innerHTML = `<div class="xpath-error">Error: ${error.message}</div>`;
379        }
380    }
381
382    function clearResults() {
383        document.getElementById('xpath-input').value = '';
384        document.getElementById('xpath-results').innerHTML = '';
385        clearHighlights();
386        console.log('Results cleared');
387    }
388
389    function clearHighlights() {
390        highlightedElements.forEach(el => {
391            el.classList.remove('xpath-highlight');
392        });
393        highlightedElements = [];
394    }
395
396    function toggleInspector() {
397        inspectorMode = !inspectorMode;
398        const btn = document.getElementById('xpath-inspector-btn');
399        
400        if (inspectorMode) {
401            btn.textContent = 'Stop Picking';
402            btn.classList.add('active');
403            document.body.classList.add('xpath-inspector-mode');
404            document.addEventListener('mouseover', handleInspectorHover);
405            document.addEventListener('click', handleInspectorClick, true);
406            console.log('Inspector mode activated');
407        } else {
408            btn.textContent = 'Pick Element';
409            btn.classList.remove('active');
410            document.body.classList.remove('xpath-inspector-mode');
411            document.removeEventListener('mouseover', handleInspectorHover);
412            document.removeEventListener('click', handleInspectorClick, true);
413            if (inspectorHighlight) {
414                inspectorHighlight.classList.remove('xpath-inspector-highlight');
415                inspectorHighlight = null;
416            }
417            console.log('Inspector mode deactivated');
418        }
419    }
420
421    function handleInspectorHover(e) {
422        if (!inspectorMode) return;
423        
424        const target = e.target;
425        
426        // Don't highlight the XPath panel itself
427        if (target.closest('#xpath-helper-panel') || target.closest('#xpath-toggle-btn')) {
428            return;
429        }
430        
431        if (inspectorHighlight) {
432            inspectorHighlight.classList.remove('xpath-inspector-highlight');
433        }
434        
435        target.classList.add('xpath-inspector-highlight');
436        inspectorHighlight = target;
437    }
438
439    function handleInspectorClick(e) {
440        if (!inspectorMode) return;
441        
442        e.preventDefault();
443        e.stopPropagation();
444        
445        const target = e.target;
446        
447        // Don't process clicks on the XPath panel
448        if (target.closest('#xpath-helper-panel') || target.closest('#xpath-toggle-btn')) {
449            return;
450        }
451        
452        const xpath = getXPath(target);
453        document.getElementById('xpath-input').value = xpath;
454        console.log('Generated XPath:', xpath);
455        
456        toggleInspector();
457        evaluateXPath();
458    }
459
460    function getXPath(element) {
461        if (element.id) {
462            return `//*[@id="${element.id}"]`;
463        }
464        
465        if (element === document.body) {
466            return '/html/body';
467        }
468        
469        let path = '';
470        let current = element;
471        
472        while (current && current.nodeType === Node.ELEMENT_NODE) {
473            let index = 0;
474            let sibling = current.previousSibling;
475            
476            while (sibling) {
477                if (sibling.nodeType === Node.ELEMENT_NODE && sibling.nodeName === current.nodeName) {
478                    index++;
479                }
480                sibling = sibling.previousSibling;
481            }
482            
483            const tagName = current.nodeName.toLowerCase();
484            const pathIndex = index > 0 ? `[${index + 1}]` : '';
485            path = `/${tagName}${pathIndex}${path}`;
486            
487            current = current.parentNode;
488        }
489        
490        return path;
491    }
492
493    function makeDraggable(element) {
494        const header = element.querySelector('#xpath-helper-header');
495        let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
496        
497        header.onmousedown = dragMouseDown;
498        
499        function dragMouseDown(e) {
500            e.preventDefault();
501            pos3 = e.clientX;
502            pos4 = e.clientY;
503            document.onmouseup = closeDragElement;
504            document.onmousemove = elementDrag;
505        }
506        
507        function elementDrag(e) {
508            e.preventDefault();
509            pos1 = pos3 - e.clientX;
510            pos2 = pos4 - e.clientY;
511            pos3 = e.clientX;
512            pos4 = e.clientY;
513            element.style.top = (element.offsetTop - pos2) + 'px';
514            element.style.left = (element.offsetLeft - pos1) + 'px';
515            element.style.right = 'auto';
516        }
517        
518        function closeDragElement() {
519            document.onmouseup = null;
520            document.onmousemove = null;
521        }
522    }
523
524    // Initialize when DOM is ready
525    if (document.body) {
526        init();
527    } else {
528        TM_runBody(init);
529    }
530
531})();
XPath Extension for Chrome | Robomonkey