본문으로 건너뛰기

© 2026 Molayo

Zenn헤드라인2026. 05. 20. 10:46

Claude Code Skills를 사용하여 팀의 E2E 워크플로우를 구축한 이야기

요약

Claude Code의 Skills 기능을 활용하여 Playwright 기반의 E2E 테스트 생성부터 실행, 스크린샷이 포함된 Markdown 리포트 생성까지의 전 과정을 자동화한 사례를 소개합니다. 생성형 AI 도입으로 빨라진 개발 속도에 대응하기 위해 품질 검증망으로서 E2E 테스트를 도입했으며, LLM이 코드를 작성하고 결과를 분석하기 용이하도록 설계하는 데 중점을 두었습니다.

핵심 포인트

  • Claude Code의 Skills를 통해 Markdown 절차서를 슬래시 명령어로 호출하여 워크플로우 자동화 가능
  • AI가 생성하는 빠른 코드 변경(diff)에 대응하기 위한 품질 보증 수단으로 E2E 테스트 도입
  • Playwright 선정 이유: TypeScript 호환성, 쉬운 환경 구축, 그리고 LLM이 학습 및 파싱하기 좋은 풍부한 데이터와 표준화된 출력
  • 테스트 작성뿐만 아니라 결과 확인, 원인 조사, 리포트 공유까지의 후속 공정을 Skill로 통합하여 운영 효율성 극대화

서론

Claude Code의 Skills는 Markdown으로 작성된 절차서를 슬래시 명령어로 호출할 수 있는 메커니즘입니다.

이 기사에서는 팀에서 운용 중인 codegen-test라는 Skill을 소재로,

  • Playwright의 E2E 테스트를
    지시 한 번으로 생성 - 그것을 Skill 측에서
    실행 + 스크린샷이 포함된 Markdown 리포트화

까지 자동화한 이야기를 쓰고자 합니다.

단순히 "이렇게 만들었습니다"가 아니라, 왜 그렇게 설계했는가에 중점을 두고 있습니다.

구체적으로는 다음 세 가지를 깊이 있게 다룹니다.

  • 왜 Playwright를 선택했는가
  • 왜 "프롬프트 (Prompt)"와 "Skill"로 2단계로 나누었는가
  • 운용하면서 겪은 함정과 그 회피책

LT(Lightning Talk)에서 발표한 내용을 재구성한 것이지만, 슬라이드에서는 시간 관계상 생략했던 설계 판단의 이유를 중심으로 다시 작성했습니다.

배경: 왜 E2E를 "지금" 도입했는가

참여하고 있는 프로젝트에서는 원래 E2E 테스트를 도입하지 않았습니다.

Next.js + TypeScript 프로젝트로, 결합 및 화면 레벨의 자동 검증은 전무했습니다.

도입 계기는 아이러니하게도 생성형 AI (Generative AI)로 인한 개발 속도가 너무 빨라진 것입니다.

  • AI가 내놓는 차분(diff)이 빠르고 범위가 넓음
  • 프론트엔드뿐만 아니라 백엔드와의 결합 부근에서 품질을 어떻게 담보할지가 과제로 떠오름...
  • 인간의 리뷰만으로는 화면 동작의 회귀(regression) 감지가 따라가지 못함

"빠르게 나오는 차분에 대응할 품질망"으로서 E2E를 사후에 추가하기로 한 것이 솔직한 배경입니다. 이는 "테스트 주도 개발 (TDD)이 중요하니까" 같은 우아한 동기가 아니라, 순수하게 운용상의 필요에 의해 도입된 것이었습니다.

그리고 E2E를 도입하게 되면 이번에는 또 다른 과제가 발생합니다.

  • 테스트 실행 후의
    결과 확인 · 원인 조사 · 공유가 은근히 무거움 - 스크린샷, 로그, 에러를 개별적으로 확인해야 하는 번거로움
  • 담당자마다 PR(Pull Request)에서의 리포트 상세도가 일치하지 않음

E2E를 "작성하고 끝"내는 것이 아니라, 후속 공정까지 세트로 편하게 만든다. 이것이 Skill화의 동기입니다.

왜 Playwright를 선택했는가

후보로는 Cypress / Selenium / Puppeteer도 있습니다. 최종적으로 Playwright를 선택한 이유는 심플하게 세 가지입니다.

  • 프로젝트 스택과 일치함
    Next.js + TypeScript이므로, 동일한 TS로 작성할 수 있는 Playwright는 도입 시 마찰이 거의 없었습니다.
  • 환경 구축이 쉽고 빠름
    pnpm install -D @playwright/test && npx playwright install chromium 명령어로 사실상 끝납니다. 팀에 전개할 때 "환경 설정 문제"가 발생하기 어렵다는 점이 큽니다.
  • 문서와 커뮤니티가 풍부함
    LLM에게 작성을 맡기는 것을 전제로 기술 선정을 한다면, 학습 데이터량과 공식 문서의 질은 매우 중요합니다. Playwright는 두 가지 모두 갖추고 있습니다. 이는 LLM이 생성하는 코드의 정확도와 직결됩니다.

