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"><${tagName}></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})();