TRENDSHIFT DEEP DIVE · MICROSOFT RESEARCH

Webwright
"터미널 하나면 충분한 웹 에이전트"

Microsoft가 공개한 1,500줄짜리 미니멀 웹 에이전트 하네스. Online-Mind2Web 86.7%, Odysseys 60.1%로 오픈소스 최고 성능을 기록했다. 어떻게 이게 가능했는지, 코드 한 줄까지 파고든다.

1프로젝트 한 줄 요약

결국 무엇인가?

핵심 메시지

"브라우저를 워크스페이스로 쓰지 말고,
워크스페이스를 워크스페이스로 써라."

대부분의 웹 에이전트는 매 스텝마다 모델에게 페이지 상태를 보여주고 "다음 클릭은 어디?"를 물어본다. Webwright는 그 루프를 버린다. 대신 모델에게 터미널을 쥐어주고, "Playwright 스크립트를 짜서 실행해라"고 시킨다. 영구 상태로 남는 건 브라우저 세션이 아니라 로컬 폴더의 코드와 로그다.

그 결과: 코어 에이전트 루프 약 450줄, Playwright 환경 약 570줄, CLI 약 150줄. 전체 1,500줄로 Online-Mind2Web 벤치마크에서 GPT-5.4 기준 86.7% 달성. 오픈소스 하네스 중 1위다.

한 줄로 줄이면 — Webwright는 "사람 엔지니어가 브라우저 자동화 스크립트를 짤 때처럼" LLM이 일하게 만드는 가장 작은 단위의 하네스다. 그 이상도 이하도 아니다. 멀티 에이전트도, 그래프 엔진도, 플러그인 레이어도, 숨겨진 오케스트레이션도 없다. 그냥 터미널, 브라우저, 모델 세 개뿐.

용어 1
웹 에이전트 하네스 (Web Agent Harness)
LLM이 실제 웹사이트를 클릭/입력/스크롤로 조작할 수 있도록 감싸주는 실행 환경. "하네스(harness)"는 직역하면 마구·안전벨트. 모델 자체는 그냥 텍스트를 뱉을 뿐이고, 하네스가 그 텍스트를 받아서 진짜 브라우저 동작으로 번역한다.
용어 2
long horizon task (긴 호라이즌 태스크)
한두 번 클릭으로 끝나지 않고 수십~수백 스텝이 필요한 작업. 예: "비행기 표 검색 → 필터링 → 정렬 → 가격 비교 → 예약 페이지까지 진입". 스텝이 길수록 중간 오류가 누적돼 망가지기 쉬워서 어렵다.

2왜 주목받는가

트렌딩 이유와 경쟁 제품 대비 장점.

2026년 5월 기준 GitHub 별은 13개로 적지만, TrendShift 상위에 오른 이유는 벤치마크 수치Microsoft 출시라는 시그널 두 가지다. AutoGen·Magentic-One 같은 거대 프레임워크를 만든 같은 회사가, 정반대 철학으로 "1,500줄이면 충분하다"고 선언한 셈이다.

경쟁 제품과 비교

비교 1

좌표 예측(xy-coordinate) 방식 vs Webwright

OpenAI의 Operator나 Claude Computer Use는 스크린샷을 보고 "여기를 클릭해라 (x=423, y=812)"를 예측한다. 이걸 코드형 액션(code-as-action)으로 바꾼 게 Webwright의 핵심 차이다.

같은 GPT-5.4라도 좌표 예측 베이스라인은 Odysseys에서 33.5%, Webwright(코드-액션)는 60.1%26.6 포인트 차이. 모델은 그대로인데 하네스만 바꿔서 두 배 가까이 올린 것이다.

비교 2

"브라우저-as-state" vs "워크스페이스-as-state"

대부분의 웹 에이전트는 브라우저 세션을 영구 상태로 잡고 그 위에서 한 스텝씩 조작한다. 세션이 죽으면 처음부터. Webwright는 다르다. 매 실행마다 새 브라우저를 띄우고, 스크립트가 끝나면 닫는다. 진짜 상태는 디스크에 남는 final_script.py·로그·스크린샷이다.

이건 사람 RPA(Robotic Process Automation) 엔지니어가 일하는 방식과 똑같다. 사이트 한 번 살펴보고, 스크립트 짜고, 돌리고, 깨지면 고치고, 결과물은 코드로 남긴다.

비교 3

거대 프레임워크 vs 1,500줄

LangGraph, AutoGen, CrewAI 같은 프레임워크는 "에이전트 노드", "라우터", "메모리 매니저" 같은 추상화를 100겹 쌓는다. 디버그할 때 어디서 멈춰야 할지도 모른다. Webwright는 httpx, pydantic, playwright, typer 네 개로 끝. 외부 의존성도 단순하다.

