15 KiB
		
	
	
	
	
	
	
	
			
		
		
	
	
			15 KiB
		
	
	
	
	
	
	
	
Advanced Chrome DevTools Protocol Techniques
Advanced patterns for complex browser automation scenarios.
Network Interception
Monitor Network Requests
// 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
// 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
// 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
// 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
// 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
{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
{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
// 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
{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
{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
{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
// 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
// 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
{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
{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
{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
{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
{action: "eval", payload: `
  performance.memory ? {
    usedJSHeapSize: performance.memory.usedJSHeapSize,
    totalJSHeapSize: performance.memory.totalJSHeapSize,
    jsHeapSizeLimit: performance.memory.jsHeapSizeLimit
  } : 'Memory API not available'
`}
Get Resource Timing
{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
{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
{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
{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.
// 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
{action: "eval", payload: `
  (() => {
    const event = new CustomEvent('myCustomEvent', {
      detail: { message: 'Hello from automation' }
    });
    document.dispatchEvent(event);
    return 'Event dispatched';
  })()
`}
Listen for Events
{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
{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
{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
{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
{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
{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)
    }));
  }
`}