ローカルの開発環境では、一般的に「開発用DB」と「テスト用DB」を分けて運用します。テストのリセット処理(migrate:freshなど)によって開発中のデータまで消えてしまったり、予期せず書き換わってしまうことがあるからです。Playwrightでも同様に、同じ環境でテストするならDBを分けておくのがベストです。今回は、開発DBとテストDBの切り替え手順について解説します。

プロジェクトの構成

今回の記事で扱うPlaywrightは、Laravelプロジェクトの中にe2eディレクトリとして存在しており、以下のような構成になっています。

laravel-project/
├── app/
├── …
└── e2e/(Playwright)

そして今回はDBだけではなく、それぞれの環境が干渉しないようサーバのポートも以下のように使い分けることにします。

ローカルの開発環境:8000
Playwrightの実行環境:8001

ではここから設定を進めていきます。
順を追って進めるためconfigファイルが複数回に分けて登場しますが、最後にconfigの全体もご紹介します。

.env.playwright

まずは.env.playwrightを作成します。以下のように最小限に内容のみ記載します。

APP_ENV=playwright
DB_DATABASE=database/playwright.sqlite

APP_ENVは今回の設定上必須ではありませんが、Laravelの動作中localと認識されないように環境をplaywrightと明示しておきます。そしてplaywright用のDBとして、今回はsqliteを使用します。

gitignoreに追加することもお忘れなく。

・・・・・
.env.playwright

.env.playwrightをconfigファイルに追加

次に、作成した.env.playwrightplaywright.config.tsに追加します。(TypeScriptの記述となっています)

import { defineConfig, devices } from '@playwright/test';
import dotenv from 'dotenv';
import { fileURLToPath } from 'url';
import path from 'path';

const __dirname = path.dirname(fileURLToPath(import.meta.url));

// .env を読み込んだあと、.env.playwright で差分を上書き
dotenv.config({ path: path.resolve(__dirname, '.env') });
dotenv.config({ path: path.resolve(__dirname, '.env.playwright'), override: true });
・・・・・

dotenv.config()を使用して.env.env.playwrightの両方を読み込んでいます。その際.env → .env.playwrightの順で読み込み、後者で上書きする形としています。そうすることで.env.playwrightの記述は最低限の変更箇所のみで済みます。

環境にdotenvがインストールされていない場合はテスト実行時にコケてしまうので、以下のコマンドでインストールしておいてくださいね。

$ npm install dotenv

Playwright用のSeeder

次にSeederを作成します。Laravelプロジェクトならすでに存在しているであろうDatabaseSeederはユニットテストで使用するため、Playwright専用のSeederを作成することにします。

namespace Database\Seeders;

use App\Models\User;
use Illuminate\Database\Seeder;

class PlaywrightSeeder extends Seeder
{
    public function run(): void
    {
        $user = User::factory()->create([
            'name'  => 'Test User',
            'email' => 'test@example.com',
        ]);

        $this->callWith(FavoriteFoodSeeder::class, ['user' => $user]);
        $this->callWith(MealSeeder::class, ['user' => $user]);
    }
}

ユーザーと、ユーザーに紐づく食事データなどの基本情報を作成するシンプルな内容となっています。

Seeder実行ファイルの作成と設定

次に、作成したSeederを実行するためのセットアップファイルを作成します。ファイル名はなんでも良いですが、今回はdb.setup.tsとしました。

import { test as setup } from '@playwright/test';
import { execSync } from 'child_process';
import { existsSync, writeFileSync } from 'fs';

setup('reset database', async () => {
  // SQLiteファイルが存在しない場合は作成する
  if (!existsSync('database/playwright.sqlite')) {
    writeFileSync('database/playwright.sqlite', '');
  }

  execSync('php artisan migrate:fresh --seeder=PlaywrightSeeder', { stdio: 'inherit' });
});

setup()を使用して、その中に実行内容を定義します。第1引数に指定している文字列はテスト実行後のHTMLレポートに表示されるので、何をしているのかわかるような内容にしたほうが良いです。

そしてexecSync()を使ってartisanコマンドを実行します。先ほどconfigファイルで設定した.env.playwrightの情報を元に実行されますので、マイグレーションはテスト用DBであるdatabase/playwright.sqliteに対して行われます。

また{ stdio: 'inherit' }を指定することで、マイグレーション実行時の進捗状況や、成功・エラーメッセージがターミナルに表示されるようになります。

DBセットアップファイルをconfigファイルで読み込む

再びplaywright.config.tsに戻ります。先ほど作成したdb.setup.tsをconfigファイルのprojectsに追加します。

少しわかりにくいので、追加前の該当部分を先にお見せします。もともとは以下のように「setup(認証情報のセットアップ)」と、「chromium(ブラウザテスト)」の2つのプロジェクトがありました。

・・・・・
  projects: [
    // Setup project for authentication
    { name: 'setup', testMatch: '**/auth.setup.ts' },

    {
      name: 'chromium',
      use: {
        ...devices['Desktop Chrome'],
        // Use prepared auth state.
        storageState: 'playwright/.auth/user.json',
      },
      dependencies: ['setup'],
    },
  ],
・・・・・

ここにDBのセットアッププロジェクトを追加すると、以下のようになります。