비유

요리에 비유하면 — 기존 웹 에이전트는 "한 입씩 떠먹여주는 베이비 푸드"고, Webwright는 "주방을 통째로 맡기고 레시피만 시키는 방식"이다. 모델이 약했던 GPT-3.5 시절에는 떠먹여줘야 했지만, GPT-5.4·Claude Opus 4.7쯤 되면 주방을 맡겨야 더 잘한다.

벤치마크 수치 정리:

성능 카드

Online-Mind2Web (300개 실사이트 태스크)

GPT-5.4 + Webwright = 86.7% (오픈소스 1위). Claude Opus 4.7 + Webwright = 84.7%인데, 어려운 split에서는 오히려 80.5% vs GPT-5.4의 76.6%로 역전. 모델별 강점이 갈린다.

성능 카드

Odysseys (200개 long-horizon, 평균 76 스텝)

Webwright + GPT-5.4 = 60.1%. 직전 SOTA(Opus 4.6 비전 기반 접근 + 영구 브라우저) 44.5% 대비 +15.6 포인트. 같은 GPT-5.4라도 코드-액션이 좌표 예측보다 무조건 좋다는 게 증명됐다.

성능 카드

작은 모델도 잘한다

Qwen-3.5-9B(9B짜리 오픈소스 모델)도 사전 제작된 5개 이상의 CLI 도구가 주어지면 Online-Mind2Web을 풀어낸다. 즉 Webwright는 한 번 짠 스크립트를 재사용 가능한 CLI 도구로 패키징해서, 다음 번에는 작은 모델로도 같은 일을 시킬 수 있다.

3기술 스택 전체 지도

백엔드·자동화 환경·도구까지 한 장으로.

pyproject.toml을 직접 까서 보면 의존성이 놀랍도록 짧다. 그래서 한눈에 그릴 수 있다.

코어 의존성 (전부 9개)

패키지 1
httpx (≥0.27)
requests를 대체하는 모던 HTTP 클라이언트. async 지원이 기본이라 OpenAI/Anthropic/OpenRouter API 호출을 비동기로 처리할 수 있다. 왜 requests 안 쓰나 — async 워크플로우에서 블로킹 호출은 치명적이라서.
패키지 2
pydantic (≥2.5)
데이터 검증 라이브러리. AgentConfig·EnvironmentConfig 같은 설정 클래스를 type-safe하게 다룬다. v2는 Rust 백엔드라 빠르다. 없으면 dict 던지다가 키 오타 한 번에 디버깅 4시간 날아간다.
패키지 3
playwright (≥1.45)
Microsoft 자체가 만든 브라우저 자동화 라이브러리. Selenium보다 빠르고, async 네이티브이며, "기다리는 동안 사라지는 DOM" 같은 동적 요소도 잘 다룬다. Chromium·Firefox·WebKit 셋 다 지원.
패키지 4
typer (≥0.12)
FastAPI 만든 사람이 만든 CLI 빌더. python -m webwright.run.cli가 깔끔한 --task / --start-url 인자를 받게 해주는 게 이놈 역할. Click 기반인데 type hint로 옵션을 자동 추출.
패키지 5
jinja2 (≥3.1)
템플릿 엔진. 모델에게 보낼 system prompt를 변수 치환으로 동적으로 만든다. {{ workspace_dir }}, {{ task }}, {{ start_url }} 같은 토큰이 런타임에 채워진다.
패키지 6~9
pyyaml · rich · python-dotenv · platformdirs
YAML 설정 로딩 / 컬러 터미널 출력 / .env 파일 처리 / OS별 표준 경로 결정. 전부 "있으면 편하고 없으면 불편한" 표준 보조 도구들.

구조적 레이어 정리

레이어 1

모델 백엔드 (~150~200줄/개)

models/openai_model.py · anthropic_model.py · openrouter_model.py. 셋 다 base.pyModel 추상 클래스를 상속한다. 핵심 메서드는 query(messages) 하나. 메시지 리스트를 받아서 JSON 응답(thought + bash_command + done)을 돌려준다.

OpenRouter를 통하면 Claude·Gemini·Llama 등을 같은 인터페이스로 쓸 수 있다. 백엔드 추가도 ~150줄짜리 파일 하나 만들면 끝.

레이어 2

실행 환경 — Environment (~570줄)

environments/local_workspace.py가 메인. Bash 명령 한 줄을 받아서 서브프로세스로 실행하고, 표준출력·표준에러·종료 코드를 dict로 묶어 반환한다. 그 안에서 Playwright 스크립트를 heredoc(python - <<'PY' ... PY) 형태로 실행할 수도 있다.

