ANTIGRAVITY LABJP
Articles/Antigravity Basics
Antigravity Basics/2026-03-27Advanced

Browser Sub-Agent Practical Guide — AI Autonomously Operating the Web

Complete guide to implementing and operating Browser Sub-Agent in Antigravity. Master web scraping, form automation, E2E testing, and production-grade security design for autonomous web workflows.

browser-sub-agent3web-automationscrapingtesting17ai-agent18

What is Browser Sub-Agent?

Google Antigravity IDE's Browser Sub-Agent gives your AI agent "hands" to operate the web. Unlike traditional web scraping or test automation tools, this AI understands natural language and controls browsers like humans would—clicking, typing, scrolling, and waiting.

Key capabilities:

  • Web Scraping: Extract data from JavaScript-heavy sites (not just static HTML)
  • Form Automation: Handle multi-step authentication, complex forms, conditional UI elements
  • E2E Testing: Run user scenarios automatically, capture screenshots, verify outcomes
  • Business Automation: Price monitoring, report generation, daily workflow execution

Browser Sub-Agent Architecture

Internal Flow

┌─────────────────────────────────────┐
│ AI Agent (Natural Language Input)   │
└──────────────┬──────────────────────┘
               │
               ↓
┌─────────────────────────────────────┐
│ Intent Parser                       │
│ Decompose into Actions              │
│ - "Click button" → click(xy)        │
│ - "Enter text" → type(selector)     │
└──────────────┬──────────────────────┘
               │
               ↓
┌─────────────────────────────────────┐
│ DOM Interpreter                     │
│ Resolve selectors & compute         │
│ element positions                   │
└──────────────┬──────────────────────┘
               │
               ↓
┌─────────────────────────────────────┐
│ Chromium Browser Instance           │
│ (Headless or Headful mode)          │
│ - Load pages                        │
│ - Execute interactions              │
│ - Capture screenshots               │
└──────────────┬──────────────────────┘
               │
               ↓
┌─────────────────────────────────────┐
│ Output Processor                    │
│ - DOM snapshots                     │
│ - Screenshots                       │
│ - Text extraction                   │
└─────────────────────────────────────┘

Important Limitations & Workarounds

LimitationWorkaround
JS rendering delay (3–5 sec)Use waitForTimeout, waitForSelector
Network speed dependentSet longer timeout values (default 30 sec)
CORS blocks API callsImplement server-side proxy
Session managementEnable persistCookies: true

Web Scraping: Implementation

Basic Pattern

// browser-scraper.ts
import { launch } from 'puppeteer';
 
interface ScrapedProduct {
  title: string;
  price: number;
  rating: number;
  url: string;
}
 
class EcommerceScraper {
  private browser: any;
 
  async initialize() {
    this.browser = await launch({
      headless: true,
      args: [
        '--no-sandbox',
        '--disable-setuid-sandbox',
        '--disable-blink-features=AutomationControlled', // Avoid bot detection
      ],
    });
  }
 
  async scrapeProductListing(
    baseUrl: string,
    pageCount: number = 3
  ): Promise<ScrapedProduct[]> {
    const results: ScrapedProduct[] = [];
    const page = await this.browser.newPage();
 
    // Spoof User-Agent
    await page.setUserAgent(
      'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
    );
 
    for (let i = 1; i <= pageCount; i++) {
      const url = `${baseUrl}?page=${i}`;
      console.log(`Scraping: ${url}`);
 
      try {
        // Wait for page load
        await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
 
        // Wait for dynamic content
        await page.waitForSelector('.product-item', { timeout: 10000 });
 
        // Extract from DOM
        const products = await page.evaluate(() => {
          const items = document.querySelectorAll('.product-item');
          return Array.from(items).map((item) => {
            const titleEl = item.querySelector('.product-title');
            const priceEl = item.querySelector('.product-price');
            const ratingEl = item.querySelector('.product-rating');
            const linkEl = item.querySelector('a.product-link');
 
            return {
              title: titleEl?.textContent?.trim() || 'N/A',
              price: parseFloat(
                priceEl?.textContent?.replace(/[^\d.]/g, '') || '0'
              ),
              rating: parseFloat(ratingEl?.textContent || '0'),
              url: linkEl?.getAttribute('href') || '',
            };
          });
        });
 
        results.push(...products);
        console.log(`Page ${i}: ${products.length} products found`);
 
        // Rate limiting: Add delay between pages
        await page.waitForTimeout(2000 + Math.random() * 3000);
      } catch (error) {
        console.error(`Error scraping page ${i}:`, error);
      }
    }
 
    await page.close();
    return results;
  }
 
  async close() {
    await this.browser.close();
  }
}
 
// Usage
async function main() {
  const scraper = new EcommerceScraper();
  await scraper.initialize();
 
  const products = await scraper.scrapeProductListing(
    'https://example-ecommerce.com/products',
    3
  );
 
  console.log(`Total products scraped: ${products.length}`);
  console.log(JSON.stringify(products, null, 2));
 
  await scraper.close();
}
 
