E2Eの世代交代 — SeleniumからPlaywrightまで
2004年、Jason HugginsのSeleniumに始まったブラウザ自動化は、WebDriver統合・Puppeteer・Cypress・Playwrightと世代交代を重ねてきた。各世代が「待機」「不安定さ」「クロスブラウザ」という課題にどう応えたかを比較する。
プロパティベーステストとファジング(第7記事)は、関数やモジュール単位の入力を自動生成することでテストの網羅性を高める技法だった。ここからは視点を大きく変え、実際にブラウザを操作してシステム全体を端から端まで検証するE2E(End-to-End)テストの歴史を辿る。この分野の約20年は、常に 「待機」「不安定さ(flakiness)」「クロスブラウザ対応」 という3つの課題との戦いだった。
Selenium誕生 — 2004年
2004年、ThoughtWorksのシカゴオフィスで、社内の勤怠管理アプリケーションのテストに困っていたジェイソン・ハギンズ(Jason Huggins)が、ブラウザ内で動くJavaScriptを使ってUIを自動操作するツールを書いた。当初「JavaScriptTestRunner」と呼ばれたこのツールは、同年末にオープンソースとして公開され、後にSelenium(Selenium Core)と呼ばれるようになった。
初期実装(Selenium Core / Selenium RC = Remote Control)は、テスト対象ページと同じオリジンにテスト用JavaScriptを注入し、ブラウザの中からDOM操作をシミュレートする方式だった。この方式には次の制約があった。
- 同一オリジンポリシーの制約: 別ドメインをまたぐ操作がしづらい
- 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)」のツールは長年、保守性の低さが指摘され続けている。
待機・不安定さという長年の宿痾
Selenium/WebDriver世代が抱え続けた最大の課題が 「待機(wait)」の問題である。ページの読み込みやAjax通信、アニメーションの完了を待たずに要素を操作しようとすると、要素がまだ存在しない・クリック不能などの理由でテストが失敗する。sleep()による固定待機、WebDriverWaitによる明示的待機、暗黙的待機といった手法が使われてきたが、いずれも「何秒待てば十分か」を経験的に調整する必要があり、フレーキーテスト(第14記事参照)の温床となり続けた。
Puppeteer — CDPによる直接制御(2017)
2017年、Googleの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月、MicrosoftはPlaywrightを発表した(1.0リリースは同年5月)。開発チームにはGoogleでPuppeteerの開発に携わっていたエンジニアたちが含まれており、PuppeteerのCDP直接制御という発想を引き継ぎながら、Puppeteerの「Chrome専用」という弱点と、Cypressの「クロスブラウザが弱い」という弱点の両方を克服することを狙った設計になっている。
- クロスブラウザ対応: Chromium系だけでなく、Firefox・WebKit(Safariのレンダリングエンジン)を単一APIで操作可能
- auto-wait: 要素が「実際に操作可能な状態」になるまで自動的に待つ仕組みをコマンドレベルで内蔵
- 並列実行・ブラウザコンテキストの分離: 1つのブラウザプロセス内に軽量な「ブラウザコンテキスト」を複数生成でき、テストごとに独立したセッションを低コストで並列実行できる
- トレース・ビデオ・スクリーンショットの標準搭載: 失敗時のデバッグ情報を標準機能として持つ
コードで見る世代差
同じ「ログインボタンをクリックして、結果を待ってから検証する」という操作でも、各世代でコードの姿は大きく異なる。
// 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) の標準化が進められている。クロスブラウザ対応・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” 仕様(草案)