Notice
Recent Posts
Recent Comments
반응형
관리 메뉴

HM소프트

AI 에이전트 기반 웹 QA 도구 개발기 (1) — 시니어 QA처럼 움직이는 자율 탐색 테스터 본문

기술 개발일지

AI 에이전트 기반 웹 QA 도구 개발기 (1) — 시니어 QA처럼 움직이는 자율 탐색 테스터

HM소프트 2026. 6. 18. 01:06
반응형

Playwright MCP 위에 시니어 QA의 판단력을 얹은 자율 웹 QA 플러그인을 만들었다. 페르소나 시스템 프롬프트, 6단계 작업 루프, 입력값 엣지 케이스 체크리스트, 증상-원인 진단 테이블, 그리고 파괴적 액션 강제 확인과 stale ref 금지 같은 자율 에이전트 안전장치를 코드 근거로 풀어낸다.

"코드는 멀쩡한데 왜 안 되죠"에서 출발

배포를 끝낸 뒤 누군가 "이거 잘 되는지 한번 봐줘"라고 부탁할 때마다 나는 매번 같은 한계에 부딪혔다. 코드 리뷰 도구는 코드를 읽고, 단위 테스트는 함수를 호출한다. 둘 다 정적 분석의 영역이다. 그런데 정작 사용자가 겪는 문제는 거기에 없다. 버튼 핸들러 코드가 흠잡을 데 없어도 그 버튼이 실제로 눌리는지는 별개고, 업로드 로직이 깔끔해도 운영 서버가 422를 뱉는 건 코드만 봐서는 절대 안 잡힌다.

"실제로 눌리는가"를 확인하려면 결국 누군가 브라우저를 열고 직접 만져봐야 했다. 그 "누군가"를 자동화하고 싶었다. 그래서 AI 코딩 에이전트 위에서 동작하는 자율 탐색형 웹 QA 플러그인을 만들었다. 실행 중인 웹앱에 진짜 브라우저로 접속해, 사람처럼 클릭·입력·업로드를 시도하고, 깨진 곳을 찾아 원인까지 추적하는 도구다. 브라우저 조작 자체는 Playwright MCP에 맡기고, 그 위에 "무엇을 시도할지 스스로 판단하는" 층을 올렸다.

이 글은 그 첫 번째 개발기다. 왜 도구가 아니라 판단력을 만들어야 했는지, 에이전트 페르소나를 어떻게 설계했는지, 6단계 작업 루프와 QA 체크리스트는 어떤 구조인지, 그리고 자율 에이전트에 반드시 필요한 안전장치까지 코드 근거와 함께 풀어 본다.

도구가 아니라 판단력이 부족했다

처음에는 단순하게 생각했다. Playwright MCP가 이미 브라우저를 다 조작해 주니, 그냥 "이 사이트 테스트해줘"라고 시키면 되지 않을까. 막상 시켜 보니 결과는 실망스러웠다. MCP는 충실한 도구다. "이 버튼 눌러봐"라고 하면 정확히 눌러 준다. 문제는 무엇을 눌러야 할지, 어떤 입력을 넣어 봐야 할지를 사람이 일일이 지시해야 한다는 점이었다. 지시하는 사람이 테스트 케이스를 떠올리지 못하면 도구는 아무것도 못 찾는다.

그래서 도구 위에 올릴 것은 더 많은 기능이 아니라 판단력이라고 결론 내렸다. 정상 시나리오를 한 번 통과시키고 "잘 됩니다"로 끝내는 건 누구나 한다. 진짜 QA의 가치는 개발자가 가장 자신 있게 "여기는 됩니다"라고 말한 곳에서 버그를 찾아내는 데 있다. 그 본능을 코드가 아니라 시스템 프롬프트로 이식하기로 했다.

구조를 그림으로 그리면 핵심이 명확해진다. 사용자와 브라우저 사이에 판단 층이 끼어 있고, 이 층이 화면을 보고 다음 행동을 스스로 정한다. 도구 층(MCP)은 그 결정을 실행만 한다. 두 층을 분리한 덕분에, 브라우저 제어 방식이 바뀌어도 판단력은 그대로 재사용된다.

페르소나를 시스템 프롬프트로 이식하기

에이전트의 정체성은 마크다운 한 장으로 정의된다. 핵심은 "10년차 시니어 QA 엔지니어"라는 페르소나와, 그 사람의 직업적 본능을 명문화한 행동 원칙이다. 추상적인 "잘 테스트해"가 아니라, 베테랑 테스터가 머릿속으로 굴리는 휴리스틱을 그대로 문장으로 박았다.

