679 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			679 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
# Advanced Chrome DevTools Protocol Techniques
 | 
						|
 | 
						|
Advanced patterns for complex browser automation scenarios.
 | 
						|
 | 
						|
## Network Interception
 | 
						|
 | 
						|
### Monitor Network Requests
 | 
						|
 | 
						|
```json
 | 
						|
// Get all network requests via Performance API
 | 
						|
{action: "eval", payload: `
 | 
						|
  performance.getEntriesByType('resource').map(r => ({
 | 
						|
    name: r.name,
 | 
						|
    type: r.initiatorType,
 | 
						|
    duration: r.duration,
 | 
						|
    size: r.transferSize
 | 
						|
  }))
 | 
						|
`}
 | 
						|
```
 | 
						|
 | 
						|
### Wait for Specific Request
 | 
						|
 | 
						|
```json
 | 
						|
// Wait for API call to complete
 | 
						|
{action: "eval", payload: `
 | 
						|
  new Promise(resolve => {
 | 
						|
    const check = () => {
 | 
						|
      const apiCall = performance.getEntriesByType('resource')
 | 
						|
        .find(r => r.name.includes('/api/data'));
 | 
						|
      if (apiCall) {
 | 
						|
        resolve(apiCall);
 | 
						|
      } else {
 | 
						|
        setTimeout(check, 100);
 | 
						|
      }
 | 
						|
    };
 | 
						|
    check();
 | 
						|
  })
 | 
						|
`}
 | 
						|
```
 | 
						|
 | 
						|
### Check Response Status
 | 
						|
 | 
						|
```json
 | 
						|
// Fetch API to check endpoint
 | 
						|
{action: "eval", payload: `
 | 
						|
  fetch('https://api.example.com/status')
 | 
						|
    .then(r => ({ status: r.status, ok: r.ok }))
 | 
						|
`}
 | 
						|
```
 | 
						|
 | 
						|
---
 | 
						|
 | 
						|
## JavaScript Injection
 | 
						|
 | 
						|
### Add Helper Functions
 | 
						|
 | 
						|
```json
 | 
						|
// Inject utility functions into page
 | 
						|
{action: "eval", payload: `
 | 
						|
  window.waitForElement = (selector, timeout = 5000) => {
 | 
						|
    return new Promise((resolve, reject) => {
 | 
						|
      const startTime = Date.now();
 | 
						|
      const check = () => {
 | 
						|
        const elem = document.querySelector(selector);
 | 
						|
        if (elem) {
 | 
						|
          resolve(elem);
 | 
						|
        } else if (Date.now() - startTime > timeout) {
 | 
						|
          reject(new Error('Timeout'));
 | 
						|
        } else {
 | 
						|
          setTimeout(check, 100);
 | 
						|
        }
 | 
						|
      };
 | 
						|
      check();
 | 
						|
    });
 | 
						|
  };
 | 
						|
  'Helper injected'
 | 
						|
`}
 | 
						|
 | 
						|
// Use injected helper
 | 
						|
{action: "eval", payload: "window.waitForElement('.lazy-content')"}
 | 
						|
```
 | 
						|
 | 
						|
### Modify Page Behavior
 | 
						|
 | 
						|
```json
 | 
						|
// Disable animations for faster testing
 | 
						|
{action: "eval", payload: `
 | 
						|
  const style = document.createElement('style');
 | 
						|
  style.textContent = '* { animation: none !important; transition: none !important; }';
 | 
						|
  document.head.appendChild(style);
 | 
						|
  'Animations disabled'
 | 
						|
`}
 | 
						|
 | 
						|
// Override fetch to log requests
 | 
						|
{action: "eval", payload: `
 | 
						|
  const originalFetch = window.fetch;
 | 
						|
  window.fetch = function(...args) {
 | 
						|
    console.log('Fetch:', args[0]);
 | 
						|
    return originalFetch.apply(this, arguments);
 | 
						|
  };
 | 
						|
  'Fetch override installed'
 | 
						|
