정의
옛 브라우저 호환성 회귀를 잡으려면, 화면이 "그려지는지"만 보는 스모크로는 부족하다. 실제 클릭·토글·제출 같은 인터랙션을 실행하면서, 넓은 콘솔 에러 필터로 런타임 오류를 테스트 실패로 승격시켜야 한다.
왜 필요한가
런타임 TypeError는 코드가 실제 실행되는 시점에만 터진다. 스모크가 페이지를 열어 "행이 N개 보이나, 특정 텍스트가 있나"처럼 렌더링만 검사하면, 정작 깨지는 이벤트 핸들러는 한 번도 실행되지 않아 테스트가 초록으로 통과한다. 그래서 "테스트는 통과하는데 실제 옛 브라우저에선 로그인이 안 되는" 상황이 생긴다.
또 하나: 5년 넘은 옛 브라우저는 최신 자동화 도구가 번들하는 최신 엔진으로는 재현 자체가 안 된다. 그 버전의 브라우저·드라이버 바이너리를 고정 버전으로 직접 내려받아 구동하고, 프로덕션 빌드를 실제 그 엔진에서 조작해야 문법·런타임 호환성을 진짜로 검증한다.
동작 원리
- 렌더 검사만 하는 스모크: 존재/텍스트 어서션. 핸들러가 다 죽어도 통과 — 호환성 회귀에 무력.
- 인터랙션 어서션: 실제 클릭·토글·제출·라우팅을 실행해 하이드레이션 + 핸들러 동작을 증명.
- 콘솔 에러 승격:
syntaxerror·referenceerror·is not a function·is not defined를 실패로 본다. 단, 네트워크성Failed to fetch는 오탐이라 제외한다. - API 존재 체크와 분리: 폴리필이 로드됐는지(커버리지)와 인터랙션이 동작하는지(동작)는 역할이 다르므로 따로 검증한다.
- 고정 바이너리 구동: 옛 브라우저 + 드라이버를 버전 핀으로 내려받아 버전 강제 검증 후 구동한다.
// 콘솔 에러 필터 예시: 호환성 신호만 승격, 네트워크성은 제외
function isCompatError(msg) {
const m = msg.toLowerCase();
if (m.includes("failed to fetch")) return false; // 네트워크 오탐 제외
return (
m.includes("syntaxerror") ||
m.includes("referenceerror") ||
m.includes("is not a function") ||
m.includes("is not defined")
);
}
실무 적용
- 페이지별 핵심 인터랙션(클릭·토글·다이얼로그·라우팅·이미지 등)을 실제로 실행하는 어서션을 추가한다.
- 콘솔 필터를 위처럼 튜닝한다.
- 옛 브라우저·드라이버를 고정 버전으로 받아 프로덕션 빌드를 그 엔진에서 구동한다.
- 옛 드라이버의 알려진 크래시(예: 자동화 액션 엔드포인트 결함)는 우회 헬퍼로 감싼다.
트레이드오프
| 선택 | 장점 | 비용 |
|---|---|---|
| 렌더-only 스모크 | 빠르고 안정 | 호환성 회귀 못 잡음 |
| 인터랙션 스모크 | 실제 동작 증명 | 느리고 관리 필요 |
| 고정 옛 엔진 구동 | 진짜 호환성 검증 | 바이너리 다운로드·버전 핀 유지 |
사용하면 안 되는 경우
- 최신 브라우저만 지원하는 프로젝트에 무거운 옛 엔진 스모크를 상시 도는 경우 — 과투자.
- 콘솔 필터를 지나치게 넓혀 네트워크·서드파티 잡음까지 실패로 만들어 flake를 유발하는 경우.
흔한 실수
- 렌더만 검사하고 "옛 브라우저 통과"라 결론 내기.
- 콘솔 필터가
syntaxerror만 봐서 없는 API(is not a function)를 놓치기. - 네트워크
Failed to fetch를 호환성 실패로 오탐하기. - 최신 엔진으로 옛 브라우저를 흉내 내며 검증했다고 여기기.
관련 개념
- syntax-transpilation-vs-runtime-polyfill — 스모크가 잡아야 할 두 종류의 호환성 오류
- hydration-failure-dead-handlers — 인터랙션 테스트가 증명하는 것(핸들러가 살아있나)
- playwright-flaky-vs-failed-triage — 진짜 실패와 flake를 가르는 스모크 트리아지