salt-dev

통합 QA를 위한 E2E 설계

.md

E2E를 도입하면서 공부하고 경험한 것들의 기록

왜 번들러 교체가 E2E 도입으로 이어졌나

번들러만 바꾸려고 했습니다. 그런데 E2E 테스트를 도입하게 됐습니다.

시작은 기술 부채 정리였습니다. 레거시 의존성 패키지를 걷어내고 빌드 시스템을 craco에서 Vite로 옮기는 작업이었고, 성과도 분명했습니다. 빌드 시간은 34.6%, 총 배포 소요 시간은 29.3% 줄었습니다.

전환 작업 자체도 순탄하지는 않았습니다. 사내 라이브러리와의 의존성 문제, 순환 참조, SVG 처리까지 — 빌드를 통과시키기 위해 여러 문제를 하나씩 해결했습니다. 그런데 빌드가 끝나자 문제가 자리를 옮겼습니다. 이제는 안정성, 즉 검증이 우리의 문제가 됐습니다. 번들러는 전체 페이지에 영향을 주는 변경이라, 우선 다 같이 통합 QA를 시작해 보기로 했습니다. 개발망에 올리고, 발견되는 이슈를 QA 시트에 쌓으면서 범위를 파악해 가자는 계획이었습니다. 하지만 시작하자마자 한계가 드러났습니다. 본격적인 점검도 아닌 가볍게 둘러보는 과정에서 예상치 못한 디자인 이슈가 연달아 발견됐고, 봐야 할 페이지는 너무 많은데 어디서 무엇이 깨질지 가늠이 되지 않았습니다. 이대로는 모두의 시간만 쓰겠다 싶어, 팀에서 더 효율적인 QA 전략을 마련한 뒤에 다시 요청하기로 정리했습니다.

돌아보면 당연한 결과였습니다. 이런 회귀가 까다로운 이유는 로직을 한 줄도 바꾸지 않았다는 데 있습니다. 유닛 테스트는 전부 통과하지만, 회귀는 코드가 아니라 빌드 레벨에서 터집니다. env 변수 주입 방식, 에셋 처리 경로, polyfill, CSS 주입 순서, lazy chunk 분리 같은 곳들입니다. 둘러보다 발견된 아이콘 누락이 정확히 그런 사례였습니다. SVG 에셋 처리 방식이 번들러마다 다르기 때문입니다. 실제로 이슈는 사내 공통 패키지, 공통 컴포넌트, 에디터처럼 빌드 설정에 민감한 지점에서 잦았습니다.

그렇다고 이런 의심 지점만 보면 되는 것도 아니었습니다. 여러 스텝을 거치는 주요 결제 플로우는 매번 손으로 확인하기가 특히 번거로운 영역입니다. 결제까지 가려면 로그인 인증을 거쳐 여러 정보를 입력해야 하고, 채팅 기반의 복잡한 이벤트 흐름 끝에 결제가 이어지는 경우도 많습니다. 수정사항이 생길 때마다 이 여정을 처음부터 끝까지 다시 밟아 보는 일은 부담이 클 수밖에 없었습니다.

게다가 이 QA가 한 번으로 끝나지 않는다는 것도 분명했습니다. 번들러 다음에는 React 18을 비롯한 메인 라이브러리 버전업이 줄줄이 기다리고 있었습니다. 전체 페이지에 영향을 주는 변경이 올 때마다 전 직군이 달라붙어 수동 QA를 반복할 수는 없었습니다.

그래서 방향을 정했습니다. 매번 사람이 확인하던 “꼭 동작해야 하는 흐름”부터 E2E 테스트로 옮겨, 통합 QA의 기준선을 자동화하기로 했습니다. 첫 대상은 매출과 직결되는 결제 플로우였습니다. 솔직히 고백하면, 요즘은 AI 덕분에 E2E 작성과 유지 비용이 크게 줄었다는 이야기에 우리도 비교적 빠르게 적용해 볼 수 있지 않을까 하는 기대도 출발점에 있었습니다.

