Browser Design QC Skill
DevTool

Browser Design QC Skill

Figma 스펙과 브라우저 CSS 실측값을 자동 비교해 색상·타이포그래피·치수·간격·문구 5가지 결함을 검출하는 Claude Code 디자인 QC 스킬을 만들었다.

1. 구현 배경

AI 덕분에 UI 구현은 빨라졌지만 검수 과정의 비효율은 여전하다. 스타일 코드 몇 줄 바뀐 PR이라도 실제 화면이 어떻게 달라졌는지는 결국 직접 눈으로 확인해야만 알 수 있기 때문이다.

수동 검수 과정에서 발생하는 휴먼 에러도 문제다. 색상이 미묘하게 어긋나거나, 폰트 굵기가 달라지거나, 간격이 몇 픽셀 벌어지는 결함은 육안으로 걸러내기 어렵다. 실제로 팀에서 처리한 디자인 QC 티켓 100개를 전수 분석한 결과, 다음과 같이 명확한 패턴이 반복되고 있었다.

  • 검색 결과 화면 내 병원 카드 padding 불일치
  • 채팅창 내 날짜 표기 박스 border-radius 불일치
  • CheckBox Spacing 간격 불일치

분석 결과 결함의 유형은 색상, 타이포그래피, 치수, 간격, 문구 등 5가지 카테고리로 수렴했다. 인프라의 환경 변화나 렌더링 방식에 구애받지 않고, Figma 스펙 수치와 브라우저가 실제로 렌더링한 CSS 실측값을 직접 비교 검증하는 자동화 도구를 구축하기로 결정한 이유다.


2. 도구 선택: agent-browser vs Playwright MCP

브라우저 자동화 도구로는 Playwright MCP가 익숙한 선택지다. 그럼에도 Vercel Labs가 만든 Rust 기반 CLI 도구인 agent-browser를 채택했다. AI 에이전트가 브라우저를 제어하는 아키텍처 관점에서 근본적인 효율성 차이가 존재하기 때문이다.

  • Playwright MCP: 에이전트가 외부 MCP 서버에 명령을 위임하고, JSON-RPC 통신을 거쳐 브라우저를 제어하는 구조다. 단일 턴으로 끝나지 않고 파일 저장 등의 추가 스텝이 필요하다.
  • agent-browser: 외부 서버 없이 AI가 터미널에서 Bash 명령어 하나로 Chrome DevTools Protocol(CDP)을 직접 조종하는 구조다.
항목agent-browser (✓ 채택)Playwright MCP
기반 기술Rust 네이티브 바이너리 (CDP 직접 제어)Node.js + Playwright (JSON-RPC 통신)
의존성Bash 접근만 필요 (MCP 클라이언트 불필요)MCP 클라이언트 및 서버 설정 필수
요소 참조접근성 트리 출력 시 요소별 고유 ref ID(@e1, @e2) 부여별도 ref 시스템 없음 (CSS 셀렉터 의존)
JS evaleval "..."로 런타임 Web API 즉시 호출파라미터 전달 방식으로 표현 제약 있음
스크린샷base64 문자열 직접 반환 (즉시 처리 가능)파일 저장 후 경로 전달 (추가 디스크 I/O 발생)

agent-browser를 선택함으로써 얻는 이점은 명확하다. Bash 명령어 하나로 실행되므로 eval 결과나 스크린샷 데이터가 AI와의 동일한 컨텍스트(Turn) 안에서 즉시 반환된다.

특히 snapshot 명령 시 부여되는 고유 ref ID(@e1 등) 덕분에, UI 변경으로 CSS 셀렉터가 달라져도 에이전트가 오작동 없이 안정적으로 요소를 추적하고 조작할 수 있다. 외부 네트워크 모킹이 필수적인 환경이 아니라면, 개발 환경에서 가볍게 실측값을 뽑아내기에 최적의 선택지다.


3. CSS 측정 방식 비교

디자인 검증 자동화를 구현하기 위해 총 세 가지 접근법을 비교 검토했다.

방식감지 원리장점단점 및 채택 여부
A. 스크린샷 픽셀 diff렌더된 이미지를 이전 버전과 픽셀 단위 비교시각적 변화 전체 감지, 초기 설정 용이탈락: OS/디바이스(DPR) 환경 차이로 오탐 다발, 구체적인 오류 속성 파악 불가
B. Figma Code ConnectFigma 컴포넌트와 정적 코드 컴포넌트 매핑디자인-코드 간 매핑 자동화탈락: 런타임에서 부모 컨테이너 압박으로 변하는 실측 렌더링 값을 잡지 못함
C. Web API 활용 ✓채택getComputedStyle + getBoundingClientRect환경 무관한 실제 렌더 값 추출, 디버깅 데이터 명확채택: SPEC 수동 등록 공수 필요, 토큰 매핑 예외 처리 필요

