🤖

Amazon Bedrock AgentCore Browser Tool ✖ ︎Claudeでブラウザ自動操作のコードを生成しよう!

に公開

LLMはともだちこわくないよ。AWS Ambassadorの積田です。
本投稿はAmazon Bedrock AgentCore Browser Tool ✖ ︎Claudeでブラウザ自動操作 〜破産街道一直線!?〜の続き記事です。
LLM破産しないために、Browser Tool ✖ ︎Claudeでブラウザ自動操作をするPlaywrightのコードを生成して、コスト・実行時間を爆下げしようというモチベーションから記載したものです。

サマリ

  • AgentCore borwser Tool ✖ ︎Claudeを組み合わせることで簡単にPlaywrightのコードを実装可能
  • 定型的なブラウザ操作であれば、フルLLMと比較して大幅に時間・コストを削減可能

検証環境

  • 利用モデル: Claude Sonnet 4.5
  • 利用リージョン: us-west-2
  • 言語: Python 3.13.7
  • フレームワーク: Strands Agents

シナリオ

今回はAmazon Bedrock AgentCore Browser Tool ✖ ︎Claudeでブラウザ自動操作 〜破産街道一直線!?〜のシナリオを用いて検証を実施します。

  1. SimpleToDoアプリにアクセスする
    ※ 今回の検証のために作りました
    image
  2. MAIL, PASSを入力してSign Inする
    image
  3. 一番上のToDoにコメントをする
    image

上記シナリオを実施した最後にPlaywrightのコードを出力するようなプロンプトを追加するだけです。

検証用コード

検証に利用したコードは以下です。利用パッケージはuvなどで適宜インストールしてください。検証時はvenv+uvを利用してパッケージ管理をしました。
また、username, passwordはexport SIMPLE_LOGIN_USER=<PASSWORD>のような形でスクリプト実行前に環境変数に設定してあります。
Playwrightコードのテンプレートを指定した上で簡素な例をLLMに渡しています。

from strands import Agent
from strands.models import BedrockModel
from strands_tools.browser import AgentCoreBrowser
import os

# AgentCore Browser ツールを初期化(us-west-2 リージョンを使用)
browser_tool = AgentCoreBrowser(region="us-west-2")

# Claude Sonnet 4.5 をグローバルエンドポイント(Cross-Region Inference)で明示的に指定
bedrock_model = BedrockModel(
    model_id="global.anthropic.claude-sonnet-4-5-20250929-v1:0", region_name="us-west-2"
)

# Browser ツールとモデルを指定してエージェントを作成
agent = Agent(model=bedrock_model, tools=[browser_tool.browser])

# 環境変数からログイン情報を取得
username = os.environ.get("SIMPLE_LOGIN_USER")
password = os.environ.get("SIMPLE_LOGIN_PASS")

if not username or not password:
    raise ValueError("環境変数USERとPASSを設定してください")

# Playwrightテンプレート定義
playwright_template = """
from playwright.async_api import async_playwright, Playwright, BrowserType
from bedrock_agentcore.tools.browser_client import browser_session
import asyncio

async def run(playwright: Playwright):
    # Create and maintain a browser session with AgentCore Browser
    with browser_session('us-west-2') as client:
        # Get WebSocket URL and authentication headers
        ws_url, headers = client.generate_ws_headers()

        # Connect to the remote browser via CDP
        chromium: BrowserType = playwright.chromium
        browser = await chromium.connect_over_cdp(
            ws_url,
            headers=headers
        )

        # Get the browser context and page
        context = browser.contexts[0]
        page = context.pages[0]

        try:
            <<<ここにPlaywrightの操作コードを実装>>>
        except Exception as e:
            print(f"✗ エラーが発生しました: {e}")
        finally:
            # Clean up resources
            await page.close()
            await browser.close()

async def main():
    async with async_playwright() as playwright:
        await run(playwright)

# Run the async function
if __name__ == "__main__":
    asyncio.run(main())
"""

# 指定された URL にアクセスしてログインするプロンプト
prompt = f"""https://d3ukli4ddc6u90.cloudfront.net/ にアクセスして、以下の情報でログインしてください。
ユーザー名: {username}
パスワード: {password}

ログイン後、一番上のToDoにコメントを追加してください。

最後に今回実施した作業を{playwright_template}をテンプレートとしてPlaywrightの実行コードをpythonで出力して。
Playwrightの実装コードはPlayWrightの機能を最大限活かしたシンプルな実装とすること。
例:
- await page.wait_for_selector('.comment-form input[type="text"]', state="visible", timeout=10000)
- await page.click('button[type="submit"]')
- await page.wait_for_load_state("networkidle", timeout=10000)
"""

# エージェントを実行
response = agent(prompt)

# レスポンスを表示
print("\nエージェントのレスポンス:")
print(response)

# Access metrics through the AgentResult
print(f"Total  tokens: {response.metrics.accumulated_usage['totalTokens']}")
print(f"Input  tokens: {response.metrics.accumulated_usage['inputTokens']}")
print(f"Output tokens: {response.metrics.accumulated_usage['outputTokens']}")
print(f"Execution time: {sum(response.metrics.cycle_durations):.2f} seconds")
print(f"Tools used: {list(response.metrics.tool_metrics.keys())}")