시나리오 문서부터 만들기

프론트엔드 챕터는 결제가 있는 주요 도메인을 나눠 E2E를 도입하기로 했습니다. 배너 광고, 채팅(매칭·1:1·마켓), 콘테스트 결제가 대상이었고, 저는 그중 우선 배너 광고를 맡아서 진행하였습니다.

가장 먼저 한 일은 테스트 코드 작성이 아니라 도메인과 시나리오 분석이었습니다. 그런데 시작하자마자 또 하나의 문제를 만났습니다. 정책도 유저 시나리오도 제대로 문서화되어 있지 않았습니다. 많은 회사가 겪는 문제라는데, 저희도 예외는 아니었습니다. 결국 코드를 기반으로 시나리오를 역추적해야 했습니다. 오히려 좋아! 이 참에 문서를 자산으로 쌓자는 마음으로, 등록부터 결제까지의 시나리오 문서와 유저 결정에 따른 플로우차트를 만들었습니다. 다행히 문서화 과정은 걱정보다 수월했습니다. Claude Code에 Notion MCP를 연결해 두니, 코드에서 역추적한 시나리오가 곧바로 노션 테이블로 정리되어 팀에 공유까지 이어졌습니다. 고마워요, Claude + Notion MCP.

배너 광고 등록은 사전 안내부터 정보 입력, 옵션 설정, 결제까지 여러 스텝을 거쳐야 완료에 도달합니다.

배너 광고 등록 절차 — 사전 안내, 기본 정보 입력, 구성 및 옵션 설정, 검토 및 제출을 거쳐 완료되는 5단계 스텝 다이어그램

그리고 ‘결제하기’ 이후에는 금액과 결제수단에 따라 경로가 갈립니다.

결제 흐름 플로우차트 — 사용자 액션 후 리소스가 먼저 생성되고, 결제 필요 여부와 결제 수단(카드·가상계좌)에 따라 경로가 갈린 뒤 완료 페이지에서 승인 토큰 유무로 서버 승인이 결정되는 구조

흐름을 그려 놓고 나니 검증할 시나리오가 그룹 단위로 정리됐습니다. 세어 보니 89개였습니다.

테스트 범위 요약 표 — 약관 모달부터 스텝 네비게이션까지 10개 그룹, 그룹별 다루는 주제와 시나리오 개수

어디까지 자동화해야 할까

시나리오가 89개나 되니, 전부 자동화해 E2E에 맡기기에는 테스트 비용으로 보나 유지보수로 보나 무리라고 판단했습니다. 그래서 시나리오 테이블에 우선순위 등급부터 매겼습니다. 기준은 실패했을 때의 영향도로, Highest부터 Lowest까지 나눴습니다. 화면에 보이는 금액과 서버 승인 금액이 다른 문제와, 스텝 네비게이션이 어색한 문제가 같은 무게일 수는 없기 때문입니다.

방향은 자연스럽게 정해졌습니다. 매출과 직결되는 결제 로직부터, 우선순위가 높은 시나리오부터 자동화한다. 이제 남은 결정은 “어느 선까지 E2E로 검증할 것인가”였습니다. 처음 계획은 단순했습니다. High 레벨 이상은 최대한 smoke 자동화에 넣어 보자는 것이었습니다.

하지만 우선순위만으로는 부족했습니다. High 이상만 추려도 수십 개인데, 그중에는 같은 코드 경로를 지나는 시나리오가 많았기 때문입니다. 예를 들어 개요 스텝에서 이메일 형식이 틀렸을 때와 연락처 형식이 틀렸을 때는 별개의 시나리오지만, 같은 검증 코드를 지납니다. 이걸 전부 E2E로 돌리면 느려지기만 하고 새로 알게 되는 것이 없습니다.

그래서 시나리오마다 “무엇으로 검증할 것인가”부터 정하기로 했습니다. 검증 수단을 E2E, 통합 테스트, 수동, 자동화 불가의 네 층으로 나누고, 이를 검증 layer라고 불렀습니다.

