こちらは ヤプリ&フラー 合同アドベントカレンダー 2025 4日目の記事になります。
はじめに
ヤプリでQAエンジニアをしている今西です。
QAチームでは、 Yappli (CMS)の一部を対象にPython+Seleniumを使ったE2Eテストの自動化を行なっています。
そこで課題となっているのが実行速度です。
現状、E2Eテストコードの新規追加や修正確認などをローカルで動作確認する際、目視での確認が必要になります。
しかし、ブラウザ上の要素操作と操作のインターバルが長いため、実行完了までに時間がかかります。
そんな中、「 Playwright なら Selenium よりE2Eテストの実行が速い」という話を耳にしました。
同じPython環境 + E2Eテストを動かした場合にどの程度の差があるのか気になったため、
今回は、SeleniumとPlaywrightで同一のE2Eテストを実行した際の違いや、なぜ差が出るのかについて調べてみました。
- はじめに
- 比較条件
- 比較に用いたE2Eテストサンプルコード
- 結論
- なぜPlaywrightでの実行は速いのか?
- Selenium と Playwright の通信処理の違い(実装ベース)
- Selenium と Playwright の待機処理の違い
- まとめ
- おまけ(直近のE2Eテストフレームワークトレンド)
比較条件
| 項目 | 内容 |
|---|---|
| 実行端末 | MacBook Pro(Apple M3 Max, 64GBメモリ) |
| OS | macOS Sequoia 15.6.1 |
| Python | 3.13.3 |
| E2Eテスト実行 | Selenium 4.38.0 / Playwright 1.48.0 |
| ブラウザ | Chrome(Selenium) / Chromium(Playwright) |
| 実行モード | headless=False(実ブラウザで挙動を確認) |
上記の条件にて、
ログイン〜ログアウトまでの短いE2Eテストフローを Selenium と Playwright でそれぞれ5回ずつ連続実行します。
比較に用いたE2Eテストサンプルコード
アクセスページのURLや認証情報、操作要素のXPathなどは config.py で切り出して import していますのでそちらは環境に合わせて作成してください。
↓Seleniumを用いた実装(Python)
import time import config from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.chrome.options import Options from selenium.webdriver.support import expected_conditions as EC def access_login_page(): start_time = time.perf_counter() # Chromeブラウザの起動 chrome_options = Options() driver = webdriver.Chrome(options=chrome_options) driver.maximize_window() # ウィンドウを最大化 driver.implicitly_wait(0) # 暗黙的待機を切る wait = WebDriverWait(driver, 5) # 明示的待機に統一 driver.get(config.login_page_url) # ログイン wait.until(EC.url_to_be(config.login_page_url)) wait.until(EC.visibility_of_element_located((By.XPATH, config.id_path))).send_keys(config.login_mail_address) wait.until(EC.visibility_of_element_located((By.XPATH, config.pw_path))).send_keys(config.login_password) wait.until(EC.visibility_of_element_located((By.XPATH, config.login_button_path))).click() # ホーム画面 wait.until(EC.url_to_be(config.home_page_url)) # レポート画面 wait.until(EC.visibility_of_element_located((By.XPATH, config.report_tab_path))).click() wait.until(EC.url_to_be(config.report_page_url)) wait.until(EC.visibility_of_element_located((By.XPATH, config.report_title_path))).text # ログアウト wait.until(EC.visibility_of_element_located((By.XPATH, config.accout_button_path))).click() wait.until(EC.visibility_of_element_located((By.XPATH, config.logout_button_path))).click() # ログインページ wait.until(EC.url_to_be(config.login_page_url)) driver.quit() end_time = time.perf_counter() elapsed_time = end_time - start_time print(f"テスト実行時間: {elapsed_time:.2f}秒") if __name__ == "__main__": access_login_page()
↓Playwrightを用いた実装(Python)
import time import config import asyncio from playwright.async_api import async_playwright, expect async def access_login_page(): start_time = time.perf_counter() async with async_playwright() as p: browser = await p.chromium.launch( headless=False, args=["--start-maximized"] ) context = await browser.new_context(no_viewport=True) page = await context.new_page() page.set_default_timeout(5000) # SeleniumのWebDriverWait(5)に対応 await page.goto(config.login_page_url) #ログイン await expect(page).to_have_url(config.login_page_url) id_input = page.locator(config.id_path) pw_input = page.locator(config.pw_path) login_button = page.locator(config.login_button_path) await id_input.fill(config.login_mail_address) await pw_input.fill(config.login_password) await login_button.click() # ホーム画面 await expect(page).to_have_url(config.home_page_url) # レポート画面 await page.locator(config.report_tab_path).click() await expect(page).to_have_url(config.report_page_url) await expect(page.locator(config.report_title_path)).to_have_text("レポート") # ログアウト処理 account_button = page.locator(config.accout_button_path) logout_button = page.locator(config.logout_button_path) await account_button.click() await logout_button.click() # ログインページに戻る await expect(page).to_have_url(config.login_page_url) await browser.close() end_time = time.perf_counter() elapsed_time = end_time - start_time print(f"テスト実行時間: {elapsed_time:.2f}秒") if __name__ == "__main__": asyncio.run(access_login_page())
結論
結論から述べると、
今回の計測では Playwright が Selenium より約 19% 高速という結果でした。
| E2Eテスト自動化ツール | 1回目 | 2回目 | 3回目 | 4回目 | 5回目 | 平均 |
|---|---|---|---|---|---|---|
| Selenium | 5.14秒 | 5.00秒 | 4.94秒 | 5.02秒 | 5.01秒 | 約5.02秒 |
| Playwright | 3.76秒 | 4.10秒 | 4.32秒 | 3.88秒 | 4.31秒 | 約4.07秒 |
| 観点 | Selenium | Playwright | 差分 | 備考 |
|---|---|---|---|---|
| 実行時間 | 5.02秒 | 4.07秒 | 0.95秒短縮 | - |
| 相対速度差 | 100% | 81.1% | 18.9%高速化 | Selenium比 |
※ Selenium、Playwrightともに初回の実行時間は長くなるので、2回目の実施から記録
ログイン~ログアウトまでの短い処理でも、実行時間が約 19% も短縮されるという結果は驚きでした。
さらに確認項目が増えてE2Eテストケース行数が膨大になった際に、より多くの実行時間削減が見込める期待がこの時点で持てました。
なぜPlaywrightでの実行は速いのか?
Playwright が Selenium に比べてE2Eテストコードの実行が速い理由は数多くあると思いますが、その中でも以下2つが大きな要因になっていると考えています。
- 通信処理
- 待機処理
Selenium と Playwright の通信処理の違い(実装ベース)
以下で色々と書いていますが、まとめると
Selenium は 「ブラウザとは別プロセスの WebDriver サーバに HTTP で命令し、さらにブラウザへ伝達する」
という二段構えになっているのに対し、
Playwright は 「ブラウザ内部に専用サーバがあり、Pipe で直結して命令できる」
状態になっており、この差がそのまま速度差として現れていそうでした。
Pipeについてはより細かい実装内容がありましたが、長くなるので割愛します。
ここでは Selenium のHTTPベースの通信より高速な、Playwright 専用の通信経路ぐらいの認識で問題ないです。
①コード呼び出し入口の違い
| 観点 | Selenium | Playwright |
|---|---|---|
| 実処理に渡す部分 | self.execute(Command.GET, {"url": url}) | self._impl_obj.goto(url) |
| 呼び出される実装 | RemoteConnection(HTTP 実装) | PageImpl |
| この層の役割 | WebDriver 仕様に沿って HTTP に変換 | 非同期で CDP(Chromium Driver)コマンドを送る |
②内部の実処理に繋がる経路
| 観点 | Selenium | Playwright |
|---|---|---|
| ユーザーの呼び出し | driver.get(url) | page.goto(url) |
| 呼び出される実装 | WebDriver.get() (webdriver.py) | Page.goto() (_generated.py) |
| この層の役割 | Wrapper 層。実処理は Impl に委譲 | HTTP コマンド名を決めて RemoteConnection に委譲 |
③ブラウザとの通信方式
| 観点 | Selenium | Playwright |
|---|---|---|
| 通信プロトコル | HTTP/JSON(WebDriver) | PipeTransport(Node <-> Browser) |
| 経路ファイル | remote_connection.py | _transport.py(PipeTransport) |
| 接続形態 | WebDriver Server(ChromeDriver/GeckoDriver)と HTTP 通信 | Node プロセスとブラウザを "パイプ" 接続 |
| コネクション維持 | 毎回 Request を送る | 1本の永続的パイプ |
④コマンドの送信方式
| 観点 | Selenium | Playwright |
|---|---|---|
| コマンド形式 | W3C WebDriver Command | JSON-RPC 形式のメッセージ |
| 送信コード | RemoteConnection._request() | PipeTransport.send_message_to_server() |
| 送信タイミング | HTTP POST を毎回独立に送信 | await で逐次(イベント駆動) |
| 送信量 | 大きい(毎回 JSON フル送信) | 小さい(差分のみ) |
Selenium と Playwright の待機処理の違い
Selenium には待機処理が存在しますが、その内部構造(selenium/webdriver/support/wait.py 内 WebDriverWait.until())を覗いてみると、ポーリングによる一定間隔毎の DOM のチェックであることがわかります。
while True: try: value = method(self._driver) if value: return value except self._ignored_exceptions as exc: screen = getattr(exc, "screen", None) stacktrace = getattr(exc, "stacktrace", None) if time.monotonic() > end_time: break time.sleep(self._poll) raise TimeoutException(message, screen, stacktrace)
一方で、Playwright が提供する自動待機は、ドキュメントで次のように書かれています。
Playwright performs a range of actionability checks on the elements before making actions to ensure these actions behave as expected. It auto-waits for all the relevant checks to pass and only then performs the requested action. If the required checks do not pass within the given timeout, action fails with the TimeoutError.
For example, for locator.click(), Playwright will ensure that:
locator resolves to exactly one element
element is Visible
element is Stable, as in not animating or completed animation
element Receives Events, as in not obscured by other elements
element is Enabled
この説明が示す通り、Playwright は要素の状態を driver 側で監視し、すべてが満たされてから指定の操作を実行しているようです。
つまり Selenium のようにユーザーが DOM で期待している要素があるかを確認する実装をしなくて済みますし、ポーリングで発生し得る絶妙なラグもないわけです。
| 観点 | Selenium | Playwright |
|---|---|---|
| 待機の仕組み | Python側で DOM の状態をポーリング | Driver がブラウザ内部イベントを監視 |
| 判断基準 | 瞬間的な DOM の状態 | DOM 変化・ネットワーク・ロードなどを総合判断 |
まとめ
今回の検証では、Selenium と Playwright の通信処理と待機処理の違いによって実行速度に差がでている理由を探りました。その結果、以下のような違いがあることが分かりました。
| 観点 | Selenium | Playwright |
|---|---|---|
| 通信処理 | ブラウザとは別プロセスの WebDriver サーバに HTTP で命令し、さらにブラウザへ伝達 | ブラウザ内部に専用サーバがあり、pipe で直結して命令できる |
| 待機の仕組み | Python側で DOM の状態をポーリング | Driver がブラウザ内部イベントを監視 |
触ってみた所感として、同じ Python で書くのであれば待機処理を意識して実装をしなくて済む点で Playwright の方が実装難易度的に低そうに見えました。
現在、ヤプリのQAで運用しているE2E自動テストの基盤は Selenium なので、即時で置き換えという訳にはいきません。
しかし、一部試験的に置き換えやE2Eテストの新規追加時に Playwright を使って実装を検討してみるのはありかもしれないと考えています。
おまけ(直近のE2Eテストフレームワークトレンド)
2024年あたりから Playwright がすごい勢いで伸びていますね。
個人的にはトラブルシューティング記事が豊富に存在するという観点で Selenium を使い続けていましたが、
最近のAIコーディングエージェントや Playwright MCP の登場により Playwright を扱うハードルがグッと下がってきたと感じております。
