はじめに
こんにちは、テクノロジー本部の田中です。 本記事は、「エムティーアイ Blog Summer 2025」の8月8日分の記事となります。
今回は、以前に個人的興味があってやってみた話「Docker上の仮想環境でブラウザを動かしてみた」の続きものとして、DockerとSeleniumを組み合わせてE2Eテスト環境を構築する方法を書きたいと思います。
Docker環境でのSelenium E2Eテスト
なぜ、DockerでE2Eテスト環境を用意するのか?
Webアプリケーションの品質保証において、E2Eテストは欠かせない要素です。
Dokcerを使うメリットとして
- どのマシンでも同じテスト環境が再現可能
- 開発者間の共有が容易
- テストに必要な全ての要素をコンテナに封じ込め
Firefoxブラウザの Docker 化
FirefoxのDocker化については、以前書いた記事「Docker上の仮想環境でブラウザを動かしてみた」で詳細に解説しています。 詳細な実装方法は上記記事をご参照ください。
前回からのアップデート
若干、アップデートをしました!
Firefoxのバージョン選択を可能にしています。(デフォルトは最新版)
Python Selenium テスト環境の構築
ここからが本記事の核心部分です。SeleniumとPytestを組み合わせたテスト環境をDockerで構築していきます。
今回のサンプルプロジェクト構造
まず、全体のプロジェクト構造を紹介します。
このプロジェクトのファイルのほとんどはAIを利用して、作成してもらいました!
project-root/ ├── counter-app/ # テスト対象アプリケーション │ ├── index.html # メインHTMLファイル │ ├── styles.css # スタイルシート │ ├── script.js # JavaScriptロジック │ ├── server.js # Node.js開発サーバー │ ├── package.json # Node.js依存関係 │ ├── counter-app.Dockerfile # アプリ用Dockerfile │ └── README.md # アプリ説明 ├── python-e2e-tests/ # E2Eテストプロジェクト │ ├── tests/ # テストファイル │ │ ├── base_test.py # ベーステストクラス │ │ ├── test_counter_basic.py # 基本機能テスト │ │ ├── test_counter_performance.py # パフォーマンステスト │ │ └── test_counter_ui.py # UIテスト │ ├── conftest.py # pytest設定 │ ├── requirements.txt # Python依存関係 │ ├── Dockerfile # テスト実行用コンテナ │ ├── docker-compose.yml # 統合環境定義 │ ├── run_tests_docker.sh # テスト実行スクリプト │ ├── reports/ # テストレポート出力先 │ │ └── screenshots/ # 失敗時スクリーンショット │ └── README.md # テストプロジェクト説明
Dockerfile
まず、Python Selenium テスト実行用のDockerfileを作成します。
FROM python:3.11-slim WORKDIR /app # Firefox と Xvfb(ヘッドレス表示用)のインストール RUN apt-get update && apt-get install -y \ wget \ curl \ firefox-esr \ xvfb \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* # geckodriver のインストール RUN GECKO_VERSION=$(curl -s https://api.github.com/repos/mozilla/geckodriver/releases/latest | grep -Po '"tag_name": "\K.*?(?=")') && \ wget -O /tmp/geckodriver.tar.gz https://github.com/mozilla/geckodriver/releases/download/$GECKO_VERSION/geckodriver-$GECKO_VERSION-linux64.tar.gz && \ tar -xzf /tmp/geckodriver.tar.gz -C /usr/local/bin && \ chmod +x /usr/local/bin/geckodriver && \ rm /tmp/geckodriver.tar.gz # Python依存関係のインストール COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . # 環境変数の設定 ENV PYTHONPATH=/app ENV DISPLAY=:99 CMD ["./run_tests_docker.sh", "tests/", "-v", "--html=reports/report.html", "--self-contained-html"]
requirements.txt
selenium==4.15.2 pytest==7.4.3 pytest-html==4.1.1 webdriver-manager==4.0.1
pytest 設定(conftest.py)
import pytest import os import time def pytest_addoption(parser): """コマンドラインオプションの追加""" parser.addoption( "--app-url", action="store", default=os.environ.get("APP_URL", "http://counter-app:3000"), help="テスト対象アプリのURL" ) @pytest.fixture(scope="session") def app_url(request): """アプリケーションのURL""" return request.config.getoption("--app-url") @pytest.fixture(scope="function") def screenshot_on_failure(request): """テスト失敗時のスクリーンショット撮影""" yield if hasattr(request.node, 'rep_call') and request.node.rep_call.failed: if hasattr(request.instance, 'driver'): screenshot_dir = "reports/screenshots" os.makedirs(screenshot_dir, exist_ok=True) screenshot_name = f"{request.node.name}_{int(time.time())}.png" screenshot_path = os.path.join(screenshot_dir, screenshot_name) try: request.instance.driver.save_screenshot(screenshot_path) print(f"スクリーンショット保存: {screenshot_path}") except Exception as e: print(f"スクリーンショット撮影失敗: {e}")
ベーステストクラス(base_test.py)
import pytest from selenium import webdriver from selenium.webdriver.firefox.options import Options from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC import time import os class BaseTest: """Docker Compose環境用のSeleniumテストのベースクラス""" @pytest.fixture(autouse=True) def setup_driver(self): """テスト実行前のSeleniumドライバー設定""" firefox_options = Options() firefox_options.add_argument("--no-sandbox") firefox_options.add_argument("--disable-dev-shm-usage") firefox_options.add_argument("--headless") # Docker環境では常にヘッドレス # プロファイル設定 firefox_options.set_preference("dom.webnotifications.enabled", False) firefox_options.set_preference("media.volume_scale", "0.0") try: self.driver = webdriver.Firefox(options=firefox_options) self.driver.set_window_size(1920, 1080) self.wait = WebDriverWait(self.driver, 10) # カウンターアプリのベースURL self.base_url = os.environ.get("APP_URL", "http://counter-app:3000") yield finally: if hasattr(self, 'driver'): self.driver.quit() def navigate_to_app(self): """カウンターアプリにナビゲート""" self.driver.get(self.base_url) self.wait.until(EC.presence_of_element_located((By.ID, "counter"))) def get_counter_value(self): """現在のカウンター値を取得""" counter_element = self.driver.find_element(By.ID, "counter") return int(counter_element.text) def click_increase_button(self): """増加ボタンをクリック""" increase_btn = self.wait.until(EC.element_to_be_clickable((By.ID, "increaseBtn"))) increase_btn.click() def click_decrease_button(self): """減少ボタンをクリック""" decrease_btn = self.wait.until(EC.element_to_be_clickable((By.ID, "decreaseBtn"))) decrease_btn.click()
E2Eテストの実装例
対象アプリケーションの概要
今回テストするのは、シンプルなカウンターアプリケーションです。
マジでここは100%AIで作成しました...
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>カウンターアプリ</title> <link rel="stylesheet" href="styles.css"> </head> <body> <div class="counter-container"> <h1>カウンター</h1> <div id="counter" class="counter-display">0</div> <div class="button-container"> <button id="decreaseBtn" class="decrease-btn">-</button> <button id="increaseBtn" class="increase-btn">+</button> </div> </div> <script src="script.js"></script> </body> </html>
実際の画面は以下のようなものです。