main().catch(console.error);

Rate Limiting

class RateLimitedScraper {
  private requestTimes: number[] = [];
  private maxRequestsPerMinute: number = 10;
 
  async ensureRateLimit() {
    const now = Date.now();
    const oneMinuteAgo = now - 60 * 1000;
 
    // Count requests in last minute
    this.requestTimes = this.requestTimes.filter((t) => t > oneMinuteAgo);
 
    if (this.requestTimes.length >= this.maxRequestsPerMinute) {
      // Rate limited: wait
      const waitTime = 60 * 1000 - (now - this.requestTimes[0]);
      console.log(`Rate limit reached. Waiting ${waitTime}ms...`);
      await new Promise((r) => setTimeout(r, waitTime));
    }
 
    this.requestTimes.push(now);
  }
 
  async scrapePage(url: string) {
    await this.ensureRateLimit();
    // Scraping logic...
  }
}

Sample Output:

[
  {
    "title": "High-Performance AI Laptop",
    "price": 1580,
    "rating": 4.8,
    "url": "https://example.com/product/ai-laptop-pro"
  },
  {
    "title": "USB-C Docking Station",
    "price": 25800,
    "rating": 4.5,
    "url": "https://example.com/product/usb-c-dock"
  }
]

Form Automation & Login

Complex Forms with Conditional Fields

class FormFiller {
  async fillComplexForm(page: any) {
    // Step 1: Text input fields
    await page.type('#name-input', 'Masaki Hirokawa');
    await page.type('#email-input', 'user@example.com');
 
    // Step 2: Radio button selection
    await page.click('input[value="business"]');
 
    // Step 3: Conditional reveal (fields appear based on selection)
    await page.waitForSelector('#company-name-field', { timeout: 5000 });
    await page.type('#company-name-field', 'Dolice Labs');
 
    // Step 4: Multi-select (Ctrl/Cmd+click for multiple)
    const tags = ['AI Development', 'Web Automation', 'Agent System'];
    for (const tag of tags) {
      await page.click(`label:contains("${tag}")`, { modifiers: ['Control'] });
    }
 
    // Step 5: File upload
    const uploadInput = await page.$('input[type="file"]');
    await uploadInput.uploadFile('/path/to/resume.pdf');
 
    // Step 6: Accept terms
    await page.click('#terms-checkbox');
    await page.click('#privacy-checkbox');
 
    // Step 7: Submit
    await Promise.all([
      page.waitForNavigation({ waitUntil: 'networkidle0' }),
      page.click('#submit-button'),
    ]);
 
    console.log('Form submitted successfully');
  }
}

Session Persistence & Auth State

class AuthenticatedSession {
  private cookiesFile = './session-cookies.json';
 
  async saveCookies(page: any) {
    const cookies = await page.cookies();
    require('fs').writeFileSync(this.cookiesFile, JSON.stringify(cookies, null, 2));
  }
 
  async restoreCookies(page: any) {
    if (require('fs').existsSync(this.cookiesFile)) {
      const cookies = JSON.parse(
        require('fs').readFileSync(this.cookiesFile, 'utf8')
      );
      await page.setCookie(...cookies);
      console.log('Cookies restored from previous session');
    }
  }
 
  async login(page: any, email: string, password: string) {
    await page.goto('https://example.com/login');
 
    await page.type('#email', email);
    await page.type('#password', password);
    await page.click('button[type="submit"]');
 
    await page.waitForNavigation({ waitUntil: 'networkidle0' });
 
    // Save cookies for future use
    await this.saveCookies(page);
  }
 
  async accessProtectedPage(url: string) {
    const browser = await launch();
    const page = await browser.newPage();
 
    // Restore previous session
    await this.restoreCookies(page);
 
    await page.goto(url);
    return await page.content();
  }
}

E2E Test Automation

User Scenario-Based Testing

class E2ETestRunner {
  async testCheckoutFlow(baseUrl: string) {
    const browser = await launch();
    const page = await browser.newPage();
 
    try {
      // Scenario 1: Search for product
      console.log('Test 1: Search for product');
      await page.goto(`${baseUrl}/`);
      await page.type('#search-input', 'AI notebook');
      await page.click('#search-button');
      await page.waitForSelector('.product-grid', { timeout: 10000 });
 
      // Scenario 2: Navigate to product detail
      console.log('Test 2: Navigate to product detail');
      await page.click('a.product-link:first-child');
      await page.waitForSelector('.product-detail', { timeout: 5000 });
 
      // Scenario 3: Add to cart
      console.log('Test 3: Add to cart');
      await page.click('#add-to-cart-button');
      const cartBadge = await page.$eval(
        '.cart-count',
        (el) => el.textContent
      );
      console.assert(cartBadge === '1', 'Cart should have 1 item');
 
      // Scenario 4: Proceed to checkout
      console.log('Test 4: Proceed to checkout');
      await page.click('a[href="/checkout"]');
      await page.waitForSelector('#shipping-form', { timeout: 5000 });
 
      // Scenario 5: Fill shipping info
      console.log('Test 5: Fill shipping info');
      await page.type('#address', '123 Tech Street, San Francisco');
      await page.select('#country', 'US');
 
      // Scenario 6: Payment info (use Stripe test keys in production)
      console.log('Test 6: Fill payment info');
      const stripeFrame = page.frames().find((f) =>
        f.url().includes('stripe.com')
      );
      if (stripeFrame) {
        await stripeFrame.type('[placeholder="Card number"]', 'YOUR_TEST_CARD_NUM');
      }
 
      // Scenario 7: Complete order
      console.log('Test 7: Complete order');
      await Promise.all([
        page.waitForNavigation({ waitUntil: 'networkidle0' }),
        page.click('#place-order-button'),
      ]);
 
      // Confirmation screenshot
      const screenshot = await page.screenshot({
        path: './test-results/order-confirmation.png',
      });
      console.log('✓ Checkout flow completed successfully');
 
      return true;
    } catch (error) {
      console.error('✗ Test failed:', error);
      await page.screenshot({ path: './test-results/error-screenshot.png' });
      return false;
    } finally {
      await browser.close();
    }
  }
}
 