게다가 후속 Skill과의 궁합도 결과적으로 효과적이었습니다.

  • test.step()을 통해 스텝 단위의 구조화가 가능함 → 그대로 리포트의 행(row)이 됨
  • JSON reporter가 표준임 → LLM이 파싱(Parsing)하기 쉬움
  • 실패 시 스크린샷, 트레이스(Trace), 영상이 표준으로 제공됨 → Skill의 책임을 "읽기 · 정형화"로 좁힐 수 있음

"LLM에게 쓰게 하고, LLM에게 읽게 하고, LLM에게 리포트하게 하는" 파이프라인을 구축할 때, 기계 판독 가능한 출력이 표준으로 갖춰져 있는 것은 상상 이상으로 큰 도움이 됩니다.

왜 "프롬프트 + Skill"의 2단계로 나누었는가

이 부분이 가장 고민했던 지점이며, 기사로서 가장 쓰고 싶었던 부분입니다.

최종적인 플로우는 다음과 같습니다.

Step 1: 테스트 코드 생성 (프롬프트)
↓
Step 2: 테스트 실행 & 리포트 생성 (Skill)

처음에는 모든 것을 하나의 Skill로 합치려고 했습니다. 하지만 이것이 잘 되지 않았습니다.

1 Skill로 합쳤더니 정확도가 떨어졌다

"화면 조작 지시를 인수로 받아 테스트 코드를 생성하고, 실행하여 리포트를 내보낸다"를 하나의 Skill에 담은 결과,

  • 절차서가 너무 길어짐
  • LLM이 자신이 어느 단계에 있는지 스스로 놓침
  • 생성되는 테스트 코드의 편차가 커짐

이라는, 전형적인 기능 과다 → 정확도 저하 증상이 나타났습니다.

「더 잘 작성했다면 하나의 Skill로도 돌릴 수 있지 않았을까」라는 마음은 솔직히 있습니다. 지금도 그렇게 생각하고 있으며, 검토 사항으로 남겨두고 있습니다. 다만, 운영을 시작하는 타이밍에는 심플하게 나누는 것이 사고가 적었습니다.

나누었더니 책임(Responsibility)이 깔끔하게 분리되었다

나누어 보니, Step 1과 Step 2는 입력의 성질이 완전히 다르다는 점이 명확해졌습니다.

Step 1 (코드 생성)Step 2 (실행 + 리포트)
입력기능마다 매번 다른 화면 조작
......

즉, **Step 1은 「가변적인 지시」, Step 2는 「고정된 절차」**입니다. 성질이 다른 것을 하나의 Skill에 억지로 밀어 넣었기 때문에 정확도가 나오지 않았던 것이 현재의 이해입니다.

게다가 Step 1과 같은 POM (Page Object Model) 생성은 Claude 이외(GPT 등의 채팅 도구)에서도 평범하게 할 수 있는 작업입니다. Claude Code 고유의 Skill에 락인 (Lock-in)할 필요가 없는 영역이기에 분리한다는 결단도 내렸습니다.

교훈: Skill화의 판단 기준은 「절차를 얼마나 고정할 수 있는가」입니다. 가변적인 부분이 큰 태스크를 무리하게 Skill화하면, 인자 (Argument)로 다 표현하지 못해 결국 프롬프트 (Prompt)에 적게 되어 이중 관리 상태가 됩니다.

전체 흐름

호출은 2개의 커맨드로 끝납니다.

# Step 1 (Claude Code에 의뢰용 프롬프트를 던짐)
→ e2e/sample_create/sample_create.spec.ts 가 생성됨
# Step 2 (Skill 기동)
...

Step 1: 프롬프트로 테스트를 생성하기

의뢰용 프롬프트는 템플릿화되어 있으며, 수정하는 곳은 「내용」과 「배치 장소」 두 곳뿐입니다.

# 의뢰
POM (또는 E2E 실행용 코드)를 작성해 주세요.
# 전제
...

포인트는 두 가지입니다.

test.step()을 필수화하고 있다

이는 후속 Skill의 편의를 위한 것입니다. step 이름이 그대로 리포트의 스텝 행이 되기 때문에, step을 나누지 않으면 입도가 거친 리포트밖에 만들 수 없습니다.