カウンターアプリのテスト実装
基本機能テストの実装例
ベーステストクラスを継承して、具体的なテストケースを実装します。
ちなみにこれもAIです。
import pytest from selenium.webdriver.common.by import By from tests.base_test import BaseTest class TestCounterBasic(BaseTest): """カウンターアプリの基本機能テスト""" def test_page_loads_correctly(self): """ページが正しく読み込まれることを確認""" self.navigate_to_app() # タイトルの確認 assert "カウンターアプリ" in self.driver.title # ヘッダーの確認 header = self.driver.find_element(By.TAG_NAME, "h1") assert header.text == "カウンター" # 初期カウント値の確認 assert self.get_counter_value() == 0 # ボタンの存在確認 increase_btn = self.driver.find_element(By.ID, "increaseBtn") decrease_btn = self.driver.find_element(By.ID, "decreaseBtn") assert increase_btn.is_displayed() assert decrease_btn.is_displayed() assert increase_btn.text == "+" assert decrease_btn.text == "-" def test_counter_increase_functionality(self): """カウンター増加機能のテスト""" self.navigate_to_app() # 初期値確認 assert self.get_counter_value() == 0 # +ボタンをクリック self.click_increase_button() assert self.get_counter_value() == 1 # さらに4回クリック for i in range(4): self.click_increase_button() assert self.get_counter_value() == 5 def test_counter_decrease_functionality(self): """カウンター減少機能のテスト""" self.navigate_to_app() # -ボタンをクリック self.click_decrease_button() assert self.get_counter_value() == -1 # さらに4回クリック for i in range(4): self.click_decrease_button() assert self.get_counter_value() == -5 def test_counter_mixed_operations(self): """増加と減少の組み合わせテスト""" self.navigate_to_app() # +ボタンを3回 for i in range(3): self.click_increase_button() assert self.get_counter_value() == 3 # -ボタンを1回 self.click_decrease_button() assert self.get_counter_value() == 2 # -ボタンを5回 for i in range(5): self.click_decrease_button() assert self.get_counter_value() == -3
テスト実行とレポート生成
docker-compose.ymlの内容
version: '3.8' services: # カウンターアプリケーション counter-app: build: context: ../counter-app dockerfile: counter-app.Dockerfile ports: - "3000:3000" networks: - test-network # Python E2Eテスト e2e-tests: build: context: . dockerfile: Dockerfile volumes: - ./reports:/app/reports networks: - test-network depends_on: - counter-app environment: - APP_URL=http://counter-app:3000 - DISPLAY=:99 networks: test-network: driver: bridge
テスト実行
# Docker Compose でテスト実行 docker-compose up -d counter-app docker-compose run --rm e2e-tests # レポートが reports/report.html に生成される
実際に実行したreport.htmlが以下です。

感想
この記事では、DockerとSeleniumを組み合わせたE2Eテスト環境の構築方法をご紹介しました。実際にやる敷居がAIによって低くなったと実感した内容でした!
今回では、行わなかったですが、以下のようなこともできると思っています。
- 複数ブラウザ対応: Chrome、Edgeなど他ブラウザへの展開
- 並列実行: pytest-xdistを使った並列テスト実行
- ビジュアルテスト: スクリーンショット比較によるビジュアルリグレッションテスト
- APIテスト統合: SeleniumとAPIテストの組み合わせ
今後も引き続き、遊び心で作成していこうと思います!