이 문장들이 실제로 차이를 만들었다. "정상은 1번, 비정상은 끈질기게"라는 한 줄이 들어가자 에이전트의 행동이 눈에 띄게 바뀌었다. 폼을 만나면 정상 입력 한 번 넣고 끝내는 게 아니라, 빈 칸·특수문자·아주 긴 값을 먼저 의심하기 시작했다. 페르소나 한 장이 곧 테스트 전략이 된 셈이다.

또 한 가지 신경 쓴 건 도구 권한의 제한이다. 이 에이전트에는 브라우저 탐색·관찰·입력 계열 도구와 로컬 코드 추적용 도구(파일 읽기·검색)만 부여했다. 판단의 본질에 필요한 것만 주고, 그 외 권한은 닫았다. 자율 에이전트일수록 "할 수 있는 일"의 경계를 좁게 잡는 게 안전하다.

6단계 작업 루프 — 정찰에서 보고까지

페르소나만으로는 부족하다. 베테랑이라도 일하는 순서가 있어야 한다. 그래서 진단 과정을 6단계 루프로 명시했다. 매번 같은 순서를 밟게 해서, 에이전트가 화면 앞에서 길을 잃지 않도록 했다.

1단계 정찰에서 의외로 중요한 결정이 하나 있었다. 화면 인식을 스크린샷이 아니라 접근성 트리(accessibility tree) 로 한다는 것. 스크린샷은 무겁고, 무엇보다 "버튼이 어디 있는지"를 좌표로만 알려 준다. 접근성 트리는 요소의 역할·라벨·구조를 텍스트로 주기 때문에 더 정확하고 가볍다. 동시에 페이지 로드 시점의 콘솔 에러와 4xx·5xx 네트워크 응답을 같이 수집한다. 화면이 멀쩡해 보여도 콘솔이 빨갛게 차 있으면 그건 이슈라는 원칙이 1단계부터 작동한다.

2단계 인벤토리에서는 화면의 인터랙티브 요소를 목록화하면서 위험도로 분류한다. 이게 자율 에이전트의 핵심 안전 설계다.

각 요소를 위험도로 분류:
- 안전(safe):        그냥 눌러봐도 되는 것 (탐색, 검색, 보기)
- 부수효과(side-effect): 데이터를 바꾸거나 외부 호출 (전송, 결제, 가입, 삭제)
- 인증벽(auth-gate):  로그인이 있어야 진행 가능

탐색하기 전에 "이 버튼을 눌러도 되는가"를 먼저 판정하게 한 것이다. safe는 마음껏 누르고, side-effect는 멈춰서 사람에게 묻고, auth-gate는 계정 정보가 없으면 우회 시도까지만 한다. 이 분류가 없으면 에이전트가 신나게 결제 버튼을 눌러 버리는 사고가 난다.

탐색 단계 — "어떻게 깨질까"를 먼저 묻는다

3단계 탐색이 이 도구의 심장이다. 여기서 에이전트는 정상 케이스를 빠르게 한 번 확인한 뒤, 곧바로 "이 입력 필드가 어떻게 깨질 수 있는가"를 가설로 세우고 검증에 들어간다. 그리고 모든 액션을 행동 전 한 줄 메모 → 실행 → 변화 관찰 → 판정의 사이클로 돌게 했다.

"기대 결과를 먼저 적게" 한 것이 핵심이다. 기대를 명시하지 않으면 무엇이 잘못됐는지 판정할 기준이 없다. 사람 테스터가 "이걸 누르면 모달이 떠야 정상"이라고 머릿속에 그리고 누르는 것과 같다. 그 기대를 글로 박제하니, 에이전트가 "변화는 있었지만 기대와 다름 → 부분 동작"이라는 미묘한 판정까지 해 낸다.

탐색이 무한정 길어지지 않도록 종료 조건도 명시했다. 설정된 탐색 깊이에 도달하거나, 핵심 플로우 1개와 사이드 플로우 2~3개를 검증했거나, 같은 종류의 에러를 3번 이상 만났을 때(패턴이 잡혔으니 더 진행할 이유가 없다) 멈춘다. 자율 에이전트에서 "언제 멈출지"는 "무엇을 할지"만큼 중요하다. 종료 조건이 없으면 토큰만 태우며 같은 자리를 맴돈다.

QA 체크리스트 — 베테랑의 머릿속을 표로

페르소나와 루프가 "어떻게 일하는가"라면, QA 체크리스트는 "무엇을 시도하는가"의 구체 목록이다. 이건 단순 참고 자료가 아니라 "실제로 시도해야 하는 항목"으로 못 박았다. 입력값 엣지 케이스, 상태·권한 우회, 다중 뷰포트, 인터랙션 패턴, 접근성 신호, "사용자가 이상하게 행동하는" 시나리오까지 6개 묶음으로 정리했다.