// Run tests
const runner = new E2ETestRunner();
runner.testCheckoutFlow('https://example-ecommerce.com')
  .then((success) => process.exit(success ? 0 : 1));

Production Security Design

Secure Credential Management

class SecureAutomation {
  private credentialsManager: CredentialsManager;
 
  async runSecurelyWithCredentials(task: string) {
    // Fetch from environment or Vault
    const credentials = await this.credentialsManager.get(task);
 
    // In-memory encryption
    const decrypted = await this.credentialsManager.decrypt(credentials);
 
    // Clear memory after use
    try {
      return await this.executeTask(task, decrypted);
    } finally {
      // Secure wipe (overwrite)
      decrypted.password = Buffer.alloc(decrypted.password.length, '\0');
    }
  }
}

IP Rotation & Proxy Support

class ProxyRotatingBrowser {
  private proxyList = [
    'http://proxy1.example.com:8080',
    'http://proxy2.example.com:8080',
    'http://proxy3.example.com:8080',
  ];
 
  async launchWithProxy(proxyIndex: number = 0) {
    const proxy = this.proxyList[proxyIndex % this.proxyList.length];
 
    return await launch({
      args: [`--proxy-server=${proxy}`],
    });
  }
 
  async scrapeWithRotation(urls: string[]) {
    for (let i = 0; i < urls.length; i++) {
      const browser = await this.launchWithProxy(i);
      const page = await browser.newPage();
 
      try {
        await page.goto(urls[i]);
        // Scraping logic...
      } finally {
        await browser.close();
      }
    }
  }
}

Request Header Spoofing

async function setupRealisticHeaders(page: any) {
  // Realistic HTTP headers
  await page.setExtraHTTPHeaders({
    'Accept-Language': 'en-US,en;q=0.9,ja;q=0.8',
    'Accept-Encoding': 'gzip, deflate, br',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Cache-Control': 'max-age=0',
    'DNT': '1',
    'Connection': 'keep-alive',
    'Upgrade-Insecure-Requests': '1',
  });
 
  // Prevent automation detection
  await page.evaluateOnNewDocument(() => {
    Object.defineProperty(navigator, 'webdriver', {
      get: () => undefined,
    });
 
    Object.defineProperty(navigator, 'plugins', {
      get: () => [1, 2, 3, 4, 5], // Fake plugins
    });
  });
}

Best Practices

  1. Error Handling is Critical

    • Network failures, timeouts, CAPTCHA
    • Implement retry logic and logging
  2. Wait Properly for Content

    • waitForNavigation(): Page transitions
    • waitForSelector(): Dynamic content loading
    • Avoid waitForTimeout() (last resort only)
  3. Prevent Memory Leaks

    • Properly close browser instances
    • Clear large Cookie/localStorage
  4. Logging & Debugging

    • Log each step with timestamps
    • Screenshots on failures

Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Antigravity2026-06-19
When CLI, Desktop, and SDK Share One Agent Harness: Designing for Consistent Behavior
Now that Antigravity's CLI, desktop, and SDK share one agent harness, here is how to separate what stays consistent from what differs by environment, and how to align behavior with smoke tests and a version-tracking habit.
Agents & Manager2026-06-16
When Your Antigravity Agent Eval Gate Keeps Flickering — Build Notes on Pass/Fail That Survives Non-Determinism
Same code, yet the eval passes in the morning and fails by noon. The first thing that breaks when you put agent evaluation into CI on Antigravity is the stability of the verdict. Here's how I separate noise from real regression and lock down pass/fail in code.
Agents & Manager2026-04-13
Building a Coding Agent System with Gemma 4 × Antigravity — A Complete Implementation Guide for Code Review, Test Generation, and Refactoring
A hands-on guide to building a 3-agent collaborative system using Gemma 4 and Antigravity AgentKit 2.0, covering code review, automated test generation, and refactoring suggestions. Includes production-quality code and pitfall solutions.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →