E2E 테스트의 세대교체 — Selenium에서 Playwright까지
2004년 제이슨 허긴스의 Selenium에서 시작된 브라우저 자동화는 WebDriver 통합, Puppeteer, Cypress, Playwright로 세대교체를 거듭해 왔다. 각 세대가 「대기」, 「불안정성」, 「크로스 브라우저」라는 과제에 어떻게 응답했는지 비교한다.
프로퍼티 기반 테스트와 퍼징(제7기사)은 함수나 모듈 단위의 입력을 자동 생성함으로써 테스트의 포괄성을 높이는 기법이었다. 여기서부터는 시점을 크게 바꾸어, 실제로 브라우저를 조작해 시스템 전체를 처음부터 끝까지 검증하는 E2E(End-to-End) 테스트의 역사를 따라간다. 이 분야의 약 20년은 늘 「대기(wait)」, 「불안정성(flakiness)」, 「크로스 브라우저 대응」 이라는 세 가지 과제와의 싸움이었다.
Selenium의 탄생 — 2004년
2004년, ThoughtWorks의 시카고 사무소에서 사내 근태관리 애플리케이션의 테스트에 어려움을 겪던 제이슨 허긴스(Jason Huggins)가, 브라우저 안에서 동작하는 JavaScript를 이용해 UI를 자동 조작하는 도구를 작성했다. 처음에는 “JavaScriptTestRunner”라 불렸던 이 도구는 그해 말 오픈소스로 공개되었고, 훗날 Selenium(Selenium Core)이라 불리게 된다.
초기 구현(Selenium Core / Selenium RC = Remote Control)은 테스트 대상 페이지와 같은 오리진에 테스트용 JavaScript를 주입해, 브라우저 안에서 DOM 조작을 시뮬레이션하는 방식이었다. 이 방식에는 다음과 같은 제약이 있었다.
- 동일 출처 정책(same-origin policy)의 제약: 다른 도메인을 넘나드는 조작이 어려움
- JavaScript를 통한 조작만 가능: 실제 마우스 클릭이나 키 입력 같은 네이티브 브라우저 조작을 완전히 재현할 수는 없음
- Remote Control(RC) 서버의 복잡한 구성: 테스트를 실행하려면 로컬에 프록시 서버를 세울 필요가 있었음
WebDriver의 등장과 Selenium 2로의 통합
이러한 제약에 맞서, 같은 ThoughtWorks의 사이먼 스튜어트(Simon Stewart)는 2006년 말 이후 전혀 다른 접근으로 WebDriver 개발을 진행하고 있었다. WebDriver는 브라우저 벤더가 제공하는 네이티브 API·드라이버를 통해 브라우저를 직접 조작하는 방식으로, JavaScript의 제약을 받지 않는, 실제에 훨씬 가까운 조작을 실현했다.
2007년 Google Test Automation Conference(GTAC)에서 허긴스와 스튜어트는 두 프로젝트의 통합을 논의했고(“Steel Cage Knife Fight”라 불리는 세션으로 알려져 있다), 통합하는 방향으로 결정했다. 작업은 2009년 8월에 시작되어 같은 해 12월 첫 통합 버전(alpha1)이 공개되었고, 2011년 7월 안정판으로서 Selenium 2.0이 정식 출시되었다. Selenium 2는 Selenium RC의 조작성과 WebDriver의 네이티브 제어를 모두 제공하는 형태로 등장했다.
이후 WebDriver 프로토콜 자체를 브라우저를 넘나드는 표준으로 정하기 위해 W3C의 Browser Testing and Tools Working Group에서 사양 제정이 진행되어, 2018년 5월 31일, WebDriver는 W3C 권고안(Recommendation)이 되었다. 이로써 주요 브라우저 벤더 각사가 자체 공식 WebDriver 구현(ChromeDriver, geckodriver 등)을 제공하게 되었고, 「드라이버마다 동작이 다르다」는 문제가 완화되었다.
Selenium IDE라는 또 하나의 계보
초기 Selenium에는 Firefox 확장 기능으로서 브라우저 조작을 그대로 기록·재생할 수 있는 Selenium IDE도 존재했다. 브라우저 상의 조작을 클릭만으로 기록해 코드를 작성하지 않고도 테스트를 만들 수 있는 간편함 덕분에 많은 비엔지니어에게도 사용되었지만, Firefox의 확장 기능 아키텍처 변경에 따라 한 차례 개발이 중단되었고, 이후 WebExtensions 버전으로 재구현되는 경위도 있었다. 「녹화형(record & playback)」 도구는 오랫동안 유지보수성이 낮다는 지적——UI 변경에 취약하고 의도를 파악하기 어렵다는 점——을 계속 받아 왔다.
오랜 고질병, 대기와 불안정성
Selenium/WebDriver 세대가 계속 안고 있던 가장 큰 과제가 「대기(wait)」의 문제였다. 페이지 로딩이나 Ajax 통신, 애니메이션의 완료를 기다리지 않고 요소를 조작하려 하면, 요소가 아직 존재하지 않거나 클릭할 수 없는 등의 이유로 테스트가 실패한다. sleep()에 의한 고정 대기, WebDriverWait에 의한 명시적 대기, 암묵적 대기 같은 기법들이 사용되어 왔지만, 어느 것이든 「몇 초를 기다리면 충분한가」를 경험적으로 조정해야 했고, 플래키 테스트(제14기사 참조)의 온상이 되어 왔다.
Puppeteer — CDP를 통한 직접 제어(2017년)
2017년, 구글의 Chrome DevTools 팀은 Node.js에서 Chrome을 직접 조작하는 라이브러리 Puppeteer를 공개했다. Puppeteer는 WebDriver와 같은 브라우저 횡단 프로토콜을 거치지 않고, Chrome 자신이 제공하는 저수준 디버깅용 프로토콜인 CDP(Chrome DevTools Protocol) 를 직접 사용해 브라우저를 제어한다.
- WebDriver의 HTTP 기반 프로토콜보다 낮은 레이어에서, 더 빠르고 정밀한 조작이 가능
- 네트워크 요청 가로채기, 성능 계측, PDF 생성, 스크린샷 등 테스트 이외의 자동화 용도에도 강함
- 반면 초기에는 Chrome(Chromium) 전용이어서, 크로스 브라우저 대응이라는 점에서는 Selenium에 뒤처져 있었다
Cypress — 브라우저 내 실행이라는 발상의 전환(2017년경)
Cypress는 2014년, 브라이언 만(Brian Mann)이 기존 도구(특히 Selenium)의 불편함에 대한 불만에서 개발을 시작해, 2017년 10월 공개 베타, 2018년 10월 상용 출시했다. Selenium/WebDriver와는 근본적으로 다른 아키텍처를 채택했다.
- 테스트 코드를 브라우저 안에서, 테스트 대상 애플리케이션과 같은 이벤트 루프 위에서 실행한다(Node.js의 프록시 서버를 통해 제어는 하지만, 실제 명령 실행은 브라우저 안에서 이루어진다)
- 이로써 DOM의 상태 변화·네트워크 호출·타이머 등을 테스트 코드가 직접, 실시간으로 관찰할 수 있다
- 자동 대기(automatic waiting): 요소의 출현·애니메이션의 완료·XHR/fetch의 완료 등을 자동으로 감지해, 명시적인
sleep이나wait작성을 거의 불필요하게 만들었다 - 타임 트래블(time-travel debugging): 테스트 실행 중 각 명령 실행 시점의 DOM 스냅숏을 기록해, 실패 시 그 시점의 상태를 브라우저의 개발자 도구에서 거슬러 확인할 수 있다
약점은 아키텍처상의 제약으로 여러 탭·여러 브라우저를 넘나드는 시나리오를 작성하기 어렵다는 점, 그리고 오랫동안 크로스 브라우저 대응(특히 WebKit/Safari 계열)이 약했다는 점이었다.
Playwright — 크로스 브라우저와 auto-wait의 통합(2020년)
2020년 1월, 마이크로소프트는 Playwright를 발표했다(1.0 릴리스는 같은 해 5월). 개발팀에는 구글에서 Puppeteer 개발에 참여했던 엔지니어들이 포함되어 있었으며, Puppeteer의 CDP 직접 제어라는 발상을 이어받으면서, Puppeteer의 「Chrome 전용」이라는 약점과 Cypress의 「크로스 브라우저가 약하다」는 약점을 모두 극복하는 것을 노린 설계로 되어 있다.
- 크로스 브라우저 대응: Chromium 계열뿐 아니라 Firefox·WebKit(Safari의 렌더링 엔진)까지 단일 API로 조작 가능
- auto-wait: 요소가 「실제로 조작 가능한 상태」가 될 때까지 자동으로 기다리는 장치를 명령 레벨에 내장
- 병렬 실행·브라우저 컨텍스트 분리: 하나의 브라우저 프로세스 안에 경량의 「브라우저 컨텍스트」를 여러 개 생성할 수 있어, 테스트마다 독립된 세션(쿠키·캐시 비공유)을 저비용으로 병렬 실행할 수 있다
- 트레이스·비디오·스크린숏의 표준 탑재: 실패 시 디버깅 정보를 표준 기능으로 갖춘다
코드로 보는 세대 차이
같은 「로그인 버튼을 클릭하고, 결과를 기다린 뒤 검증한다」는 조작이라도, 각 세대마다 코드의 모습은 크게 다르다.
// Selenium WebDriver: 명시적 대기를 직접 작성해야 한다
WebElement button = driver.findElement(By.id("login"));
button.click();
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("welcome")));
// Cypress: 명령 체인 내부에서 자동으로 요소의 출현·안정을 기다린다
cy.get("#login").click();
cy.get("#welcome").should("be.visible");
// Playwright: auto-wait가 기본 탑재되어 있어, 크로스 브라우저에서 같은 코드가 동작한다
await page.click("#login");
await expect(page.locator("#welcome")).toBeVisible();
Selenium 세대에서는 「기다리는 코드」를 명시적으로 작성해야 했던 반면, Cypress·Playwright 세대에서는 명령 자체가 「대상이 조작 가능해질 때까지」를 자동으로 기다린다. 이것이 「플래키 테스트의 주요 원인을 메커니즘 수준에서 없앤다」는 설계 사상의 진화를 단적으로 보여준다.
각 세대가 해결한 과제
| 도구(등장 연도) | 제어 방식 | 주로 해결한 과제 | 남은 과제 |
|---|---|---|---|
| Selenium Core/RC(2004) | 브라우저 내 JS 주입 | 수동 테스트의 자동화 자체를 가능케 함 | 동일 출처 제약, 네이티브 조작의 불완전함 |
| WebDriver / Selenium 2(2007~2011) | 브라우저 네이티브 API 경유 | RC의 프록시 구성·JS 제약으로부터의 탈피 | 대기 처리는 여전히 명시적 작성이 필요 |
| WebDriver의 W3C 표준화(2018) | 표준화된 HTTP 프로토콜 | 벤더별 구현 차이·비호환성 | 대기 문제는 미해결 |
| Puppeteer(2017) | CDP 직접 제어 | 저수준에서의 빠르고 정밀한 조작 | Chromium 계열 한정으로 크로스 브라우저 비대응 |
| Cypress(2017) | 브라우저 내 실행·동일 이벤트 루프 | 대기 처리의 자동화, 디버깅 경험 | 멀티 탭/크로스 브라우저의 제약 |
| Playwright(2020) | CDP 계열 + 각 브라우저 고유 프로토콜 | 크로스 브라우저 대응과 auto-wait의 양립, 병렬 실행 | 생태계의 짧은 역사 |
WebDriver BiDi라는 다음 표준
Selenium이나 Playwright 외에도, Node.js판 WebDriver 래퍼인 WebdriverIO, .NET/JS용 TestCafe 등, 각 생태계에 맞춘 도구들이 등장해 왔다. 어느 것이든 근본 사상은 「대기의 자동화」, 「읽기 쉬운 API」, 「CI 환경에서의 안정적 실행」이라는 공통의 목표를 추구하고 있다.
WebDriver 프로토콜 자체도 계속 진화하고 있다. 기존의 WebDriver(통칭 WebDriver Classic)는 HTTP 기반의 단방향 요청·응답 방식이었기 때문에, 네트워크 이벤트나 콘솔 로그를 실시간으로 받는 데는 약했다. 이 약점을 해소하기 위해 W3C에서는 WebSocket 기반의 양방향 통신을 가능케 하는 차세대 사양 WebDriver BiDi(BiDirectional) 의 표준화가 진행되고 있다. WebDriver Classic이 가진 크로스 브라우저 대응·W3C 표준이라는 강점과, CDP가 가진 빠른 양방향 통신·저수준 제어력이라는 강점을 통합하는 것을 노린 것으로, 브라우저 자동화의 표준화는 여전히 진화를 계속하고 있는 주제다.
이 기사에서 다음 기사로
Selenium, WebDriver, Puppeteer, Cypress, Playwright——브라우저 테스트의 세대교체를 관통하는 축은 일관되게 「제어의 정확성·속도」와 「대기·타이밍 문제의 자동화」의 추구였다. 다음으로 살펴볼 것은 이 역사와 나란히 달려온 또 하나의 무대, JavaScript라는 테스트 대상 언어 자체의 생태계다. Jasmine, Mocha, Jest, Vitest 같은 테스트 러너의 변천을 따라간다.
참고문헌
- Selenium 공식 문서·프로젝트 역사(selenium.dev)
- W3C, “WebDriver” Recommendation(2018년 5월 31일)
- Puppeteer 공식 문서(pptr.dev)
- Cypress 공식 문서(docs.cypress.io)
- Playwright 공식 문서(playwright.dev)
- W3C, “WebDriver BiDi” 사양(초안)