ブラウザ操作するシンプルなコードにPlaywright出力の指示を追記しています。
image

実行結果

実行時間を計測するためにtimeコマンドを利用しています。
今回の検証ではツール実行17回、総利用トークン138,294、コスト70円弱、実行時間1分40秒弱でした。

$ time python browser_access_and_create_playwright_code.py
それでは、指定されたURLにアクセスして、ログイン後にToDoにコメントを追加する作業を実行します。

まず、ブラウザ操作を行います。
Tool #1: browser

Tool #2: browser

Tool #3: browser

Tool #4: browser

Tool #5: browser

Tool #6: browser

Tool #7: browser

Tool #8: browser
ログイン成功しました!一番上のToDoに「Comments (2)」ボタンがあることが確認できました。コメントを追加します。
Tool #9: browser

Tool #10: browser

Tool #11: browser

Tool #12: browser
完璧です!コメントフォームが表示されました。コメントを入力します。
Tool #13: browser

Tool #14: browser

Tool #15: browser

Tool #16: browser
完璧です!コメントが正常に追加されました。最後にスクリーンショットを撮ってセッションをクローズします。
Tool #17: browser
完了しました!以下が実行した作業のPlaywrightコードです:

from playwright.async_api import async_playwright, Playwright, BrowserType
from bedrock_agentcore.tools.browser_client import browser_session
import asyncio

async def run(playwright: Playwright):
    # Create and maintain a browser session with AgentCore Browser
    with browser_session('us-west-2') as client:
        # Get WebSocket URL and authentication headers
        ws_url, headers = client.generate_ws_headers()

        # Connect to the remote browser via CDP
        chromium: BrowserType = playwright.chromium
        browser = await chromium.connect_over_cdp(
            ws_url,
            headers=headers
        )

        # Get the browser context and page
        context = browser.contexts[0]
        page = context.pages[0]

        try:
            # 指定されたURLに移動
            await page.goto('https://d3ukli4ddc6u90.cloudfront.net/')
            await page.wait_for_load_state("networkidle", timeout=10000)
            
            # ログイン情報を入力
            await page.wait_for_selector('input[type="email"]', state="visible", timeout=10000)
            await page.fill('input[type="email"]', '<SIMPLE_LOGIN_USER>')
            await page.fill('input[type="password"]', '<SIMPLE_LOGIN_PASS>')
            
            # ログインボタンをクリック
            await page.click('button[type="submit"]')
            await page.wait_for_load_state("networkidle", timeout=10000)
            
            # ログイン成功を確認(ToDoリストが表示されるまで待機)
            await page.wait_for_selector('.todo-list', state="visible", timeout=10000)
            
            # 一番上のToDoのコメントボタンをクリック
            await page.click('.todo-item:first-child .comment-btn')
            
            # コメントフォームが表示されるまで待機
            await page.wait_for_selector('.comment-form input[type="text"]', state="visible", timeout=10000)
            
            # コメントを入力
            await page.fill('.comment-form input[type="text"]', 'This is a test comment added via automation')
            
            # コメント送信ボタンをクリック
            await page.click('.comment-form button[type="submit"]')
            
            # コメントが追加されるまで待機
            await page.wait_for_timeout(2000)
            
            print("✓ コメントが正常に追加されました")
            
        except Exception as e:
            print(f"✗ エラーが発生しました: {e}")
        finally:
            # Clean up resources
            await page.close()
            await browser.close()

async def main():
    async with async_playwright() as playwright:
        await run(playwright)

# Run the async function
if __name__ == "__main__":
    asyncio.run(main())

## 実施した作業の概要:

1. **ログインページにアクセス** - `https://d3ukli4ddc6u90.cloudfront.net/` にアクセス
2. **ログイン情報を入力** - ユーザー名 `<SIMPLE_LOGIN_USER>` とパスワード `<SIMPLE_LOGIN_PASS>` を入力
3. **ログイン実行** - ログインボタンをクリックして認証
4. **ToDoリストの確認** - ログイン後、ToDoリストが表示されることを確認
5. **コメントフォームを開く** - 一番上のToDoの「Comments」ボタンをクリック
6. **コメントを追加** - 「This is a test comment added via automation」というコメントを入力して送信
7. **完了確認** - コメントが正常に追加されたことを確認

コメントは正常に追加され、既存の2件のコメントに加えて、新しいコメントが3件目として追加されました!
エージェントのレスポンス:
完了しました!以下が実行した作業のPlaywrightコードです:

from playwright.async_api import async_playwright, Playwright, BrowserType
from bedrock_agentcore.tools.browser_client import browser_session
import asyncio