local_browser.py는 별도 Playwright 환경. persistent_browser.yaml로 영구 브라우저 모드도 옵션으로 제공.

레이어 3

에이전트 루프 — Agent (~450줄)

agents/default.pyDefaultAgent가 전부. run() → step() → query() → execute_actions() 네 메서드가 한 사이클. 종료 조건은 done=true가 들어오거나 step_limit(기본 100)을 넘기는 것.

흥미로운 디테일: _compact_history() 메서드가 있다. 매 N 스텝마다 대화 히스토리를 LLM에게 요약시켜 토큰을 절약하는 자동 컨텍스트 압축 기능. 긴 호라이즌 태스크에 필수다.

레이어 4

도구 (Tools)

두 개뿐. tools/image_qa.py = 저장된 스크린샷을 OpenAI Vision으로 질문/검증. tools/self_reflection.py = 최종 실행이 끝난 후 모든 스크린샷+로그를 보고 "성공/실패" 판정.

Claude Code 스킬 버전에서는 이 도구들이 빠진다. Claude가 PNG를 네이티브로 읽을 수 있어서 직접 검증하면 되기 때문.

레이어 5

설정 — Config (YAML 스택)

config/base.yaml(공통) + model_openai.yaml 또는 model_claude.yaml(모델별)을 -c 플래그로 스택. persistent_browser.yaml, local_browser.yaml, task_showcase.yaml 등 시나리오별 오버레이도 있다. recursive_merge로 dict 깊이 병합.

인프라 / 통합

외부 인프라는 거의 없다. 선택사항으로 Browserbase(브라우저 클라우드)에 연결할 수 있고, 모델 API 키만 cred.sh에 export하면 끝. 도커도, 쿠버네티스도, 메시지 큐도 없다. 1인 개발자 PC에서 그대로 돌아간다.

용어
Browserbase
서버사이드에서 헤드리스 브라우저를 임대해주는 SaaS. BROWSER_MODE=browserbase로 설정하면 로컬 Chromium 대신 클라우드 세션에 CDP(Chrome DevTools Protocol)로 연결한다. 캡차 우회·IP 분산 등이 필요할 때 쓴다.

4아키텍처 심화 분석

루프의 한 사이클을 코드 레벨로 해부한다.

핵심을 한 그림으로:

┌──────────────────────────────────────────────────────────────┐
│              Webwright Agent Loop (한 스텝)                  │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│   ┌──────────┐    1) query(messages)                         │
│   │ messages │ ──────────────────────────┐                   │
│   │  list    │                           │                   │
│   └──────────┘                           ▼                   │
│        ▲                          ┌──────────────┐           │
│        │                          │  Model       │           │
│        │ 5) add_messages(...)     │ (OpenAI /    │           │
│        │                          │  Anthropic / │           │
│        │                          │  OpenRouter) │           │
│        │                          └──────┬───────┘           │
│        │                                 │                   │
│        │                                 │ 2) JSON 응답      │
│        │                                 ▼                   │
│        │                       {                             │
│        │                         "thought": "...",           │
│        │                         "bash_command": "...",      │
│        │                         "done": false,              │
│        │                         "final_response": ""        │
│        │                       }                             │
│        │                                 │                   │
│        │                                 │ 3) execute        │
│        │                                 ▼                   │
│        │                    ┌────────────────────────┐       │
│        │                    │  Environment           │       │
│        │                    │ (local_workspace)      │       │
│        │                    │                        │       │
│        │                    │  subprocess.run(bash)  │       │
│        │                    │       │                │       │
│        │                    │       ▼                │       │
│        │                    │  ┌──────────────┐      │       │
│        │                    │  │ Playwright   │      │       │
│        │                    │  │ heredoc      │      │       │
│        │                    │  │   browser    │      │       │
│        │                    │  │   page       │      │       │
│        │                    │  │   screenshot │      │       │
│        │                    │  └──────────────┘      │       │
│        │                    └──────────┬─────────────┘       │
│        │                               │                     │
│        │                               │ 4) Observation:     │
│        │                               │    stdout/stderr/   │
│        │                               │    returncode       │
│        └───────────────────────────────┘                     │
│                                                              │
│   ⇣ 반복 (step_limit=100 까지)                              │
│   ⇣ summary_every_n_steps=20 마다 자동 컨텍스트 압축         │
│   ⇣ done=true 가 들어오면 self_reflection 게이트 통과 후 종료│
│                                                              │
└──────────────────────────────────────────────────────────────┘

핵심 설계 패턴 4가지

패턴 1

JSON-only 응답 강제