인증 정보는 .env를 통해 반드시 외부로 분리

프롬프트에 인증 정보를 적지 않는 것은 LLM 운용의 기본이지만, 템플릿에 전제로 적어두면 사고가 줄어듭니다.

참고로 이곳은 Claude Code에서도 ChatGPT에서도 생성할 수 있습니다. 채팅 UI에서 가볍게 생성 → 파일로 저장 → Skill에 적용하는 분업을 의도적으로 허용하고 있습니다.

Step 2: Skill의 내용 (Phase 0~5)

Skill 본체는 6개의 Phase로 나뉘어 있습니다. 「LLM에게 전부 맡긴다」가 아니라, 각 Phase에서 무엇을 할지를 고정하는 것이 설계의 핵심입니다.

Phase 0: 인자 분석 및 유효성 검사 (Validation)
Phase 1: 테스트 파일 분석
Phase 2: 테스트 실행
...

Phase 0: 입구를 엄격하게

인자 체크, 테스트 파일 존재 확인, @playwright/test 유무, 설정 파일 확인. 여기서 대충 넘어가면 후속 단계가 전부 망가지므로, 처음에 엄격하게 차단합니다.

게다가 리포트 저장 위치의 도메인 이름을 테스트 파일의 상위 디렉토리 이름에서 자동으로 결정하는 규칙으로 만들었습니다.

테스트 파일도메인 이름
e2e/sample_create/sample_create.spec.tssample_create
e2e/login/login.spec.tslogin

「테스트를 늘릴 때마다 명명 규칙을 생각해야 하는」 번거로움을 없애기 위한 작은 장치입니다.

Phase 1: 실행 전에 테스트를 읽기

사소해 보이지만 효과적입니다. test.describe / test() / test.step()의 이름과 사용된 로케이터 (Locator) · 어서션 (Assertion)을 먼저 구조화해 둡니다.

실행 로그만으로는 스텝 이름밖에 나오지 않지만, 사전 분석과 병합하면 「이 스텝은 이 버튼을 눌러 이 전환을 검증하고 있다」까지 리포트에 쓸 수 있게 됩니다.

Phase 2: Playwright를 고정 옵션으로 호출

npx playwright test <file> \
--config e2e/playwright.config.ts \
--timeout 60000 \
...

--retries 0

이 Skill에서 가장 중요한 옵션입니다. 이유는 다음 장에서 설명하겠습니다.

Phase 3: 실패 시 스크린샷을 LLM에게 읽히기

Playwright가 e2e/test-results/에 생성하는 실패 시 스크린샷을, Skill이 Read 도구를 통해 실제로 이미지로서 읽어 들여 화면 상태를 텍스트로 기술합니다.

  • 입력이 완료된 상태로 로그인 페이지에서 멈춰 있음
  • 에러 표시가 나타났는지 / 로딩 중인지
  • 어떤 모달(Modal)이 열려 있는지

로그만으로는 "타임아웃(Timeout)되었다"는 사실만 알 수 있는 부분을, 화면 상태까지 언어화함으로써 원인의 범위를 단번에 좁힐 수 있습니다. 멀티모달 LLM (Multimodal LLM)의 활용처로서 이 유스케이스는 궁합이 매우 좋다고 생각합니다.

실제로 생성되는 리포트의 이미지는 다음과 같습니다. 스크린샷 아래에 "화면 상태", "추측되는 원인", "수정 제안"이 문장으로 나열됩니다.

Phase 4: 리포트 md를 고정 템플릿으로 생성

리포트 구성은 고정되어 있습니다:

  • 기본 정보 (파일 / URL / 실행 일시 / 실행 시간)
  • 테스트 구조 (describe / test / step 표)
  • 조작 상세 (각 스텝의 로케이터(Locator)와 어서션(Assertion))
  • 에러 상세 (실패 시에만: 에러 메시지 전문 / Call log / 스크린샷 / 화면 상태 / 추측 원인)
  • 요약

"조작 상세" 섹션은 각 스텝에서 호출한 로케이터와 어서션이 구체적인 코드 단위로 나열됩니다. 리뷰어는 이곳을 확인하면 테스트 파일을 직접 열지 않고도 테스트 흐름을 따라갈 수 있습니다.

규칙으로서 **"사실과 추측을 분리한다"**를 철저히 지키고 있습니다.

  • 사실: Playwright의 에러 메시지 · Call log (생략 금지, 원문 그대로)
  • 추측: 원인의 범위, 수정 제안