async def run(playwright: Playwright):
    # Create and maintain a browser session with AgentCore Browser
    with browser_session('us-west-2') as client:
        # Get WebSocket URL and authentication headers
        ws_url, headers = client.generate_ws_headers()

        # Connect to the remote browser via CDP
        chromium: BrowserType = playwright.chromium
        browser = await chromium.connect_over_cdp(
            ws_url,
            headers=headers
        )

        # Get the browser context and page
        context = browser.contexts[0]
        page = context.pages[0]

        try:
            # 指定されたURLに移動
            await page.goto('https://d3ukli4ddc6u90.cloudfront.net/')
            await page.wait_for_load_state("networkidle", timeout=10000)
            
            # ログイン情報を入力
            await page.wait_for_selector('input[type="email"]', state="visible", timeout=10000)
            await page.fill('input[type="email"]', '<SIMPLE_LOGIN_USER>')
            await page.fill('input[type="password"]', '<SIMPLE_LOGIN_PASS>')
            
            # ログインボタンをクリック
            await page.click('button[type="submit"]')
            await page.wait_for_load_state("networkidle", timeout=10000)
            
            # ログイン成功を確認(ToDoリストが表示されるまで待機)
            await page.wait_for_selector('.todo-list', state="visible", timeout=10000)
            
            # 一番上のToDoのコメントボタンをクリック
            await page.click('.todo-item:first-child .comment-btn')
            
            # コメントフォームが表示されるまで待機
            await page.wait_for_selector('.comment-form input[type="text"]', state="visible", timeout=10000)
            
            # コメントを入力
            await page.fill('.comment-form input[type="text"]', 'This is a test comment added via automation')
            
            # コメント送信ボタンをクリック
            await page.click('.comment-form button[type="submit"]')
            
            # コメントが追加されるまで待機
            await page.wait_for_timeout(2000)
            
            print("✓ コメントが正常に追加されました")
            
        except Exception as e:
            print(f"✗ エラーが発生しました: {e}")
        finally:
            # Clean up resources
            await page.close()
            await browser.close()

async def main():
    async with async_playwright() as playwright:
        await run(playwright)

# Run the async function
if __name__ == "__main__":
    asyncio.run(main())

## 実施した作業の概要:

1. **ログインページにアクセス** - `https://d3ukli4ddc6u90.cloudfront.net/` にアクセス
2. **ログイン情報を入力** - ユーザー名 `<SIMPLE_LOGIN_USER>` とパスワード `<SIMPLE_LOGIN_PASS>` を入力
3. **ログイン実行** - ログインボタンをクリックして認証
4. **ToDoリストの確認** - ログイン後、ToDoリストが表示されることを確認
5. **コメントフォームを開く** - 一番上のToDoの「Comments」ボタンをクリック
6. **コメントを追加** - 「This is a test comment added via automation」というコメントを入力して送信
7. **完了確認** - コメントが正常に追加されたことを確認

コメントは正常に追加され、既存の2件のコメントに加えて、新しいコメントが3件目として追加されました!

Total  tokens: 138294
Input  tokens: 135524
Output tokens: 2770
Execution time: 13.44 seconds
Tools used: ['browser']
python3 browser_access.py  1.45s user 0.32s system 1% cpu 1:38.11 total
(.venv)

Playwrightコード実行結果

生成されたPlaywrightコードを実行したところ、コメントも正常に保存されており、一発OKでした。

$ time python simpletodo_playwright.py                                                                                                                                                                                             ?[main]
✓ コメントが正常に追加されました
python3 simpletodo_playwright.py  0.66s user 0.19s system 3% cpu 21.296 total
(.venv)

鬼の100連コメント投稿

生成されたPlaywrightコードで100連コメントができるか検証してみましょう。
LLMで100連コメント投稿すると、5-10分程度の実行時間と1万円弱のコスト(※)が発生することが見込まれますが、PlaywrightコードにはLLMは利用していないため、BrowserToolのコストだけで実行できます。
※ LLMのコストのみ。Browser Toolなど、その他コストは含まない。

検証コマンド

bash -lc 'date; set +m; pids=(); for a in {1..100}; do python simpletodo_playwright.py >/dev/null 2>&1 & pids+=($!); sleep 0.5; done; set -m; wait "${pids[@]}"; date'

検証結果

1分9秒で実行が完了しました。エラー発生・コメント欠損も無しです。

$ bash -lc 'date; set +m; pids=(); for a in {1..100}; do python simpletodo_playwright.py >/dev/null 2>&1 & pids+=($!); sleep 0.5; done; set -m; wait "${pids[@]}"; date'

2025年 11月 3日 月曜日 16時33分20秒 JST
~~ 中略 ~~
[99]-  Done                    python simpletodo_playwright.py > /dev/null 2>&1
[100]+  Done                    python simpletodo_playwright.py > /dev/null 2>&1
2025年 11月 3日 月曜日 16時34分29秒 JST
(.venv) 

正常に100回コメント投稿できていました。

Before:
image

After
image

終わりに

全てをLLMにやらせるのではなく、LLMとうまく組み合わせることで実行時間・コストを大きく削減できることがお分かりいただけたでしょうか?
「LLMはともだちこわくないよ」。この言葉を胸にこれからもLLMとうまく付き合っていき適材適所で利用していきたいと思います。

今回の記事を読んでくださった方の助けになれば幸いです。

Discussion