밤새 돌린 테스트가 새벽 3시에 깨졌을 때
- 02 Dec, 2025
새벽 3시 알람, 그리고 깨진 파이프라인
어제 저녁 8시에 큰 기능 PR을 머지했다. 전체 테스트 스위트를 도는 건 보통 4시간 걸린다. 자동화 엔지니어라면 알 거다. 밤샘 CI는 숙명이다.
침대에 누웠는데 핸드폰이 울렸다. Jenkins 알람이다. 새벽 3시 정각. 빌드는 FAILED. 화면을 봤을 때 느낌이 왔다. 이건 단순한 실패가 아니다. 뭔가 깊은 거다.
일어나 앉았다. 잠은 포기했다. 아무도 지금 연락할 수 없다. 개발팀은 자고 있고, 나만 깨어 있다. 자동화의 밤은 혼자 버티는 거다.

노트북을 켰다. 모니터 세 개를 돌렸다. 한쪽에는 Jenkins 로그, 한쪽에는 테스트 리포트, 한쪽에는 IDE를 띄웠다. 새벽 3시의 나는 이미 자동화 엔지니어가 아니라 범죄 수사관이다.
파이프라인 사건의 현장
FAILED: test_checkout_with_coupon
FAILED: test_user_profile_update
FAILED: test_notification_badge_count
ERROR: Connection timeout at step_wait_for_element
보자. 셀레늄 테스트 세 개가 떴다. 패턴이 있다. 모두 사용자 액션이 들어가는 테스트들이다. 그리고 마지막 줄. Connection timeout. 이게 핵심이다.
CI 환경을 봤다. 새벽에도 파이프라인은 도는데, 리소스 사용량이 이상했다. 다른 배치 작업이랑 겹쳤나보다. 메모리 70%, CPU 50%. 정상은 아니다.
여기서 대부분의 자동화 엔지니어가 실수한다. “아, 환경 문제구나” 하고 재실행한다. 그리고 다음날 또 깨진다. 반복한다.
나는 다르게 생각했다. 환경 문제가 맞으면, 이 테스트는 프로덕션에서도 깨질 가능성이 높다. 즉 내 테스트가 너무 민감하다는 뜻이다.

그래서 코드를 뜯어봤다.
wait_for_element(locator, timeout=10)
10초다. 10초 wait는 신뢰할 수 없다. CI 환경이 느릴 땐 평정심이 필요하다. 내가 쓴 코드를 봤다.
def wait_for_element(self, locator, timeout=10):
try:
WebDriverWait(self.driver, timeout).until(
EC.presence_of_element_located(locator)
)
except TimeoutException:
raise AssertionError(f"Element not found: {locator}")
이 코드의 문제는 뭔가? Retry 로직이 없다. 한 번 깨지면 끝이다. Flaky 테스트의 정의다.
새벽 3시, 나는 이 코드를 고쳤다.
def wait_for_element(self, locator, timeout=10, retries=3):
for attempt in range(retries):
try:
WebDriverWait(self.driver, timeout).until(
EC.presence_of_element_located(locator)
)
return True
except TimeoutException:
if attempt == retries - 1:
raise AssertionError(f"Element not found after {retries} attempts: {locator}")
time.sleep(2) # 2초 대기 후 재시도
재시도 로직을 넣었다. 3번까지 시도한다. 2초 간격으로.
그 다음은 뭔가? 테스트 순서다. 새벽 파이프라인에선 병렬 실행도 있다. 리소스가 줄어드니까 어떤 테스트가 먼저 끝날지 모른다. 그럼 상태 관리가 문제가 된다.
@pytest.fixture(autouse=True)
def setup_and_teardown():
# 각 테스트마다 클린 상태에서 시작
driver.delete_all_cookies()
driver.execute_script("window.sessionStorage.clear();")
yield
# 테스트 후 정리
if driver:
driver.quit()
이건 이미 있었다. 좋다. 그럼 다음은?
왜 3시에 깨졌는가
파이프라인을 분석했다. 같은 시간대에 돌고 있던 게 뭐였나?
- 01:00 - 앱 빌드 (30분)
- 01:30 - 데이터베이스 마이그레이션 테스트 (90분)
- 02:10 - 내 자동화 테스트 (4시간)
아. DB 마이그레이션이 끝나는 게 2시간 40분 즈음이다. 거기서 뭔가 리소스를 남기고 있었나?
로그를 더 들었다.
[02:47] Database migration completed
[02:48] Automation tests started
[02:49] Chrome driver spawned (port 4444)
[03:02] Connection pool exhausted
아하. 크롬 인스턴스가 정상 종료가 안 되고 있었다. 이전 빌드에서 고아 프로세스가 남아있었다. 메모리를 점점 먹고 있었고, 결국 3시쯤에 터진 거다.

이건 내 코드 문제가 아니다. CI 환경 설정 문제다. 하지만 내 책임이다. 왜냐면 내가 테스트를 견고하게 만들지 못했으니까.
새벽 4시, 나는 두 가지를 했다.
1. Dockerfile 수정
# 이전
RUN apt-get install -y chromium-browser
# 이후
RUN apt-get install -y chromium-browser
RUN echo "pkill -f chrome || true" > /cleanup.sh
CI 시작 전에 고아 프로세스를 모두 정리하는 스크립트를 넣었다.
2. 테스트 타임아웃 늘림
@pytest.mark.timeout(300) # 5분으로 늘림
def test_checkout_with_coupon():
...
느린 CI 환경을 고려했다. 10초는 너무 짧다.
3. 로그 레벨 상향
logging.basicConfig(level=logging.DEBUG)
logger.debug(f"Waiting for {locator}, timeout={timeout}")
logger.debug(f"Attempt {attempt+1}/{retries}")
다음에 같은 일이 생기면 더 빨리 디버깅할 수 있게.
아침 6시, 결론
다시 실행했다. 이번엔 통과했다. 모든 테스트. 파이프라인이 초록색이 됐다.
아침 9시, 팀 미팅에서 뭐라고 할까 고민했다. 아무것도 안 하기로 했다. 개발팀에게 “야간 파이프라인에 환경 문제가 있었어요”라고 하면 뭐 하냐. 나 혼자 밤새 고칠 수 있는 거다.
대신 테스트 리포트에는 이렇게 적었다.
[FIX] Improved wait_for_element stability
- Added retry mechanism (3 attempts, 2s interval)
- Extended timeout for CI environment
- Added debug logging for failed attempts
[INFRA] Cleaned up Docker initialization
- Added pre-cleanup for orphaned Chrome processes
- Improved resource allocation
개발팀은 읽지 않을 거다. 하지만 다음 누군가 밤샘할 때 필요한 정보다.
자동화 엔지니어라는 게 이런 거다. 야간 파이프라인의 짐을 혼자 지는 거. 그 대신 그 파이프라인이 믿을 수 있는 파이프라인이 되는 거.
새벽 3시 깨진 테스트는 나한테 뭘 줬나? 2시간의 수면 부채? 아니다. 안정적인 자동화 프레임워크의 법칙 하나를 줬다.
테스트는 최악의 환경을 고려해서 만들어야 한다. 왜냐면 CI 환경은 항상 최악이니까.
오늘따라 커피가 여섯 잔이다.
밤샘은 버티는 거지만, 버티는 방법을 배우는 게 진짜 자동화 엔지니어다.
[IMAGE_1]
[IMAGE_2]
[IMAGE_3]
