はじめに Link to heading
前回の記事では、n8nとDifyのDocker環境構築について解説しました。今回は、その環境に対する自動テスト戦略を構築します。
ブラウザ確認だけでは不十分な理由:
- 手動確認は時間がかかる
- 再現性が低い
- CI/CDに組み込めない
- 回帰テストが困難
本記事では、Playwright + pytestを使った3層テスト戦略で、これらの課題を解決します。
テストピラミッド:3層の戦略 Link to heading
/\
/ \ E2E Tests (Playwright)
/____\ - UI自動化
/ \ - ワークフロー確認
/ \
/ 統合 \ Integration Tests
/____________\ - サービス間連携
/ \
/ API Tests \ API Level Tests (最優先)
/________________\- ヘルスチェック (0.8秒)
レイヤー1: API Tests(最速・最重要) Link to heading
目的: サービスが起動しているか即座に確認
1@pytest.mark.api
2@pytest.mark.smoke
3def test_n8n_health(sync_http_client, n8n_url):
4 """n8nが正常に起動しているか確認"""
5 response = sync_http_client.get(n8n_url)
6 assert response.status_code in [200, 401]
7
8@pytest.mark.api
9@pytest.mark.smoke
10def test_weaviate_health(sync_http_client, weaviate_url):
11 """Weaviateベクトルデータベースが準備完了か確認"""
12 response = sync_http_client.get(f"{weaviate_url}/v1/.well-known/ready")
13 assert response.status_code == 200
実行速度:
1$ pytest -m smoke
27 passed in 0.81s
レイヤー2: Integration Tests(将来実装) Link to heading
サービス間の連携を確認:
- n8n → Dify API連携
- ワークフロー実行結果の検証
レイヤー3: E2E Tests(Playwright) Link to heading
実際のブラウザ操作を自動化
1@pytest.mark.e2e
2@pytest.mark.asyncio
3async def test_n8n_homepage_loads(page, n8n_base_url):
4 """n8nのホームページが読み込まれることを確認"""
5 await page.goto(n8n_base_url, timeout=30000)
6 await page.wait_for_load_state("networkidle")
7
8 title = await page.title()
9 assert "n8n" in title.lower() or len(title) > 0
10
11 print(f"✓ n8n page loaded: {title}")
環境構築:ステップバイステップ Link to heading
1. 依存関係のインストール Link to heading
1# Python仮想環境の作成
2python3 -m venv venv
3source venv/bin/activate
4
5# テスト用ライブラリのインストール
6pip install pytest pytest-asyncio pytest-cov
7pip install httpx requests
8pip install playwright psycopg2-binary redis
9
10# Playwrightブラウザのインストール
11playwright install chromium
2. pytest設定 Link to heading
pytest.ini:
1[pytest]
2testpaths = tests
3python_files = test_*.py
4addopts = -v --strict-markers --tb=short
5markers =
6 api: API level tests
7 e2e: End-to-end tests
8 smoke: Smoke tests for quick validation
9 slow: Tests that take longer to run
10asyncio_mode = auto
3. Playwrightフィクスチャの設定 Link to heading
重要なポイント:session-scopedイベントループ
1# tests/e2e/conftest.py
2import pytest
3import pytest_asyncio
4import asyncio
5from playwright.async_api import async_playwright
6
7@pytest.fixture(scope="session")
8def event_loop():
9 """セッション全体で共有するイベントループ"""
10 loop = asyncio.get_event_loop_policy().new_event_loop()
11 yield loop
12 loop.close()
13
14@pytest_asyncio.fixture(scope="session")
15async def playwright_instance():
16 """Playwrightインスタンス"""
17 async with async_playwright() as p:
18 yield p
19
20@pytest_asyncio.fixture(scope="session")
21async def browser(playwright_instance):
22 """ブラウザインスタンス"""
23 browser = await playwright_instance.chromium.launch(headless=True)
24 yield browser
25 await browser.close()
26
27@pytest_asyncio.fixture
28async def page(context):
29 """各テストで新しいページを作成"""
30 page = await context.new_page()
31 yield page
32 await page.close()
よくあるエラーと解決方法:
❌ AttributeError: 'async_generator' object has no attribute 'goto'
- 原因:asyncフィクスチャが正しく初期化されていない
- 解決:
@pytest_asyncio.fixtureを使用し、session-scopedイベントループを追加
実践:Dify初期セットアップの自動化 Link to heading
Difyを初めて起動すると、データベースエラーが発生することがあります。
トラブルシューティング実例 Link to heading
問題:Internal Server Error
1$ curl http://localhost:3000
2# → "Internal Server Error"
3
4$ docker compose logs dify-api
5# → sqlalchemy.exc.ProgrammingError: relation "dify_setups" does not exist
解決:データベースマイグレーション
1# マイグレーション実行
2docker compose exec dify-api flask db upgrade
3
4# サービス再起動
5docker compose restart dify-api dify-web dify-worker
6
7# 確認
8curl -s http://localhost:3000 | grep "Setting up"
9# → "Setting up an admin account" が表示されればOK
セットアップ自動化 Link to heading
1@pytest.mark.e2e
2@pytest.mark.asyncio
3async def test_dify_initial_setup(page, dify_base_url):
4 """Dify初期セットアップを自動化"""
5 await page.goto(dify_base_url, timeout=30000)
6 await page.wait_for_load_state("networkidle")
7 await page.wait_for_timeout(2000) # React描画を待つ
8
9 if "/install" in page.url:
10 # メールアドレス入力
11 email_input = page.locator('input[type="email"]').first
12 await email_input.fill("admin@example.com")
13
14 # 名前入力
15 name_input = page.locator('input[type="text"]').first
16 await name_input.fill("Admin User")
17
18 # パスワード入力
19 password_input = page.locator('input[type="password"]').first
20 await password_input.fill("Admin123456!")
21
22 # セットアップボタンをクリック
23 submit_button = page.locator('button:has-text("Set up")').first
24 await submit_button.click()
25
26 await page.wait_for_timeout(3000)
27 await page.wait_for_load_state("networkidle")
28
29 # /appsにリダイレクトされれば成功
30 assert "/install" not in page.url
31 assert "/apps" in page.url
32 print("✓ Setup completed successfully!")
実行結果:
1$ pytest tests/e2e/test_dify_setup.py -v -s
2=== Starting Dify Setup ===
3Current URL: http://localhost:3000/install
4On install page, proceeding with setup...
5Found email input
6Found name input
7Found 1 password inputs
8Found submit button, clicking...
9After submit URL: http://localhost:3000/apps
10✓ Setup completed successfully!
11PASSED
ヘルスチェックスクリプト Link to heading
シンプルで高速なシェルスクリプトも作成:
1#!/bin/bash
2# scripts/health_check.sh
3
4check_service() {
5 local name=$1
6 local url=$2
7 local expected_codes=$3
8
9 http_code=$(curl -L -s -o /dev/null -w "%{http_code}" "$url")
10
11 if [[ $expected_codes == *"$http_code"* ]]; then
12 echo -e "${GREEN}✓${NC} $name: HTTP $http_code"
13 return 0
14 else
15 echo -e "${RED}✗${NC} $name: HTTP $http_code"
16 return 1
17 fi
18}
19
20check_service "n8n" "http://localhost:5678" "200 401"
21check_service "Dify Web" "http://localhost:3000" "200"
22check_service "Dify API" "http://localhost:5001" "200 404"
23check_service "Weaviate" "http://localhost:8080/v1/.well-known/ready" "200"
実行:
1$ ./scripts/health_check.sh
2=== Service Health Check ===
3
4Checking n8n... ✓ HTTP 200
5Checking Dify Web... ✓ HTTP 200
6Checking Dify API... ✓ HTTP 200
7Checking Weaviate Ready... ✓ HTTP 200
8
9All services are healthy!
CI/CD統合 Link to heading
GitHub Actionsでの自動テスト例:
1name: Test Workflow
2
3on: [push, pull_request]
4
5jobs:
6 test:
7 runs-on: ubuntu-latest
8
9 steps:
10 - uses: actions/checkout@v3
11
12 - name: Start Docker services
13 run: docker compose up -d
14
15 - name: Wait for services
16 run: sleep 30
17
18 - name: Run health check
19 run: ./scripts/health_check.sh
20
21 - name: Setup Python
22 uses: actions/setup-python@v4
23 with:
24 python-version: '3.12'
25
26 - name: Install dependencies
27 run: |
28 python -m venv venv
29 source venv/bin/activate
30 pip install -r requirements.txt
31
32 - name: Run smoke tests
33 run: |
34 source venv/bin/activate
35 pytest -m smoke --cov=. --cov-report=xml
36
37 - name: Run E2E tests
38 run: |
39 source venv/bin/activate
40 pytest -m e2e -v
テスト実行パターン Link to heading
開発時(高速フィードバック) Link to heading
1# スモークテストのみ(0.8秒)
2pytest -m smoke
3
4# 特定のテストファイル
5pytest tests/api/test_health.py -v
6
7# 詳細出力
8pytest -v -s
デプロイ前(完全テスト) Link to heading
1# すべてのテスト
2pytest
3
4# カバレッジレポート付き
5pytest --cov=. --cov-report=html
6open htmlcov/index.html
デバッグ時 Link to heading
1# Playwrightのデバッグモード
2PWDEBUG=1 pytest tests/e2e/
3
4# ヘッドフルモード(ブラウザ表示)
5pytest tests/e2e/ --headed
6
7# スローモーション
8pytest tests/e2e/ --slowmo=1000
スクリーンショット活用 Link to heading
テスト実行時に自動でスクリーンショットを保存:
1# 失敗時のスクリーンショット
2await page.screenshot(path=f"screenshots/error_{test_name}.png")
3
4# フルページスクリーンショット
5await page.screenshot(path="screenshots/full_page.png", full_page=True)
生成されるスクリーンショット:
screenshots/n8n_homepage.png(28KB)screenshots/dify_install_page.png(30KB)screenshots/dify_after_setup.png(正常に/appsに遷移)
ベストプラクティス Link to heading
1. テストの独立性を保つ Link to heading
❌ 悪い例:
1# test_a が test_b に依存
2def test_a():
3 create_user("test@example.com")
4
5def test_b():
6 login("test@example.com") # test_a に依存
✅ 良い例:
1@pytest.fixture
2def test_user():
3 user = create_user("test@example.com")
4 yield user
5 cleanup_user(user)
6
7def test_b(test_user):
8 login(test_user.email)
2. 適切な待機時間 Link to heading
❌ 悪い例:
1await page.goto(url)
2await page.click("button") # 要素がまだ存在しない可能性
✅ 良い例:
1await page.goto(url)
2await page.wait_for_load_state("networkidle")
3await page.wait_for_timeout(2000) # React描画を待つ
4button = page.locator("button:has-text('Submit')").first
5await button.click()
3. エラーハンドリング Link to heading
1try:
2 await page.goto(url, timeout=30000)
3except Exception as e:
4 await page.screenshot(path="screenshots/error.png")
5 print(f"Error: {e}")
6 raise
トラブルシューティング Link to heading
Playwrightが動作しない Link to heading
1# ブラウザの再インストール
2playwright install --force chromium
3
4# 依存関係の確認
5playwright install-deps
Difyで"Internal Server Error" Link to heading
1# データベースマイグレーション
2docker compose exec dify-api flask db upgrade
3
4# ログ確認
5docker compose logs dify-api | tail -50
ポート競合 Link to heading
1# 使用中のポートを確認
2sudo lsof -i :5678
3sudo lsof -i :3000
4
5# docker-compose.ymlでポート変更
6ports:
7 - "15678:5678" # n8n
最終テスト結果 Link to heading
1$ pytest tests/
2============== test session starts ==============
3collected 18 items
4
5tests/api/test_health.py ✓✓✓✓✓✓✓ [ 38%]
6tests/e2e/test_n8n_ui.py ✓✓✓ [ 55%]
7tests/e2e/test_dify_ui.py ✓✓✓ [ 72%]
8tests/e2e/test_dify_setup.py ✓✓ [ 83%]
9tests/e2e/test_dify_detailed.py ✓✓ [100%]
10
11======== 18 passed in 33.21s ========
12Coverage: 85%
まとめ Link to heading
本記事で構築した自動テスト環境:
✅ 3層テスト戦略
- API Tests(0.8秒)
- E2E Tests(33秒)
- カバレッジ85%
✅ Playwright + pytest
- 非同期APIで高速実行
- スクリーンショット自動保存
- CI/CD統合可能
✅ 実践的なトラブルシューティング
- Difyのデータベースマイグレーション
- 初期セットアップ自動化
- エラー時のデバッグ方法
次のステップ Link to heading
- 統合テストの追加 - n8nとDifyの連携テスト
- パフォーマンステスト - 負荷試験
- ビジュアルリグレッションテスト - UI変更の検出
リポジトリ Link to heading
完全な実装は以下で公開しています:
参考リソース Link to heading
管理者ログイン情報(開発環境):
- Dify:
admin@example.com/Admin123456! - n8n: http://localhost:5678 (初回アクセス時にセットアップ)
自動テスト環境を構築することで、開発速度と品質の両立が実現できました。ぜひ皆さんのプロジェクトにも取り入れてみてください!