`}
 | 
						|
```
 | 
						|
 | 
						|
---
 | 
						|
 | 
						|
## Complex Waiting Patterns
 | 
						|
 | 
						|
### Wait for Multiple Conditions
 | 
						|
 | 
						|
```json
 | 
						|
{action: "eval", payload: `
 | 
						|
  Promise.all([
 | 
						|
    new Promise(r => {
 | 
						|
      const check = () => document.querySelector('.element1') ? r() : setTimeout(check, 100);
 | 
						|
      check();
 | 
						|
    }),
 | 
						|
    new Promise(r => {
 | 
						|
      const check = () => document.querySelector('.element2') ? r() : setTimeout(check, 100);
 | 
						|
      check();
 | 
						|
    })
 | 
						|
  ])
 | 
						|
`}
 | 
						|
```
 | 
						|
 | 
						|
### Wait with Mutation Observer
 | 
						|
 | 
						|
```json
 | 
						|
{action: "eval", payload: `
 | 
						|
  new Promise(resolve => {
 | 
						|
    const observer = new MutationObserver((mutations) => {
 | 
						|
      const target = document.querySelector('.dynamic-content');
 | 
						|
      if (target && target.textContent.trim() !== '') {
 | 
						|
        observer.disconnect();
 | 
						|
        resolve(target.textContent);
 | 
						|
      }
 | 
						|
    });
 | 
						|
    observer.observe(document.body, {
 | 
						|
      childList: true,
 | 
						|
      subtree: true,
 | 
						|
      characterData: true
 | 
						|
    });
 | 
						|
  })
 | 
						|
`}
 | 
						|
```
 | 
						|
 | 
						|
### Wait for Idle State
 | 
						|
 | 
						|
```json
 | 
						|
// Wait for network idle
 | 
						|
{action: "eval", payload: `
 | 
						|
  new Promise(resolve => {
 | 
						|
    let lastActivity = Date.now();
 | 
						|
    
 | 
						|
    // Monitor network activity
 | 
						|
    const originalFetch = window.fetch;
 | 
						|
    window.fetch = function(...args) {
 | 
						|
      lastActivity = Date.now();
 | 
						|
      return originalFetch.apply(this, arguments);
 | 
						|
    };
 | 
						|
    
 | 
						|
    // Check if idle for 500ms
 | 
						|
    const checkIdle = () => {
 | 
						|
      if (Date.now() - lastActivity > 500) {
 | 
						|
        window.fetch = originalFetch;
 | 
						|
        resolve('idle');
 | 
						|
      } else {
 | 
						|
        setTimeout(checkIdle, 100);
 | 
						|
      }
 | 
						|
    };
 | 
						|
    
 | 
						|
    setTimeout(checkIdle, 100);
 | 
						|
  })
 | 
						|
`}
 | 
						|
```
 | 
						|
 | 
						|
---
 | 
						|
 | 
						|
## Data Manipulation
 | 
						|
 | 
						|
### Parse and Transform Table
 | 
						|
 | 
						|
```json
 | 
						|
{action: "eval", payload: `
 | 
						|
  (() => {
 | 
						|
    const table = document.querySelector('table');
 | 
						|
    const headers = Array.from(table.querySelectorAll('thead th'))
 | 
						|
      .map(th => th.textContent.trim());
 | 
						|
    
 | 
						|
    const rows = Array.from(table.querySelectorAll('tbody tr'))
 | 
						|
      .map(tr => {
 | 
						|
        const cells = Array.from(tr.cells).map(td => td.textContent.trim());
 | 
						|
        return Object.fromEntries(headers.map((h, i) => [h, cells[i]]));
 | 
						|
      });
 | 
						|
    
 | 
						|
    // Filter and transform
 | 
						|
    return rows
 | 
						|
      .filter(row => parseFloat(row['Price'].replace('$', '')) > 100)
 | 
						|
      .map(row => ({
 | 
						|
        ...row,
 | 
						|
        priceNum: parseFloat(row['Price'].replace('$', ''))
 | 
						|
      }))
 | 
						|
      .sort((a, b) => b.priceNum - a.priceNum);
 | 
						|
  })()
 | 
						|
