ANTIGRAVITY LABEN
記事一覧/Antigravity 基本
Antigravity 基本/2026-03-27上級

Browser Sub-Agent 実践ガイド — AIがWebを自律的に操作する

Antigravity の Browser Sub-Agent の実装と運用方法を完全解説。Web スクレイピング、フォーム自動入力、E2E テストの自動化から、本番環境のセキュリティ設計まで、実践的なワークフローを習得します。

browser-sub-agent3web-automationscrapingtesting15ai-agent17

Browser Sub-Agent とは

Google Antigravity IDE の Browser Sub-Agent は、AI エージェントに「手」を与えるための機能です。従来の Web スクレイピングツールやテスト自動化ツールと異なり、自然言語命令を理解した AI が、ブラウザを通じて人間のように Web サイトを操作します。

具体的には以下が可能になります:

  • Web スクレイピング: JavaScript レンダリングに対応した複雑なサイトからのデータ抽出
  • フォーム自動入力: CAPTCHA 回避(自社サイトの場合)、多段階ログイン、複雑なチェックボックス選択
  • E2E テスト: ユーザーシナリオに基づいた自動テスト実行・スクリーンショット取得
  • 自動化ワークフロー: 日次業務の完全自動化(例: 競合商品の価格監視、定期レポート生成)

Browser Sub-Agent のアーキテクチャ

内部動作フロー

┌─────────────────────────────────────┐
│ AI Agent (自然言語命令受け取り)      │
└──────────────┬──────────────────────┘
               │
               ↓
┌─────────────────────────────────────┐
│ Intent Parser                       │
│ 命令を「アクション」に分解           │
│ - "ボタンをクリック" → click(xy)    │
│ - "テキスト入力" → type(selector)   │
└──────────────┬──────────────────────┘
               │
               ↓
┌─────────────────────────────────────┐
│ DOM Interpreter                     │
│ セレクター解析・要素位置計算         │
└──────────────┬──────────────────────┘
               │
               ↓
┌─────────────────────────────────────┐
│ Chromium Browser Instance           │
│ (Headless or Headful mode)          │
│ - ページロード                      │
│ - インタラクション実行              │
│ - スクリーンショット取得            │
└──────────────┬──────────────────────┘
               │
               ↓
┌─────────────────────────────────────┐
│ Output Processor                    │
│ - DOM スナップショット              │
│ - スクリーンショット                │
│ - テキスト抽出                      │
└─────────────────────────────────────┘

重要な制限と対応策

制限事項対応策
JavaScript レンダリング遅延(3~5秒)waitForTimeoutwaitForSelector で待機
ネットワーク速度に依存タイムアウト値を長めに設定(デフォルト 30秒)
CORS ポリシーによる API 呼び出し不可Server-side proxy を用意
セッション保持(クッキー)persistCookies: true で Cookie を保存

Web スクレイピング:実装例

基本パターン

// 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', // ロボット検出回避
      ],
    });
  }
 
  async scrapeProductListing(
    baseUrl: string,
    pageCount: number = 3
  ): Promise<ScrapedProduct[]> {
    const results: ScrapedProduct[] = [];
    const page = await this.browser.newPage();
 
    // 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 {
        // ページ読み込み待機
        await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
 
        // 動的コンテンツの読み込み待機
        await page.waitForSelector('.product-item', { timeout: 10000 });
 
        // 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`);
 
        // レート制限回避: ページ間に遅延を入れる
        await page.waitForTimeout(2000 + Math.random() * 3000);
      } catch (error) {
        console.error(`Error scraping page ${i}:`, error);
        // フォールバック: AI に手動確認を依頼
      }
    }
 
    await page.close();
    return results;
  }
 
  async close() {
    await this.browser.close();
  }
}
 
// 使用例
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);

レート制限への対応

class RateLimitedScraper {
  private requestTimes: number[] = [];
  private maxRequestsPerMinute: number = 10;
 