이미지 비교 방식(A)은 macOS와 Linux CI 환경의 폰트 렌더링 차이로 인해 수많은 오탐을 발생시킨다. 정적 코드 비교 방식(B)은 w-[132px]로 선언했더라도 부모 flex 속성에 의해 압박받는 런타임 실측치를 계산하지 못한다.

반면 getComputedStylegetBoundingClientRect를 조합하는 방식(C)은 브라우저가 스타일 상속과 우선순위를 모두 계산한 뒤 도출한 ‘최종 결과물’을 측정한다. headless Linux CI에서도 동일한 수치(font-size: 14px)를 보장하며, 결함 발생 시 변경된 속성을 명확한 데이터로 제공하므로 채택했다.


4. 실험 설계 및 서브에이전트 구조

디자인 QC 티켓 100개를 분석해 도출한 5대 결함 카테고리를 바탕으로, 각 항목을 브라우저 환경에서 실측할 검증 방법을 정의했다.

카테고리대표 증상검증 방법 (Web API)
색상텍스트/배경색 CSS 스펙 미준수getComputedStylecolor, backgroundColor hex 비교
타이포그래피폰트 크기 변경, 굵기 스펙 미준수fontSize, fontWeight 추출 후 비교
치수라벨 및 구분선 너비/두께 변경getBoundingClientRect() width/height 실측
간격요소 간 gap 변경, 마진 미준수getBoundingClientRect() 두 요소 간 좌표 차이 계산
문구오타, 띄어쓰기 오류, 문구 변경SPEC 텍스트 → DOM 존재 여부 역방향 확인 (MISSING)

실험 방식

대상 화면은 ‘예약 오픈 설정 모달’이다. 검증은 사이클 단위로 진행했다. 사이클마다 특정 카테고리의 CSS 스펙을 의도적으로 어긋나게 수정한 뒤(예: text-CoolGray-800text-blue-600), 에이전트 스킬이 이를 잡아내는지 확인한다. 변화를 놓치면 SKILL.md에 규칙을 보강한 뒤 다음 사이클로 넘어간다. 마지막 6회차 사이클에서는 5대 카테고리의 결함을 동시에 주입해 전체 회귀 테스트(Full Regression)를 수행했다.

서브에이전트 설계

역할 분담과 데이터의 투명성을 확보하기 위해 5개의 Claude Code 서브에이전트가 상태 파일(state.json)을 매개로 컨텍스트를 주고받도록 설계했다. 그리고 Bug Writer와 Inspector를 엄격히 격리했다. Inspector는 어떤 버그가 주입되었는지 모르는 블라인드 상태로 QC를 실행한다. 검증과 개선 주체를 분리함으로써 검출 실패를 축소 보고할 여지를 차단하고 객관적인 검출률 데이터를 확보했다.

  • Planner: 5대 결함 카테고리를 도출하고, 사이클마다 테스트할 버그 주입 명세를 결정한다. 종료 후 Inspector의 리포트를 채점한다.
  • Bug Writer: Planner의 주문서에 따라 타깃 컴포넌트 코드를 수정해 의도적인 디자인 결함을 심고, 사이클이 끝나면 원복한다.
  • Inspector: 현재 SKILL.md 기준에 따라 agent-browser를 구동해 디자인 QC를 수행하고 HTML 리포트를 작성한다.
  • Optimizer: Inspector가 직면한 런타임 병목을 분석해 토큰과 실행 시간을 줄이는 최적화 방안을 도출한다.
  • Skill Builder: Planner의 채점 결과와 보강 지시문을 바탕으로 SKILL.md 내부 검증 규칙을 수정한다.

5. 실험 기록 및 스킬 진화 과정

Cycle 1. 색상(Color) 검증

구분선 배경색, 텍스트 컬러 2종, 헬프텍스트 컬러 등 총 4개의 색상 CSS 불일치를 주입하고 첫 번째 eval을 실행했다. 테스트 결과 3개의 오류는 감지했으나 1개의 오류를 놓쳤다.

