E2E 테스트와 시각적 회귀 테스트

김진아·2026년 6월 22일

이번 주차는 E2E 테스트시각적 회귀 테스트에 대해 공부했다.

큰 흐름은 이렇게 볼 수 있다.

E2E 테스트:
사용자가 실제로 서비스를 쓰는 흐름이 끝까지 잘 동작하는지 확인

시각적 회귀 테스트:
화면 UI가 의도치 않게 깨지거나 바뀌지 않았는지 확인

관련 도구들은 역할이 조금씩 다르다.

Cypress / Playwright → E2E 테스트 도구
Storybook → 컴포넌트를 독립적으로 보여주는 도구
Chromatic → Storybook 화면을 캡처해서 이전 화면과 비교하는 도구

1. E2E 테스트란?

E2E는 End-to-End의 줄임말이다.

말 그대로 사용자가 서비스를 사용하는 흐름을 처음부터 끝까지 테스트하는 것이다.

예를 들어 로그인 기능이라면 이런 흐름을 테스트한다.

로그인 페이지 접속
이메일 입력
비밀번호 입력
로그인 버튼 클릭
홈 화면으로 이동했는지 확인

즉, E2E 테스트의 핵심은 이거다.

사용자가 이 기능을 실제로 끝까지 사용할 수 있는가?

Unit Test가 작은 함수나 컴포넌트 단위를 본다면, E2E 테스트는 실제 브라우저에서 사용자가 하는 행동을 기준으로 본다.

예를 들어 공동구매 서비스라면 이런 흐름이 E2E 테스트 대상이 될 수 있다.

로그인 → 상품 목록 → 상품 상세 → 참여하기 → 수량 선택 → 참여 완료 → 마이페이지 확인

이렇게 핵심 흐름을 자동화해두면 배포 전에 중요한 기능이 깨졌는지 확인할 수 있다.


2. E2E 테스트의 장단점

장점은 실제 사용자 관점에 가장 가깝다는 것이다.

그래서 이런 문제를 잡을 수 있다.

버튼이 실제로 클릭되는지
로그인 후 페이지 이동이 되는지
토큰 저장 후 인증 상태가 유지되는지
폼 제출 후 결과 화면이 잘 뜨는지

하지만 단점도 있다.

브라우저를 실제로 띄우고 페이지 이동, 클릭, 입력을 수행하기 때문에 상대적으로 느리고 무겁다.

또 요소를 너무 구현 세부사항에 의존해서 찾으면 테스트가 쉽게 깨질 수 있다.

예를 들어 className으로 찾는 경우:

cy.get('.login-button').click();

디자인 수정으로 className이 바뀌면 기능은 멀쩡한데 테스트만 실패할 수 있다.

그래서 E2E 테스트는 모든 기능에 다 작성하기보다는, 서비스에서 중요한 핵심 흐름 위주로 작성하는 것이 좋다.


3. Cypress

Cypress는 대표적인 E2E 테스트 도구다.

사용자가 브라우저에서 하는 행동을 코드로 작성할 수 있다.

describe('로그인', () => {
  it('이메일과 비밀번호를 입력하면 로그인된다', () => {
    cy.visit('/login');

    cy.get('[data-cy=email-input]').type('test@example.com');
    cy.get('[data-cy=password-input]').type('password123');
    cy.get('[data-cy=login-button]').click();

    cy.url().should('include', '/home');
    cy.contains('환영합니다').should('be.visible');
  });
});

처음에는 Cypress가 className이나 id로 요소를 찾는 도구처럼 느껴졌는데, 실제로는 className보다 테스트 전용 속성을 쓰는 것이 더 안정적이다.

<input data-cy="email-input" />
<button data-cy="login-button">로그인</button>
cy.get('[data-cy=email-input]').type('test@example.com');
cy.get('[data-cy=login-button]').click();

className은 스타일 변경 때문에 자주 바뀔 수 있지만, data-cy는 테스트를 위한 이름이라 상대적으로 안정적이다.


4. Playwright

Playwright도 E2E 테스트 도구다.

Cypress처럼 실제 브라우저에서 페이지에 접속하고, 클릭하고, 입력하고, 결과를 확인할 수 있다.

import { test, expect } from '@playwright/test';