  async ensureRateLimit() {
    const now = Date.now();
    const oneMinuteAgo = now - 60 * 1000;
 
    // 1分以内のリクエストを数える
    this.requestTimes = this.requestTimes.filter((t) => t > oneMinuteAgo);
 
    if (this.requestTimes.length >= this.maxRequestsPerMinute) {
      // レート制限に引っかかった場合、待機
      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();
    // スクレイピング処理...
  }
}

出力例:

[
  {
    "title": "高性能 AI ノートパソコン",
    "price": 1580,
    "rating": 4.8,
    "url": "https://example.com/product/ai-laptop-pro"
  },
  {
    "title": "USB-C ドッキングステーション",
    "price": 25800,
    "rating": 4.5,
    "url": "https://example.com/product/usb-c-dock"
  }
]

フォーム自動入力とログイン

複雑なチェックボックスと条件分岐

class FormFiller {
  async fillComplexForm(page: any) {
    // ステップ1: テキスト入力フィールドを埋める
    await page.type('#name-input', 'Masaki Hirokawa');
    await page.type('#email-input', 'user@example.com');
 
    // ステップ2: ラジオボタン選択
    await page.click('input[value="business"]');
 
    // ステップ3: 条件分岐(選択内容に応じて出現するフィールド)
    await page.waitForSelector('#company-name-field', { timeout: 5000 });
    await page.type('#company-name-field', 'Dolice Labs');
 
    // ステップ4: マルチセレクト(複数選択可能)
    const tags = ['AI Development', 'Web Automation', 'Agent System'];
    for (const tag of tags) {
      // Cmd/Ctrl + click で複数選択
      await page.click(`label:contains("${tag}")`, { modifiers: ['Control'] });
    }
 
    // ステップ5: ファイルアップロード
    const uploadInput = await page.$('input[type="file"]');
    await uploadInput.uploadFile('/path/to/resume.pdf');
 
    // ステップ6: 利用規約に同意
    await page.click('#terms-checkbox');
    await page.click('#privacy-checkbox');
 
    // ステップ7: 送信ボタンをクリック
    await Promise.all([
      page.waitForNavigation({ waitUntil: 'networkidle0' }),
      page.click('#submit-button'),
    ]);
 
    console.log('Form submitted successfully');
  }
}

セッション保持とログイン状態の維持

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' });
 
    // クッキーを保存(次回以降の復元用)
    await this.saveCookies(page);
  }
 
  async accessProtectedPage(url: string) {
    const browser = await launch();
    const page = await browser.newPage();
 
    // 前回のセッションを復元
    await this.restoreCookies(page);
 
    await page.goto(url);
    return await page.content();
  }
}

E2E テスト自動化

ユーザーシナリオベースのテスト

class E2ETestRunner {
  async testCheckoutFlow(baseUrl: string) {
    const browser = await launch();
    const page = await browser.newPage();
 
    try {
      // シナリオ1: 商品検索
      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 });
 
      // シナリオ2: 商品詳細ページへ移動
      console.log('Test 2: Navigate to product detail');
      await page.click('a.product-link:first-child');
      await page.waitForSelector('.product-detail', { timeout: 5000 });
 
      // シナリオ3: カートに追加
      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');
 
      // シナリオ4: チェックアウト
      console.log('Test 4: Proceed to checkout');
      await page.click('a[href="/checkout"]');
      await page.waitForSelector('#shipping-form', { timeout: 5000 });
 
      // シナリオ5: 配送情報入力
      console.log('Test 5: Fill shipping info');
      await page.type('#address', '123 Tech Street, San Francisco');
      await page.select('#country', 'US');
 
      // シナリオ6: 決済情報入力(本番では Stripe テストキーを使用)
      console.log('Test 6: Fill payment info');
      // iframe 内の Stripe フォーム
      const stripeFrame = page.frames().find((f) =>
        f.url().includes('stripe.com')
      );
      if (stripeFrame) {
        await stripeFrame.type('[placeholder="Card number"]', 'YOUR_TEST_CARD_NUM');
      }
 
      // シナリオ7: 注文完了
      console.log('Test 7: Complete order');
      await Promise.all([
        page.waitForNavigation({ waitUntil: 'networkidle0' }),
        page.click('#place-order-button'),
      ]);
 
      // 確認ページのスクリーンショット
      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();
    }
  }
}
 
// テスト実行
const runner = new E2ETestRunner();
runner.testCheckoutFlow('https://example-ecommerce.com')
  .then((success) => process.exit(success ? 0 : 1));