・・・・・
  projects: [
    // DB reset and seeding
    { name: 'db-setup', testMatch: '**/db.setup.ts' },  // ここを追加

    // Setup project for authentication
    { name: 'setup', testMatch: '**/auth.setup.ts', dependencies: ['db-setup'] },  // ここを追加

    {
      name: 'chromium', // ここは変更なし
      use: {
        ...devices['Desktop Chrome'],
        // Use prepared auth state.
        storageState: 'playwright/.auth/user.json',
      },
      dependencies: ['setup'],
    },
  ],
・・・・・

追加したのは{ name: 'db-setup', testMatch: '**/db.setup.ts' },の行と、setupプロジェクトへの依存関係dependencies: ['db-setup'] の部分です。

元からあったchromiumプロジェクトにも依存関係dependencies: ['setup']がありますので、今回の変更により、テスト開始時の実行順序は以下のようになります。

  1. db-setup:まずDBをリセットしてデータを投入する
  2. setup:DBの準備が整ったあとで、ログイン認証を行い状態を保存する
  3. chromium:全ての準備(DBと認証)が終わったあとで、実際のテストを開始する

webServerの設定

最後は、テスト用サーバを起動するための設定です。

Playwrightには、テスト開始時に自動でローカルサーバを立ち上げてくれるwebServerという機能があります。これを利用して、開発用の8000番とは異なるテスト専用ポートの8001番でアプリを起動するようconfigファイルを編集します。

・・・・・
export default defineConfig({
  use: {
    /* Base URL to use in actions like `await page.goto('/')`. */
    baseURL: 'http://127.0.0.1:8001',
・・・・・
  },
  webServer: {
    command: 'php artisan serve --port=8001',
    url: 'http://127.0.0.1:8001',
    reuseExistingServer: !process.env.CI,
    stdout: 'ignore',
    stderr: 'pipe',
  },
});

useセクションのbaseURLを、ポート8000から8001に変更。そしてwebServerセクションを新規に追加しました。webServerでは以下のような項目を設定しています。

  • command:テスト開始前に実行するシェルコマンドを指定
  • url:サーバーの起動完了を判定するためのURL(このURLにアクセス可能になるまでテストを待機)
  • reuseExistingServer:すでにサーバーが起動している場合の挙動を制御(!process.env.CIとすることで、ローカルなどCI以外の環境の場合、既存サーバーがあれば再利用、無ければ起動する、という動作になる)
  • stdout:通常ログの出力設定(ignoreを指定すると、正常時のログは表示しない)
  • stderr:エラーログの出力設定(pipeを指定すると、起動失敗やエラー時のみターミナルに表示)

このようにwebServerを設定しておくことで、テスト用サーバーを手動でオン・オフにする必要がなく、またテスト中に意図しない開発用DBに接続してしまう危険を回避できます。

これでテスト環境とのDB分離・リセットの設定が完了しました!

最終的なplaywright.config.ts

ここまでの設定を反映した、最終的なplaywright.config.tsは以下のようになります。

import { defineConfig, devices } from '@playwright/test';
import dotenv from 'dotenv';
import { fileURLToPath } from 'url';
import path from 'path';

const __dirname = path.dirname(fileURLToPath(import.meta.url));

// .env を読み込んだあと、.env.playwright で差分を上書き
dotenv.config({ path: path.resolve(__dirname, '.env') });
dotenv.config({ path: path.resolve(__dirname, '.env.playwright'), override: true });

/**
 * Playwright E2E Test Configuration
 * @see https://playwright.dev/docs/test-configuration
 */
export default defineConfig({
  testDir: './e2e',

  /* Run tests in files in parallel */
  fullyParallel: false,

  /* Fail the build on CI if you accidentally left test.only in the source code. */
  forbidOnly: !!process.env.CI,

  /* Retry on CI only */
  retries: process.env.CI ? 2 : 0,

  /* Opt out of parallel tests on CI. */
  // workers: process.env.CI ? 1 : undefined,
  workers: 1,

  /* Reporter to use. See https://playwright.dev/docs/test-reporters */
  reporter: 'html',

  /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
  use: {
    /* Base URL to use in actions like `await page.goto('/')`. */
    baseURL: 'http://127.0.0.1:8001',

    /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
    trace: 'on-first-retry',

    /* Screenshot on failure */
    screenshot: 'only-on-failure',

    /* Video on failure */
    video: 'retain-on-failure',
  },

  /* Configure projects for major browsers */
  projects: [
    // DB reset and seeding
    { name: 'db-setup', testMatch: '**/db.setup.ts' },

    // Setup project for authentication
    { name: 'setup', testMatch: '**/auth.setup.ts', dependencies: ['db-setup'] },

    // Authenticated tests
    {
      name: 'chromium',
      use: {
        ...devices['Desktop Chrome'],
        // Use prepared auth state.
        storageState: 'playwright/.auth/user.json',
      },
      dependencies: ['setup'],
    },
  ],

  /* Run your local dev server before starting the tests */
  webServer: {
    command: 'php artisan serve --port=8001',
    url: 'http://127.0.0.1:8001',
    reuseExistingServer: !process.env.CI,
    stdout: 'ignore',
    stderr: 'pipe',
  },
});
メルマガ購読の申し込みはこちらから。

By hmatsu