특히 공들인 건 입력값 엣지 케이스다. 모든 텍스트 필드에 최소 2~3개는 비정상 입력을 넣어 보게 했다.

이 한 묶음이 실제로 가장 많은 버그를 잡았다. 특수문자 입력으로 XSS 미방어를 드러내고, 1,000자 입력으로 레이아웃이 무너지는 화면을 찾아냈다.

또 하나 강하게 박은 원칙은 "반드시 모바일도 본다" 였다. 데스크탑만 보고 끝내면 절반밖에 못 본 것이라고 명시하고, 모바일·태블릿·데스크탑 세 뷰포트에서 메인 CTA가 화면 안에 들어오는지, 가로 스크롤이 새지 않는지, 터치 타깃이 충분히 큰지를 확인하게 했다. 권한 우회 항목에서는 "비로그인 상태로 보호된 URL 직접 진입", "URL의 ID를 타인 ID로 바꿔 인가 체크 확인" 같은, 클라이언트만 막고 서버는 뚫리는 흔한 패턴을 정조준했다.

증상에서 원인으로 — 진단 테이블

버그를 찾는 것과 원인을 짚는 것은 다른 일이다. "버튼이 안 눌려요"는 증상이지 원인이 아니다. 그래서 4단계 진단에는 증상 → 1차 의심 → 검증 방법을 매핑한 테이블을 넣어, 추측이 아니라 증거 기반으로 원인을 좁히게 했다.

이 테이블이 진단의 품질을 한 단계 끌어올렸다. 예를 들어 "폼 제출 시 4xx"를 만나면 에이전트는 막연히 추측하지 않고 곧장 요청 본문과 헤더를 들여다본다. 클라이언트 검증은 통과했는데 서버가 막은 것인지, 인증 토큰이 빠진 것인지를 증거로 가른다. 그리고 소스가 로컬에 있으면 5단계로 넘어가, 에러 메시지·API 경로·컴포넌트 텍스트로 코드를 검색해 파일경로:라인 형태로 수정 위치까지 가리킨다. 소스가 없으면 거기서 멈추고 진단까지만 보고한다 — 없는 코드를 지어내지 않는 것도 신뢰의 일부다.

마지막 6단계 보고는 형식을 고정했다. 한 줄 요약(정상/부분동작/실패 개수), 시도한 시나리오, 이슈별 증상·재현·증거·추정 원인·수정 제안, 그리고 검증하지 못한 영역까지. 특히 "못 한 것"을 솔직히 적게 한 게 중요하다. 로그인을 못 해서 못 본 부분, 부수효과 때문에 보류한 부분을 명시해야 사용자가 나머지를 직접 챙긴다.

자율 에이전트의 안전장치 — 멈출 줄 아는 자동화

사람처럼 움직이는 에이전트라서 생기는 고유한 위험이 있다. 진짜로 결제 버튼을 누르거나, 진짜로 회원가입을 해 버리거나, 삭제 확인 다이얼로그의 "예"를 눌러 버리면 곤란하다. 그래서 파괴적 액션은 설정과 무관하게 항상 사용자 확인을 거치도록 못 박았다.

설정 가능한 값(탐색 깊이, 액션 타임아웃, 헤드리스 여부, 리포트 저장 경로)은 사용자 설정으로 열어 뒀지만, 파괴적 액션 허용 플래그가 켜져 있어도 결제·가입·삭제·메일 발송은 한 번 더 묻는다. 결제 폼은 끝까지 가지 않고 "결제 직전 단계까지 도달, 카드정보 입력은 생략"처럼 경계선까지만 간다. 회원가입은 일회용 이메일 패턴을 제안한 뒤 동의를 받는다.

기본값도 의도적으로 보수적으로 잡았다. 브라우저를 창을 띄운 채(헤드리스 끔) 도는 걸 기본으로 했는데, 신뢰가 쌓이기 전까지는 "보이는 자동화"가 맞다고 판단했기 때문이다. 에이전트가 지금 무슨 액션을 하는지 사람이 눈으로 같이 보면, 사고를 막기도 쉽고 도구를 믿게 되기도 쉽다. 1번 원칙("추측한 ref를 쓰지 말 것")과 6번 원칙("이슈 보고 전 재현 확인")도 자율 에이전트 특유의 환각·일회성 오보를 막는 안전장치다. 스냅샷을 다시 안 찍었는데 페이지가 바뀌었다면 이전 ref는 더 이상 유효하지 않다 — 이런 stale 참조가 거짓 버그 리포트의 단골 원인이라, 아예 원칙으로 박았다.