test('로그인할 수 있다', async ({ page }) => {
  await page.goto('/login');

  await page.getByLabel('이메일').fill('test@example.com');
  await page.getByLabel('비밀번호').fill('password123');
  await page.getByRole('button', { name: '로그인' }).click();

  await expect(page).toHaveURL(/home/);
  await expect(page.getByText('환영합니다')).toBeVisible();
});

Playwright는 getByRole, getByLabel, getByText처럼 사용자가 실제로 인식하는 방식으로 요소를 찾는 API가 잘 제공된다.

그래서 React Testing Library와 문법 느낌이 비슷하게 느껴졌다.


5. 내가 헷갈렸던 부분

Playwright와 React Testing Library는 관련 있는 도구일까?

처음에는 Playwright 코드가 React Testing Library와 비슷해서 둘이 관련 있는 도구인가 싶었다.

React Testing Library:

screen.getByRole('button', { name: '로그인' });
screen.getByLabelText('이메일');
screen.getByText('환영합니다');

Playwright:

await page.getByRole('button', { name: '로그인' }).click();
await page.getByLabel('이메일').fill('test@example.com');
await expect(page.getByText('환영합니다')).toBeVisible();

정리하면, 둘이 직접 연결된 도구라서 비슷한 것은 아니다.

둘 다 사용자 관점으로 테스트하자는 철학을 가지고 있어서 비슷한 API가 나온 것이다.

Vitest → 테스트 실행기
React Testing Library → React 컴포넌트 테스트 도구
Playwright → 실제 브라우저 기반 E2E 테스트 도구

즉, Playwright가 Vitest와 비슷하다기보다는, Playwright의 요소 찾는 방식이 React Testing Library와 비슷하다고 보는 게 맞다.


React Testing Library로 주소창 테스트도 할 수 있을까?

React Testing Library는 실제 브라우저를 띄우는 도구가 아니다.

보통 jsdom이라는 가짜 DOM 환경에서 컴포넌트를 테스트한다.

그래서 input 입력, 버튼 클릭, 텍스트 확인은 가능하다.

render(<LoginForm />);

await user.type(screen.getByLabelText('이메일'), 'test@example.com');
await user.click(screen.getByRole('button', { name: '로그인' }));

expect(screen.getByText('로그인 성공')).toBeInTheDocument();

하지만 실제 주소창 이동, 새로고침, 뒤로가기 같은 브라우저 동작을 테스트하는 도구는 아니다.

React Router의 MemoryRouter를 쓰면 라우팅을 흉내낼 수는 있다.

render(
  <MemoryRouter initialEntries={['/login']}>
    <App />
  </MemoryRouter>
);

하지만 이것은 실제 주소창이 바뀌는 것이 아니라, 메모리상에서 라우팅 상태를 흉내내는 것이다.

실제 URL 이동을 확인하려면 Playwright나 Cypress를 써야 한다.

await expect(page).toHaveURL(/home/);

6. Cypress와 Playwright 비교

구분CypressPlaywright
역할E2E 테스트 도구E2E 테스트 도구
코드 느낌cy.visit, cy.get, cy.containspage.goto, page.getByRole, expect
요소 찾기data-cy 같은 selector를 많이 사용role, label, text 기반 locator를 많이 사용
장점실행 과정을 보기 쉽고 디버깅이 직관적사용자 관점 locator, 여러 브라우저 테스트에 강함

둘 중 뭐가 무조건 더 좋다기보다는 팀 상황과 테스트 목적에 따라 선택하면 될 것 같다.

처음 이해할 때는 이렇게 잡았다.

Cypress:
테스트용 이름(data-cy)으로 요소를 안정적으로 찾는 느낌

Playwright:
사용자가 보는 이름(role, label, text)으로 요소를 찾는 느낌

물론 Cypress도 Testing Library를 추가하면 사용자 관점 query를 사용할 수 있다.


7. 시각적 회귀 테스트란?

시각적 회귀 테스트는 UI의 생김새가 의도치 않게 바뀌지 않았는지 확인하는 테스트다.

여기서 회귀는 기존에 잘 되던 것이 다시 망가지는 것을 의미한다.

예를 들어 버튼 스타일을 조금 수정했는데, 그 버튼이 여러 페이지에서 쓰이고 있었다면 예상 못한 화면 깨짐이 생길 수 있다.