`}
 | 
						|
```
 | 
						|
 | 
						|
### Extract Nested JSON from Script Tags
 | 
						|
 | 
						|
```json
 | 
						|
{action: "eval", payload: `
 | 
						|
  (() => {
 | 
						|
    const scripts = Array.from(document.querySelectorAll('script[type="application/ld+json"]'));
 | 
						|
    return scripts.map(s => JSON.parse(s.textContent));
 | 
						|
  })()
 | 
						|
`}
 | 
						|
```
 | 
						|
 | 
						|
### Aggregate Multiple Elements
 | 
						|
 | 
						|
```json
 | 
						|
{action: "eval", payload: `
 | 
						|
  (() => {
 | 
						|
    const sections = Array.from(document.querySelectorAll('section.category'));
 | 
						|
    
 | 
						|
    return sections.map(section => ({
 | 
						|
      category: section.querySelector('h2').textContent,
 | 
						|
      items: Array.from(section.querySelectorAll('.item')).map(item => ({
 | 
						|
        name: item.querySelector('.name').textContent,
 | 
						|
        price: item.querySelector('.price').textContent,
 | 
						|
        inStock: !item.querySelector('.out-of-stock')
 | 
						|
      })),
 | 
						|
      total: section.querySelectorAll('.item').length
 | 
						|
    }));
 | 
						|
  })()
 | 
						|
`}
 | 
						|
```
 | 
						|
 | 
						|
---
 | 
						|
 | 
						|
## State Management
 | 
						|
 | 
						|
### Save and Restore Form State
 | 
						|
 | 
						|
```json
 | 
						|
// Save form state
 | 
						|
{action: "eval", payload: `
 | 
						|
  (() => {
 | 
						|
    const form = document.querySelector('form');
 | 
						|
    const data = {};
 | 
						|
    new FormData(form).forEach((value, key) => data[key] = value);
 | 
						|
    localStorage.setItem('formBackup', JSON.stringify(data));
 | 
						|
    return data;
 | 
						|
  })()
 | 
						|
