Selenium Grid로 병렬 테스트 실행: 8시간이 2시간으로
- 11 Dec, 2025
Selenium Grid로 병렬 테스트 실행: 8시간이 2시간으로
월요일 오전, 테스트는 아직 돌고 있다
월요일 아침 10시. 출근했다. 주말에 돌린 회귀 테스트가 아직도 실행 중이다.
진행률 67%. 시작한 지 6시간째. 전체 테스트 케이스 1,200개. 순차 실행. 예상 완료 시간은 오후 1시.
“이거 언제 끝나요?” PM이 묻는다. 세 번째 질문이다.
“1시요.” “배포는 2시인데요?” “알아요.”
테스트 결과 보고 버그 리포트 작성하면 시간 빠듯하다. 혹시 테스트 실패하면? 다시 돌린다. 4시간 더. 배포는 미뤄진다. 매번 이랬다.

문제는 명확했다. 순차 실행. 1,200개 테스트가 한 줄로 서서 기다린다. 크롬 브라우저 하나에서. 내 노트북에서.
한 테스트 평균 20초. 1,200개 × 20초 = 24,000초 = 6.6시간. 수학적으로 맞다. 하지만 현실적으로 틀렸다.
금요일 오후에 시작해도 월요일 아침까지. 주말 내내 내 노트북은 일한다. 나는 쉬는데. 비효율이다.
“Grid 알아봐야겠다.” 혼잣말했다.
Grid의 원리: 일을 나눠주는 것
Selenium Grid는 간단하다. Hub 하나. Node 여러 개. Hub는 사장. Node는 직원.
테스트 케이스가 들어온다. Hub가 받아서 Node에게 배분한다. 여러 Node가 동시에 일한다. 병렬 처리.
예를 들면 이렇다. 1,200개 테스트. Node 4개. 각 Node가 300개씩 맡는다. 동시 실행. 시간은 1/4.
6시간이 1.5시간 된다. 이론상으로는.
실제로 Grid 구축하면서 배웠다. 이론과 실전은 다르다는 걸.
첫 시도는 Docker로 했다. Hub 컨테이너 하나. Node 컨테이너 4개. docker-compose.yml 작성.
version: '3'
services:
selenium-hub:
image: selenium/hub:4.15.0
ports:
- "4444:4444"
chrome-node-1:
image: selenium/node-chrome:4.15.0
environment:
- SE_EVENT_BUS_HOST=selenium-hub
Node 4개 띄웠다. Chrome Node 2개. Firefox Node 2개. 브라우저 호환성 테스트도 같이.
터미널에서 확인.
docker ps
컨테이너 5개 돌고 있다. Hub 1개, Node 4개. 정상이다.
Grid Console 접속. http://localhost:4444/ui
화면에 Node 4개 보인다. 각각 브라우저 아이콘. 상태 표시. 처음엔 신기했다.

테스트 코드 수정: 병렬화 준비
Grid 띄웠다고 끝이 아니다. 테스트 코드를 수정해야 한다. 병렬 실행 가능하게.
기존 코드는 이랬다.
from selenium import webdriver
driver = webdriver.Chrome()
driver.get("https://example.com")
로컬 크롬 브라우저 실행. Grid에서는 안 된다.
RemoteWebDriver로 바꿨다.
from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
driver = webdriver.Remote(
command_executor='http://localhost:4444',
desired_capabilities=DesiredCapabilities.CHROME
)
Hub 주소 지정. 브라우저 타입 지정. 이제 Grid Node에서 실행된다.
하지만 문제가 있었다. 테스트들이 서로 간섭했다.
예를 들면 로그인 테스트. 여러 테스트가 동시에 같은 계정으로 로그인. 세션 충돌. 테스트 실패.
해결책: 테스트 독립성. 각 테스트마다 다른 계정 사용. 또는 테스트 데이터 격리.
import pytest
@pytest.fixture
def driver():
driver = webdriver.Remote(...)
yield driver
driver.quit()
def test_login_user1(driver):
# user1 계정 사용
pass
def test_login_user2(driver):
# user2 계정 사용
pass
Pytest의 fixture 활용. 각 테스트마다 독립적인 driver. 테스트 끝나면 driver 종료.
다음은 병렬 실행 설정. Pytest-xdist 플러그인 사용.
pip install pytest-xdist
실행 명령어.
pytest -n 4 tests/
-n 4: 4개 워커로 병렬 실행.
Pytest가 테스트를 나눠서 실행한다.
각 워커가 Grid Node에 요청.
처음 돌렸을 때. 터미널에 로그 쏟아진다. 4개 테스트가 동시에.
신기했다. 그리고 무서웠다. 제대로 돌아가는 건지.
첫 실행: 2시간 11분
금요일 오후 4시. 첫 Grid 테스트 실행. 전체 1,200개 케이스. 4개 Node.
pytest -n 4 --html=report.html tests/
시작했다. Grid Console 보면서 모니터링.
Node 4개 모두 바쁘게 움직인다. 브라우저 열리고 닫히고. 테스트 케이스 이름 스쳐 지나간다.
진행 상황 실시간 업데이트. 50개… 100개… 200개…
중간에 멈칫한다. Node 하나가 응답 없음. 타임아웃. 재시작.
다시 진행. 500개… 800개… 1,000개…