버튼 크기가 커짐
카드 레이아웃이 밀림
텍스트가 두 줄로 밀림
모바일 화면에서 요소가 잘림
모달 위치가 어긋남

이런 문제는 일반 테스트 코드로 하나하나 확인하기 어렵다.

그래서 시각적 회귀 테스트는 화면을 스크린샷으로 비교한다.

이전 정상 화면 캡처
현재 화면 캡처
두 화면 비교
차이가 있으면 알려줌

의도한 변경이면 승인하고, 의도하지 않은 변경이면 수정하면 된다.


8. Storybook

Storybook은 컴포넌트를 앱에서 분리해서 독립적으로 볼 수 있게 해주는 도구다.

예를 들어 버튼 컴포넌트를 앱의 특정 페이지까지 이동하지 않고도 따로 확인할 수 있다.

<Button variant="primary">확인</Button>
<Button variant="secondary">취소</Button>
<Button disabled>비활성화</Button>

Storybook에서는 컴포넌트의 상태 하나하나를 story로 만든다.

export const Default = {
  args: {
    children: '확인',
  },
};

export const Disabled = {
  args: {
    children: '비활성화',
    disabled: true,
  },
};

이렇게 해두면 버튼의 기본 상태, 비활성화 상태 등을 독립적으로 확인할 수 있다.

Storybook 자체는 테스트 도구라기보다는 UI 컴포넌트를 개발하고 문서화하는 도구에 가깝다.

하지만 컴포넌트 상태를 story로 정리해두면, 이후 시각적 회귀 테스트의 기준으로 활용할 수 있다.


9. Chromatic

Chromatic은 Storybook과 함께 사용하는 시각적 회귀 테스트 도구다.

Storybook에 등록된 story들을 캡처하고, 이전 화면과 현재 화면을 비교한다.

Storybook → 컴포넌트 상태를 story로 정리
Chromatic → story 화면을 캡처해서 이전 화면과 비교

예를 들어 Button story가 있다고 하면:

Button / Default
Button / Disabled
Button / Loading

Chromatic은 각각의 화면을 캡처해두고, PR에서 UI가 바뀌면 이전 화면과 비교해준다.

의도한 변경이면 승인하고, 의도하지 않은 변경이면 수정하면 된다.

비유하면 이렇게 이해했다.

Storybook = 컴포넌트 전시장
Chromatic = 전시장 사진을 찍고 이전 사진과 비교하는 도구

10. E2E 테스트와 시각적 회귀 테스트의 차이

구분E2E 테스트시각적 회귀 테스트
관심사기능 흐름화면 생김새
질문사용자가 기능을 끝까지 사용할 수 있는가?UI가 의도치 않게 바뀌지 않았는가?
대표 도구Cypress, PlaywrightStorybook, Chromatic
검증 방식클릭, 입력, 이동, 결과 확인스크린샷 비교

예를 들어 로그인 버튼을 기준으로 보면:

E2E 테스트:
로그인 버튼을 누르면 실제로 로그인이 되는가?

시각적 회귀 테스트:
로그인 버튼의 크기, 색상, 위치가 의도치 않게 바뀌지 않았는가?

둘은 대체 관계가 아니라 보완 관계다.


11. 전체 정리

이번 주차 내용을 정리하면 이렇게 볼 수 있다.

Vitest + React Testing Library:
컴포넌트가 사용자 행동에 잘 반응하는지 테스트

Cypress / Playwright:
실제 브라우저에서 사용자의 핵심 흐름을 테스트

Storybook:
컴포넌트를 독립적으로 보여주고 상태별로 정리

Chromatic:
Storybook 화면을 이전 화면과 비교해서 UI 변경 감지

테스트마다 보는 관점이 다르다.

Unit Test:
작은 코드가 맞는지

Integration Test:
여러 조각이 함께 잘 동작하는지

E2E Test:
사용자가 실제로 기능을 끝까지 쓸 수 있는지

Visual Regression Test:
사용자가 보는 화면이 깨지지 않았는지

결국 이번 주차의 핵심은 이거다.

E2E 테스트는 기능 흐름을 확인하고,
시각적 회귀 테스트는 화면의 생김새를 확인한다.

둘 다 사용자가 실제로 마주하는 경험을 안정적으로 만들기 위한 테스트라고 이해하면 될 것 같다.

0개의 댓글