모델이 매 턴 반환해야 하는 응답은 {thought, bash_command, done, final_response} 네 필드를 가진 JSON 객체 하나. 마크다운도, 코드 펜스도, 산문도 금지. 이 강제 덕분에 파싱이 단순해진다.

만약 모델이 형식을 어기면 FormatError가 발생하고, format_error_template으로 "제대로 된 JSON으로 다시 응답해라"를 모델에게 돌려준다. n_format_errors에 카운트가 쌓이고 추적 가능.

패턴 2

code-as-action (코드를 액션으로)

모델이 클릭 좌표를 예측하는 게 아니라 Playwright 스크립트를 짠다. page.get_by_role("button", name="필터 적용").click() 같이 의미적 셀렉터를 쓰면, 사이트 디자인이 살짝 바뀌어도 깨지지 않는다.

또한 한 번 짠 스크립트는 로컬 함수로 추상화 가능. "날짜 선택 5스텝"이 함수 하나가 되고, 다른 날짜로 재사용 가능. 좌표 예측은 이게 불가능하다.

패턴 3

self_reflection 게이트

모델이 done=true를 외쳐도 바로 끝나지 않는다. _self_reflection_gate_error()가 검사한다 — 최신 final_runs/run_<id>/ 폴더에 self_reflect_result.json이 있고 predicted_label == 1인가? 아니면 done을 false로 되돌리고, "왜 안되는지" 에러 메시지를 모델에게 돌려준다.

이게 "성공처럼 보이지만 실제로는 실패"를 막는 안전벨트다. 모델이 자기 자신을 자기 손으로 검증한 후에만 종료할 수 있다.

패턴 4

워크스페이스 = 영구 상태

매 실행마다 새 브라우저를 띄우지만, workspace_dir 안의 plan.md · self_reflect_config.json · final_runs/run_001/... · final_runs/run_002/...는 디스크에 남는다. 실행 실패 시 다음 시도(run_002)에서 이전 시도의 스크린샷·로그를 참고할 수 있다.

2-stage 검증이 핵심: ①각 스크린샷을 critical point 리스트와 매칭 → Score 1-5 + Reasoning, ②모든 reasoning + action 로그를 종합해 "success / failure" 판정.

함정
"풀 페이지 스크린샷" 절대 금지

코드 곳곳에 "Always avoid taking full page screenshot, use viewport 1280x1800"라고 못 박혀 있다. page.screenshot(full_page=True)는 사이트가 길수록 메모리 폭주 + 다음 image_qa 호출에서 토큰 폭발. 반드시 뷰포트 크기로 캡처한다.

5디렉토리 구조 해부

레포에 뭐가 어디 있는지.

Webwright/
├── pyproject.toml                # 패키지 메타 + 9개 의존성
├── README.md                     # 18KB, 자세한 설명
├── .claude-plugin/               # Claude Code 플러그인 메타
├── .codex-plugin/                # Codex(OpenAI 에이전트) 플러그인 메타
│
├── assets/
│   ├── webwright_logo.svg
│   ├── demo.mp4                  # 5.5MB 데모 영상
│   ├── om2w_autoeval_step100.png # Online-Mind2Web 결과 그래프
│   ├── odysseys_eval_step100.png # Odysseys 결과 그래프
│   └── task_showcase/            # 6개 실제 태스크 사례 (gallery)
│       ├── app.py                # Flask 갤러리 앱
│       ├── tasks/
│       │   ├── austin_apartments/{task.json, report.json}
│       │   ├── driving_weather/...
│       │   ├── nz_au_legal_jobs/...
│       │   ├── ophelia_pittsburgh/...
│       │   ├── pokemon_tcg/...
│       │   └── slickdeals/...
│       └── templates/{dashboard.html, task.html}
│
├── skills/webwright/             # Claude Code 스킬로 패키징된 버전
│   ├── SKILL.md                  # description + workflow
│   ├── commands/
│   │   ├── run.md                # /webwright:run 슬래시 커맨드
│   │   └── craft.md              # /webwright:craft (CLI 도구 생성)
│   └── reference/
│       ├── workflow.md           # plan→explore→final→verify 흐름
│       ├── playwright_patterns.md# heredoc 패턴, ARIA 스냅샷 레시피
│       └── cli_tool_mode.md      # 재사용 가능한 CLI 생성 규약
│
├── src/webwright/                # 실제 패키지 본체
│   ├── __init__.py
│   ├── exceptions.py             # FormatError, InterruptAgentFlow, LimitsExceeded
│   │
│   ├── agents/
│   │   └── default.py            # ~450줄, DefaultAgent 본체 ★
│   │
│   ├── environments/
│   │   ├── local_workspace.py    # Bash 실행 환경
│   │   └── local_browser.py      # Playwright 환경
│   │
│   ├── models/
│   │   ├── base.py               # Model 추상 클래스
│   │   ├── openai_model.py       # GPT-5.4 등 OpenAI 백엔드
│   │   ├── anthropic_model.py    # Claude Opus 4.7 등
│   │   └── openrouter_model.py   # OpenRouter 통합 백엔드
│   │
│   ├── tools/
│   │   ├── image_qa.py           # 스크린샷 질의응답
│   │   ├── self_reflection.py    # 최종 성공 판정
│   │   └── persistent_local_browser.py
│   │
│   ├── config/
│   │   ├── base.yaml             # 공통 설정 (system_template 포함!) ★
│   │   ├── model_openai.yaml     # OpenAI 오버레이
│   │   ├── model_claude.yaml     # Anthropic 오버레이
│   │   ├── model_openrouter.yaml
│   │   ├── local_browser.yaml
│   │   ├── persistent_browser.yaml
│   │   ├── crafted_cli.yaml
│   │   └── task_showcase.yaml
│   │
│   ├── run/
│   │   ├── cli.py                # webwright CLI 엔트리포인트
│   │   └── run_command.sh
│   │
│   └── utils/
│       ├── logging.py
│       ├── runtime.py
│       └── serialize.py          # recursive_merge 등
│
└── tests/
    ├── conftest.py
    └── unit/
        ├── test_doctor.py
        └── test_tool_model_routing.py

