Webwright Documentation

repository·main·Indexed 26 days ago

https://github.com/microsoft/webwright

A lightweight SWE-style web agent harness that enables LLMs to act as browser agents by writing and executing Python/Playwright scripts. It utilizes a 'code-as-action' approach to handle complex, long-horizon web tasks. Webwright supports OpenAI, Anthropic, and OpenRouter backends and can be integrated as a plugin for Claude Code, OpenAI Codex, OpenClaw, and Hermes Agent.

Tokens
9.9K
Snippets
19
Records
45
Agent score
91%

What's inside Webwright

  1. Overview of Webwright

    main
    Webwright is a framework that turns LLMs into state-of-the-art browser agents by providing them with a terminal and a Playwright-based browser environment. Unlike traditional agents that predict single web actions (clicks, types), Webwright allows the model to write and execute Python scripts to interact with the web. This 'code-as-action' approach enables the agent to handle complex, long-horizon tasks, loops, and dynamic web behaviors more robustly. The agent's state is preserved in the local workspace (code, logs, and screenshots) rather than just the browser session.
  2. Instrument Final Scripts

    main

    Final scripts located at final_runs/run_<id>/final_script.py must follow a specific instrumentation pattern for logging and screenshots:

    1. Screenshots: Save to final_runs/run_<id>/screenshots/final_execution_<step>_<action>.png.
    2. Logging: Reset and append to final_runs/run_<id>/final_script_log.txt.
    3. Final Output: Print the final extracted datum at the end of the log using the format FINAL_RESPONSE: <value>.

    Use a log(step, msg) helper to ensure consistency.

    import asyncio, os
    from pathlib import Path
    from playwright.async_api import async_playwright
    
    RUN_DIR = Path(__file__).parent
    SCREENSHOTS = RUN_DIR / "screenshots"
    SCREENSHOTS.mkdir(parents=True, exist_ok=True)
    LOG = RUN_DIR / "final_script_log.txt"
    LOG.write_text("")  # reset
    
    def log(step: int, msg: str) -> None:
        line = f"step {step} action: {msg}\n"
        LOG.open("a").write(line)
        print(line, end="")
    
    async def main():
        async with async_playwright() as playwright:
            browser = await playwright.firefox.launch(headless=True)
            context = await browser.new_context(viewport={"width": 1280, "height": 1800})
            page = await context.new_page()
    
            await page.goto("<START_URL>", wait_until="domcontentloaded")
            await page.screenshot(path=str(SCREENSHOTS / "final_execution_1_open_start_page.png"))
            log(1, "open start page")
    
            # ... apply CP1, screenshot, log ...
            # ... apply CP2, screenshot, log ...
    
            # End of run: capture the final datum visibly and in the log
            final_value = "<extracted price / code / winner>"
            with LOG.open("a") as f:
                f.write(f"\nFINAL_RESPONSE: {final_value}\n")
    
            await browser.close()
    
    asyncio.run(main())
  3. Craft a reusable Webwright CLI tool with the `craft` command

    main

    The craft command allows you to parameterize a natural-language web task into a reusable Python CLI tool. The process transforms a specific, concrete task into a script (final_script.py) that can be executed with different arguments via argparse.

    Workflow for crafting a tool:

    1. Identify parameters: Determine which values (search terms, locations, dates, etc.) should be variable. Keep site-specific constants (start URL, selectors) hard-coded.
    2. Create plan.md: Define a # Parameters table with name | type | source phrase | default | allowed/format.
    3. Author final_script.py:
      • Place in a new directory: final_runs/run_<id>/.
      • Implement a reusable function (e.g., def search_domain(arg_a, arg_b): ...).
      • Use argparse in the if __name__ == "__main__": block to mirror function arguments.
      • Ensure the script is side-effect-free at import time (no browser/network activity at top-level).
      • The first log line after reset must be: step 0 params: <name>=<value> ....
      • Use standard instrumentation: 1280×1800 viewport, headless local Firefox, no full_page=True.
    4. Verify: Run the script with no arguments to ensure it reproduces the original task, then run with --help to confirm the CLI interface.
  4. Install Webwright as a Claude Code Plugin

    main

    You can integrate Webwright into Claude Code using the built-in marketplace. After installation, restart your Claude Code session to load the plugin.

    Using the Marketplace:

    /plugin marketplace add microsoft/Webwright
    /plugin install webwright@webwright

    Using a Local Checkout:

    /plugin marketplace add /absolute/path/to/Webwright
    /plugin install webwright@webwright

    Usage in Claude Code:

    • One-shot task: Use /webwright:run <task> or plain English to generate a final_script.py.
    • Reusable tool: Use /webwright:craft <task> to generate a parameterized CLI tool with an argparse wrapper.
    # 1. Add this repo as a Claude Code plugin marketplace
    /plugin marketplace add microsoft/Webwright
    
    # 2. Install the plugin from that marketplace
    /plugin install webwright@webwright
  5. Compare Webwright Trajectories

    main

    Use the trajectory viewer to compare token usage and execution paths between the Webwright harness and Codex/GitHub Copilot.

    1. Start the viewer server:
      cd assets/compare_trajectory/
      python3 -m http.server
    2. Upload Data:
      • Upload Webwright's raw_responses.jsonl and trajectory.json.
      • Upload Codex or GitHub Copilot traces.

    Locating Traces:

    • Codex: ~/.codex/sessions/YYYY/MONTH/DAY/SESSION_ID.jsonl
    • GitHub Copilot: Use /export file session in the interface to get the session.md file.
    cd assets/compare_trajectory/
    python3 -m http.server
  6. Use Webwright as a Plugin for Claude Code or Codex

    main

    Webwright can be integrated into existing coding agents like Claude Code, Codex, OpenClaw, and Hermes. For Claude Code and Codex, you can install the plugin manifest directly within their environments.

    /plugin install webwright@webwright
  7. Install Webwright as a Hermes Agent Skill

    main

    Webwright is compatible with Hermes Agent via the skills/webwright/ directory. To use it, symlink the skill directory into your Hermes user-skills directory.

    Installation:

    mkdir -p ~/.hermes/skills
    ln -sfn /absolute/path/to/Webwright/skills/webwright ~/.hermes/skills/webwright

    Usage: Start hermes and use natural language or the /webwright command. Note that Claude/Codex-specific subcommands like :run or :craft are inert in Hermes but the core skill remains functional.

  8. Run the Task Showcase dashboard

    main

    The Task Showcase is a Flask application that consolidates Webwright runs for repeatable tasks into a single dashboard. To run the dashboard using the local task files, install Flask and execute app.py.

    To view results from a specific Webwright run without moving files, use the --tasks-dir flag to point the app to the generated tasks directory within that run's workspace.

  9. Use Webwright modes: Run vs Craft

    main

    Webwright operates in two primary modes depending on whether you want a one-shot execution or a reusable tool:

    • Default (one-shot): Solves the task for the literal values provided. Triggered by a plain prompt or the /webwright:run <task> command.
    • CLI tool (parameterized): Creates a reusable final_script.py with a Google-style Args: docstring and an argparse wrapper. This allows you to rerun the script later with different arguments. Triggered by /webwright:craft <task> or requests to "parameterize" or "make it reusable".
  10. Use the Playwright Browser Launch Skeleton

    main

    When writing automation scripts for Webwright, use the following skeleton.

    Critical Rules:

    • Always set the viewport to viewport={"width": 1280, "height": 1800}.
    • Never call page.screenshot(full_page=True) for exploration, debugging, or final runs.
    • Each run must be fresh: navigate from the start URL and reconstruct state in code; there is no persistent session.
    import asyncio
    import os
    from pathlib import Path
    
    from playwright.async_api import async_playwright
    
    WORKSPACE = Path(os.environ.get("WORKSPACE_DIR", "."))
    SCREENSHOTS = WORKSPACE / "screenshots"
    SCREENSHOTS.mkdir(parents=True, exist_ok=True)
    
    async def main():
        async with async_playwright() as playwright:
            browser = await playwright.firefox.launch(headless=True)
            context = await browser.new_context(viewport={"width": 1280, "height": 1800})
            page = await context.new_page()
    
            await page.goto("<START_URL>", wait_until="domcontentloaded")
            await page.screenshot(path=str(SCREENSHOTS / "explore_1_start.png"))
    
            print("URL:", page.url)
            print("TITLE:", await page.title())
    
            # Inspect the region you care about with an ARIA snapshot
            snapshot = await page.locator("body").aria_snapshot()
            print("ARIA:", snapshot)
    
            await browser.close()
    
    asyncio.run(main())