Playwright를 자율적으로 작동시키는 방법 (AI 에이전트가 무분별하게 움직이는 것을 막으면서)
요약
본 글은 Playwright의 자동화 기능을 LLM과 결합하여, 단순한 테스트 스크립트를 넘어 목표 기반의 자율적인 웹 에이전트를 구축하는 방법을 설명합니다. 핵심은 거대한 프레임워크 없이도 접근성 스냅샷 관찰, LLM을 통한 다음 행동 요청, 그리고 Playwright 실행 및 상태 검증 루프를 통해 구현할 수 있다는 점입니다.
핵심 포인트
- Playwright에 목표(Goal) 자체를 부여하여 자율적인 자동화를 시도합니다.
- LLM과 Playwright만으로 최소한의 에이전트 구조를 만들 수 있습니다.
- 행동 결정은 LLM에게 맡기되, 최종 검증 및 단언(assert)은 코드 레벨에서 분리해야 합니다.
- 접근성 스냅샷을 관찰하고 작은 작업 집합 내에서 다음 행동을 요청하는 루프가 핵심입니다.
Playwright는 지침을 따르는 데 매우 뛰어납니다.
버튼을 클릭하라고 지시하면 버튼을 클릭합니다. 입력 필드를 채우라고 지시하면 입력 필드를 채웁니다. 40단계로 구성된 테스트를 주면, 페이지가 정상적으로 작동한다고 가정할 때 모든 40단계를 실행할 것입니다.
문제는 누군가가 그 단계를 직접 작성해야 한다는 것입니다.
만약 Playwright에게 목표(goal) 자체를 준다면 어떨까요?
쇼핑 카트에 상품 두 개를 추가하고, 할인 코드를 적용한 다음, 총액이 정확한지 확인하라.
브라우저는 페이지를 검사하여 무엇을 클릭해야 할지 알아내고, 무언가 변경되는 것을 감지하며, 다음에 무엇을 해야 할지 결정해야 합니다. 이것은 다른 종류의 자동화입니다. 그리고 이는 재미있는 공학적 문제이지만, '에이전트가 계획을 완료했다'는 것과 '애플리케이션이 테스트를 통과했다'는 것을 혼동해서는 안 됩니다.
작은 버전을 만들어 봅시다. 거대한 에이전트 프레임워크는 필요 없습니다. Playwright, LLM API, 그리고 우리가 검사할 수 있는 루프만 있으면 충분합니다.
실제로 구축하는 것
일반적인 Playwright 스크립트는 일련의 지침 목록입니다:
await page.getByRole('button', { name: 'Add notebook' }).click();
await page.getByRole('button', { name: 'Checkout' }).click();
우리의 에이전트는 이와 더 가까운 방식으로 작동할 것입니다:
- 접근성 스냅샷(accessibility snapshot)을 사용하여 현재 페이지를 관찰합니다.
- 언어 모델(language model)에게 정확히 하나의 다음 행동을 요청합니다.
- 해당 행동이 허용된 작은 작업 집합에 속하는지 검증합니다.
- Playwright로 실행합니다.
- 새로운 상태를 관찰하고 반복합니다.
- 코드에서 별도로 결과를 단언(assert)합니다.
중요한 단어는 별도로입니다. 에이전트는 인터페이스를 통과하는 경로를 찾는 데 유용합니다. 시스템이 작동하는지에 대한 최종 결정권은 가져서는 안 됩니다.
테스트를 위한 작은 앱
이 튜토리얼이 다른 사람의 웹사이트나 실제 결제 정보가 포함된 계정에 의존하지 않도록 로컬 체크아웃 페이지를 사용할 것입니다.
디렉터리를 생성하고 종속성을 설치하세요:
mkdir autonomous-playwright
cd autonomous-playwright
npm init -y
...
또한 OpenAI와 호환되는 채팅 완료(chat completions) 엔드포인트에 대한 API 키가 필요합니다. 아래 예제는 OpenAI의 엔드포인트를 사용하며 기본값으로 gpt-4.1-mini를 사용합니다. 환경 변수를 통해 다른 호환 모델을 선택할 수 있습니다.
index.html 생성:
<!doctype html>
<html lang="en">
<head>
...
이것은 의도적으로 지루하게 만들었습니다. 지루한 더미(fixtures)는 좋습니다. 만약 실험이 엉뚱하게 흘러가더라도, 문제가 에이전트에 있는지 스토어에 있는지를 알고 싶기 때문입니다.
터미널 하나에서 페이지 실행:
python3 -m http.server 4173
이제 http://127.0.0.1:4173에서 결제 페이지를 갖게 됩니다.
Playwright에 두뇌는 주되, 손은 매우 작게 하라
API 키를 .env 파일에 넣으세요:
OPENAI_API_KEY=your_api_key_here
MODEL=gpt-4.1-mini
이 파일을 Git에 커밋하지 마십시오.
이제 agent.mjs를 생성하세요:
import 'dotenv/config';
import { chromium } from 'playwright';
import fs from 'node:fs/promises';
...
터미널 두 개에서 실행:
node agent.mjs
성공적으로 실행되면, 모델은 노트북 추가, 펜 추가, SAVE5 입력, 적용, 결제 및 완료에 해당하는 액션을 선택할 것입니다. 정확한 순서와 모델 호출 횟수는 다를 수 있습니다. 마지막으로 스크립트는 세 가지 구체적인 사실을 확인합니다: 두 개의 장바구니 항목, $5 할인, 그리고 $10 영수증.
실패하더라도 agent-history.json과 trace.zip 파일을 얻게 되며; 실패 시에는 스크린샷도 시도됩니다. 트레이스는 다음 명령어로 엽니다:
npx playwright show-trace trace.zip
API에 대한 참고 사항: response_format: { type: 'json_object' }는 모델에게 보장된 유효한 브라우저 명령어 대신 JSON을 요청합니다. 이것이 validateAction()이 여전히 존재하는 이유입니다. 이 코드는 Playwright Test가 아닌 원시(raw) Playwright 라이브러리를 사용하므로, 트레이스에는 브라우저 활동은 포함되지만 Playwright Test의 단언(assertion) 메타데이터는 포함되지 않습니다.
아키텍처가 모델보다 더 중요하다
LLM에게 얼마나 적은 권한을 주었는지 주목하십시오.
이것은 **이름이 지정된 버튼(named button)**을 클릭하거나 **이름이 지정된 텍스트 상자(named textbox)**를 채울 수 있다는 의미입니다. 그게 전부입니다. 모델은 JavaScript를 실행하거나, 다른 웹사이트로 이동하거나, 파일을 삭제하거나, 내부 API를 호출하거나, 실패하는 단언(assertion)을 무시하기로 결정할 수는 없습니다.
또한 전체 DOM을 받지도 않습니다. 우리는 Playwright의 locator.ariaSnapshot()을 사용하여 접근 가능한 인터페이스의 읽기 쉬운 뷰를 전송합니다. 이 API는 Playwright 1.49 이상에서 사용 가능하며, 버튼, 링크, 양식(form), 페이지 텍스트에는 종종 충분합니다. 이것이 마법은 아닙니다. 레이블링이 제대로 되지 않은 컨트롤이나 시각적인 인터페이스만으로는 여전히 어렵습니다.
가장 많은 역할을 하는 세 가지 설계 선택 사항이 있습니다:
한 번에 하나의 동작. 만약 모델이 미리 12단계 계획을 제안하더라도, 두 번째 단계에서 대화 상자가 열렸기 때문에 네 번째 단계는 틀릴 수 있습니다. 모든 동작 후에 다시 관찰해야 합니다.
고정된 예산(fixed budget). 자율적이라는 것이 무한하다는 의미는 아닙니다. 저희의 결제 과정에는 12번의 동작이면 충분합니다. 프로덕션 앱의 경우, 각 시나리오에 시간 제한, 단계 제한 및 명확한 실패 상태를 제공해야 합니다.
독립적인 오라클(independent oracle). 우리는
저희의 작은 페이지에서는 모든 액션 후에 모델을 호출하는 것이 관리 가능합니다. 하지만 일반적인 엔터프라이즈 플로우라면 수치가 다르게 보입니다.
예를 들어, 25개의 브라우저 액션으로 구성된 회원가입 여정이 있다고 가정해 봅시다. 액션당 하나의 모델 호출이라면, 한 번의 시도에 25개의 모델 요청이 발생합니다. 이것을 네 개의 브라우저에서 실행하면 재시도를 제외하고 최대 100개 요청이 됩니다. 여기에 200가지 시나리오를 추가하면 지연 시간(latency), 컨텍스트 크기(context size), 모델 가용성, 그리고 비용에 신경 쓰기 시작합니다.
그리고 앱이 더 흥미로울수록 엣지 케이스(edge cases)는 더 나빠집니다:
- 버튼을 클릭하면 새 탭이 열리지만, 에이전트는 여전히 이전 탭만 관찰하고 있습니다.
- 결제 단계가 iframe 내부에서 실행됩니다.
- 파일 업로드는 숨겨진 입력 필드(hidden input)와 상호 작용해야 합니다.
- 이메일 인증 단계는 다른 시스템에 의존합니다.
- 모델이 그럴듯하지만 잘못된 '계속' 버튼을 선택합니다.
- 느린 UI 업데이트 때문에 성공적인 액션이 실패처럼 보이게 되어, 에이전트가 두 번 클릭합니다.
이것들은 Playwright에 대한 비판이 아닙니다. Playwright는 팝업, 프레임, 업로드, 대기(waiting)를 위한 API를 제공합니다. 하지만 이제 _사용자의 에이전트_는 이 모든 것에 대한 정책과 더불어 복구 로직(recovery logic), 보고서 작성(reporting), 재시도(retries), 그리고 인간이 결정을 수정할 수 있는 방법을 필요로 합니다.
물론 구축할 수는 있습니다. 문제는 그러한 플랫폼을 유지 관리하는 것이 팀 시간의 최선의 사용처인지 여부입니다.
CI에서 사용하기 전에 변경하고 싶은 것들
저는 위의 스크립트를 프로덕션 계정에 연결하지 않을 것입니다. 이것은 의도적으로 제한된 권한을 가진 학습 예제일 뿐, 강화된 보안 경계(hardened security boundary)가 아닙니다. 여기서 원본 출처 확인(origin check)은 단계 사이에 발생합니다. 실제 배포 환경에서는 네트워크 접근을 제한하고 격리된 테스트 계정을 사용해야 합니다.
진지하게 출시하기 전에, 몇 가지 변경 사항을 적용할 것입니다:
- 결정론적 어설션(deterministic assertions)과 픽스처(fixtures)를 사용하세요. LLM이 탐색을 담당하게 하고, 성공의 정의는 맡기지 마세요.
- 접근 가능한 환경을 제한하세요. 테스트 데이터, 제한된 자격 증명(credentials), 네트워크 제어가 적용된 일회용 브라우저 컨텍스트에서 실행하세요. 애플리케이션의 페이지 콘텐츠에는 모델을 목표로 하는 지침이 포함될 수 있습니다.
- 액션 히스토리(action history)를 증거가 아닌 데이터로 취급하세요. 트레이스(traces)와 스크린샷을 저장하되, 외부 모델에 페이지 콘텐츠를 전송하거나 아티팩트(artifacts)를 저장하기 전에 비밀 정보는 반드시 제거하세요.
- 탭(tabs), 프레임(frames), 다운로드(downloads) 처리를 명시적으로 하세요.
page가 항상 올바른 페이지이거나, 접근성 스냅샷(accessibility snapshots)이 모든 유용한 대상을 노출한다고 가정하지 마세요. - 성공적인 경로는 검토용으로 보관하세요. 에이전트가 안정적인 결제 흐름을 발견하면, 이를 일반 Playwright 테스트로 변환하는 것을 고려해 보세요. LLM에게 매일 아침 같은 다섯 번의 클릭을 재발견하도록 시키는 데는 아무런 이득이 없습니다.
마지막 항목은 특히 유용합니다. 자율적인 탐색(Autonomous discovery)과 결정론적 회귀 테스트(deterministic regression testing)는 약간 다른 문제를 해결합니다. 둘 중 하나만 선택할 필요는 없습니다.
자체 제작 에이전트가 아닌 것을 사용해야 할 때
브라우저 에이전트가 어떻게 작동하는지 배우고 있다면, 이 프로젝트를 진행해 볼 가치가 있습니다. 관찰/액션 루프(observation/action loop), 실패 모드, 그리고 좋은 테스트 오라클(test oracles)이 왜 중요한지 이해하게 될 것입니다.
만약 팀의 회귀 스위트(regression suite)를 책임지고 있다면, 계산 방식이 달라집니다.
이미 자율적인 브라우저 탐색과 테스트 생성 및 관리 실행을 패키징한 플랫폼들이 있습니다. Endtest가 그 예 중 하나입니다. 이 플랫폼의 Endtest Bot은 웹 애플리케이션을 탐색하고 테스트 시나리오를 생성하도록 설계되었으며, 더 광범위한 플랫폼이 웹 테스트 실행 및 관리를 담당합니다. 이는 대부분의 QA 팀이 자체 모델 프롬프트와 브라우저 오케스트레이션 코드(browser orchestration code)를 유지하는 것보다 훨씬 목표에 가깝습니다.
저는 로그인 데모가 아니라, iframe, 여러 탭, 업로드, 조건부 경로, 그리고 요소가 변경될 때 어떤 일이 발생하는 것과 같은 어려운 흐름(difficult flows)을 통해 그러한 제품을 평가할 것입니다. 또한 생성된 단계를 검사하고 편집하도록 요청하세요. 자율적인 테스트가 무엇을 했는지 이해할 수 없다면, 그것은 AI 기능으로 위장된 유지보수 문제입니다.
맞춤형 Playwright 에이전트는 여전히 모델, 인프라 또는 특이한 브라우저 동작에 대한 완전한 제어가 필요하다면 더 나은 선택일 수 있습니다. 당신은 플랫폼 비용을 엔지니어링 시간과 맞바꾸는 것입니다. 어느 쪽도 공짜가 아닙니다.
유지할 가치가 있는 부분
놀라울 정도로 적은 코드로 Playwright를 자율적으로 만들 수 있습니다. 브라우저 라이브러리 자체가 어려운 부분은 아니었습니다.
어려운 부분은 에이전트에게 무엇을 할 수 있도록 허용할지, 그것이 올바른 일을 했는지 어떻게 판단할지, 그리고 그렇지 않았을 때 어떤 증거를 가지고 있는지를 결정하는 것입니다.
하나의 비즈니스 핵심 흐름(business-critical flow)부터 시작하세요. 실패가 결정론적(deterministic)이 되도록 만드세요. 그런 다음 가장 화려한 데모를 만들 수 있는 곳이 아니라, 작업 시간을 절약해 주는 곳에 자율성을 추가하세요.
그것은 모든 것을 클릭할 수 있는 로봇을 만들고 그것이 괜찮다고 말할 때 신뢰하는 것보다 훨씬 더 나은 테스트 자동화 전략입니다.
참고 자료: Playwright 접근성 스냅샷, Playwright locator API, Playwright 트레이싱, Playwright 브라우저 컨텍스트.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기