주목할 위치:

읽기 순서 추천
코드를 처음 본다면

src/webwright/config/base.yaml (system_template만 약 220줄, 에이전트의 "헌법" 같은 곳) → ② src/webwright/agents/default.py (메인 루프) → ③ src/webwright/run/cli.py (어떻게 시작되는지) → ④ skills/webwright/SKILL.md (Claude Code용 어댑테이션).

6학습 포인트

이 레포에서 무엇을 배울 수 있는가.

① Playwright async + heredoc 패턴

Webwright의 모든 브라우저 동작은 bash 한 줄 안에 들어가는 Python 스크립트다. 이 패턴은 LLM이 텍스트 한 덩어리만 출력할 때 매우 유용하다.

python - <<'PY'
import asyncio
from playwright.async_api import async_playwright

async def main():
    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=True)
        ctx = await browser.new_context(viewport={"width": 1280, "height": 1800})
        page = await ctx.new_page()
        await page.goto("https://www.google.com/flights")
        await page.get_by_role("button", name="Search").click()
        snapshot = await page.locator("body").aria_snapshot()
        print(snapshot)
        await browser.close()

asyncio.run(main())
PY

여기서 핵심은 get_by_role() + aria_snapshot() 조합. CSS 셀렉터(div.btn-primary:nth-child(3))는 사이트가 바뀌면 깨지지만, ARIA role/name은 접근성 표준이라 안정적이다.

② JSON schema-enforced LLM 응답

system_template이 명령한다 — "응답은 strict JSON 객체 하나. 마크다운 금지, 코드 펜스 금지". 이건 LLM에게 매우 익숙하지 않은 제약인데, Webwright는 그걸 강하게 밀어붙인다. 결과적으로 모든 응답을 json.loads() 한 번으로 파싱 가능.

비유

LLM에게 "자유롭게 써라"라고 하면 시인이 되고, "이 양식대로만 채워라"라고 하면 회계사가 된다. Webwright는 회계사를 원한다. 예측 가능성이 곧 디버깅 가능성이기 때문에.

③ recursive_merge로 YAML 스택 합치기

-c base.yaml -c model_claude.yaml -c persistent_browser.yaml처럼 여러 YAML을 깊이 병합해서 최종 설정을 만든다. utils/serialize.pyrecursive_merge가 핵심. dict이면 키별로 재귀적 병합, list이면 덮어쓰기, UNSET sentinel로 "이 값은 안 건드림"을 표현.

④ 컨텍스트 압축 (history compaction)

매 20스텝(설정값)마다 자동으로 트리거되는 _compact_history(). 시스템 메시지를 보존한 채, 그 외 모든 메시지를 LLM에게 "요약해라"고 시켜서 한 줄 user 메시지로 치환한다. 100스텝짜리 long-horizon에서 토큰 폭발을 막는 트릭.

# default.py 의 _compact_history()
summary_request = self.model.format_message(
    role="user",
    content=self.config.summary_user_prompt,
)
response = self.model.query(self.messages + [summary_request])
self.messages = [system_message, summary_message]  # 압축!

⑤ 멀티 모델 백엔드 추상화