실패 원인은 크게 두 가지였다. 먼저 text-CoolGray-600 클래스가 Tailwind config에 정의되지 않아 실제 브라우저 렌더링 시 아무런 색상 변화가 일어나지 않았다. 여기에 더해 JSX 템플릿 리터럴 뒤쪽에 공백(trailing space)이 포함되면서 SPEC 키 매칭마저 실패했다. 스타일도 적용되지 않은 데다 문자열까지 불일치해 발생한 누락이었다.

이 과정에서 등록된 요소만 검증하는 ‘SPEC 화이트리스트 방식’의 사각지대를 확인했다. 등록이 누락된 요소는 결함이 생겨도 잡아낼 수 없다는 맹점이 있었다. 이를 해결하기 위해 결함 상태를 FAIL / UNVERIFIED / PASS로 세분화하여, 검증 대상에서 누락된 요소까지 추적할 수 있는 3단계 분류 체계를 도입했다.

개선점

  • 텍스트 앵커 기반 특정: 배경색만으로 요소를 찾으면(querySelector('[class*="bg-\\[#f5f7f9\\]"]')) 동일한 스타일을 가진 다른 요소가 오탐된다. 반드시 주변의 텍스트 앵커(el.textContent.includes('예약 오픈'))로 대상을 좁힌 뒤 CSS를 추출해야 정확하다.
  • Figma 색상 매핑 테이블 표준화:eval 스크립트마다 하드코딩하던 색상 값을 SKILL.md에 단일 매핑 테이블(예: CoolGray-800#506073)로 정의하여 수동 입력 오차를 제거했다.

Cycle 2. 타이포그래피(Typography) 검증

font-bold, text-base font-bold, text-base 세 종류의 타이포그래피 CSS 불일치를 적용했다. TreeWalkergetComputedStyle 조합으로 fontSizefontWeight를 실측해 3개 오류를 모두 감지했다.

// ✅ 3종 세트 검증 방식
'오픈 주기': { fontSize: '14px', color: '#506073', fontWeight: '400' },

// ❌ color만 등록 시 — fontSize·fontWeight 오류 미검출
// '오픈 주기': { color: '#506073' },

개선점

  • 폰트 속성 3종 세트 검증 필수화: color만 검증하면 fontSizefontWeight 오류를 놓친다. 텍스트 요소는 반드시 fontSize, color, fontWeight 세 속성을 함께 묶어 SPEC에 등록하도록 규칙을 강제했다.

Cycle 3. 치수(Sizing) 검증

w-[132px]w-[200px], h-[1px]h-[4px], w-[140px]w-[90px] 형태로 세 줄의 치수 변형을 주입했다. 결과는 3개 모두 감지했다.

  • 실패 원인 분석: 기존 구분선 탐지 조건이 h > 2px이면 검증을 스킵하도록 설계되어 있어, 4px로 커진 구분선이 인식되지 않고 누락될 뻔했다. 탐색 범위를 h = 1~10px 범위로 확장하여 즉시 감지했다.

개선점

  • getBoundingClientRect() 실측: CSS 클래스 값과 실제 렌더 값은 불일치할 수 있다. 코드에 w-[132px]를 적었더라도, 부모 flex 컨테이너가 자식의 width를 수축시키면 실제 너비는 달라진다. 따라서 정적 클래스명이 아닌 getBoundingClientRect()로 런타임 수치를 직접 측정해야 한다.
  • snapshot → eval 대체 (토큰 100배 감소): agent-browser snapshot은 전체 접근성 트리를 텍스트로 반환하므로 1회 실행 시 약 2~4K 토큰을 소비한다. 요소 좌표 하나를 얻기 위해 전체 트리를 받을 필요가 없으므로, 필요한 요소만 eval로 런타임 호출하여 토큰 소비량을 100배 줄였다.

Cycle 4. 간격·정렬(Spacing) 검증

수직 간격(gap-4gap-8), 수평 간격(gap-x-2gap-x-8), 구분선 마진(my-6my-2) 등 3개 결함을 주입했고, 전원 FAIL로 판정했다(3/3).

  • 실패 원인 분석: 결함 감지에는 성공했으나, 행 수직 간격의 측정값이 -41px로 왜곡되어 연산되었다. [class*="flex"][class*="items-center"] 조건으로 “dialog 내 flex 행 전체”를 탐색하다 보니 모달 헤더 행이 첫 번째 요소로 오탐된 것이 원인이었다.

개선점

  • 상위 트리 탐색 체인 구현: 요소를 특정할 때 앵커 텍스트 → closest 행 → parentElement → children 순으로 올라가는 구조를 설계하여 탐색 정확도를 높였다.
  • getBoundingClientRect() 기반 거리 계산: gap 속성은 flex나 grid 컨테이너에만 존재하며, margin이나 padding으로 구현된 간격은 normal로 반환된다. 따라서 두 요소 사이의 픽셀 거리는 각 요소의 getBoundingClientRect() 값을 구한 뒤 오차를 계산해야 한다. 서브픽셀 렌더링 오차를 흡수하기 위해 ±2px 허용 오차를 적용했다.
  • eval 응답 페이로드 슬리밍 (토큰 70% 감소): 기존 eval 스크립트는 SPEC에 등록되지 않은 텍스트 스타일까지 actual에 담아 반환했다. 5개 카테고리, 평균 25개 텍스트 노드, 3개 속성이 겹쳐 수백 개의 데이터가 매번 낭비되었다. 이를 불일치가 발생한 failsunverifiedKeys만 반환하도록 변경하여 응답 크기를 약 70% 감소시켰다.

Cycle 5. 문구·오타(Text Content) 검증

‘당일 예약’ → ‘당일예약’, ‘예약 마감 시간’ → ‘예약마감 시간’, ‘전까지만 예약할 수 있습니다.’ → ‘전까지만 예약하실 수 있습니다.’ 등 3개의 문구 결함을 주입하여 모두 감지했다.

  • 실패 원인 분석: 기존 eval 방식은 DOM 텍스트를 키(Key)로 사용해 SPEC과 스타일을 비교했다. 문구 자체가 바뀌면 키가 달라지므로 SPEC 매칭이 불가능해지고, 오타가 아닌 unverified로 분류되는 한계가 있었다.

개선점

  • SPEC_TEXTS MISSING 패턴 도입: DOM을 기준으로 매칭하는 대신, SPEC에 명시된 올바른 텍스트 리스트가 DOM 내에 존재하는지 확인하는 역방향 체크 로직을 추가했다. 이를 통해 오타와 문구 변경을 정확히 가려냈다.
const SPEC_TEXTS = ['당일 예약', '예약 마감 시간', '전까지만 예약할 수 있습니다.'];
const textFails = {};
for (const expected of SPEC_TEXTS) {
  if (!domTexts.has(expected)) {
    textFails[expected] = 'MISSING: text not found in DOM';
  }
}
  • 모달 상태 유지 및 eval만 재실행: 코드를 수정하고 재검증할 때마다 페이지 전체를 navigate하고 모달을 다시 여는 과정에서 병목이 있었다. HMR이 적용 중이거나 코드 변경 없는 검증이라면 navigate를 생략하고 eval 명령어만 재실행하도록 유도하여 회당 약 3초를 절약했다.

Cycle 6. Full Regression

앞선 5대 카테고리에서 2개씩 추출한 총 10개의 CSS 스펙 미준수를 동시에 적용하고 단일 eval 스크립트로 회귀 테스트를 돌렸다. 결과는 10/10 전원 검출이다. Cycle 1에서 3/4 검출에 그쳤던 스킬이 5번의 단계적 보강을 거치며 복합 결함을 공백 없이 잡아내는 상태로 진화했음을 확인했다.

최적화 결과 분석 요약

검증 단계최적화 전최적화 후절약 지표
open + 모달 오픈~5초~5초 (첫 실행) / 0초 (재실행)재실행 시 인터랙션 비용 제로
agent-browser snapshot~3초 + 3K 토큰~0.5초 + 30 토큰실행 속도 6배 단축, 토큰 100배 감소
eval 실행 및 데이터 반환~1초 + 5K 토큰~1초 + 1.5K 토큰페이로드 압축으로 토큰 70% 감소
전체 프로세스 합계~11초~7초런타임 타임아웃 약 35% 감소

6. 에이전트 및 스킬 구조 개선 회고

에이전트 설계 개선

실험을 마치고 에이전트 구조를 다시 돌아보니 잘 작동한 부분과 아쉬운 점이 명확히 보였다.

✅ 잘된 것
  • 에이전트 역할 격리: Bug WriterInspector를 철저히 분리했다. Inspector가 버그 주입 내역을 모르는 블라인드 상태로 QC를 진행해 인지 편향을 차단했고, 덕분에 ‘3/4 검출’이라는 객관적인 데이터를 얻을 수 있었다. InspectorSkill Builder 역시 분리하여 검증 실패가 축소 보고될 동기를 차단했다.
  • 선순환 루프 형성: Skill Builder가 업데이트한 최신 SKILL.md를 다음 사이클의 Inspector가 매번 새로 학습하도록 설계했다. 사이클이 반복될수록 스킬이 고도화되며 검출률이 상승하는 선순환이 완성됐다.
  • state.json 기반 정보 격리: Bug Writer → Inspector 채널에서는 블라인드 테스트 무결성을 위해 버그 명세를 제외했다. 반면 Inspector → Optimizer 채널에는 실제 작성한 eval 코드를 완전 공유하여 구체적인 스크립트 병목(중복 호출 등)을 진단할 수 있도록 채널을 이원화했다.
⚠️ 아쉬운 것
  • Optimizer 배치 주기: Optimizer가 마지막 Full Regression 직전에만 호출되어, 도출된 최적화 방안을 매 사이클에 점진적으로 반영하지 못했다. 다음 설계에는 매 사이클 종료 시마다 Optimizer가 자동 실행되는 루프가 필요하다.
  • Planner 역할 중첩: Planner가 기획과 채점을 동시에 수행하여 주관이 개입될 여지가 있었다. 채점 기준 수식을 사이클 시작 전에 고정해 두는 엄밀함이 보완되어야 한다.

스킬 구조 개선

실험 종료 후 Anthropic의 skill-creator 가이드를 기준으로 스킬 구조를 리팩토링했다.

  • SKILL.md 다이어트 (594줄 → 187줄): SKILL.md가 트리거될 때마다 발생하는 토큰 낭비를 막기 위해, 결정론적 코드와 상세 원칙을 외부로 이관하고 워크플로 골격과 포인터만 남겼다.
  • 결정론적 코드의 스크립트화: 인라인에 섞여 있던 코드를 qc-eval.js, crop-coords.js, crop-modal.py, cleanup-reports.shscripts/ 디렉토리로 격리했다. 이를 통해 스킬 구조를 건드리지 않고 로직만 업데이트할 수 있는 구조를 확립했다.
  • 상세 원칙의 레퍼런스화: 앵커 텍스트 특정법, 허용 오차 표, Figma 매칭 원칙 등 인지적 판단 기준들을 references/ 하위의 measurement.md, report-build.md 등 4개 문서로 분리해 필요할 때만 참조하도록 개선했다.
  • 에셋 참조의 명시적 선언: 에이전트가 탐색 범위 밖의 에셋을 인지하지 못하는 한계를 해결하기 위해, 루트에 방치되어 있던 리포트 HTML 템플릿의 경로를 스킬 내부에 명시적으로 선언했다.
  • description 트리거 조건 구체화 (WHAT + WHEN): 수행 작업(WHAT)만 적혀 있어 발생하던 언더트리거를 막기 위해, “디자인 QC / Figma 대조 / browser-agent-qc 언급 시 발동”과 같이 구체적인 실행 시점(WHEN)을 추가 기술했다.

7. 실제 QC 실행 결과

browser-design-qc 스킬로 실제 화면을 검증했다. 테스트 브랜치에 결함을 의도적으로 주입하고, 스킬이 이를 검출하는지 확인했다. 대상은 예약 오픈 설정 모달의 매일·매주·매월 3개 모드로 가정했다.

검출 결함 내역

모드카테고리속성Figma 기준실측값
공통색상뱃지 background#dbeafe#dcfce7
공통색상뱃지 color#1e40af#166534
공통border-radius카드 border-radius8px0px
공통색상구분선 배경색CoolGray-200red-300
매일타이포”열려요.” fontSize14px16px
매일타이포”열려요.” fontWeight400700
매일치수입력 필드 width110px60px
매주간격요소 간 flex gap8px32px
매주문구·오타”열려요.” 텍스트열려요.열여요. (오타)
매월문구·오타”열려요.” 텍스트열려요.열립니다.
매월간격flex-col gap-y4px24px

8. 다음 목표

AI 도입으로 코드 생산성이 급증하는 상황에서, 검증 단계의 수동 의존성을 제거하는 것은 전체 프로세스의 병목을 해결하기 위한 필수 과제다. 본 작업에서는 브라우저 실측값을 기반으로 디자인 스펙을 검증하는 도구를 구축하여 이 검수 병목을 해결할 수 있는 기반을 마련했다.

다음 고도화 목표는 시각적 실측을 넘어 기능적 명세를 자동으로 추적하는 기획 QC(browser-spec-qc) 파이프라인의 구축이다. 이번에 확보한 agent-browser 인프라를 그대로 활용하되, 버튼 클릭에 따른 모달 개폐 상태, 토스트 팝업 감지, API 응답 status 매칭 등 비즈니스 로직의 인터랙션 흐름까지 자동 검증하는 단계로 영역을 확장할 계획이다.