원격 팀이 웹 변경 사항을 병합한 뒤 가장 놓치기 쉬운 것은 Chromium 경로가 아니라 Safari 고유의 폼, 포커스, 레이아웃 동작입니다. 배포 직전에 원격 데스크톱을 열어 페이지마다 일일이 확인하기보다는 GPUMini 클라우드 Mac에 고정된 Safari WebDriver 회귀 작업을 구성하는 편이 낫습니다. 배포 후보가 나올 때마다 동일한 핵심 흐름을 실행하고, 실패하면 나중에 검토할 수 있도록 당시 페이지 상태를 남깁니다.
반복 가능한 실행 경계부터 고정하기
Safari 자동화는 macOS 그래픽 세션에 의존하므로 헤드리스 브라우저와 같은 방식으로 단순 실행할 수 없습니다. 먼저 전용 테스트 사용자를 만들고 그래픽 세션이 로그인된 상태를 유지하며, 자동 작업과 사람이 수행하는 원격 작업이 동시에 실행되지 않도록 해야 합니다. 기록, 다운로드 파일, 창 상태가 서로 영향을 주지 않게 동일한 사용자 디렉터리에서는 한 번에 하나의 Safari 세션만 실행합니다.
노드를 처음 준비할 때 대화형 터미널에서 드라이버를 활성화하고 진단을 실행합니다.
sudo safaridriver --enable
safaridriver --diagnose
mkdir -p "$HOME/browser-tests/artifacts"
--enable은 노드 준비 단계에서만 필요한 작업이므로 매번 실행하는 자동 작업에 넣지 마십시오. 일상적인 작업에서는 진단만 실행하고, 진단이 실패하면 테스트를 중단해야 합니다. 원인을 설명하기 어려운 타임아웃 결과를 계속 생성해서는 안 됩니다. Python 환경에서도 의존성 버전을 고정하고, 실행할 때마다 최신 버전을 설치하는 대신 프로젝트 의존성 파일에 selenium의 정확한 버전을 명시해야 합니다.
브라우저 회귀 테스트의 첫 번째 기준은 “실행할 수 있는가”가 아니라 동일한 커밋, 동일한 테스트 데이터, 동일한 대기 조건에서 같은 결론을 얻을 수 있는가입니다.
로컬 픽스처로 외부 변동 차단하기
로그인, 검색, 결제 같은 엔드투엔드 흐름은 테스트 환경에 연결해도 되지만, 컴포넌트 상호작용과 브라우저 호환성 검사는 가능한 한 로컬 픽스처를 사용해야 합니다. 폼, 팝업, 업로드, 키보드 탐색 페이지를 저장소에 포함하고 루프백 주소를 통해 제공하면 DNS, 인증서, 외부 인터페이스의 변동을 배제할 수 있습니다.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")" && pwd)"
ARTIFACTS="$ROOT/artifacts"
mkdir -p "$ARTIFACTS"
python3 -m http.server 8080 \
--bind 127.0.0.1 \
--directory "$ROOT/fixtures" \
>"$ARTIFACTS/http-server.log" 2>&1 &
SERVER_PID=$!
cleanup() {
kill "$SERVER_PID" 2>/dev/null || true
}
trap cleanup EXIT INT TERM
"$ROOT/.venv/bin/python" "$ROOT/tests/safari_smoke.py"
고정 포트를 사용하기 전에 lsof -nP -iTCP:8080 -sTCP:LISTEN으로 점유 여부를 확인합니다. 여러 작업이 노드를 공유한다면 프로세스를 무작위로 종료하지 마십시오. 스케줄링 계층에서 상호 배제를 보장하거나 작업마다 명시적인 포트를 할당하고, 해당 포트를 테스트 설정에 기록해야 합니다.
대기 조건을 비즈니스 상태로 표현하기
가장 흔한 취약 패턴은 클릭한 뒤 몇 초 동안 고정적으로 대기하는 것입니다. 머신 부하가 조금만 달라져도 대기 시간이 짧으면 오탐이 발생하고, 너무 길면 전체 테스트가 느려집니다. 대신 버튼을 클릭할 수 있는 상태, 결과 목록의 표시, 상태 텍스트가 예상 값으로 바뀌는 것처럼 관찰 가능한 비즈니스 상태를 기다려야 합니다.
from pathlib import Path
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
artifacts = Path("artifacts")
driver = webdriver.Safari()
driver.set_page_load_timeout(20)
try:
driver.get("http://127.0.0.1:8080/login.html")
wait = WebDriverWait(driver, 12)
wait.until(
EC.visibility_of_element_located((By.ID, "email"))
).send_keys("tester@local.invalid")
driver.find_element(By.ID, "continue").click()
status = wait.until(
EC.visibility_of_element_located((By.ID, "result"))
)
assert status.text == "Ready"
finally:
driver.quit()
로케이터는 레이아웃 변경에 따라 달라질 수 있는 계층형 선택자 대신 안정적인 id나 테스트 전용 속성을 우선 사용합니다. 어설션도 요소의 존재 여부만이 아니라 실제 결과를 검증해야 합니다. 제출 버튼이 표시된다고 해서 제출이 성공한 것은 아니며, 목록 컨테이너가 있다고 해서 데이터 렌더링이 끝난 것도 아닙니다.
실패할 때마다 현장을 재현할 수 있게 만들기
브라우저 테스트에서 가장 큰 비용을 유발하는 상태는 “CI에는 타임아웃이라고 표시되지만 당시 상황을 확인할 자료가 없는 경우”입니다. 각 테스트 케이스가 실패하는 즉시 스크린샷, 페이지 소스, 현재 URL, 테스트 케이스 이름, 시간 정보를 저장해야 합니다. 전체 테스트 스위트가 끝난 뒤에만 스크린샷을 찍으면 이미 다른 페이지로 이동했거나 닫힌 화면만 남는 경우가 많습니다.
| 실패 현상 | 우선 저장할 자료 | 우선 확인할 항목 |
|---|---|---|
| 요소가 계속 표시되지 않음 | 스크린샷, 페이지 소스 | 오버레이, 반응형 레이아웃, 로케이터 |
| 클릭해도 결과가 없음 | 현재 URL, 버튼 상태 | 비활성화 상태, 포커스, 이벤트 바인딩 |
| 페이지 로드 타임아웃 | URL, 서비스 로그 | 로컬 서비스, 리디렉션, 리소스 차단 |
| 드라이버가 세션을 만들지 못함 | 드라이버 진단 | 그래픽 세션, 남은 프로세스, 권한 |
스크린샷 파일 이름에는 테스트 케이스 식별자와 고유 실행 번호를 포함하되 토큰, 이메일 주소, 프로젝트 키를 넣지 마십시오. 페이지 소스에도 테스트 데이터가 포함될 수 있으므로 보관 전에 동일하게 민감 정보를 제거하고 명확한 보존 기간을 설정해야 합니다.
예외가 발생한 지점에서 즉시 저장하기
테스트 프레임워크의 실패 훅은 공통 증거 수집 함수를 호출해야 합니다. 저장 작업 자체도 실패할 수 있으므로 스크린샷과 페이지 소스 기록 오류를 각각 처리해야 하며, 보관 과정의 예외가 원래 어설션 실패를 덮어쓰게 해서는 안 됩니다.
무작정 재시도하지 말고 남은 세션 복구하기
작업이 중단되면 Safari나 드라이버 프로세스가 세션을 계속 점유할 수 있습니다. 다음 실행에서 바로 재시도하면 세션 생성이 연속으로 실패하는 경우가 많습니다. 먼저 테스트 사용자에게 속한 작업이 아직 실행 중인지 확인하고, 다른 정상 테스트가 실행되지 않는다는 사실을 확인한 뒤 남은 세션을 종료해야 합니다. 공유 노드에서는 광범위한 프로세스 이름을 기준으로 일괄 정리하지 마십시오.
재시도는 이미 알려진 일시적인 세션 생성 실패에만 적용하며, 최대 한 번으로 제한해야 합니다. 어설션 실패, 페이지 구조 변경, 비즈니스 상태 불일치는 자동으로 재시도해서는 안 됩니다. 그렇게 하면 실제 회귀 문제가 우발적인 문제처럼 위장될 수 있습니다. 한 번 재시도한 뒤 정상화되었다면 작업을 완전히 정상으로 표시하지 말고 별도의 불안정 이벤트로 기록해야 합니다.
마지막으로 게이트를 두 단계로 나눕니다. 커밋마다 로컬 픽스처 스모크 테스트를 실행하고, 배포 후보 버전에서는 소수의 핵심 엔드투엔드 흐름을 추가로 실행합니다. 출시 전에 드라이버 진단 통과, 테스트 사용자의 독점 사용, 픽스처 서비스 접근 가능 여부, 고정 대기 없는 명시적 대기, 실패 증거의 가독성, 정리 함수의 실행 보장을 확인합니다. 이렇게 구축하면 “가끔 통과하는” 브라우저 스크립트 묶음이 아니라 변화를 장기간 추적할 수 있는 Safari 회귀 기준선을 확보할 수 있습니다.
자주 묻는 질문
활성 그래픽 로그인 세션 없이 Safari WebDriver를 실행할 수 있나요?
안정적인 방식으로 권장되지 않습니다. 전용 테스트 사용자의 macOS 그래픽 세션을 유지하고 작업을 받기 전에 드라이버 진단을 실행해야 합니다.
한 대의 클라우드 Mac에서 Safari 세션을 병렬로 실행해도 되나요?
먼저 사용자 환경당 하나의 세션을 직렬로 실행하세요. 처리량이 필요하면 동일한 브라우저 프로필을 공유하지 말고 독립 노드에 테스트 묶음을 분산합니다.
Safari 테스트 실패 시 어떤 자료를 보관해야 하나요?
실패 화면, 페이지 소스, 현재 URL, 테스트 이름, 드라이버 진단 결과를 보관합니다. 서버 로그가 필요하면 민감한 값을 제거한 뒤 첨부합니다.
개발 및 빌드 작업에 사용할 클라우드 Mac 선택
두 가지 M4 구성, 네 가지 대여 기간 및 판매 중인 네 개 노드를 비교한 뒤 작업에 맞게 배포하세요.