models/base.pyModel 추상 클래스는 4개 메서드만 요구한다 — query(), format_message(), format_observation_messages(), get_template_vars(). 새 백엔드 추가는 이걸 구현하면 끝. OpenAI든, Gemini든, 자체 호스팅 vLLM이든.

⑥ 자기검증(self-verification) 아키텍처

이게 Webwright의 진짜 비밀 무기다. 모델은 plan.md(critical points 체크리스트)를 먼저 만들고, 그 다음 final_script.py를 작성하고, 실행 후 self_reflection이 "각 critical point에 대응되는 스크린샷이 정말 그 조건을 만족하는가?"를 2-stage로 검증한다.

실제 self_reflection 프롬프트는 모델에게 "엄격한 평가자" 역할을 부여한다. 응답 마지막 라인이 정확히 Status: success 또는 Status: failure여야 하고, 그게 아니면 자동으로 failure.

실습 아이디어

실습 1

로컬에서 단일 태스크 돌려보기

pip install -e .playwright install chromium → OpenAI 키 export → python -m webwright.run.cli -c base.yaml -c model_openai.yaml -t "찾아라 ...". outputs/<task_id>_<timestamp>/에 trajectory.json + 스크린샷이 쌓이는 걸 직접 본다.

실습 2

Claude Code 스킬로 설치

/plugin marketplace add microsoft/Webwright/plugin install webwright@webwright 실행. Claude Code를 재시작하면 /webwright:run, /webwright:craft 슬래시 명령이 활성화된다. 모델 API 키 추가 불필요 — Claude Code 구독으로 모든 게 돌아간다.

7하드웨어/시스템 요구사항

실제로 돌리려면 뭐가 필요한가.

필수

최소 사양

Python 3.10+ (3.11 권장, async 성능 차이). Playwright 호환 Chromium(또는 Firefox — Claude Code 스킬은 Firefox 사용). RAM 8GB+ (Chromium이 사이트당 1-2GB 잡아먹음). 디스크 5GB+ (브라우저 + 스크린샷 누적).

필수

API 키 (백엔드 중 하나)

OPENAI_API_KEY (GPT-5.4 권장, 가장 강함) 또는 ANTHROPIC_API_KEY (Claude Opus 4.7, 어려운 split에서 더 강함) 또는 OPENROUTER_API_KEY (다양한 모델 시도용). 한 번 풀 태스크에 $0.50~$2 정도 소비. 100스텝 한도까지 가면 더 들 수 있음.

선택

Browserbase

로컬 머신이 약하거나 사이트가 IP 차단을 강하게 하면 BROWSERBASE_API_KEY + BROWSERBASE_PROJECT_ID 설정 후 browser_mode: browserbase로 클라우드 브라우저 임대. 가격은 분 단위 과금.

선택

Claude Code (가장 저렴)

Claude Code 구독($20/월)만 있으면 skills/webwright/를 통째로 가져와 LLM API 비용 0원으로 돌릴 수 있다. PNG 검증도 Claude가 직접 함.

함정
캡차와 봇 차단

Cloudflare·reCAPTCHA가 강한 사이트(은행, 항공권 일부)는 로컬 Chromium에서 막힐 수 있다. 그래서 Webwright 스킬 버전은 Firefox로 기본 변경(ERR_HTTP2_PROTOCOL_ERROR 회피). 그래도 막히면 Browserbase로 넘어가야 한다.

8직접 해볼 수 있는 실습 과제

난이도별 5단계.

난이도 1 / 입문

네이버 영화 1위 찾기

start_url을 네이버 영화 페이지로, task를 "오늘 박스오피스 1위 영화의 제목과 평점을 알려줘"로 설정. 한 페이지 안에서 끝나는 단순 추출 태스크. 배울 점: ARIA 스냅샷 출력하고 텍스트 파싱하는 기본 흐름.

난이도 2 / 초급

쿠팡에서 조건 검색

"쿠팡에서 무선이어폰 10만원 이하, 평점 4.5↑, 무료배송 필터 적용한 상위 3개의 가격과 모델명". 필터 컨트롤 확장 → 체크박스 클릭 → 적용 확인 → 결과 캡처. 배울 점: 필터가 드로어/아코디언 뒤에 숨어있을 때 어떻게 다루는지.

난이도 3 / 중급

호텔 예약 폼 채우기까지

"부킹닷컴에서 2026-08-10 ~ 2026-08-13, 성인 2명, 강남 위치, 4성 이상, 50만원 이하 호텔 중 평점 가장 높은 곳"의 예약 페이지 진입까지(예약 버튼 클릭 직전). 날짜 피커, 다중 필터, 정렬, 다음 페이지 진입. 배울 점: long horizon에서 self_reflection 게이트가 어떻게 작동하는지 직접 체감.