layer 배정 원칙은 Testing Trophy를 참고했습니다. E2E는 실제 사용 환경에 가장 가까워 안정성을 확인하기 좋은 대신, 실행이 느리고 작성·관리 비용이 큽니다. 모든 시나리오를 E2E로 감당할 수는 없습니다. 그래서 빈값·형식 검사처럼 단계 안에서 확인할 수 있는 것은 최대한 통합 테스트에 넘기고, E2E는 등록부터 결제까지 하나의 흐름이 실제로 이어지는지 검증하는 데만 씁니다. 시나리오 테이블에 영향도와 검증 layer를 나란히 표기하고 나니, “89개를 어떻게 다 자동화하지”라는 막막함이 감당 가능한 계획으로 바뀌었습니다.

마지막으로 문서와 코드가 어긋나지 않도록 규약을 하나 뒀습니다. 시나리오 번호(7-1 같은)를 테스트 코드의 타이틀에 그대로 남깁니다. 테스트가 실패하면 어느 시나리오가 깨졌는지 코드에서 바로 역추적할 수 있습니다.

E2E 스캐폴딩

이제 시나리오를 기반으로 Playwright를 쌓기 시작했습니다. 가장 먼저 한 것은 폴더 구조 설계입니다. 도메인 단위로 나누고, 그 안에서 pages(페이지 객체), tests(spec), docs(시나리오·전략 문서)를 다시 나눴습니다.

e2e/
├─ config/                # 타임아웃·경로·대상 URL 같은 공통 설정
├─ domain/                # 도메인 슬라이스 모음
│  ├─ banner/             # 도메인 슬라이스
│  │  ├─ pages/           # 페이지 객체(POM)
│  │  ├─ components/      # 페이지를 구성하는 UI 조각 객체 (모달·Nav 등)
│  │  ├─ tests/           # 시나리오 spec
│  │  └─ docs/            # 도메인 시나리오·전략 문서
│  ├─ deal/               # (동일 구조)
│  └─ contest/            # (동일 구조)
├─ shared/                # 모든 도메인의 전제 — 로그인 setup, 공통 fixture
└─ docs/                  # 인프라 결정 기록(ADR)

이렇게 나눈 근거는 유지보수입니다. 한 도메인을 손볼 때 봐야 할 테스트·페이지 객체·문서가 한 폴더에 모여 있고, 도메인끼리는 서로 참조하지 않아 한쪽을 고치거나 지워도 다른 도메인 테스트에 영향이 없습니다. 채팅·콘테스트로 확장할 때도 같은 구조를 복제해서 시작하면 됩니다.

Playwright API와 테스트 코드를 공부하다 보니 낯선 용어들도 자연스럽게 익히게 됐습니다. 자주 등장한 것만 추리면 이렇습니다.

용어
fixture테스트 시작 전에 필요한 것들을 준비해 테스트에 주입해 주는 장치. Playwright에서 test()의 인자로 받는 page·context가 전부 fixture이고, 직접 확장할 수도 있다
POM (Page Object Model)페이지의 셀렉터와 조작을 클래스로 묶는 패턴. 테스트는 “무엇을 하는지”만 말하고, “어디를 어떻게 클릭하는지”는 페이지 객체가 안다
smoke핵심 기능이 최소한 동작하는지 넓고 얕게 확인하는 테스트. Playwright에서는 project(같은 설정으로 묶어 실행하는 테스트 그룹)를 나눠 smoke 스위트만 따로 돌릴 수 있다
flaky코드 변경이 없는데도 실행할 때마다 pass/fail이 뒤바뀌는 비결정적 테스트

여기까지가 본격적인 테스트 작성 전의 설계입니다. 인증 처리, 배너 결제 smoke 테스트, CI 적용까지 — 실제 테스트를 쌓으며 겪은 경험은 다음 글에서 다루겠습니다.