오후 6시 11분. 완료. 결과 확인.
실행 시간: 2시간 11분. 기존: 6시간 30분. 시간 단축: 66%.
성공한 건가? 애매했다. 이론상 1.5시간 예상했는데 2시간 넘었다.
원인 분석했다.
- Node 재시작 시간 약 15분
- 테스트 간 대기 시간 (동기화)
- Hub-Node 통신 오버헤드
- 일부 테스트가 다른 것보다 훨씬 김
그래도 만족했다. 배포 전날 밤에 시작하면 아침에 결과 본다. 충분하다.
브라우저 매트릭스: Chrome, Firefox, Safari
Grid의 진짜 장점은 여기서 나왔다. 여러 브라우저 동시 테스트.
기존에는 이랬다. Chrome으로 전체 테스트 → 6시간. Firefox로 다시 전체 테스트 → 6시간. 총 12시간.
하루가 걸렸다. 크로스 브라우저 테스트는 사치였다. 중요한 기능만 Firefox 확인.
Grid로 바꾼 후. Chrome Node 2개. Firefox Node 2개. 동시 실행.
@pytest.fixture(params=['chrome', 'firefox'])
def driver(request):
browser = request.param
if browser == 'chrome':
caps = DesiredCapabilities.CHROME
else:
caps = DesiredCapabilities.FIREFOX
driver = webdriver.Remote(
command_executor='http://localhost:4444',
desired_capabilities=caps
)
yield driver
driver.quit()
Pytest의 parametrize 기능. 같은 테스트를 다른 브라우저로.
실행하면 자동으로 두 번 돌아간다. Chrome 버전, Firefox 버전.
실제 실행 시간. Chrome 1,200개 + Firefox 1,200개 = 2,400개. 4개 Node로 병렬 실행. 약 3시간 30분.
기존 12시간이 3.5시간 됐다. 시간 단축 70%.
더 나갔다. Safari도 추가하고 싶었다. 문제는 Safari는 macOS에서만 돌아간다.
해결책: AWS EC2 Mac 인스턴스. 비싸다. 하지만 필요했다.
mac1.metal 인스턴스 띄웠다. Safari Node 설치. Grid Hub에 연결.
이제 Node 5개. Chrome 2개, Firefox 2개, Safari 1개.
테스트 코드에 Safari 추가.
@pytest.fixture(params=['chrome', 'firefox', 'safari'])
def driver(request):
# ...
3개 브라우저 동시 테스트. 총 3,600개 케이스. 5개 Node. 약 4시간 30분.
크로스 브라우저 호환성 문제 많이 잡혔다. Safari에서만 깨지는 CSS. Firefox에서만 안 되는 JavaScript.
Grid 없었으면 못 찾았을 버그들이다.
Node 관리: 죽고 살리고
Grid 운영하면서 배웠다. Node는 자주 죽는다.
메모리 부족. 브라우저 크래시. 네트워크 타임아웃. 알 수 없는 오류.
처음엔 당황했다. 테스트 중간에 Node 하나 죽으면? 전체가 멈춘다. 재시작해야 한다.
모니터링 스크립트 만들었다.
import requests
def check_grid_health():
response = requests.get('http://localhost:4444/status')
data = response.json()
for node in data['value']['nodes']:
if not node['availability'] == 'UP':
print(f"Node down: {node['id']}")
restart_node(node['id'])
5분마다 실행. cron으로. Node 상태 확인. 죽으면 재시작.
자동 복구 시스템이다. 밤새 테스트 돌려도 안심.
Docker로 구성해서 재시작이 쉬웠다.
docker restart chrome-node-1
10초면 다시 살아난다. Grid Hub에 자동 재연결.
하지만 근본 원인도 찾아야 했다. 왜 죽는가?
가장 흔한 원인: 메모리 누수. 브라우저 driver를 제대로 종료 안 하면. 메모리 계속 쌓인다.
def teardown():
try:
driver.quit()
except:
pass # 이미 죽었을 수도
항상 quit() 호출. try-except로 안전하게.
두 번째 원인: 타임아웃. 느린 페이지 로딩. 무한 대기.
driver.set_page_load_timeout(30)
driver.implicitly_wait(10)
타임아웃 설정 필수. 30초 넘으면 실패 처리.
세 번째 원인: Stale Element. DOM 변경되는데 이전 element 참조.
from selenium.common.exceptions import StaleElementReferenceException
try:
element.click()
except StaleElementReferenceException:
element = driver.find_element(...)
element.click()
재탐색 로직 추가.
이런 개선들로 Node 안정성 올라갔다. 24시간 연속 실행 가능해졌다.
비용: 클라우드 vs 온프레미스
Grid 구축하면서 비용 고민했다. 두 가지 선택지.
- 클라우드: AWS, Azure, BrowserStack
- 온프레미스: 자체 서버
우리는 하이브리드로 갔다.
기본 Node는 온프레미스. 사무실 구석에 서버 3대. Ubuntu 설치. Docker 세팅. Chrome Node 6개 돌린다.
비용: 서버 구매비 약 600만원. 전기세 월 10만원 정도.
Safari 테스트만 클라우드. AWS EC2 mac1.metal. 시간당 약 1.5달러.
하루 8시간만 켠다. 월 약 360달러 = 45만원.
BrowserStack도 고려했다. 월 200달러부터. 병렬 5개 기준. 편하다. 모든 브라우저 지원.
하지만 우리는 온프레미스 선택. 이유: 테스트 데이터 보안. 내부 API 테스트가 많았다. 외부로 나가면 안 되는 데이터.
BrowserStack은 퍼블릭 사이트 테스트용으로만. 중요 테스트는 내부 Grid.
비용 정리.
- 초기 투자: 600만원 (서버)
- 월 고정: 10만원 (전기)
- 월 변동: 45만원 (AWS Mac)
- 총 월 55만원
기존에 QA 인력 늘리려면? 1명 추가 = 월 400만원. Grid가 훨씬 싸다.
실전 팁: 내가 배운 것들
1. Node 수는 테스트 특성에 맞춰라
처음엔 무작정 Node 많이 띄웠다. 10개 띄우면 10배 빠를 줄 알았다.
아니다. 테스트가 API 호출 많으면? 서버가 병목이다. Node 10개가 동시에 API 때리면. 서버 죽는다.
적절한 수를 찾아야 한다. 우리는 4-6개가 최적이었다.
2. 테스트 그룹화
빠른 테스트와 느린 테스트 분리. 유닛 테스트: 5분. E2E 테스트: 2시간.
따로 돌린다.
pytest -n 4 tests/unit/ # 빠른 것
pytest -n 2 tests/e2e/ # 느린 것
느린 테스트는 Node 적게. 한 Node가 오래 걸리는 테스트 하나 맡으면. 다른 Node는 놀고 있다.
밸런싱이 중요하다.
3. 실패 재실행
Grid에서 테스트 실패하면. 원인이 두 가지다.
진짜 버그 vs Flaky 테스트.
Flaky는 재실행하면 성공한다. Pytest에 옵션 있다.
pytest --reruns 2 --reruns-delay 1
실패하면 2번 재시도. 1초 대기 후.
이것만으로 false positive 많이 줄었다.
4. 리포트 통합
4개 Node에서 나온 결과. 따로따로 보면 혼란스럽다.
통합 리포트 필요하다. pytest-html 사용.
pytest -n 4 --html=report.html --self-contained-html
하나의 HTML 파일. 모든 테스트 결과 정리. 실패 원인, 스크린샷 포함.
이걸 Slack으로 자동 전송.
import requests
def send_to_slack(report_url):
webhook_url = "https://hooks.slack.com/..."
message = {
"text": f"테스트 완료: {report_url}"
}
requests.post(webhook_url, json=message)
아침에 출근하면 Slack에 결과 있다.
5. 캐시 활용
테스트마다 매번 로그인하면 느리다. 로그인 상태를 캐시한다.
@pytest.fixture(scope='session')
def auth_token():
# 한 번만 로그인
token = login()
return token
def test_something(driver, auth_token):
driver.add_cookie({'name': 'auth', 'value': auth_token})
session scope fixture. 전체 테스트에서 한 번만 실행. 시간 많이 절약된다.
2개월 후: 8시간이 1시간 45분으로
지금은 Grid 없이 못 산다.
통계를 냈다.
- 전체 테스트: 1,500개 (늘었다)
- 3개 브라우저 (Chrome, Firefox, Safari)
- 총 4,500개 케이스
- 실행 시간: 1시간 45분
기존 순차 실행이었으면? 약 25시간.
시간 단축: 93%.
금요일 오후 3시에 시작. 5시 전에 결과 나온다. 배포 여유 있게 진행.
더 좋은 점. PR마다 테스트 돌린다. GitHub Actions에 Grid 연동.
개발자가 코드 푸시하면. 자동으로 테스트 실행. 10분 안에 결과.
버그가 프로덕션 가기 전에 잡힌다. 이게 QA의 목표다.
팀장이 물었다. “Grid 투자 대비 효과는?”
계산해봤다.
- 시간 절약: 주 40시간
- 인건비 환산: 월 약 200만원
- Grid 운영비: 월 55만원
- 순이익: 월 145만원
ROI 264%. 숫자로 증명됐다.
다음 목표: Kubernetes와 Auto-scaling
Grid는 끝이 아니다. 다음 단계를 본다.
지금은 고정 Node. 6개 Node가 항상 돌아간다. 테스트 없을 때도.
비효율이다.
Kubernetes로 옮기고 싶다. Auto-scaling 설정.
테스트 요청 들어오면. Pod 자동 생성. 필요한 만큼만 Node 띄운다.
테스트 끝나면. Pod 자동 종료. 비용 절감.
아직 공부 중이다. Helm Chart 보고 있다. 복잡하다. 하지만 재밌다.
Grid 덕분에 주말이 편해졌다. 노트북도 쉰다. 나도 쉰다.
