에이전트에게 눈을 달아준다는 것 — 시각적 검증의 병목
어느날 에이전트에게 SVG 다이어그램을 만들어달라고 했다. 코드는 깔끔했다. 타입 에러도 없었고 빌드도 한 번에 통과했다. “잘 됐네” 싶어서 브라우저를 열었는데, 텍스트가 박스를 삐져나가 있었고 모바일에서는 그림이 너무 작아서 읽을 수가 없었다.
다음 날은 코드 블록 스타일을 수정해달라고 했다. 에이전트는 CSS를 고치고 빌드를 통과시켰다. 데스크톱에서는 괜찮아 보였는데, 모바일에서 열어보니 코드 블록의 마진이 사라져서 화면 끝까지 꽉 차 있었다. 다시 수정을 요청했다. 또 빌드 통과. 이번에는 마진은 생겼는데 오버플로우가 잘려서 긴 코드 라인을 볼 수 없었다. 이런 사이클이 두세 번 반복됐다.
한두 번이면 우연이겠지만, 반복되면 패턴이 보인다. 에이전트는 코드를 완벽히 읽는다. 문법 오류, 타입 불일치, 누락된 import — 텍스트 레벨의 문제라면 사람보다 정확하게 잡아낸다. 하지만 렌더링 결과는 볼 수 없다. CSS 한 줄을 바꿨을 뿐인데 3개 페이지의 레이아웃이 무너지는 현실. 빌드 통과와 시각적 정상은 전혀 다른 이야기다.
지금 AI 코딩 에이전트는 멀티 에이전트 조합이 트렌드다. Garry Tan(YC CEO)의 gstack이 대표적인데, AI를 “하나의 만능 도구”로 쓰지 않고 역할별 전문가로 분리하는 접근이다. CEO 모드(제품 전략), Eng Manager 모드(아키텍처 리뷰), Staff Engineer 모드(코드 리뷰), QA Engineer 모드(테스트)를 슬래시 커맨드로 전환하면서 하나의 Claude Code 인스턴스를 가상의 팀처럼 운영한다. 최대 12개 병렬 워커를 동시에 돌리면서 60일 동안 60만 줄 이상의 프로덕션 코드를 작성했다.
멀티 에이전트 조합도 일반화되고 있다. Claude Code는 복잡한 아키텍처 작업, Cursor는 인라인 편집, Copilot은 자동완성 — 이렇게 도구마다 강점에 맞춰 배치하는 방식이 6개월 이상 실전에서 운영되고 있다.
그런데 이 모든 도구의 공통 약점이 바로 시각 검증이다. 코드를 쓰는 능력은 비약적으로 발전했지만, 그 코드가 화면에서 어떻게 보이는지 확인하는 능력은 여전히 사람 몫이다. gstack이 주목받는 진짜 이유도 코드량이 아니라 /browse 명령 — 에이전트에게 실제 브라우저를 연결해서 렌더링 결과를 직접 피드백하게 만든 것, 즉 에이전트에게 “눈”을 달아준 것이다.
에이전트에게 없는 감각
에이전트의 전형적인 검증 루프는 코드 생성 → 빌드 → 타입체크 → 테스트 → 완료다. 여기에 “화면에서 실제로 보기”는 빠져 있다. 빌드가 성공했으니 끝이라고 판단한다. 하지만 사람이라면 당연히 브라우저를 열어 확인할 것이다.
에이전트에게 “모바일에서 코드 블록 마진 수정해줘”라고 하면, CSS를 고치고 빌드를 돌려서 성공하면 끝이라고 보고한다. 하지만 실제 모바일 뷰포트에서 확인해보면 여전히 문제가 있을 수 있다. margin: 0 16px을 넣었는데 부모 컨테이너의 overflow: hidden이 내용을 자르고 있다거나, 미디어 쿼리 순서가 엉켜서 데스크톱 스타일이 모바일을 덮어쓰고 있다거나. 에이전트의 “완료”와 사용자의 “완료”는 다른 기준이다.
기존 E2E 테스트도 이 문제를 해결하지 못한다. getByText('Submit')이 DOM에 존재하는지는 확인하지만, 그 버튼이 다른 요소에 가려져 클릭할 수 없는 상태인지는 모른다. expect(element).toBeVisible()조차도 CSS의 overflow: hidden으로 잘린 콘텐츠까지 잡아내지는 못한다. DOM 기반 테스트의 한계다.
에이전트가 생성하는 코드의 비율이 높아질수록 이 시각 검증 간극은 비례해서 커진다. 사람이 직접 짠 코드라면 짜면서 중간중간 브라우저를 확인한다. 하지만 에이전트가 한 번에 10개 파일을 수정하면, 사람이 그 결과를 하나하나 확인할 여력이 없다. 에이전트 생산성이 올라갈수록 검증 병목도 함께 올라가는 역설이다.
에이전트에게 수정을 맡기고 렌더링 결과를 확인하지 않은 채 커밋하는 것은, 코드리뷰 없이 머지하는 것과 같다. 코드는 맞는데 결과가 틀린 상태가 프로덕션에 나간다.
선두 개발자들의 접근법
이 문제에 대한 접근은 크게 세 가지로 나뉜다.
| 접근 | 핵심 도구 | 원리 | |
|---|---|---|---|
| 1 | 에이전트에 브라우저 연결 | Playwright, Puppeteer | 실제 렌더링 결과를 스크린샷/DOM으로 피드백 |
| 2 | Visual Regression Testing | Applitools, Percy | 베이스라인 스크린샷 대비 변경점 자동 탐지 |
| 3 | Intent 기반 로케이터 | Momentic | 자연어로 UI 요소를 찾아 셀렉터 변경에 강건 |
에이전트에게 진짜 브라우저를 준다
gstack의 /browse 명령은 Playwright 기반 Chromium 데몬을 활용한다. 응답 시간 100ms. diff가 생기면 영향받는 라우트를 자동으로 식별하고, 해당 페이지를 실제 브라우저에서 렌더링한 뒤 스크린샷을 에이전트에게 돌려준다.
핵심은 단일 뷰포트가 아니라는 점이다. /qa 에이전트가 git diff를 분석해서 변경이 영향을 미치는 라우트 목록을 추출한다. 그리고 /browse가 각 라우트를 데스크톱(1440px), 태블릿(768px), 모바일(375px) 세 가지 뷰포트에서 캡처한다. 사람이 에디터와 브라우저를 Alt-Tab으로 오가며 확인하는 것과 본질적으로 같은 행위를 자동화한 것이다.
에이전트가 직접 보고, 문제를 발견하고, 고치고, 다시 확인하는 루프가 만들어진다. 사람이 개입하지 않아도 셀프 교정이 가능해진다. 여기에 시각 회귀 테스트까지 자동 생성하면 같은 문제가 재발하지 않는다. “눈이 있는 에이전트”와 “눈이 없는 에이전트”의 차이는 생각보다 크다.
나도 비슷한 문제를 다른 각도에서 풀고 있다. 병원 EMR SaaS를 개발하면서 만든 파이프라인인데, gstack이 런타임에 에이전트가 브라우저를 조작하는 방식이라면, 내 쪽은 빌드타임에 코드를 정적 분석해서 테스트를 미리 생성하는 방식이다.
구조는 이렇다. ts-morph로 TSX 파일을 파싱해서 Page → Modal → Button → Input 같은 컴포넌트 계층 관계를 그래프 JSON으로 추출한다. 이 그래프를 Claude API에 넘기면 테스트 케이스 스키마가 생성되고, 그걸 다시 Playwright 스펙으로 변환한다. “수납 확인 모달에서 결제 버튼을 클릭하면 영수증 페이지로 이동한다” 같은 시나리오가 코드 변경에 맞춰 자동으로 만들어지는 것이다.
TSX 파싱 (ts-morph) → 컴포넌트 그래프 JSON → Claude API (TC 스키마) → Playwright 스펙
gstack의 /qa가 git diff에서 영향받는 라우트를 찾는 것처럼, 이 파이프라인도 변경된 컴포넌트에서 영향받는 페이지와 플로우를 그래프 관계로 추적한다. 다만 접근이 다르다. gstack은 이미 렌더링된 화면을 에이전트가 직접 보는 것이고, 이 파이프라인은 렌더링 전에 “무엇을 테스트해야 하는가”를 코드 구조에서 미리 도출하는 것이다. 런타임 시각과 빌드타임 분석, 둘 다 필요하다. 이상적으로는 빌드타임에 테스트를 생성하고, 런타임에 그 테스트를 브라우저에서 실행하면서 스크린샷까지 캡처하는 이중 루프가 완성된다.
이 파이프라인의 토대가 되는 것이 data-testid 기반 시맨틱 레이어다. 모든 주요 UI 요소에 의미 있는 testid를 부여해두면, DOM 구조가 바뀌어도 테스트가 깨지지 않는 안정적인 접점이 생긴다. 구현 세부사항은 이전 글에서 다뤘으니 여기서는 시각 검증 도구들과의 비교에 집중하자.
스크린샷 비교로 시각 회귀 잡기
Applitools Visual AI나 Percy 같은 도구는 PR마다 변경 전후의 스크린샷을 비교한다. 의도치 않은 시각 변경을 자동으로 잡아내는 것이 핵심이다.
순수 픽셀 비교는 false positive가 너무 많다. 폰트 렌더링 차이, 안티앨리어싱, 서브픽셀 렌더링 등으로 실제 문제가 아닌데 경고가 울린다. 그래서 Applitools는 AI 기반 비교를 도입했다. 구조적으로 의미 있는 변경만 잡아내고 나머지는 무시한다.
Percy는 GitHub PR 워크플로우와 긴밀하게 통합된다. PR을 올리면 자동으로 시각 diff 대시보드가 생성되고, 리뷰어가 시각 변경을 승인하거나 거부할 수 있다. “visual review approved”가 머지 조건이 되는 것이다.
여기서 솔직히 짚고 넘어갈 점이 있다. 이런 도구들은 “변경 감지”는 탁월하지만, 그것이 의도된 변경인지 버그인지 판단하는 것은 여전히 사람 몫이다. 테마를 리뉴얼하면 수십 개의 스크린샷이 한꺼번에 바뀌고, 누군가가 새 베이스라인을 승인해야 한다. Applitools가 유사한 변경을 그룹핑해서 일괄 승인할 수 있게 해주고, self-healing 로케이터로 셀렉터 변경에도 테스트가 깨지지 않게 해주긴 한다. 하지만 완전 자동은 아니다. 반자동이다.
결국 이 도구는 단독 해법이 아니라 CI/CD 파이프라인의 “안전망”으로 봐야 한다. 브라우저 연결이 에이전트에게 능동적 시각을 주는 것이라면, Visual Regression Testing은 수동적 감시 카메라에 가깝다. 둘을 조합했을 때 — 에이전트가 직접 보고 고치고, 최종적으로 베이스라인 비교가 잡아내는 이중 루프 — 비로소 실질적인 시각 검증이 완성된다.
프로세스 측면에서도, 누가 베이스라인을 승인할 것인가(디자이너? 프론트엔드 리드? PM?)가 명확하지 않으면 승인 대기 자체가 병목이 된다. 도구를 도입하는 것보다 프로세스를 설계하는 것이 더 어려운 경우가 많다.
테스트 유지보수 비용 줄이기
시각 검증 파이프라인을 만들어도, 테스트 자체의 유지보수가 병목이 되면 의미가 없다. DOM 구조가 바뀔 때마다 셀렉터가 깨지고, 고치면 또 깨지는 악순환.
테스트 유지보수는 자동화의 숨겨진 비용이다. 한 팀이 E2E 테스트를 500개 작성했다고 하자. 초기에는 잘 돌아간다. 하지만 6개월이 지나면 UI 리뉴얼, 컴포넌트 라이브러리 교체, 라우트 구조 변경 등으로 셀렉터가 깨지기 시작한다. 엔지니어링 시간의 30%가 테스트 유지보수에 들어간다는 조사 결과도 있다. 테스트를 짜는 것보다 유지하는 것이 더 비싸다.
Momentic은 자연어 로케이터로 이 문제를 공략한다. “로그인 버튼을 클릭”이라고 쓰면 DOM 구조가 바뀌어도 해당 요소를 알아서 찾는다. #btn-login-v3이 [data-testid="auth-submit"]으로 바뀌어도 테스트가 깨지지 않는다. 셀렉터 관리라는 지루한 작업에서 해방되는 셈이다.
트레이드오프는 있다. 자연어는 CSS 셀렉터보다 정밀도가 떨어진다. “장바구니 버튼”이 페이지에 두 개 있으면 어느 것을 클릭할지 모호해진다. 하지만 유지보수 비용을 생각하면, 약간의 정밀도를 포기하는 대신 훨씬 높은 회복력(resilience)을 얻는 교환이다. 500개 테스트를 30% 시간으로 유지할 것인가, 자연어 기반으로 5% 시간으로 유지할 것인가. 대부분의 팀에게 답은 명확하다.
앞서 언급한 내 파이프라인에서는 이 문제를 data-testid 시맨틱 레이어로 풀고 있다. Momentic이 자연어로 “OK 버튼 클릭”을 해석하는 것과 방향은 같다 — DOM 구조 변경에 테스트가 안 깨지게 하려는 것이니까. 차이는 추상화 수준이다.
“셀렉터가 깨지면 테스트가 깨진다”는 하나의 근본 원인을, 세 가지 도구가 각각 다른 높이에서 우회하고 있다.
| 접근 | 추상화 수준 | 장점 | 약점 |
|---|---|---|---|
| Applitools self-healing | AI가 요소를 재탐색 | 기존 테스트 수정 불필요 | 반자동, 베이스라인 승인 필요 |
data-testid 시맨틱 레이어 | 명시적 식별자 부여 | 정밀하고 결정론적 | 초기 세팅 비용, testid 유지 필요 |
| Momentic 자연어 | 의도 기반 해석 | 유지보수 최소 | 모호성, 정밀도 트레이드오프 |
Applitools는 기존 테스트를 건드리지 않고 AI가 알아서 적응하게 한다 — 가장 낮은 노력, 가장 낮은 통제력. 내 testid 레이어는 모든 주요 UI 요소에 data-testid="payment-confirm-button" 같은 명시적 계약을 심는다 — 초기 비용이 있지만 결정론적이다. Momentic은 “결제 버튼 클릭”이라는 자연어로 가장 높은 추상화를 제공한다 — 유지보수가 거의 없지만 모호한 경우에 취약하다.
어떤 것이 정답이라기보다, 프로젝트의 성격에 따라 선택이 달라진다. 빠르게 변하는 스타트업 UI라면 Momentic의 유연함이 맞고, 의료 SaaS처럼 정확한 플로우 검증이 필수인 도메인이라면 testid 기반이 안전하다. 레거시 테스트가 대량으로 있는 팀이라면 Applitools의 self-healing이 현실적인 출발점이 된다.
마무리
코드를 빨리 작성하는 것은 이미 에이전트가 잘 한다. 코드 작성 능력은 빠르게 commoditize되고 있다. 모든 에이전트가 코드를 잘 쓰게 되면, 차이를 만드는 것은 “얼마나 빨리 쓰느냐”가 아니라 결과가 진짜 맞는지 어떻게 확인하느냐가 된다.
에이전트에게 눈을 달아주는 사람 — 시각 검증 파이프라인을 설계하고, 렌더링 피드백 루프를 구축하고, 시각 회귀 테스트를 자동화하는 사람 — 이 에이전트 시대의 차별화 포인트다. 결국 중요한 것은 에이전트가 무엇을 만들 수 있느냐가 아니라, 만들어진 것이 정말로 맞는지를 어떻게 확인하느냐이다.
에이전트의 눈은 단순히 스크린샷을 찍는 것이 아니다. 사용자의 의도와 렌더링 결과를 대조하는 것이다. “이 버튼은 모바일에서도 누를 수 있어야 한다”는 의도를 375px 뷰포트의 스크린샷에서 검증하는 것. 이 간극을 메우는 것이 다음 단계의 엔지니어링이다.