난이도 4 / 고급

재사용 CLI 도구 생성

/webwright:craft로 "구글 플라이트에서 [출발지] → [도착지] [날짜] 최저가 이코노미 검색"을 짠 후, final_script.py --origin ICN --destination LAX --depart-date 2026-09-01로 파라미터를 바꿔 재실행. 배울 점: 한 번 짠 자동화를 재사용 가능한 함수로 추상화하는 패턴.

난이도 5 / 마스터

새 모델 백엔드 추가

models/base.py를 상속해 models/local_vllm_model.py를 만들고, 로컬 호스팅된 Qwen2.5-72B나 Llama-3-70B를 백엔드로 붙인다. config/model_qwen.yaml도 작성. 그 다음 동일한 태스크를 GPT-5.4와 Qwen으로 각각 돌려서 trajectory.json 비교. 배울 점: Webwright의 추상화가 정말 모델-아그노스틱(model-agnostic)인지 검증 + 작은 모델의 한계 파악.

9관련 기술 심화 학습 로드맵

4주 코스로 정리.

1주차

Playwright 마스터하기

Playwright 공식 문서의 "Locators" 챕터를 정독. get_by_role, get_by_text, get_by_label, get_by_placeholder의 차이를 손에 익힌다. page.wait_for_selector, page.wait_for_load_state("networkidle") 등 대기 전략도 학습. 매일 한 사이트씩 자동화 스크립트 짜기.

2주차

LLM API 직접 만지기

httpx + asyncio로 OpenAI API 콜을 직접 짜본다(SDK 안 쓰고). 스트리밍, 재시도, 백오프, 토큰 카운팅을 손으로 구현. webwright/models/openai_model.py를 따라 만들어보고, 자기만의 "JSON 응답 강제" 패턴을 시도. 참고: tiktoken 또는 anthropic-tokenizer로 토큰 계산.

3주차

에이전트 루프 설계

SWE-agent/mini-swe-agent(Webwright의 영감 소스)와 Webwright의 default.py를 나란히 놓고 비교. ReAct 패턴(Reason + Act), Reflexion(자기 회고), Plan-and-Solve 같은 학술 패턴이 코드에서 어떻게 등장하는지 매핑. 읽을 논문: ReAct (Yao et al., 2022), Reflexion (Shinn et al., 2023).

4주차

벤치마크 + 평가

Online-Mind2Web · WebArena · VisualWebArena · Odysseys 벤치마크 페이퍼를 읽고, 각각이 무엇을 측정하는지 이해. 작은 부분집합(10~20 태스크)을 직접 받아서 Webwright로 돌려본다. trajectory.json을 분석해서 "어떤 종류의 태스크에서 실패하는지" 패턴 분석. 최종적으로 자기만의 critic 모델을 self_reflection에 끼워본다.

10핵심 키워드 사전

한 번 더 정리.

개념 1
code-as-action
LLM이 "다음에 뭐 할까?"의 답을 자연어 액션이나 좌표 예측 대신 실행 가능한 코드로 출력하는 패러다임. Webwright의 핵심 철학. SWE-agent에서 가져왔다.
개념 2
workspace-as-state
브라우저 세션을 영구 상태로 잡지 않고, 디스크의 워크스페이스 폴더(plan.md, final_script.py, final_runs/...)를 영구 상태로 삼는 설계. 매 실행마다 깨끗한 브라우저로 다시 시작.
개념 3
critical points (CP)
태스크가 성공으로 인정되려면 만족해야 하는 명시적 조건들의 체크리스트. plan.md에 - [ ] CP1: ... 형식으로 기록. 각 CP는 스크린샷 한 장 또는 로그 한 줄로 독립적으로 검증 가능해야 한다.
개념 4
self_reflection (자기 검증)
실행이 끝난 후, 모든 스크린샷을 critical point 리스트와 매칭해 점수(1~5)와 reasoning을 받고, 그것들을 종합해 Status: success 또는 Status: failure를 출력하는 2-stage 평가. predicted_label==1일 때만 done 허용.
개념 5
heredoc 패턴
Bash에서 python - <<'PY' ... PY 형태로 stdin에 코드 블록을 흘려넣어 실행. LLM이 한 번에 출력해야 하는 "bash_command 한 문자열"에 멀티라인 Python을 담는 트릭.
개념 6
ARIA snapshot
Accessible Rich Internet Applications(접근성 표준)이 정의한 페이지의 의미적 트리 표현. page.locator("body").aria_snapshot()로 추출. 픽셀 좌표 대신 "이 페이지에 어떤 의미적 요소가 있는지"를 텍스트로 보여준다.
개념 7
history compaction (컨텍스트 압축)
긴 대화를 LLM이 자기 자신을 시켜 한 줄 요약으로 압축. summary_every_n_steps마다 트리거. 100스텝짜리 호라이즌에서 토큰 폭주 방지.
개념 8
long-horizon task
수십~수백 스텝이 필요한 복합 웹 작업. 짧은 작업과 달리, 중간 오류가 누적되고 LLM의 컨텍스트가 폭주하기 쉬워 어렵다. Odysseys 벤치마크가 대표적.
개념 9
CDP (Chrome DevTools Protocol)
크롬 개발자 도구가 내부적으로 쓰는 프로토콜. Playwright나 Puppeteer는 이걸로 브라우저를 원격 조작한다. Browserbase 같은 클라우드 브라우저에 붙을 때도 CDP로 연결.
개념 10
recursive_merge
중첩된 dict을 키 단위로 깊이 병합하는 함수. utils/serialize.py에 정의. -c base.yaml -c model_claude.yaml처럼 여러 YAML을 합칠 때 핵심.
개념 11
format_error_template
모델이 응답 JSON 형식을 어겼을 때 자동으로 돌아가는 "다시 제대로 써라" 프롬프트. base.yaml에 정의. FormatError 예외와 짝.
개념 12
trajectory.json
실행 종료 후 디스크에 저장되는 전체 대화/액션 기록 JSON. messages, model usage, exit_status, config 스냅샷, format_errors 카운트 등이 들어 있다. 외부 judge가 이걸 읽어서 최종 평가.