LLM에게 리포트를 쓰게 하면 추측을 은근슬쩍 사실처럼 작성하는 경향이 있습니다. 템플릿을 통해 명시적으로 섹션을 나눔으로써, 읽는 사람이 "실제로 무엇이 일어났는지"와 "그로부터 무엇을 추측했는지"를 구분할 수 있도록 했습니다.

Phase 5: 한 줄로 결과 반환

✅ 테스트 성공
리포트: e2e/reports/sample_create/sample_create.spec_20260311_183500.md
❌ 테스트 실패 — 「생성하기」 버튼 클릭 후 전환 확인에서 에러
리포트: ...
에러 개요: timeout 30000ms exceeded

빠졌던 함정: retry가 테스트에 거짓말을 하게 만든다

설계 판단 중에서 절대로 타협할 수 없었던 것이 바로 이것입니다.

처음에는 별생각 없이 Playwright의 기본 설정으로 동작시켰습니다. 그러자 어느 순간 이런 일이 발생했습니다.

"실패했어야 할 테스트가 Skill을 통해 실행하면 성공으로 처리된다"

무슨 일이 일어나고 있었을까요? LLM에게 --retries를 허용하면, 실패를 발견한 순간 테스트 측의 대기 시간을 늘리거나 어서션을 느슨하게 만들어 재실행 시 성공하게 만드는 케이스가 발생했습니다.

요컨대, LLM이 "성공시키는 것이 목적"이라고 해석하여 테스트를 고쳐버리는 것입니다. 버그를 고치는 것이 아니라, 테스트를 고쳐서 통과시키는 것이죠.

이는 E2E로서 치명적입니다. 버그를 숨기고 통과시킨다는 의미이기 때문입니다.

회피책은 간단합니다. Skill 레벨에서 --retries 0을 강제하고, 추가로 **"테스트가 실패해도 자동 수정하지 않는다"**라는 방침을 Skill 본체에 명문화했습니다.

테스트가 실패한 경우, 자동 수정은 수행하지 않는다. 즉시 Phase 3로 진행한다.

수정 판단은 인간의 몫으로 남긴다. Skill의 책무는 "실행하여 보고하는 것"까지로 제한한다.

이는 단순한 운영 규칙이 아니라, LLM에게 맡길 범위를 명시적으로 끊어내는 설계 판단입니다. 실패를 고칠 권한까지 Skill에 넘겨주면, LLM은 가장 쉬운 경로(테스트를 느슨하게 만들기)를 선택해 버립니다. 그래서 넘겨주지 않는 것입니다.

LLM을 포함한 워크플로우를 만들 때, "LLM에게 무엇을 시키지 않을 것인가"를 결정하는 것이 무엇을 시킬 것인가만큼 중요하다는 것을 뼈저리게 느꼈습니다.

설계 판단 4가지 요약

마지막으로, 이 Skill의 설계를 4가지로 압축하면 다음과 같습니다.

  • Phase를 고정한다 ─ LLM의 자유도를 제한하여 편차를 없앤다
  • 자동 수정하지 않는다 ─ 실패를 숨기지 않고, 판단은 인간에게 남긴다
  • 사실과 추측을 분리한다 ─ 리포트의 신뢰성을 지킨다
  • 멀티모달로 화면 상태를 텍스트화한다 ─ 리포트만으로 상황이 전달된다

「LLM에게 전부 맡긴다」가 아니라, 맡길 부분과 형식을 정할 부분을 확실히 구분한다. 이것이 Skill 설계의 핵심이라고 생각합니다.

자동화의 경계선

마지막으로, 자동화하지 않은 영역에 대해서도 적어두겠습니다. 무엇이든 Skill화하면 되는 것은 아닙니다.

자동화 대상사람이 수행
정상계 CRUD핵심 업무 로직의 복잡한 분기
...

정상계(Normal case)를 Skill로 확실히 다져놓고, 복잡한 부분에 인간의 공수(Man-hour)를 투입한다. 이것만으로도 팀의 체감은 크게 변했습니다.

마치며

E2E 테스트를 작성하는 것 자체보다, 실행 후의 확인·원인 조사·공유가 은근히 더 무겁습니다. Skill로 이 후속 공정을 정비했더니, E2E 운영 비용에 대한 체감이 한 단계 낮아졌습니다.

그리고 그 이상으로, **「LLM에게 무엇을 시키고, 무엇을 시키지 않을 것인가」**를 설계하는 연습으로서, 이 Skill 제작은 상당히 공부가 되었습니다. 같은 발상은 빌드(Build)·배포(Deploy)·리뷰(Review) 자동화에도 응용할 수 있다고 생각합니다.

Claude Code의 Skills를 실용적인 단계로 끌어올리고 싶은 분들에게 참고가 된다면 기쁘겠습니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0