AI가 클릭하게 하고, AI에게 판단을 맡기지 마세요
요약
본 글은 AI 에이전트 기반 E2E(End-to-End) 테스트 설계에 대한 질문을 다룹니다. 모델의 판단과 코드로 구현된 검증 단계를 혼합하는 것이 핵심이며, 특히 '리플레이 캐시' 기능을 통해 이전 실행 기록을 재현하여 안정성과 효율성을 높이는 방법을 제시합니다.
핵심 포인트
- AI 에이전트 테스트는 목표 설정(agent.act)과 화면 판단(agent.assert)에 모델을 사용하고, 검증은 코드로 남겨야 합니다.
- TesterArmy의 e2e 프레임워크는 웹/iOS/Android를 지원하며, 3가지 단계 유형을 혼합합니다.
- 리플레이 캐시 기능을 통해 실제 모델 호출 없이 이전 실행 기록을 재현하여 테스트 속도와 안정성을 확보할 수 있습니다.
- 실제 버그(예: 고장 난 버튼)가 발생했을 때, 에이전트가 이를 포착하고 수정하는 과정을 보여줍니다.
저는 작은 할 일(todo) 페이지의 '추가' 버튼을 고장 내고, 모델이 이동할 스크립트 더미를 사용해 에이전트 테스트를 실행했습니다.
에이전트 단계는 녹색으로 돌아왔습니다. 하지만 그 테스트는 한 줄 뒤에서 여전히 실패했는데, 평범한 로케이터가 '1 open'을 예상했지만 '0 open'을 읽었기 때문입니다.
이 한 줄의 차이가 AI 엔드투엔드(e2e) 테스트 전체 디자인 질문입니다: 어떤 단계에 모델을 적용하고, 어떤 단계는 코드로 남겨둘 것인가?
영어로 목표를 설정하고, 코드로 검증하기
TesterArmy에서 만든 e2e는 10월 5일 GitHub 트렌딩 목록에서 #2를 차지하며 하루에 1,398개의 스타를 받았습니다. 이것은 웹(Playwright 기반), iOS 및 Android용 Apache-2.0 TypeScript 테스트 러너입니다. 이 테스트는 세 가지 종류의 단계를 혼합합니다. agent.act는 일반 영어로 목표를 제시하여 앱을 구동하는 모델에 전달합니다. agent.assert는 모델에게 화면을 판단하도록 요청합니다. screen과 expect는 절대 모델을 호출하지 않는 일반 로케이터입니다.
제가 제 페이지에 대해 작성한 테스트는 다음과 같습니다:
test('agent step: add a todo', async ({ app, agent, screen }) => {
await app.open('/');
await agent.act('add a todo named {title}', { params: { title: 'Buy milk' } });
...
지금까지는 새로운 것이 없습니다. 충분한 AI 테스트 도구들이 첫 번째 실행을 수행할 수 있습니다. 흥미로운 부분은 두 번째 실행입니다.
두 번째 실행이 제품이다
회사가 6월에 HN(Hacker News)에서 호스팅된 제품을 출시했을 때, poisonborz로부터 가장 날카로운 질문이 나왔습니다: 직접 작성한 E2E 테스트는 '결정론적이고 실행 비용이 저렴'하므로, 에이전트가 어떻게 경쟁할 수 있는가? 이 오픈 소스 프레임워크는 리플레이 캐시(replay cache)로 답합니다.
나중에 검증된 agent.act는 기록됩니다: 동작들, 역할 및 이름에 의한 제어 내용, 그리고 그 후에 화면이 어떤 모습이어야 하는지 말입니다. 다음 실행에서 러너는 모델 호출 없이 이 기록을 재현(replay)합니다. 만약 컨트롤이 사라졌거나 최종 상태가 일치하지 않으면, 모델은 앱이 있는 곳부터 다시 시작합니다.
저는 모델 없이 이것을 보고 싶었습니다. 이 게시물에 대한 API 키가 없었기 때문에 문서에서 제공하는 커스텀 실행기 후크(custom executor hook)를 사용해서 17줄짜리 스크립트 대체품(scripted stand-in)을 작성했습니다. 이 코드는 편집된 화면 텍스트에서 텍스트 상자와 버튼을 찾고, 실행기가 체크한 동작들을 타이핑하고 탭하며, 모든 호출을 파일에 기록합니다.
- 실행 1회차:
Cache 1 missed, 실행기(executor)가 한 번 호출됨. - 실행 2회차와 3회차:
Cache 1 replayed, 실행기는 전혀 호출되지 않음.
이 기록은 1.4 KB 크기의 JSON 파일입니다. 여기에는 두 가지 동작(`텍스트 상자
내 고장 난 버튼은 두 번째 이유를 보여줍니다. 리플레이가 end-mismatch에 부딪히자, 제가 대신 나섰고, 그 죽은 버튼을 다시 탭하여 성공했다고 보고했습니다. 이는 어떤 에이전트가 자신의 작업을 평가하는 방식과 같습니다. expect가 이를 포착했습니다. issue #884에서 한 사용자가 의사 결정 모델 실행기(decision-model executor)를 사용하여 실제 버전을 테스트했을 때, 5번의 실행 중 4번에서 탭 단계가 클릭 없이 완료되었다고 주장했으며, "오직 우리의 toHaveURL 단언문(assertion)만이 이를 포착했습니다."라고 했습니다.
실행이 빨간색으로 변하는 세 가지 방법
결정론적(Determinism)이라는 것은 한 가지 것이 아니며, e2e는 종료 코드별로 실패를 분리합니다. 저는 그중 세 가지에 부딪혔습니다.
- 제품 자체의 문제. 내 죽은 버튼: 종료 코드 1,
ASSERTION_FAILED, 예상값 "1 open", 관찰값 "0 open"이며, 화면 덤프가 첨부되었습니다. - 모델 접근 불가. 제 첫 번째 실행은 키가 없는 기본 설정(default config)을 사용했습니다. 로케이터 테스트는 통과했습니다. 에이전트 테스트는
MODEL_PROVIDER_FAILED로 실패했으며, 이는infrastructure카테고리 아래 종료 코드 3으로 분류되었습니다. 죽은 키는 여러분의 앱에서 버그처럼 보이지 않으며, 이것이 CI가 정확히 필요로 하는 것입니다. - 녹화본의 구식화. "Add"를 "Create"로 이름을 바꿨습니다.
--strict-cache아래에서 실행은REPLAY_STALE과 종료 코드 2로 실패했으며, 실행기(executor)는 전혀 호출되지 않았습니다.
플래그가 없으면 동일한 이름 변경도 통과했습니다. 리플레이는 텍스트를 입력했지만, 이전 버튼을 찾지 못했고, 작업을 넘겼습니다 (Cache 1 handed off). 이 단계는 약 0.2초 대신 14.6초가 걸렸고, 문서에서는 누락된 컨트롤이 나타나는 데 15초가 걸린다고 설명합니다. 문서는 이러한 기본 설정에 대해 스스로 경고합니다: 고장 난 녹화본은 누군가 다시 녹화하기 전까지 모든 빌드에서 라이브 모델 실행으로 조용히 흘러 들어갑니다. CI에서는 첫날부터 strict 모드를 켜야 합니다.
비용이 드는 기본 설정들
어떤 모델을 선택하는지보다 두 가지 설정이 더 중요합니다.
첫째, e2e init은 .e2e/cache/를 .gitignore에 추가하며, 캐시는 CI에서 기본적으로 읽기 전용(read-only)입니다. 이 둘이 합쳐져서, CI는 녹화본 없이 시작하고 아무것도 저장할 수 없으므로, 캐시 폴더를 커밋하기 전까지 모든 액트가 매번 풀 리퀘스트(pull request)에서 라이브로 실행됩니다. 문서는 방법을 설명합니다. 이는 선택 사항입니다.
둘째, CI는 기본적으로 실패한 테스트를 한 번 재시도하며, 재시는 절대 재생하지 않습니다. 이것이 정직한 호출인 이유는, 재현된 재시도는 단지 실패했던 것을 반복할 뿐이기 때문입니다. 또한 이는 불안정한(flaky) 테스트가 모델 호출에서 단순히 분만 비용을 지불하는 것이 아니라 두 번째 시도에 대한 비용까지 지불한다는 의미이기도 합니다.
다음으로 아직 열려 있는 issue #850가 있습니다. 핸드오프(hand-off) 이후, 기록(recording)이 저장하는 대기 시간은 실행마다 증가하여 120초에 도달할 수 있습니다. 리포터는 거기에 고정된 26개 항목 중 8개를 발견했으며, 두 줄짜리 패치로 캐시된 스위트 시간을 6분 51초에서 1분 35초로 단축했습니다.
제가 실행한 것과 실행하지 않은 것
저는 임시 폴더에서 Node 24.15를 사용하여 npx e2e init --yes를 실행하고, 이어서 npm install을 했습니다(패키지 40개, 72 MB). Python으로 정적 페이지를 제공했고, Chromium은 기존 Playwright 캐시에서 가져왔습니다. 로케이터 테스트는 --repeat-each 20으로 연속 20번 실행되었고, 6초 만에 통과했습니다. 에이전트(agent) 테스트는 실패 여부를 확인하기 위해 키 없이 한 번 실행한 후, 스크립팅된 실행기(scripted executor)를 사용하여 캐시 기록, 재생, 오래됨(stale)을 관찰하고 회귀(regression)를 포착하도록 했습니다. 또한 E2E_TELEMETRY_DISABLED=1로 설정했습니다. CLI는 옵트아웃하지 않는 한 익명 사용량 데이터를 전송합니다.
저는 실제 모델을 실행하지 않았습니다. 따라서 내장 에이전트가 목표를 얼마나 자주 완료하는지, 라이브 액트(live act)가 얼마나 불안정한지, 또는 단계 하나가 실제로 얼마의 비용이 드는지에 대한 저만의 수치는 없습니다. 위의 달러 금액은 문서의 샘플입니다. 이 프로젝트는 또한 1.0 미만이며, 0.17.0 버전에서 10월 4일에 두 가지 중단(breaking) 변경 사항이 배포되었습니다.
제가 AI를 배치할 곳
제가 이것을 채택한다면, 규칙은 네 줄에 들어맞습니다:
- 어딘가로 이동하기 위한
agent.act: 로그인하거나, 마법사(wizard)를 작성하거나, 테스트 대상 화면에 도달합니다. - 모든 액트 직후의
expect. 이것이 기록을 존재하게 만들고, 에이전트가 자신의 숙제를 채점하도록 만듭니다. agent.assert는 오직 정답이 문자열(string)이 아닌 의미일 때만 사용합니다.- CI에서
--strict-cache를 사용하고 캐시 폴더를 커밋합니다.
귀하의 스위트에서 모델만이 수행할 수 있는 검사는 무엇이며, 습관 때문에 모델에게 맡겨왔던 검사는 무엇입니까?
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기