
이번 시간에는 Selenium 을 활용한 브라우저 자동화 테스트와 E2E(End-to-End) 테스트 자동화를 진행했습니다. 단위 테스트에서 한 발 더 나아가 실제 사용자의 행동 흐름을 자동으로 검증하는 과정입니다.
Selenium 은 브라우저를 코드로 자동 조작할 수 있는 오픈 소스 테스트 프레임워크입니다. 실제 브라우저를 실행하고 클릭, 입력, 스크롤 등의 사용자 동작을 자동화하여 UI가 의도한 대로 동작하는지 검증합니다.
단위 테스트가 개별 함수나 컴포넌트를 검증한다면, Selenium은 실제 브라우저 환경에서 사용자 입장의 시나리오를 검증합니다.
| 구성 요소 | 설명 |
|---|---|
| Selenium WebDriver | 브라우저를 직접 제어하는 핵심 도구입니다 |
| Selenium IDE | 브라우저 확장 프로그램으로 테스트를 녹화·재생합니다 |
| Selenium Grid | 여러 브라우저와 OS 환경에서 병렬로 테스트를 실행합니다 |
from selenium import webdriver
from selenium.webdriver.common.by import By
# 브라우저 실행
driver = webdriver.Chrome()
# 페이지 접속
driver.get("http://localhost:3000")
# 요소 찾기 및 클릭
driver.find_element(By.ID, "login-btn").click()
# 입력
driver.find_element(By.NAME, "email").send_keys("test@test.com")
driver.find_element(By.NAME, "password").send_keys("password123")
# 제출
driver.find_element(By.CSS_SELECTOR, "button[type='submit']").click()
# 브라우저 종료
driver.quit()
좋은 테스트 전략은 테스트 피라미드 구조를 따릅니다.
/ E2E 테스트 \ ← 적게, 핵심 시나리오만
/ 통합 테스트 \ ← 중간
/ 단위 테스트 \ ← 많이, 빠르게
E2E 테스트는 실행 시간이 길고 환경에 민감하기 때문에 핵심 사용자 시나리오에만 집중하는 것이 좋습니다. 모든 케이스를 E2E로 검증하려 하면 테스트 유지 비용이 급격히 늘어납니다.
| 시나리오 | 검증 내용 |
|---|---|
| 회원가입 | 이메일·비밀번호 입력 → 가입 완료 → 로그인 페이지 이동 |
| 로그인 | 자격증명 입력 → 로그인 성공 → 홈 화면 이동 |
| 문서 생성 | 새 문서 클릭 → 제목·내용 입력 → 저장 → 목록에 표시 |
| 문서 편집 | 기존 문서 클릭 → 내용 수정 → 자동 저장 확인 |
| 로그아웃 | 로그아웃 클릭 → 로그인 페이지로 이동 |
Selenium IDE 는 Chrome/Firefox 확장 프로그램으로, 브라우저에서 사용자의 행동을 녹화(Record) 하여 자동으로 테스트 케이스를 생성해주는 도구입니다.
직접 코드를 작성하지 않아도 버튼 클릭, 텍스트 입력 등의 동작이 자동으로 기록되어 테스트 케이스가 만들어집니다.
1. Selenium IDE 확장 프로그램 설치
2. 새 프로젝트 생성
3. Record 버튼 클릭 → 브라우저에서 테스트할 동작 수행
4. Stop 버튼 클릭 → 녹화된 테스트 케이스 확인
5. Play 버튼으로 재생하여 동작 검증
6. 필요 시 assertion 추가 (텍스트 확인, 요소 존재 여부 등)
녹화 후 생성된 테스트를 코드로 내보내면 다음과 같은 형태가 됩니다.
# 로그인 테스트 케이스
def test_login(driver):
driver.get("http://localhost:3000/login")
# 이메일 입력
driver.find_element(By.ID, "email").send_keys("test@test.com")
# 비밀번호 입력
driver.find_element(By.ID, "password").send_keys("password123")
# 로그인 버튼 클릭
driver.find_element(By.CSS_SELECTOR, ".login-btn").click()
# 로그인 성공 후 홈으로 이동했는지 확인
assert driver.current_url == "http://localhost:3000/"
assert "내 문서" in driver.page_source
종단간(End-to-End) 테스트 는 사용자가 실제로 서비스를 사용하는 전체 흐름을 처음부터 끝까지 자동으로 검증하는 테스트입니다. 프론트엔드, 백엔드, 데이터베이스가 모두 연동된 실제 환경에서 실행됩니다.
def test_create_document(driver):
# 1. 로그인
login(driver, "test@test.com", "password123")
# 2. 새 문서 생성 버튼 클릭
driver.find_element(By.ID, "new-doc-btn").click()
# 3. 제목 입력
title_input = driver.find_element(By.ID, "doc-title")
title_input.clear()
title_input.send_keys("테스트 문서")
# 4. 내용 입력
content_area = driver.find_element(By.ID, "editor")
content_area.send_keys("# 안녕하세요\n\n이것은 테스트 문서입니다.")
# 5. 저장 버튼 클릭
driver.find_element(By.ID, "save-btn").click()
# 6. 저장 완료 토스트 메시지 확인
WebDriverWait(driver, 5).until(
EC.presence_of_element_located((By.CLASS_NAME, "toast-success"))
)
# 7. 문서 목록에서 생성된 문서 확인
driver.get("http://localhost:3000/")
assert "테스트 문서" in driver.page_source
비동기 요청이 완료될 때까지 기다리는 처리가 필요합니다. time.sleep() 으로 무작정 기다리는 것보다 WebDriverWait 를 사용해 특정 조건이 충족될 때까지만 기다리는 방식이 훨씬 안정적입니다.
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# 최대 10초 동안 요소가 나타날 때까지 대기
element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.ID, "save-success-msg"))
)
E2E 테스트를 매번 수동으로 실행하는 것은 비효율적입니다. GitHub Actions 파이프라인에 통합하면 PR이 올라올 때마다 자동으로 E2E 테스트가 실행됩니다.
# .github/workflows/e2e.yml
name: E2E Test
on:
pull_request:
branches: [main]
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Docker Compose로 앱 실행
run: docker-compose up -d
- name: 앱 준비 대기
run: sleep 10
- name: Python 설치 및 의존성 설치
run: |
pip install selenium pytest webdriver-manager
- name: E2E 테스트 실행
run: pytest tests/e2e/
- name: Docker Compose 종료
run: docker-compose down
CI 환경에는 브라우저 화면이 없기 때문에 헤드리스(Headless) 모드 로 실행해야 합니다.
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_argument("--headless") # 화면 없이 실행
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
driver = webdriver.Chrome(options=options)
테스트 코드가 많아지면 중복 코드가 늘어나고 유지보수가 어려워집니다. Page Object Model 패턴을 적용하면 각 페이지의 요소와 동작을 클래스로 분리해 관리할 수 있습니다.
# pages/login_page.py
class LoginPage:
def __init__(self, driver):
self.driver = driver
def navigate(self):
self.driver.get("http://localhost:3000/login")
def enter_email(self, email):
self.driver.find_element(By.ID, "email").send_keys(email)
def enter_password(self, password):
self.driver.find_element(By.ID, "password").send_keys(password)
def click_login(self):
self.driver.find_element(By.CSS_SELECTOR, ".login-btn").click()
def login(self, email, password):
self.navigate()
self.enter_email(email)
self.enter_password(password)
self.click_login()
# test_login.py
def test_login_success(driver):
login_page = LoginPage(driver)
login_page.login("test@test.com", "password123")
assert driver.current_url == "http://localhost:3000/"
POM 패턴을 적용하면 페이지 구조가 바뀌어도 테스트 코드가 아닌 Page 클래스만 수정하면 되기 때문에 유지보수 비용이 크게 줄어듭니다.