11참고 링크

더 깊이 파고들 자료.

공식

레포 & 블로그 & 데모

GitHub: github.com/microsoft/Webwright

Microsoft Research 블로그: Webwright: A terminal is all you need for web agents

웹사이트 & 데모 비디오: microsoft.github.io/Webwright

영감 소스

SWE-agent / mini-swe-agent

Webwright가 "design inspiration"이라고 명시한 레포. 미니멀 에이전트 루프의 원형. github.com/SWE-agent/mini-swe-agent

기술 문서

Playwright 공식 문서

playwright.dev/python — 특히 "Locators", "Auto-waiting", "Accessibility" 챕터 추천.

벤치마크

Online-Mind2Web · Odysseys

Mind2Web: 실사이트 대상의 GUI 에이전트 벤치마크. Online 변형은 실시간 사이트에서 평가. Mind2Web 홈

Odysseys: 200개의 long-horizon 태스크 벤치마크. Webwright 블로그 글에 자세한 소개.

관련 논문

학술적 배경

ReAct (Yao et al., 2022) — Reasoning + Acting을 교차로 하는 에이전트 패턴의 시작.

Reflexion (Shinn et al., 2023) — 에이전트의 자기 회고를 통한 성능 향상.

WebArena (Zhou et al., 2023) — 웹 에이전트 벤치마크의 표준.

Code as Policies (Liang et al., 2022) — 코드를 액션 공간으로 쓰는 초기 아이디어.

실용 가이드

지금 시도해볼 만한 것들

  1. 10분 데모 — 레포 클론 → pip install -e .playwright install chromium → OpenAI 키 export → README에 있는 SEA→JFK 항공권 검색 명령 그대로 실행. outputs/ 폴더에 trajectory.json과 스크린샷이 쌓이는 걸 본다.
  2. Claude Code 사용자라면skills/webwright~/.claude/skills/webwright에 심볼릭 링크하고 Claude Code 재시작. /webwright:run으로 자연어 한 줄에 자동화 시작. API 키 없이도 동작한다는 게 진짜 매력.
  3. base.yaml의 system_template 정독 — 약 220줄짜리 프롬프트가 "에이전트의 헌법"이다. 모델이 어떻게 행동하는지 그 안에 다 들어있다. 자기 프로젝트의 LLM 프롬프트에 그대로 차용해도 좋다.
  4. default.py의 _self_reflection_gate_error() 메서드 읽기 — 100줄 정도. "성공처럼 보이지만 실제로는 실패"를 어떻게 막는지 그 로직이 통째로 들어있다. 자기 프로젝트의 평가 게이트에 참고.
  5. 나만의 critic 만들기self_reflect_config.json의 4개 프롬프트를 자기 도메인에 맞게 다시 짠다. 예: 쇼핑 가격 비교 태스크 전용 critic, 데이터 추출 태스크 전용 critic. Webwright는 이런 prompt-engineering 친화적 설계가 돋보인다.
원문 · microsoft/Webwright, 2026-05 · github.com/microsoft/Webwright