`}
 | 
						|
 | 
						|
// Restore form state
 | 
						|
{action: "eval", payload: `
 | 
						|
  (() => {
 | 
						|
    const data = JSON.parse(localStorage.getItem('formBackup'));
 | 
						|
    const form = document.querySelector('form');
 | 
						|
    Object.entries(data).forEach(([name, value]) => {
 | 
						|
      const input = form.querySelector(\`[name="\${name}"]\`);
 | 
						|
      if (input) input.value = value;
 | 
						|
    });
 | 
						|
    return 'Form restored';
 | 
						|
  })()
 | 
						|
`}
 | 
						|
```
 | 
						|
 | 
						|
### Session Management
 | 
						|
 | 
						|
```json
 | 
						|
// Save session state
 | 
						|
{action: "eval", payload: `
 | 
						|
  ({
 | 
						|
    cookies: document.cookie,
 | 
						|
    localStorage: JSON.stringify(localStorage),
 | 
						|
    sessionStorage: JSON.stringify(sessionStorage),
 | 
						|
    url: window.location.href
 | 
						|
  })
 | 
						|
`}
 | 
						|
 | 
						|
// Restore session (on new page load)
 | 
						|
{action: "eval", payload: `
 | 
						|
  (() => {
 | 
						|
    const session = {/* saved session data */};
 | 
						|
    
 | 
						|
    // Restore cookies
 | 
						|
    session.cookies.split('; ').forEach(cookie => {
 | 
						|
      document.cookie = cookie;
 | 
						|
    });
 | 
						|
    
 | 
						|
    // Restore localStorage
 | 
						|
    Object.entries(JSON.parse(session.localStorage)).forEach(([k, v]) => {
 | 
						|
      localStorage.setItem(k, v);
 | 
						|
    });
 | 
						|
    
 | 
						|
    return 'Session restored';
 | 
						|
  })()
 | 
						|
`}
 | 
						|
```
 | 
						|
 | 
						|
---
 | 
						|
 | 
						|
## Visual Testing
 | 
						|
 | 
						|
### Check Element Visibility
 | 
						|
 | 
						|
```json
 | 
						|
{action: "eval", payload: `
 | 
						|
  (selector) => {
 | 
						|
    const elem = document.querySelector(selector);
 | 
						|
    if (!elem) return { visible: false, reason: 'not found' };
 | 
						|
    
 | 
						|
    const rect = elem.getBoundingClientRect();
 | 
						|
    const style = window.getComputedStyle(elem);
 | 
						|
    
 | 
						|
    return {
 | 
						|
      visible: rect.width > 0 && rect.height > 0 && style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0',
 | 
						|
      rect: rect,
 | 
						|
      computed: {
 | 
						|
        display: style.display,
 | 
						|
        visibility: style.visibility,
 | 
						|
        opacity: style.opacity
 | 
						|
      }
 | 
						|
    };
 | 
						|
  }
 | 
						|
`}
 | 
						|
```
 | 
						|
 | 
						|
### Get Element Colors
 | 
						|
 | 
						|
```json
 | 
						|
{action: "eval", payload: `
 | 
						|
  (() => {
 | 
						|
    const elem = document.querySelector('.button');
 | 
						|
    const style = window.getComputedStyle(elem);
 | 
						|
    
 | 
						|
    return {
 | 
						|
      backgroundColor: style.backgroundColor,
 | 
						|
      color: style.color,
 | 
						|
      borderColor: style.borderColor
 | 
						|
    };
 | 
						|
  })()
 | 
						|
`}
 | 
						|
```
 | 
						|
 | 
						|
### Measure Element Positions
 | 
						|
 | 
						|
```json
 | 
						|
{action: "eval", payload: `
 | 
						|
  (() => {
 | 
						|
    const elements = Array.from(document.querySelectorAll('.item'));
 | 
						|
    
 | 
						|
    return elements.map(elem => {
 | 
						|
      const rect = elem.getBoundingClientRect();
 | 
						|
      return {
 | 
						|
        id: elem.id,
 | 
						|
        x: rect.x,
 | 
						|
        y: rect.y,
 | 
						|
        width: rect.width,
 | 
						|
        height: rect.height,
 | 
						|
        inViewport: rect.top >= 0 && rect.bottom <= window.innerHeight
 | 
						|
      };
 | 
						|
    });
 | 
						|
  })()
 | 
						|
`}
 | 
						|
```
 | 
						|
 | 
						|
---
 | 
						|
 | 
						|
## Performance Monitoring
 | 
						|
 | 
						|
### Get Page Load Metrics
 | 
						|
 | 
						|
```json
 | 
						|
{action: "eval", payload: `
 | 
						|
  (() => {
 | 
						|
    const nav = performance.getEntriesByType('navigation')[0];
 | 
						|
    const paint = performance.getEntriesByType('paint');
 | 
						|
    
 | 
						|
    return {
 | 
						|
      dns: nav.domainLookupEnd - nav.domainLookupStart,
 | 
						|
      tcp: nav.connectEnd - nav.connectStart,
 | 
						|
      request: nav.responseStart - nav.requestStart,
 | 
						|
      response: nav.responseEnd - nav.responseStart,
 | 
						|
      domLoad: nav.domContentLoadedEventEnd - nav.domContentLoadedEventStart,
 | 
						|
      pageLoad: nav.loadEventEnd - nav.loadEventStart,
 | 
						|
      firstPaint: paint.find(p => p.name === 'first-paint')?.startTime,
 | 
						|
      firstContentfulPaint: paint.find(p => p.name === 'first-contentful-paint')?.startTime
 | 
						|
    };
 | 
						|
  })()
 | 
						|
`}
 | 
						|
```
 | 
						|
 | 
						|
### Monitor Memory Usage
 | 
						|
 | 
						|
```json
 | 
						|
{action: "eval", payload: `
 | 
						|
  performance.memory ? {
 | 
						|
    usedJSHeapSize: performance.memory.usedJSHeapSize,
 | 
						|
    totalJSHeapSize: performance.memory.totalJSHeapSize,
 | 
						|
    jsHeapSizeLimit: performance.memory.jsHeapSizeLimit
 | 
						|
  } : 'Memory API not available'
 | 
						|
`}
 | 
						|
```
 | 
						|
 | 
						|
### Get Resource Timing
 | 
						|
 | 
						|
```json
 | 
						|
{action: "eval", payload: `
 | 
						|
  (() => {
 | 
						|
    const resources = performance.getEntriesByType('resource');
 | 
						|
    
 | 
						|
    // Group by type
 | 
						|
    const byType = {};
 | 
						|
    resources.forEach(r => {
 | 
						|
      if (!byType[r.initiatorType]) byType[r.initiatorType] = [];
 | 
						|
      byType[r.initiatorType].push({
 | 
						|
        name: r.name,
 | 
						|
        duration: r.duration,
 | 
						|
        size: r.transferSize
 | 
						|
      });
 | 
						|
    });
 | 
						|
    
 | 
						|
    return {
 | 
						|
      total: resources.length,
 | 
						|
      byType: Object.fromEntries(
 | 
						|
        Object.entries(byType).map(([type, items]) => [
 | 
						|
          type,
 | 
						|
          {
 | 
						|
            count: items.length,
 | 
						|
            totalDuration: items.reduce((sum, i) => sum + i.duration, 0),
 | 
						|
            totalSize: items.reduce((sum, i) => sum + i.size, 0)
 | 
						|
          }
 | 
						|
        ])
 | 
						|
      )
 | 
						|
    };
 | 
						|
  })()
 | 
						|
`}
 | 
						|
```
 | 
						|
 | 
						|
---
 | 
						|
 | 
						|
## Accessibility Testing
 | 
						|
 | 
						|
### Check ARIA Labels
 | 
						|
 | 
						|
```json
 | 
						|
{action: "eval", payload: `
 | 
						|
  Array.from(document.querySelectorAll('button, a, input')).map(elem => ({
 | 
						|
    tag: elem.tagName,
 | 
						|
    text: elem.textContent.trim(),
 | 
						|
    ariaLabel: elem.getAttribute('aria-label'),
 | 
						|
    ariaDescribedBy: elem.getAttribute('aria-describedby'),
 | 
						|
    title: elem.getAttribute('title'),
 | 
						|
    hasAccessibleName: !!(elem.getAttribute('aria-label') || elem.textContent.trim() || elem.getAttribute('title'))
 | 
						|
  }))
 | 
						|
`}
 | 
						|
```
 | 
						|
 | 
						|
### Find Focus Order
 | 
						|
 | 
						|
```json
 | 
						|
{action: "eval", payload: `
 | 
						|
  Array.from(document.querySelectorAll('a, button, input, select, textarea, [tabindex]'))
 | 
						|
    .filter(elem => {
 | 
						|
      const style = window.getComputedStyle(elem);
 | 
						|
      return style.display !== 'none' && style.visibility !== 'hidden';
 | 
						|
    })
 | 
						|
    .map((elem, index) => ({
 | 
						|
      index: index,
 | 
						|
      tag: elem.tagName,
 | 
						|
      tabIndex: elem.tabIndex,
 | 
						|
      text: elem.textContent.trim().substring(0, 50)
 | 
						|
    }))
 | 
						|
`}
 | 
						|
```
 | 
						|
 | 
						|
---
 | 
						|
 | 
						|
## Frame Handling
 | 
						|
 | 
						|
### List Frames
 | 
						|
 | 
						|
```json
 | 
						|
{action: "eval", payload: `
 | 
						|
  Array.from(document.querySelectorAll('iframe, frame')).map((frame, i) => ({
 | 
						|
    index: i,
 | 
						|
    src: frame.src,
 | 
						|
    name: frame.name,
 | 
						|
    id: frame.id
 | 
						|
  }))
 | 
						|
`}
 | 
						|
```
 | 
						|
 | 
						|
### Access Frame Content
 | 
						|
 | 
						|
Note: Cross-origin frames cannot be accessed due to security restrictions.
 | 
						|
 | 
						|
```json
 | 
						|
// For same-origin frames only
 | 
						|
{action: "eval", payload: `
 | 
						|
  (() => {
 | 
						|
    const frame = document.querySelector('iframe');
 | 
						|
    try {
 | 
						|
      return {
 | 
						|
        title: frame.contentDocument.title,
 | 
						|
        body: frame.contentDocument.body.textContent.substring(0, 100)
 | 
						|
      };
 | 
						|
    } catch (e) {
 | 
						|
      return { error: 'Cross-origin frame - cannot access' };
 | 
						|
    }
 | 
						|
  })()
 | 
						|
`}
 | 
						|
```
 | 
						|
 | 
						|
---
 | 
						|
 | 
						|
## Custom Events
 | 
						|
 | 
						|
### Trigger Custom Events
 | 
						|
 | 
						|
```json
 | 
						|
{action: "eval", payload: `
 | 
						|
  (() => {
 | 
						|
    const event = new CustomEvent('myCustomEvent', {
 | 
						|
      detail: { message: 'Hello from automation' }
 | 
						|
    });
 | 
						|
    document.dispatchEvent(event);
 | 
						|
    return 'Event dispatched';
 | 
						|
  })()
 | 
						|
`}
 | 
						|
```
 | 
						|
 | 
						|
### Listen for Events
 | 
						|
 | 
						|
```json
 | 
						|
{action: "eval", payload: `
 | 
						|
  new Promise(resolve => {
 | 
						|
    const handler = (e) => {
 | 
						|
      document.removeEventListener('myCustomEvent', handler);
 | 
						|
      resolve(e.detail);
 | 
						|
    };
 | 
						|
    document.addEventListener('myCustomEvent', handler);
 | 
						|
    
 | 
						|
    // Timeout after 5 seconds
 | 
						|
    setTimeout(() => {
 | 
						|
      document.removeEventListener('myCustomEvent', handler);
 | 
						|
      resolve({ timeout: true });
 | 
						|
    }, 5000);
 | 
						|
  })
 | 
						|
`}
 | 
						|
```
 | 
						|
 | 
						|
---
 | 
						|
 | 
						|
## Browser Detection
 | 
						|
 | 
						|
### Get Browser Info
 | 
						|
 | 
						|
```json
 | 
						|
{action: "eval", payload: `
 | 
						|
  ({
 | 
						|
    userAgent: navigator.userAgent,
 | 
						|
    platform: navigator.platform,
 | 
						|
    language: navigator.language,
 | 
						|
    cookiesEnabled: navigator.cookieEnabled,
 | 
						|
    doNotTrack: navigator.doNotTrack,
 | 
						|
    viewport: {
 | 
						|
      width: window.innerWidth,
 | 
						|
      height: window.innerHeight
 | 
						|
    },
 | 
						|
    screen: {
 | 
						|
      width: screen.width,
 | 
						|
      height: screen.height,
 | 
						|
      colorDepth: screen.colorDepth
 | 
						|
    }
 | 
						|
  })
 | 
						|
`}
 | 
						|
```
 | 
						|
 | 
						|
---
 | 
						|
 | 
						|
## Testing Helpers
 | 
						|
 | 
						|
### Get All Interactive Elements
 | 
						|
 | 
						|
```json
 | 
						|
{action: "eval", payload: `
 | 
						|
  Array.from(document.querySelectorAll('a, button, input, select, textarea, [onclick], [role=button]'))
 | 
						|
    .filter(elem => {
 | 
						|
      const style = window.getComputedStyle(elem);
 | 
						|
      return style.display !== 'none' && style.visibility !== 'hidden';
 | 
						|
    })
 | 
						|
    .map(elem => ({
 | 
						|
      tag: elem.tagName,
 | 
						|
      type: elem.type,
 | 
						|
      id: elem.id,
 | 
						|
      class: elem.className,
 | 
						|
      text: elem.textContent.trim().substring(0, 50),
 | 
						|
      selector: elem.id ? \`#\${elem.id}\` : \`\${elem.tagName.toLowerCase()}\${elem.className ? '.' + elem.className.split(' ').join('.') : ''}\`
 | 
						|
    }))
 | 
						|
`}
 | 
						|
```
 | 
						|
 | 
						|
### Validate Forms
 | 
						|
 | 
						|
```json
 | 
						|
{action: "eval", payload: `
 | 
						|
  (() => {
 | 
						|
    const forms = Array.from(document.querySelectorAll('form'));
 | 
						|
    
 | 
						|
    return forms.map(form => ({
 | 
						|
      id: form.id,
 | 
						|
      action: form.action,
 | 
						|
      method: form.method,
 | 
						|
      fields: Array.from(form.elements).map(elem => ({
 | 
						|
        name: elem.name,
 | 
						|
        type: elem.type,
 | 
						|
        required: elem.required,
 | 
						|
        value: elem.value,
 | 
						|
        valid: elem.checkValidity()
 | 
						|
      }))
 | 
						|
    }));
 | 
						|
  })()
 | 
						|
`}
 | 
						|
```
 | 
						|
 | 
						|
---
 | 
						|
 | 
						|
## Debugging Tools
 | 
						|
 | 
						|
### Log Element Path
 | 
						|
 | 
						|
```json
 | 
						|
{action: "eval", payload: `
 | 
						|
  (selector) => {
 | 
						|
    const elem = document.querySelector(selector);
 | 
						|
    if (!elem) return null;
 | 
						|
    
 | 
						|
    const path = [];
 | 
						|
    let current = elem;
 | 
						|
    
 | 
						|
    while (current && current !== document.body) {
 | 
						|
      let selector = current.tagName.toLowerCase();
 | 
						|
      if (current.id) selector += '#' + current.id;
 | 
						|
      if (current.className) selector += '.' + current.className.split(' ').join('.');
 | 
						|
      path.unshift(selector);
 | 
						|
      current = current.parentElement;
 | 
						|
    }
 | 
						|
    
 | 
						|
    return path.join(' > ');
 | 
						|
  }
 | 
						|
`}
 | 
						|
```
 | 
						|
 | 
						|
### Find Element by Text
 | 
						|
 | 
						|
```json
 | 
						|
{action: "eval", payload: `
 | 
						|
  (text) => {
 | 
						|
    const elements = Array.from(document.querySelectorAll('*'));
 | 
						|
    const matches = elements.filter(elem => 
 | 
						|
      elem.textContent.includes(text) && 
 | 
						|
      !Array.from(elem.children).some(child => child.textContent.includes(text))
 | 
						|
    );
 | 
						|
    
 | 
						|
    return matches.map(elem => ({
 | 
						|
      tag: elem.tagName,
 | 
						|
      id: elem.id,
 | 
						|
      class: elem.className,
 | 
						|
      text: elem.textContent.trim().substring(0, 100)
 | 
						|
    }));
 | 
						|
  }
 | 
						|
`}
 | 
						|
```
 |