本番環境での セキュリティ設計

認証情報の安全な管理

class SecureAutomation {
  private credentialsManager: CredentialsManager;
 
  async runSecurelyWithCredentials(task: string) {
    // 認証情報を環境変数または Vault から取得
    const credentials = await this.credentialsManager.get(task);
 
    // メモリ内暗号化
    const decrypted = await this.credentialsManager.decrypt(credentials);
 
    // 使用後は即座にメモリをクリア
    try {
      return await this.executeTask(task, decrypted);
    } finally {
      // セキュアクリア(オーバーライト)
      decrypted.password = Buffer.alloc(decrypted.password.length, '\0');
    }
  }
}

IP ローテーションとプロキシ

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]);
        // スクレイピング処理...
      } finally {
        await browser.close();
      }
    }
  }
}

リクエストヘッダースプーフィング

async function setupRealisticHeaders(page: any) {
  // 人間らしいリクエストヘッダーを設定
  await page.setExtraHTTPHeaders({
    'Accept-Language': 'ja-JP,ja;q=0.9,en-US;q=0.8,en;q=0.7',
    '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',
  });
 
  // JavaScript による ブラウザ自動化検出回避
  await page.evaluateOnNewDocument(() => {
    Object.defineProperty(navigator, 'webdriver', {
      get: () => undefined,
    });
 
    Object.defineProperty(navigator, 'plugins', {
      get: () => [1, 2, 3, 4, 5], // ダミープラグイン
    });
 
    // その他のチェック回避...
  });
}

ベストプラクティス

  1. エラーハンドリングは徹底的に

    • ネットワーク障害・タイムアウト・CAPTCHA への対応を実装
    • フェイルオーバー戦略を用意(リトライ・ログと通知)
  2. ページ読み込み待機を適切に

    • waitForNavigation(): ページ遷移時
    • waitForSelector(): 動的コンテンツ読み込み時
    • waitForTimeout(): は最後の手段(できるだけ避ける)
  3. メモリリークを防ぐ

    • ブラウザインスタンスを適切にクローズ
    • 大量の Cookie/ローカルストレージをクリア
  4. ログを活用する

    • 各ステップでログを記録
    • スクリーンショットを取得(失敗時は必須)

個人開発12年の現場で実感したこと

線引きするときの3つの判断軸

  • 失敗時の影響が金銭やユーザー体験にどれだけ波及するか
  • 復旧オペレーションが明文化されているか
  • 観測ログから人間が再現できる粒度に整っているか

個人開発12年とアーティスト活動から見るエージェント設計

線引きするときの3つの判断軸

  • 失敗時の影響が金銭やユーザー体験にどれだけ波及するか
  • 復旧オペレーションが明文化されているか
  • 観測ログから人間が再現できる粒度に整っているか

まとめ — 検証ステップの推奨

  1. 観測メトリクスとアラートを設置
  2. 限定ロールアウトで本番検証
シェア

お読みいただきありがとうございます

Antigravity Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

Antigravity 基本2026-06-19
同じエージェントハーネスを共有する時代 — CLI・デスクトップ・SDK で挙動を揃える設計
Antigravity の CLI・デスクトップ・SDK が同じエージェントハーネスを共有するようになった今、面ごとに挙動が揃う前提と揃わない要因を切り分け、スモークテストとバージョン追従の手順で挙動を一致させる設計をまとめました。
Agents & Manager2026-06-16
Antigravity のエージェント評価ゲートが揺れて信用できないとき — 確率的な揺らぎに負けない合否判定の実装メモ
同じコードなのに評価が通ったり落ちたりする。Antigravity でエージェント評価を CI に組んだとき最初に壊れるのは判定の安定性です。揺らぎを区別し、静かな劣化を捕まえる合否設計を実装で固めます。
Agents & Manager2026-04-13
Gemma 4 × Antigravity で作るコーディングエージェントシステム — コードレビュー・テスト生成・リファクタリング支援を一括処理する実装ガイド
Gemma 4とAntigravity AgentKit 2.0を使い、コードレビュー・テスト自動生成・リファクタリング提案を担う3エージェント協調システムをゼロから構築する実践ガイド。プロダクション品質のコードと落とし穴対策を網羅。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →