지난 포스팅에서는 화면 뒤에 숨겨진 순수 자바스크립트 로직을 검증하기 위해 Vitest를 성공적으로 구축했다.
단위 테스트로 기초 체력을 다졌으니 이제는 실전이다. 사용자가 실제로 내 포트폴리오 사이트에 들어와서 마우스를 움직이고, 버튼을 클릭하고, 스크롤을 내리는 일련의 행동들을 그대로 복사해 줄 '눈과 손발', 바로 Cypress를 심어줄 차례다.
Cypress를 실제로 사용해보니, 전용 GUI 대시보드 프로그램을 통한 시각적인 DX(개발자 경험)와 신선함이 상상 이상이었다. 직접 세팅해보며 마주한 트러블슈팅과 고민을 기록해본다.✍️
https://hjinn0813.tistory.com/200
테스트 실행기 입문 과정 (vitest 적용)
이전 글에서 테스트 자동화 도구들의 개념에 대해 공부해보며, 현재 나의 학습 단계와 프로젝트 규모 등을 고려했을때 가장 잘 맞는 툴로 vitest와 cypress를 선택했다. 이제 실제로 코드를 작성하
hjinn0813.tistory.com
Cypress 환경 세팅하기
역시 가장 먼저 해야할 일은 의존성을 설치하는 것이다.
프로젝트 루트 터미널에서 cypress를 개발 의존성으로 설치했다.
npm install -D cypress
설치가 완료된 후, package.json에 명령어 스크립트를 등록했다.
이전 편에서 Vitest 명령어를 세분화했던 것처럼, Cypress 역시 로컬 개발용 대시보드를 여는 명령어(cy:open)와 향후 CI 환경에서 백그라운드로 실행할 명령어(cy:run)를 깔끔하게 분리하였다.
"scripts": {
"cy:open": "cypress open", // 로컬 디버깅용 GUI 오픈
"cy:run": "cypress run" // CI/CD 배포용 백그라운드 실행
}
명령어 등록을 마치고 터미널에 npm run cy:open을 입력하여 Cypress를 처음으로 구동했고, 화면에 Cypress Launchpad(GUI 대시보드) 프로그램 창이 새로 팝업되었다. 말로만 듣던 Cypress GUI를 실제로 보니 너무 신기했다.😆




처음 Cypress GUI가 등장해서 [E2E Testing] 버튼을 클릭한 순간, 프로젝트 루트 디렉토리에 /cypress 라는 폴더와 cypress.config.ts 설정파일이 자동으로 생성되었다.
/cypress는 별개의 디렉토리 같아서 기본 구조가 어떻게 되는지 GPT에게 물어봤다.
## cypress 기본 구조
cypress/
├── e2e/ # 실제 E2E 테스트 코드
│ ├── about.cy.ts
│ ├── contact.cy.ts
│ └── project.cy.ts
│
├── fixtures/ # mock 데이터(json 등)
│ └── example.json
│
├── support/
│ ├── commands.ts # custom command
│ └── e2e.ts # 전역 설정
│
├── screenshots/ # 실패 스크린샷
├── videos/ # 실행 영상
└── downloads/ # 다운로드 파일
이 중에 스크린샷, 비디오, 다운로드 등은 Cypress가 테스트를 구동하다가 실패한 순간의 화면을 저장하는 공간이라고 한다.
예전에 실무에서 직접 테스트를 하다가 에러 상황을 발견하면, 화면녹화 버튼을 클릭하고 다시 같은 상황을 재현했던게 생각났다. 그러다 간혹 재현이 안되면 보류하곤 했었는데, Cypress가 자동으로 플로우에 맞춰 테스트를 하고 에러 상황을 녹화해준다니 너무 유용하다는 생각이 들었다!👏
아무튼, 이 파일들이 그대로 깃허브 레포지토리에 올라가는 것을 방지하기 위해, 실전 테스트코드를 작성하기 전에 .gitignore에 해당 폴더들을 미리 추가하였다.
# cypress
/cypress/videos
/cypress/screenshots
/cypress/downloads
설정 최적화하기
기본 세팅을 마쳤으니, 이제 내 포트폴리오 사이트에 맞게 설정을 최적화하고 테스트 코드를 작성할 차례다. 자동 생성된 파일들 중에 딱 필요한 2가지만 깔끔하게 정리했다.
1. /fixtures/example.json 삭제
Cypress가 기본적으로 만들어주는 파일 중, 가짜 데이터 예시 파일(example.json)이 들어있었다. 내 포트폴리오 사이트는 단순 정적 페이지 이동과 클릭 인터랙션 중심이라 해당 json이 필요하지 않아서, 과감하게 이 파일을 삭제했다.
2. cypress.config.ts에 baseUrl 설정
기본 상태에서는 테스트 코드를 짤 때마다 cy.visit('http://localhost:5173/'), cy.visit('http://localhost:5173/about')처럼 전체 주소를 매번 하드코딩해야 한다. 이는 번거로울 뿐만 아니라, 나중에 포트 번호가 바뀌거나 배포 주소로 변경할 때 모든 테스트 코드를 찾아가며 고쳐야 한다. 이를 해결하기 위해 cypress.config.ts 파일의 e2e 속성 내부에 baseUrl을 미리 심어두었다.
import { defineConfig } from "cypress";
export default defineConfig({
e2e: {
baseUrl: 'http://localhost:5173',
},
});
이렇게 설정을 박아두면 앞으로 테스트 코드를 작성할 때 cy.visit('/') 혹은 cy.visit('/about')처럼 상대 경로만 적어도 Cypress가 알아서 베이스 주소를 붙여서 접속하게 된다. 개발자 경험(DX)이 한층 더 쾌적해지는 순간이다.👍
실전 테스트 코드 작성 및 트러블슈팅
본격적인 테스트 코드 작성에 앞서, 포트폴리오 사이트의 구조를 분석하고 어떻게 테스트할지 계획을 수립했다.
- (헤더나 푸터 같은) 공통 컴포넌트 테스트를 위한 코드 파일 1개
- 사이트 내 페이지 단위별로 5개
이렇게 총 6개의 테스트 코드를 작성하기로 하고, 우선 공통 컴포넌트용 테스트 코드를 작성하기 시작했다.
그런데 코드를 입력하자마자 에디터 곳곳이 빨간줄로 뒤덮였다.
타입스크립트와 ESLint에서 발생한 에러였는데, Cypress 설정 초기에 많이 발생하는 에러라고 한다.
1. 타입스크립트 전역 타입 미인지 에러
첫번째 이슈는, 테스트 코드 안에서 describe()나 cy.visit()과 같은 기본 메서드를 입력할 때마다 컴파일러가 "@@ is not defined"라며 빨간 밑줄을 그었다.
Cypress 설치 시 전역 타입들이 함께 들어왔지만 tsconfig.json에 cypress 타입을 등록하지 않았던게 원인이었다.
tsconfig.json 파일의 compilerOptions에 "cypress"를 타입으로 추가했다.
"compilerOptions": {
"types": ["vitest/globals", "cypress", "vite/client"],
}
2. ESLint 전역 변수 미선언 에러
타입만 설정해주면 해결될 줄 알았는데, ESLint의 에러가 남아있었다.
cy나 beforeEach 같은 변수들이 선언되지 않은 채 사용되었다며 no-undef 에러를 사정없이 뿜어냈다. 이를 해결하기 위해 내 프로젝트의 eslint.config.js에서 Cypress가 사용하는 전역 변수들을 안전한 '화이트리스트'로 등록해 주었다.
// cypress
{
files: ["cypress/**/*.ts"],
languageOptions: {
globals: {
...globals.browser,
cy: "readonly",
Cypress: "readonly",
describe: "readonly",
it: "readonly",
beforeEach: "readonly",
afterEach: "readonly",
expect: "readonly",
},
},
},
테스트 코드 구동해보기
예상치 못하게 마주했던 에러들을 해결하고, 본격적으로 테스트 코드들을 작성했다.
가벼운 페이지이기 때문에 하나씩 작성하고 명령어를 실행하여 구동해보는 것보다는, 모든 테스트 코드를 작성해놓고 한번에 구동시키는게 낫겠다고 판단했다. 6개의 테스트 코드를 부지런하게 작성하고, npm run cy:open을 입력해봤다.
💡Cypress 실행을 위한 가장 기본적인 조건
Cypress를 처음 돌릴 때 가장 많이 하는 실수가 있다. 바로 로컬 개발 서버를 켜지 않고 Cypress 명령어부터 입력하는 것이다. Cypress 로봇은 내 프로젝트의 소스 코드를 읽는 게 아니라, 실제 켜져 있는 웹사이트 주소로 접속하여 테스트를 진행한다. 따라서 반드시 아래와 같이 터미널 창을 2개 열어두고 실행해야 한다.
- 터미널 1:npm run dev를 입력해 내 로컬 웹 서버(http://localhost:5173)를 먼저 켜둔다.
- 터미널 2: 서버가 켜진 것을 확인하고npm run cy:open을 입력해 Cypress를 실행한다.
명령어를 입력하자 크롬 브라우저가 새롭게 열리며 Cypress 대시보드가 등장했다. 화면의 [Specs] 메뉴를 보니, 내가 정성껏 작성했던 6개의 테스트 코드 파일들이 폴더 트리 구조로 예쁘게 나열되어 있었다.
가장 먼저 작성했던 공통 컴포넌트용 테스트 파일을 마우스로 클릭하자, 눈앞에서 정말 신기한 장관이 펼쳐졌다.
- 오른쪽 화면: 내 진짜 포트폴리오 사이트가 렌더링되었고, 로봇이 스스로 스크롤을 내리고 버튼을 클릭하며 요리조리 움직였다.
- 왼쪽 화면: 내가 작성한 테스트 코드들이 순서대로 적혀있고, 로봇이 각 단계를 통과할 때마다 실시간으로 초록색 체크 마크(✓)를 띄워주었다.
처음엔 테스트 코드를 잘못 작성해서 빨간 불이 켜지며 에러가 나기도 했지만, Cypress 대시보드에서 에러가 발생한 코드에 마우스를 갖다대면 에러가 발생한 정확한 시점을 화면을 통해 매칭해서 보여줬다. 덕분에 어떤 인터랙션이 꼬였는지 직관적으로 파악하고 코드를 수정할 수 있었다.


모든 테스트 파일이 문제없이 통과하는 것을 확인한 후, 기분 좋게 로컬 작업을 마무리했다. 깃허브에 Pull Request(PR)를 생성한 뒤, 내 포트폴리오 사이트의 main 브랜치로 최종 합병까지 성공적으로 완료했다.🙌
처음으로 내 프로젝트에 E2E 테스트 환경을 구축하며 많은 생각이 들었다. 특히 말로만 듣던 Cypress를 직접 사용해보니, 유저 플로우에 맞춰 사람이 직접 전수검사를 해보는 과정도 분명 필요하겠지만, 운영에 배포되기 이전에는 이런 자동화 툴을 이용해서 테스트를 하는게 DX(개발자 경험)적인 측면에서 상당히 효율적일 것이라는 생각이 들었다.
단위 테스트를 책임지는 Vitest와 사용자의 손발이 되어주는 Cypress까지 직접 포트폴리오 사이트에 적용해보며, 테스트 자동화 파이프라인의 기초를 탄탄하게 다져놓았다. 앞으로 어떤 새로운 기능을 리팩토링하고 추가하더라도, 두려움 없이 든든하게 방어해낼 수 있을 것 같다.

'💻 Frontend > JS, TS' 카테고리의 다른 글
| 테스트 실행기 입문 과정 (vitest 적용) (0) | 2026.05.26 |
|---|---|
| 테스트 자동화 프레임워크 개념 정리 (cypress, playwright, puppeteer) (0) | 2026.05.25 |
| 테스트 실행기 기본 개념 정리 (jest, vitest) (0) | 2026.05.22 |
| 쿠키 설정하기 (0) | 2025.07.04 |
| 교차상태 감지 웹 API - IntersectionObserver() (0) | 2025.04.23 |