패키징과의 씨름 — 자동 발견에 맡겨라

기능보다 의외로 오래 잡아먹은 건 플러그인 패키징이었다. 커밋 로그가 그 시행착오를 그대로 보여 준다. 첫 릴리스 후 이름을 한 번 갈았고, 마켓플레이스 등록에서 두 번 막혔다.

첫 번째는 마켓플레이스 매니페스트의 source 필드였다. 로컬 경로를 그대로 적었더니 "지원하지 않는 source 타입"이라며 거부됐고, 스키마가 요구하는 ./ 로 시작하는 상대경로 패턴으로 고쳐야 통과했다. 두 번째가 더 흥미로웠다. 플러그인 매니페스트에 commands·agents 필드를 명시했더니 agents: Invalid input 오류가 났다. 알고 보니 그 필드들은 "디렉터리 포인터"가 아니라 "추가 개별 .md 파일 경로"로 해석되는 스키마였다. 그런데 표준 commands/·agents/ 폴더는 이미 자동 발견되고 있었다. 즉 자동 발견되는 걸 필드로 또 선언해서 충돌이 난 것이다. 해결은 두 필드를 매니페스트에서 들어내는 것이었다.

{
  "name": "<플러그인-이름>",
  "version": "0.1.0",
  "description": "실행 중인 웹앱을 사람처럼 자율 탐색하며 동작을 검증하는 플러그인.",
  "keywords": ["e2e", "qa", "playwright", "diagnostics", "auto-explore"],
  "license": "MIT",
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": ["-y", "@playwright/mcp@latest"],
      "env": { "PLAYWRIGHT_HEADLESS": "false" }
    }
  }
}

매니페스트에는 메타 정보와 MCP 서버 자동 등록만 남기고, 나머지는 디렉터리 구조가 말하게 했다. 커맨드는 commands/ 폴더에, 에이전트는 agents/ 폴더에 두면 끝이다. Playwright MCP도 npx로 받아지도록 선언해 둬서, 플러그인을 깔면 별도 설치 없이 브라우저 제어 도구가 같이 따라온다. 여기서 다시 확인한 교훈은 단순하다 — 설정은 줄일수록 덜 깨진다. 자동 발견 규약이 있는데 굳이 명시적으로 또 선언하면, 두 곳이 어긋나는 순간 깨진다.

사용자가 조정할 수 있는 값은 별도의 사용자 설정 스키마로 노출했다. 헤드리스 여부, 액션 타임아웃, 리포트 저장 경로, 탐색 최대 깊이, 그리고 파괴적 액션 허용 여부. 각 항목에 타입·기본값·한국어 설명을 달아, 플러그인 설정 화면에서 그대로 읽히게 했다. 안전에 직결되는 기본값(헤드리스 끔, 파괴적 액션 불허)은 가장 보수적인 쪽으로 잡았다.

정리하며

이번 1편에서 만든 것을 한 문장으로 줄이면, 브라우저 조작 도구 위에 시니어 QA의 판단력을 얹은 자율 탐색 테스터다. 정리하면 이렇다.

  • 정적 분석이 못 잡는 "실제로 눌리는가"를 확인하려면 실행 중인 앱을 진짜 브라우저로 만지는 수밖에 없다. 부족했던 건 도구가 아니라 무엇을 시도할지 정하는 판단력이었다.
  • 그 판단력을 페르소나 + 6단계 루프 + QA 체크리스트라는 시스템 프롬프트로 이식했다. "정상은 1번, 비정상은 끈질기게", "기대 결과를 먼저 적기" 같은 한 줄 원칙이 실제 행동을 바꿨다.
  • 진단은 증상-원인 테이블로 추측이 아닌 증거 기반으로 좁히고, 소스가 있으면 파일:라인까지, 없으면 진단까지만 — 모르는 건 지어내지 않는다.
  • 자율 에이전트에는 요소 위험도 분류, 파괴적 액션 강제 확인, stale ref 금지, 재현 자기검증 같은 안전장치가 기능만큼 중요하다. 멈출 줄 아는 자동화가 신뢰받는다.
  • 플러그인 패키징은 자동 발견 규약에 맡기고 최소한만 선언하는 게 덜 깨진다. 중복 선언은 어긋나는 순간 사고다.

다음 편에서는 이 도구를 실제 사이트에 풀어 두고 어떤 버그들이 잡혔는지, 그리고 진단 결과를 곧바로 코드 수정으로 잇는 흐름을 다룬다.

반응형