Playwright로 E2E(End-to-End) 테스트 코드를 작성하면서 개별 테스트 케이스마다 수동으로 로그인 페이지에 접속하고 아이디와 비밀번호를 입력하게 만든 경험이 있을 것이다.
수십 개의 테스트 파일이 실행될 때마다 매번 로그인 UI를 거치면 전체 테스트 수행 시간이 분 단위로 늘어나고, 인증 서버 타임아웃이나 Rate Limit으로 인해 테스트가 간헐적으로 실패하는 현상이 빈번하게 발생한다.
다만 많은 개발자들이 이를 해결하는 고유 기능인 storageState의 저장 및 주입 메커니즘을 정확히 알지 못해 불필요한 반복 코드를 방치하곤 한다.
이번에는 Playwright의 storageState 개념부터 세션 저장 파일 자동화 생성 방법, 실제 구성 코드와 주의사항까지 완벽하게 정리해서 소개하겠다.
E2E 테스트에서 인증 상태를 유지하는 가장 비효율적인 방법은 모든 테스트의 beforeEach 블록에서 로그인 UI 폼을 직접 입력하는 것이다.
Playwright는 브라우저 컨텍스트(Browser Context) 단위로 독립된 쿠키, LocalStorage, SessionStorage 상태를 격리하여 관리한다.
storageState 기능을 활용하면 한 번 로그인한 시점의 모든 웹 스토리지와 쿠키 데이터를 파일(JSON) 형태로 저장하고, 이후 실행되는 모든 테스트 컨텍스트에 해당 저장 상태를 즉각 주입할 수 있다.
| 구분 | 매번 UI 로그인 방식 | storageState 세션 재사용 방식 |
|---|---|---|
| 실행 속도 | 매 매우 느림 (테스트마다 2~5초 추가 소요) | 매우 빠름 (JSON 로드로 0.01초 소요) |
| 인증 서버 부하 | 테스트 수만큼 로그인 API 과호출 발생 | 글로벌 셋업 시 단 1회만 호출 |
| 테스트 안정성 | 인증 UI/네트워크 불안정 시 전체 테스트 실패 | 인증 상태가 파일로 고정되어 높은 안정성 제공 |
| 코드 중복성 | 테스트 파일마다 로그인 헬퍼 함수 호출 필요 | 설정 파일(playwright.config.ts)에서 일괄 적용 |
storageState를 적용하는 방법은 크게 두 가지로 나뉜다.
첫 번째는 globalSetup 스크립트를 사용하여 전체 테스트 실행 전에 한 번 로그인하고 저장하는 방식이다.
두 번째는 Playwright v1.31 이상에서 권장하는 Setup Project 패턴을 사용하여 테스트 의존성(Dependencies)으로 인증 프로세스를 분리하는 방식이다.
Setup Project 패턴을 활용하면 Playwright UI 모드나 HTML 리포터에서도 인증 상태 생성 과정이 명확하게 시각화되는 장점이 있다.
잘못 구현된 불필요한 반복 로그인 코드와, Setup Project 및 storageState를 활용해 최적화한 올바른 구현 코드를 비교해보자.
import { test, expect } from '@playwright/test';
test.beforeEach(async ({ page }) => {
// 테스트 실행 때마다 매번 UI에 접속하여 로그인 진행 (엄청난 시간 낭비)
await page.goto('https://example.com/login');
await page.fill('#username', 'testuser');
await page.fill('#password', 'password123');
await page.click('#login-button');
await page.waitForURL('https://example.com/dashboard');
});
test('마이페이지 정보 조회', async ({ page }) => {
await page.goto('https://example.com/mypage');
await expect(page.locator('.user-name')).toHaveText('testuser');
});✗ 문제점: 테스트 케이스가 50개라면 로그인 폼 입력도 50번 실행된다. UI 요소 변경이나 네트워크 지연 시 지연 시간이 극대화된다.
먼저 인증 상태를 생성하는 setup 테스트 파일(tests/auth.setup.ts)을 작성한다.
import { test as setup, expect } from '@playwright/test';
import path from 'path';
const authFile = path.join(__dirname, '../playwright/.auth/user.json');
setup('사용자 인증 및 세션 저장', async ({ page }) => {
await page.goto('https://example.com/login');
await page.fill('#username', 'testuser');
await page.fill('#password', 'password123');
await page.click('#login-button');
// 로그인 완료 후 대시보드 이동 확인
await page.waitForURL('https://example.com/dashboard');
// 현재 브라우저 컨텍스트의 모든 쿠키 및 LocalStorage 상태를 JSON 파일로 저장
await page.context().storageState({ path: authFile });
});이어서 playwright.config.ts 파일에 setup 프로젝트와 일반 테스트 프로젝트 간의 의존관계를 설정한다.
import { defineConfig, devices } from '@playwright/test';
import path from 'path';
export const STORAGE_STATE = path.join(__dirname, 'playwright/.auth/user.json');
export default defineConfig({
projects: [
// 1. 인증 상태를 생성하는 Setup 프로젝트
{
name: 'setup',
testMatch: /.*\.setup\.ts/,
},
// 2. 인증 상태를 재사용하는 메인 테스트 프로젝트
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
storageState: STORAGE_STATE, // 저장된 세션 파일 주입
},
dependencies: ['setup'], // setup 프로젝트가 먼저 실행되도록 지정
},
],
});이제 실제 기능 테스트 코드는 별도의 로그인 과정 없이 곧바로 인증이 필요한 페이지에 접근할 수 있다.
import { test, expect } from '@playwright/test';
test('마이페이지 정보 조회', async ({ page }) => {
// 이미 로그인된 세션 상태로 바로 접속 가능
await page.goto('https://example.com/mypage');
await expect(page.locator('.user-name')).toHaveText('testuser');
});
Running 2 tests using 2 workers
[1/2] [setup] › tests/auth.setup.ts:6:5 › 사용자 인증 및 세션 저장 (1.2s)
[2/2] [chromium] › tests/mypage.spec.ts:3:5 › 마이페이지 정보 조회 (0.3s)
2 passed (1.8s)
storageState를 도입할 때 자주 겪는 트러블슈팅 포인트를 잘 파악해두어야 한다.
✗ 잘못된 것: 저장된 인증 JSON 파일을 Git 저장소에 그대로 커밋하는 행위
✓ 올바른 것: .gitignore 파일에 playwright/.auth/ 경로를 추가하여 민감한 세션 토큰이나 쿠키 정보 유출을 방지해야 한다.
✗ 잘못된 것: 토큰 만료 시간을 고려하지 않고 오래된 storageState 파일 재사용
✓ 올바른 것: 테스트 실행 전 만료 여부를 검증하거나 CI/CD 파이프라인 시작 시 세션 파일을 새로 생성하도록 유도해야 한다.
✗ 잘못된 것: OAuth2 소셜 로그인이나 2FA(2단계 인증)가 걸려있는 계정을 매번 UI 테스트에 직접 사용
✓ 올바른 것: 테스트 전용 로컬 DB에 세션 쿠키를 직접 주입하거나 백엔드 전용 테스트 토큰 발급 API(REST API)를 request 컨텍스트로 호출하여 storageState를 바이패스 작성해야 한다.
Playwright의 storageState 기능은 repetitive한 로그인 동작을 제거하여 E2E 테스트 전체 수행 시간을 획기적으로 줄여주는 필수 최적화 기술이다.
반복 로그인 연산 감소 효과는 테스트 수가 늘어날수록 더욱 커진다.
작은 최적화 습관이 모여서 견고하고 빠른 CI/CD 테스트 파이프라인을 만든다는 점을 잊지 말자.
이 글의 Setup Project 구성과 storageState 설정을 참고해 프로젝트에 즉시 적용하면, Flaky 테스트 방지와 테스트 실행 속도 10배 향상이라는 최종 결과를 얻을 수 있을 것이다.