Webwright Documentation
repository·main·Indexed 26 days ago
https://github.com/microsoft/webwrightA 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.
What's inside Webwright
- 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.
Set up Playwright Firefox for Webwright
mainWebwright uses Playwright Firefox as its default engine to avoid TLS/H2 fingerprinting issues (e.g.,
ERR_HTTP2_PROTOCOL_ERROR) common with Chromium on certain sites. You must install the Firefox binary before running tasks.Run the following command once:
playwright install firefoxInstall Webwright prerequisites
mainTo use Webwright, you must install the Firefox browser via Playwright from the repository root. No API keys are required.
playwright install firefoxInstrument Final Scripts
mainFinal scripts located at
final_runs/run_<id>/final_script.pymust follow a specific instrumentation pattern for logging and screenshots:- Screenshots: Save to
final_runs/run_<id>/screenshots/final_execution_<step>_<action>.png. - Logging: Reset and append to
final_runs/run_<id>/final_script_log.txt. - 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())- Screenshots: Save to
Craft a reusable Webwright CLI tool with the `craft` command
mainThe
craftcommand 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 viaargparse.Workflow for crafting a tool:
- Identify parameters: Determine which values (search terms, locations, dates, etc.) should be variable. Keep site-specific constants (start URL, selectors) hard-coded.
- Create
plan.md: Define a# Parameterstable withname | type | source phrase | default | allowed/format. - 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
argparsein theif __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.
- Place in a new directory:
- Verify: Run the script with no arguments to ensure it reproduces the original task, then run with
--helpto confirm the CLI interface.
Install Webwright as a Claude Code Plugin
mainYou 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@webwrightUsing a Local Checkout:
/plugin marketplace add /absolute/path/to/Webwright /plugin install webwright@webwrightUsage in Claude Code:
- One-shot task: Use
/webwright:run <task>or plain English to generate afinal_script.py. - Reusable tool: Use
/webwright:craft <task>to generate a parameterized CLI tool with anargparsewrapper.
# 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- One-shot task: Use
Compare Webwright Trajectories
mainUse the trajectory viewer to compare token usage and execution paths between the Webwright harness and Codex/GitHub Copilot.
- Start the viewer server:
cd assets/compare_trajectory/ python3 -m http.server - Upload Data:
- Upload Webwright's
raw_responses.jsonlandtrajectory.json. - Upload Codex or GitHub Copilot traces.
- Upload Webwright's
Locating Traces:
- Codex:
~/.codex/sessions/YYYY/MONTH/DAY/SESSION_ID.jsonl - GitHub Copilot: Use
/export file sessionin the interface to get thesession.mdfile.
cd assets/compare_trajectory/ python3 -m http.server- Start the viewer server:
Use Webwright as a Plugin for Claude Code or Codex
mainWebwright 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@webwrightInstall Webwright as a Hermes Agent Skill
mainWebwright 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/webwrightUsage: Start
hermesand use natural language or the/webwrightcommand. Note that Claude/Codex-specific subcommands like:runor:craftare inert in Hermes but the core skill remains functional.Run the Task Showcase dashboard
mainThe 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-dirflag to point the app to the generatedtasksdirectory within that run's workspace.Use Webwright modes: Run vs Craft
mainWebwright 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.pywith a Google-styleArgs:docstring and anargparsewrapper. This allows you to rerun the script later with different arguments. Triggered by/webwright:craft <task>or requests to "parameterize" or "make it reusable".
- Default (one-shot): Solves the task for the literal values provided. Triggered by a plain prompt or the
Use the Playwright Browser Launch Skeleton
mainWhen 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())- Always set the viewport to