
회사에서 개발 중인 서비스의 첫인상을 결정하는 중요한 시나리오에 안정성을 확보하고 QA에 드는 공수를 줄이고자 E2E 테스트 도입을 제안받았다. 영어 교육 서비스인 우리 프로젝트는 본격적으로 컨텐츠를 소비하기에 앞서 ‘레벨테스트’를 통해 사용자의 레벨을 평가하여 알맞은 반에 배치하는 선행 작업이 이루어져야 한다.
레벨테스트는 문제를 듣거나 보고 답안을 선택하는 유형의 ‘Written Test’와 주어진 자료를 보고 Speaking을 하는 ‘Oral Test’로 구성되어 있다. 이 중에서 Oral Test의 경우, 웹캠을 사용하여 사용자의 화상 비디오와 음성을 동시에 녹화하여 레벨을 결정하는 중요한 자료로 쓰이고 있다.
TDD(Test Driven Development, 테스트 주도 개발)로 개발된 프로젝트가 아니며, E2E 테스트 자체가 다른 테스트에 비해 무거운 테스트라는 성격을 지니고 있기 때문에 모든 요소에 테스트 코드를 붙이기 보다는 중요하다고 생각되는 시나리오에 대해서 테스트를 도입하는게 현명하다고 생각했다.
최근 iPad 사용자에 한해 Oral Test의 녹화 데이터가 정상적으로 넘어오지 않는 이슈가 발생하였으며, Written Test에서 선택지의 답안을 모두 선택했는데 다음 문제로 이동하는 버튼이 활성화되지 않는 이슈가 발생하였다. 짐작되는 원인으로는 리액트 내부 부수 효과함수 useEffect들이 스파게티 코드로 많이 동작하여 state의 타이밍 이슈로 짐작된다. 하지만, 리팩토링하여 코드를 다시 작성하기에는 들이는 공수 대비 아웃풋에 대한 효율성이 좋지 않았기 때문에 기존에 개발하신 분께서 의심되는 부분을 수정하셨다고 하였으니, 테스트 코드만 작성하면 되는 상황이었다.
리액트 진영에서 e2e 테스트 후보로 꼽히는 것은 크게 cypress, playwright이었다. cypress는 선발주자로 다운로드 수가 여전히 압도적으로 많긴 하지만, 시간이 오래 걸리는 e2e 테스트에서 병렬 테스트를 사용하려면 유료로 전환해야 사용할 수 있는 치명적인 단점이 있었다. 따라서, 병렬 테스트가 무료이며 훨씬 더 가볍고, 여러 브라우저와 모바일 에뮬레이터까지 지원하는 playwright을 최종적으로 채택하게 되었다.
우선, Chrome 브라우저와 Edge 브라우저, Safari 브라우저, Tablet Safari 브라우저 이렇게 네 개의 크로스 브라우징 환경에서 테스트를 하기 위해 playwright 설정 파일을 다음과 같이 구성했다.
1// playwright.config.ts23export default defineConfig({4 // ...5 projects: [6 {7 name: 'Chrome',8 use: {9 ...devices['Desktop Chrome'],10 },11 },12 {13 name: 'Edge',14 use: {15 ...devices['Desktop Edge'],16 },17 },18 {19 name: 'Webkit',20 use: {21 ...devices['Desktop Safari'],22 },23 },24 {25 name: 'WebKit Tablet',26 use: {27 ...devices['iPad Pro 11 landscape'],28 },29 },30 ]31})테스트할 요소로는 아래와 같은 요소들이 있었다.
전반적인 테스트 코드 작성은 크게 어렵지 않았다. 공식 문서가 잘 정리되어 있기도 하고, playwright에서 제공하는 npx playwright codegen을 사용하면 사용자의 동작을 그대로 테스트 코드로 변환해주기 때문에 편리했다. 하지만, 몇 가지 문제점에 직면하고 해결하는 경험을 하게 되었다.
Written Test와 Oral Test는 각각 테스트 문제에 접근하기 전에 문제를 푸는 방법을 동영상을 통해 가이드하고 있다. 하지만, Chrome 브라우저에서 분명 개발 환경에서는 잘 보이던 동영상이 아예 재생이 되지 않는 문제가 발생하였고, 이에 따라 다음 화면으로 넘어가지 못하는 상황에 직면하였다.
이는 playwright 테스트 브라우저 환경에서 비디오 코덱이 설정이 되어 있지 않기 때문에 발생한 에러로, 설정 파일에서 비디오 코덱을 추가해줌으로써 문제를 해결했다.
사실 개발을 시작하면서 걱정이 되었던 문제이기도 했다. e2e 테스트를 도입한 타사의 블로그 리뷰를 보면 대부분 QA를 하기에는 비효율적인 지엽적인 부분들(예를 들면, 회원가입 시 입력 폼의 입력값에 따른 상태 변화 체크 등)에 테스트 코드를 도입한 케이스가 많이 보였다.
반면에, 우리 서비스는 테스트 시나리오에서 레벨 테스트 문제를 한 문제 진행할 때마다 진도가 서버에 저장되고, 다시 처음부터 레벨테스트에 접근을 하면 저장된 마지막 문제부터 시작되도록 로직이 구현되어 있었다. 따라서 항상 같은 테스트 환경에 접근할 수가 없었고, 조치가 필요했다.
첫 번째 방법은 테스트를 위한 프론트엔드 코드를 추가하는 것이었다. 테스트를 위한 계정을 추가하고, 해당 테스트 계정은 서버로부터 isTestUser라는 플래그 값을 받아서 해당 값이 true일 때만 동작하는 테스트 코드를 추가하는 것이다. 하지만 이 방식을 채택할 경우 향후 코드 관리가 더욱 어려워지고, 가급적 기존 코드를 수정하지 않는 채로 테스트만 진행하는 것이 장기적으로 더 현명하다고 판단했다.
두 번째 방법은 테스트를 진행하면서 변경되는 진도 값을 초기화하는 API 호출 로직을 테스트 앞, 뒤에 붙여서 매번 테스트를 진행하기 전에 상태를 초기화 시켜주는 것이다. 이 방법이 첫 번째 방법보다는 유지보수 측면에서 훨씬 깔끔하다고 생각이 되어 두 번째 방법을 채택하게 되었다.
Oral Test에서 웹캠을 녹화하는 로직이 포함되어 있기 때문에 정상적인 테스트 통과를 위해서는 비디오와 오디오에 대한 권한 허용이 필수적이었다. 각 브라우저마다 권한이 필요할 때마다 팝업 요청이 뜨긴 했지만, 최종적으로 CI/CD에서 자동으로 테스트를 실행하는 로직을 포함시키기 위해서는 권한에 대한 허용을 테스트 코드 안에서 녹여내야 했다.
Chrome 브라우저에서는 권한 허용에 대한 내장 옵션을 제공하여, 해당 코드를 추가할 시 권한을 코드 딴에서 허용해서 팝업이 뜨지 않도록 할 수 있었다.
하지만, Safari 브라우저에서는 권한 허용에 대한 유저의 직접적인 인터랙션 없이는 권한 설정이 허용되지 않으며, Chrome 브라우저처럼 내장된 코드를 제공하지 않았다. 따라서, 테스트를 돌릴 때마다 브라우저 권한 요청 팝업이 등장하며 허용을 해줘야 원활하게 테스트가 진행되었다.
이는 테스트 진행에는 크게 문제가 되지 않지만, CI/CD 로직에 포함하여 prod 브랜치에 병합될 때 테스트를 실행시켜서 안정성을 확보하려는 목표 달성에서는 거리가 멀어지게 되는 것이다. 결과적으로 비록 모든 브라우저에 대해서 CI/CD 로직에 테스트 코드를 실행시킬 수는 없지만, Chrome 브라우저에 한해 포함시키기로 결정했다.
변경한 코드를 최종 배포 전에 테스트 코드를 통과시키기 위해서는 배포된 환경의 주소가 아닌, 배포 전의 로컬에서 실행시킨 주소의 프로젝트가 필요했다. 이를 위해, playwright 설정 파일에서 webServer를 추가했다.
하지만, 로컬 환경에서는 프로젝트 실행을 시키지 않아도 정상적으로 동작하던 webServer가 CI 프로세스를 거치면 환경설정 파일을 찾지 못하여 에러가 발생하는 현상에 직면했다.
내부적으로 yarn start:dev는 yarn prestart && env-cmd -f .env.dev craco start --max_old_space_size=8192 명령어를 실행하는데, .env.dev 환경설정 파일을 경로에서 찾지 못했다는 에러이다.
생각해보면 .env와 같은 환경설정 파일은 로컬에서 빌드할 때 사용하기 위해 넣었던 것이지, 우리 서비스의 CI 환경에서는 해당 파일없이 빌드 스펙 내에 적힌 env > variables를 참조하기 때문에 해당 파일을 찾지 못하는 것이 당연한 것이다.
대신, webServer의 env 속성에 환경설정 파일에 있는 값을 옮겨적은 후, craco start --max_old_space_size=8192 명령어로 실행을 대체했다.
하지만, 여전히 aws codebuild를 통과하지 못하는 상황이 발생하였다.
다른 포트번호로 테스트를 진행하면 테스트 내에서 서버와의 통신 중 CORS 문제가 발생함에 따라 로컬에서 필요에 따라 테스트를 진행하는 것으로 개발을 중단되었다.