
Playwright Test Agents(Planner / Generator / Healer)를 실제로 사용해 본 경험 ―― 막혔던 부분과
요약
Playwright v1.56에 도입된 Test Agents(Planner, Generator, Healer)의 실제 사용 경험과 설정 노하우를 다룹니다. 특히 Planner가 사이트 탐색을 위해 seed 파일과 MCP 서버를 활용하는 메커니즘을 상세히 설명합니다.
핵심 포인트
- Planner, Generator, Healer의 역할 분담 구조 이해
- seed.spec.ts를 통한 컨텍스트(URL, 인증) 전달의 중요성
- Playwright MCP를 활용한 에이전트의 브라우저 제어 방식
- Claude Code와 연동한 에이전트 초기화 및 구성 방법
Playwright v1.56부터 도입된 Playwright Test Agents를 실제로 직접 다루며 테스트해 보았습니다.
AI 에이전트가 브라우저를 조작하면서 테스트 계획을 세우고, 테스트 코드를 생성하며, 깨진 테스트를 자동으로 복구(Heal)해 주는 기능입니다. 직접 사용해 보니 "과연, 이렇게 작동하는구나"라는 발견과 "어라, 왜 이렇게 되지?"라는 막히는 부분이 여러 번 있었기에, 경험을 바탕으로 정리해 두겠습니다.
마찬가지로 "기사를 읽고 따라 했지만, 기사에 적혀 있지 않은 전제 조건 때문에 막혔다"는 분들에게 도움이 되기를 바랍니다.
참고: 에이전트 호출 방식이나 도구 구성은 Playwright / Claude Code의 버전에 따라 달라지는 부분이 있습니다. 이 기사는 집필 시점(2026년 7월)의 경험 메모로 읽어주세요.
3개의 에이전트가 역할을 분담하여 E2E 테스트의 "만들기·쓰기·유지보수"를 나누어 담당하는 구조입니다.
🎭 Planner… 앱을 실제로 탐색하여 Markdown 형식의 테스트 계획을 작성함 -
🎭 Generator… 테스트 계획을 읽고 실행 가능한 Playwright 테스트 코드를 생성함 -
🎭 Healer… 테스트를 실행하고 실패한 테스트를 자동으로 복구함
각각 단독으로도, 혹은 순서대로 연결해서도 사용할 수 있습니다. 이번에는 Planner → Generator 흐름을 중심으로 살펴보았습니다.
- Playwright
v1.56 이후 - 이번에는 Claude Code를 AI 도구로 사용하는 구성(
--loop=claude
)
npm install -D @playwright/test@latest
npx playwright install chromium
npx playwright init-agents --loop=claude
--loop는 사용하는 AI 도구에 따라 변경합니다(vscode / codex / opencode 등).
--loop=claude로 초기화하면 다음과 같은 구성이 되었습니다.
repo/
.claude/ # planner / generator / healer의 에이전트 정의
node_modules/
...
각 파일의 역할을 대략적으로 정리하면 다음과 같습니다:
| 파일 / 폴더 | 역할 |
|---|---|
.claude/ | 3개 에이전트의 정의 (지시문 + 사용 가능한 도구의 집합) |
.mcp.json | Playwright MCP 서버 설정. 에이전트가 브라우저에 접근하는 경로 |
seed.spec.ts | 테스트의 "기점"을 만드는 준비 테스트. Planner / Generator의 본보기가 됨 |
specs/ | Planner가 출력하는 테스트 계획(Markdown)의 저장 위치 |
많은 기사에서는 "Planner에게 지시를 내리면 알아서 사이트를 탐색하여 계획을 세워준다"라고 적혀 있습니다. 하지만 냉정하게 생각하면, 사이트의 URL도 인증 정보도 전달하지 않았는데 어떻게 앱을 보러 가는 것인가? 라는 의문이 들었습니다.
답은 기사에서 생략된 두 가지 전제 조건에 있었습니다.
Planner는 단순한 프롬프트만으로 작동하는 것이 아닙니다. seed.spec.ts가 컨텍스트(Context)로 전달되며, Planner는 이를 실제로 실행한 후 앱 탐색을 시작합니다.
- 어떤 사이트를 볼 것인가 → seed 내의
page.goto(...)나 config의baseURL로 결정됨 - 인증 정보 → seed(와 fixture)가 로그인 처리나
storageState로드를 담당함
즉 "정보가 없는" 것이 아니라, "seed에 적혀 있는" 것입니다. 기사에서는 이 seed 준비 단계를 건너뛰고 있었기에 혼란이 생겼습니다.
Planner는 .mcp.json의 Playwright MCP를 통해 실제로 브라우저를 열고, DOM을 보면서 탐색합니다. 따라서 "사이트 구조를 사전에 알려줄" 필요 없이 그 자리에서 관찰합니다. 반대로 말하면, 실행 중인 앱이 없으면 Planner는 움직일 수 없습니다.
초기 상태의 seed는 내용이 비어 있는 틀(雛形)이었습니다.
import { test, expect } from '@playwright/test';
test.describe('Test group', () => {
test('seed', async ({ page }) => {
...
우선 공개된 사이트(TodoMVC)에서 실행하려면, 최소한 이것만으로도 OK입니다.
import { test, expect } from '@playwright/test';
test.describe('Test group', () => {
test('seed', async ({ page }) => {
...
seed의 역할은 "Planner가 앱과 상호작용할 수 있는 상태까지 가져가는 것"입니다. 최소한 "시작 URL을 여는 것"만으로도 기능합니다. 인증이 필요한 플로우(Flow)의 경우, Planner를 돌리기 전에 세션을 확립해 두어야 합니다(seed에서 storageState를 불러오는 등).
Claude Code를 프로젝트 루트에서 실행하고, planner 에이전트(Agent)를 호출하여 지시합니다.
use the planner agent
seed test: seed.spec.ts
todo 추가와 완료 테스트 계획을 작성해줘
포인트는 3가지입니다.
- planner 에이전트를 명시적으로 지정할 것
- seed test를 컨텍스트(Context)에 포함할 것 (이것이 기점이 됩니다)
- 주제를 전달할 것 (처음에는 좁은 주제로 설정해야 결과를 파악하기 쉽습니다)
실행하면 Planner가 브라우저를 열어 앱을 조작하며 탐색하고, 잠시 기다리면 Markdown 형식의 테스트 계획이 출력됩니다.
지시를 내리면 "에이전트가 백그라운드에서 작동 중입니다. 완료되는 대로 알림을 드립니다"라는 메시지가 뜨지만, 지금 어떻게 진행되고 있는지 알 수 없습니다.
이는 Claude Code 측의 기능으로 확인할 수 있었습니다.
/tasks를 실행하면, 동작 중인 백그라운드 프로세스가 ID와 상태와 함께 목록으로 표시됩니다.- 최신 버전이라면 Agent View(
\키로 열기)를 통해 각 에이전트가 현재 어떤 도구(Tool)를 사용하고 있는지까지 볼 수 있습니다.
Planner의 탐색은 브라우저에서 사이트를 한 차례 조작하므로 어느 정도 시간이 걸립니다. 우선은 /tasks로 상태를 확인하는 것이 가장 간단합니다.
계획은 만들어졌는데 "Write 도구가 없어 파일을 저장할 수 없으므로 대신 저장합니다"라는 메시지가 나왔습니다. 외부 에이전트에는 쓰기 권한이 없는 것인가? 하는 궁금증이 생겨, planner의 정의 파일을 살펴보았습니다.
.claude/agents/의 planner 정의 내 tools:를 보면, 확실히 범용 Write는 없습니다. 하지만 리스트 안에 이것이 있었습니다.
mcp__playwright-test__planner_save_plan
즉, Planner는 "범용 Write는 갖지 않고, 계획 저장 전용인 planner_save_plan을 사용하는 설계"였습니다. 불필요한 쓰기 권한을 주지 않고, 용도를 한정한 도구 세트(Tool set)로 구성되어 있는 것입니다.
이번 경우에는 이 전용 도구가 제대로 사용되지 않아 메인 Claude Code가 폴백(Fallback)으로 저장했기 때문에, 원래 specs/에 들어가야 할 계획이 tests/todo-add-complete.plan.md라는 다른 경로와 이름으로 저장되었습니다. 동작 자체에는 문제가 없지만, "전용 도구를 통했다면 위치가 바뀌었을 것"이라는 배움을 얻었습니다.
교훈: "에이전트에게 쓰기 권한이 없다"라는 일반적인 규칙이 있는 것이 아니라, 해당 에이전트 정의에 어떤 도구가 할당되어 있느냐에 달려 있습니다. 동작이 궁금하다면 정의 파일을 읽어보는 것이 가장 빠릅니다.
계획이 완료되면 Generator에게 코드 생성을 시킵니다.
use the generator agent
@tests/todo-add-complete.plan.md
이 계획으로부터 Playwright 테스트 코드를 생성해줘
호출하는 각 행의 의미는 다음과 같습니다.
use the generator agent… 서브 에이전트(Sub-agent) 지정 (자연어이므로 엄격한 규칙은 없습니다)@파일경로… 파일 경로 지정 (단순히 "어떤 파일을 사용할지"를 전달하는 것입니다)
는 Claude Code의 파일 참조 단축키로, 파일 내용을 컨텍스트(Context)에 확실하게 포함할 수 있습니다. 자동 완성도 지원되므로, 확실하게 읽히고 싶을 때는 이 방법을 사용하는 것이 안정적입니다. 경로를 문자열로 전달하기만 해도 에이전트가 스스로 파일을 열어주지만, 망설여진다면 @를 사용하는 것이 안전했습니다.
Generator는 계획을 읽고, 실제로 브라우저에서 각 시나리오를 조작하며 셀렉터(Selector)나 어설션(Assertion)을 라이브로 검증하면서 .spec.ts를 생성합니다.
Healer는 "실패한 테스트를 리플레이하고, UI를 보며 동일한 요소를 찾아, 패치(Patch)를 적용하여 통과할 때까지 재실행"해 줍니다.
여기서 궁금했던 점은 앱 측의 사양이 비결정적(Non-deterministic)인 경우 어떻게 되는가입니다. 예를 들어 "성공 메시지가 기능에 따라 스낵바(Snackbar)이거나, 다이얼로그(Dialog)이거나, 네이티브 다이얼로그(Native Dialog)인 등 통일되어 있지 않은" 경우 말입니다.
결론부터 말하자면, Healer는 이러한 종류의 비결정적인 사양을 영리하게 흡수해주지는 않는다고 생각하는 것이 안전합니다.
- Healer가 잘하는 것은 "UI는 바뀌었지만 정답은 하나로 정해지는" 케이스 (셀렉터 불일치, 대기 부족 등)
- "같은 기능인데 실행할 때마다 정답이 바뀌는" 케이스는 방어 범위 밖
- 오히려 우연히 관측된 한 가지 패턴으로 테스트가 고정되어 버려, 다음 실행에서 다른 패턴이 나와 실패하고 → 다시 수정하는... 식의 "두더지 잡기"가 될 수 있음
1. 테스트 측에서 OR를 허용하기
const snackbar = page.getByRole('status').filter({ hasText: '저장했습니다' });
const dialog = page.getByRole('dialog').filter({ hasText: '저장했습니다' });
await expect(snackbar.or(dialog)).toBeVisible();
단, 네이티브 다이얼로그(alert, confirm 등)는 DOM 요소가 아니므로 로케이터(Locator)로 잡을 수 없으며, page.on('dialog', ...)로 별도 핸들링이 필요합니다. 감지 메커니즘이 다르기 때문에 단순한 OR로는 통합할 수 없다는 점에 주의해야 합니다.
2. 의미 기반의 검증으로 넘기기
표시 방법으로 검증하지 않고, "저장 후 레코드가 증가했는지", "목록에 반영되었는지", "API가 200을 반환하는지" 등 표시 수단에 의존하지 않는 결과로 검증합니다. 표시의 변동에 휘둘리지 않고 더 견고(Robust)합니다.
3. 계획 단계에서 명시하기
Planner의 계획 Markdown에 "성공 메시지는 여러 가지 표시 방법이 있을 수 있다. 어떤 것이든 성공으로 판정한다"라고 적어두면, Generator가 이를 반영하기 쉽습니다. 계획은 수동으로 편집할 수 있으므로, 이런 식의 설계가 효과적입니다.
애초에 "표시 방법이 통일되어 있지 않다"는 것은 앱 측 설계의 무질서함이며, 테스트에서 이를 흡수하려고 할수록 테스트는 복잡해지고 취약해집니다. 테스트의 복잡성은 종종 프로덕트 설계의 무질서를 비추는 거울입니다. 가능하다면 앱 측에 통일을 요구하는 것이 본질적인 해결책입니다.
spec.ts 파일은 존재하고 에러도 없는데, VSCode 사이드바의 Test를 클릭해도 "이 워크스페이스에서 아직 테스트를 찾을 수 없습니다"라고 표시됩니다. Playwright Test for VSCode 확장은 이미 설치된 상태입니다.
Playwright 확장은 spec.ts를 직접 찾는 것이 아니라, 먼저 playwright.config.ts를 찾고, 그 testDir를 기준으로 테스트를 찾습니다.
제 경우에는 설정(Config) 파일이 하위 폴더에 있었습니다.
e2e/playwright.config.ts ← config는 여기
export default defineConfig({
testDir: './tests', // ← config 위치로부터의 상대 경로 = e2e/tests/
// ...
});
testDir: './tests'는 config 파일 자체의 위치를 기준으로 한 상대 경로이므로, 실제로 찾아가는 곳은 e2e/tests/가 됩니다. config가 하위 폴더에 숨겨져 있으면, VSCode에서 열려 있는 루트(Root)와 일치하지 않아 확장이 제대로 찾아내지 못할 수 있습니다 (Test 뷰에 Python용 버튼이 나타났던 것이 그 신호였습니다).
커맨드 팔레트(Ctrl+Shift+P)에서:
Test: Refresh Tests
이를 실행하면 무사히 테스트가 인식되었습니다 🎉
그 외에 효과가 있을 수 있는 대처법:
Developer: Reload Window로 창 재로드 - 만약 config가 중첩되어 있다면, 해당 폴더 자체를 VSCode의 루트(Root)로 다시 열기- 확장 프로그램의 명령으로 config를 명시적으로 선택하기
교훈: spec.ts 단일 파일이 올바르더라도, 확장 프로그램은 config를 기점으로 탐색하기 때문에 config의 위치, testDir, 그리고 VSCode에서 열려 있는 루트(Root) 세 가지의 위치 관계가 맞지 않으면 감지되지 않는다.
Playwright Test Agents를 실제로 다루며 얻은 배움을 정리합니다.
- 사이트 URL과 인증 모두 여기(그리고 config의
baseURL)가 모든 기점이다. 기사에서는 이 준비 과정을 생략하는 경우가 많으니 주의할 것. - Planner는 라이브 앱을 브라우저로 직접 확인하므로, 실행 중인 앱이 필요함.
- 백그라운드 실행 상태는
/tasks나 Agent View에서 확인할 수 있음. - 에이전트의 도구 권한(Tool permissions)은 정의 파일에 따라 다름. 궁금하다면
.claude/agents/의 정의를 읽을 것. @파일경로는 Claude Code의 파일 참조 단축키. 확실하게 읽히고 싶을 때 유용함.- Healer는 비결정적인(Non-deterministic) UI 사양을 흡수해주지 않음. 테스트 측에서 OR 조건이나 의미 기반 검증을 정교하게 만들거나, 앱 측의 통일성을 검토해야 함.
- VSCode에서 테스트가 나타나지 않을 때는 config의 위치,
testDir, 그리고 열려 있는 루트 세 가지를 의심하고, 우선Test: Refresh Tests를 실행할 것.
AI에게 모든 것을 떠넘기는 것이 아니라, "AI가 기초 작업(Groundwork)을 수행하고, 인간이 요점을 리뷰 및 정교화한다"는 것이 적절한 거리감이라고 느꼈습니다. 특히 비결정적인 사양이나 비즈니스 크리티컬한 경로는 인간이 명시적으로 작성한다는 전제하에 사용하는 것이 좋아 보입니다.
조금씩 다뤄보면서 다시 다음 내용을 써 내려가겠습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기