0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Laravel 13 + WSLで始めるPlaywright【3. E2E Laravel環境構築編】

0
Last updated at Posted at 2026-09-06

目次


はじめに

第1回ではPlaywright TestとChromiumを導入し、第2回ではPlaywright用のTypeScript設定を作成しました。

ここまででPlaywright自体を実行する準備はできましたが、まだ「どのLaravelへ接続してブラウザテストを実行するのか」が決まっていません。

今回の開発環境では、通常開発用LaravelとPlaywright用Laravelを同じものとして扱わず、Playwright専用のLaravel実行環境をLaravel Sail上に分離しました。

最終的な役割分担は次のようになります。

WSL / Ubuntu
├─ Node.js
├─ npm
├─ Playwright Test
├─ TypeScript
└─ Chromium
        │
        │ HTTP
        ▼
http://localhost:83
        │
        ▼
Laravel Sail
└─ laravel.e2e
   └─ Laravel

この記事では、Playwrightの基本設定からE2E専用Laravel service、Viteのhot file分離、artisan serve --no-reloadを採用するまでを振り返ります。


この記事で扱う範囲

この記事は、Playwright導入時のIssue #157で構築した内容を中心にしています。

この時点では、まだE2E専用データベースは構築していません。

今回
→ Playwrightが接続するE2E専用Laravel環境を作る

次回
→ E2E専用DB・MySQLユーザー・reset・fixture基盤を作る

そのため、この記事中のDB設定は通常開発DBへ誤接続しないために意図的に接続不能にした#157時点の構成です。

後続でE2E専用DBを導入した現在の構成とは異なります。


環境

Playwright導入時点の環境です。

PHP             8.4.24
Laravel         13.26.1
Laravel Sail    1.58.0
Node.js         24.19.0
npm             11.17.0
Playwright Test 1.62.1
TypeScript      7.0.2
WSL             Ubuntu 24.04

ローカルでは、通常Laravelをhttp://localhost:82で利用していました。

今回、Playwright用Laravelには別ポートのhttp://localhost:83を使用します。


コマンドを実行するディレクトリ

今回も、WSLのUbuntu上でLaravelプロジェクトのルートへ移動してから作業します。

cd ~/projects/review-app-laravel

Laravelプロジェクトルートには次のようなファイルがあります。

artisan
composer.json
compose.yaml
package.json
package-lock.json
playwright.config.ts
vite.config.js

以降のnpm、npx、Docker Compose関連コマンドは、特に記載がない限りこのディレクトリで実行します。


PlaywrightとLaravelの実行場所を分ける

今回の構成では、PlaywrightとChromiumはWSL側で実行します。

LaravelはLaravel Sailのコンテナ側です。

WSL
├─ Playwright Test
├─ Chromium
└─ HTTPリクエスト
      │
      ▼
Docker / Laravel Sail
└─ laravel.e2e
   └─ Laravel

PlaywrightをSailコンテナへ入れるのではなく、ブラウザテストを実行する側とテスト対象のWebアプリケーションを分離しました。

この構成にすると、

Playwright
→ ブラウザを操作するテストランナー

laravel.e2e
→ ブラウザからアクセスされるテスト対象

と役割を整理できます。


Playwrightの基本設定を作る

プロジェクトルートへplaywright.config.tsを作成しました。

#157時点の初期設定は次のような構成です。

import { defineConfig } from '@playwright/test';

export default defineConfig({
  testDir: './e2e',
  workers: 1,

  use: {
    baseURL: 'http://localhost:83',
    screenshot: 'only-on-failure',
    trace: 'retain-on-failure',
    video: 'off',
  },

  projects: [
    {
      name: 'chromium',
      use: {
        browserName: 'chromium',
      },
    },
  ],
});

Playwright公式では、テストの配置場所やworker数、baseURL、projectなどをplaywright.config.tsで設定できます。

testDir

testDir: './e2e',

Playwrightのテストをe2e/から読み込みます。

Laravel側のPHPUnitは既存のtests/を使用しているため、別ディレクトリにしました。

workers: 1

workers: 1,

初期導入では並列実行せず、1workerで実行します。

後続で共有するE2E fixtureを導入することも考慮し、まずはテスト実行順やDB状態を追いやすい単純な構成から始めました。

baseURL

baseURL: 'http://localhost:83',

Playwright公式ではbaseURLを設定すると、

await page.goto('/login');

のように相対URLでアクセスできます。

この場合、実際には次のURLへアクセスします。

http://localhost:83/login

screenshot / trace / video

screenshot: 'only-on-failure',
trace: 'retain-on-failure',
video: 'off',

初期構成では、

screenshot
→ 失敗時だけ保存

trace
→ 失敗したテストの調査用に保持

video
→ 無効

としました。

Playwright公式でも、これらはuseオプションとして設定できます。

Chromiumだけをprojectにする

projects: [
  {
    name: 'chromium',
    use: {
      browserName: 'chromium',
    },
  },
],

初期導入ではChromiumだけを対象にしました。

Firefox / WebKitやモバイル端末のエミュレーションは、基盤が安定してから必要性を判断する方針です。


PHPUnitとPlaywrightのテスト配置を分ける

テストディレクトリは次のように分離しました。

tests/
└─ PHPUnit

e2e/
└─ Playwright

今回Playwrightを追加した理由は、PHPUnitを置き換えるためではありません。

役割は次のように分けます。

PHPUnit
→ Laravel内部のHTTP処理
→ validation
→ 認証・認可
→ DB更新
→ Controller / Service等の振る舞い

Playwright
→ 実ブラウザ
→ JavaScript
→ フォーカス
→ キーボード操作
→ モーダル
→ 実際のページ遷移

この段階では、ブラウザとLaravelの基本疎通だけを確認します。


E2E専用Laravel serviceを追加する

通常開発用のlaravel.testとは別に、compose.yamllaravel.e2eを追加しました。

#157時点では、概略として次のような構成です。

laravel.e2e:
    image: 'sail-8.4/app'
    ports:
        - '83:80'
    environment:
        WWWUSER: '${WWWUSER}'
        LARAVEL_SAIL: 1
        IGNITION_LOCAL_SITES_PATH: '${PWD}'
        APP_ENV: e2e
        SESSION_COOKIE: review_app_e2e_session

        DB_CONNECTION: mysql
        DATABASE_URL: ''
        DB_HOST: e2e-database-not-configured
        DB_PORT: 3306
        DB_DATABASE: e2e_testing
        DB_USERNAME: e2e_not_configured
        DB_PASSWORD: e2e_not_configured

        SUPERVISOR_PHP_COMMAND: '/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80 --no-reload'

    volumes:
        - '.:/var/www/html'
    networks:
        - sail

注意

上記のDB値は実credentialではありません。
#157時点ではE2E専用DBをまだ構築していなかったため、通常開発DBへ接続しないよう意図的に無効な値を設定しています。

通常LaravelとE2E Laravelを、同じLaravelコードを利用しながら別serviceとして起動します。


通常LaravelとE2E Laravelを別ポートにする

通常開発環境では、

http://localhost:82

を利用しています。

E2E Laravelは、

http://localhost:83

としました。

Compose側では、

ports:
    - '83:80'

です。

これにより、

通常ブラウザ確認
→ localhost:82

Playwright
→ localhost:83

と接続先を明確に分離できます。

Playwright側のbaseURLhttp://localhost:83へ合わせます。

補足:83:80はlocalhost専用の設定ではありません

#157の実装では83:80としており、この記事でも当時の構成を掲載しています。
Docker Composeでは、ホストIPを省略したport mappingは、既定では全ネットワークインターフェース(0.0.0.0)へbindされます。

ホストのIPv4ループバックアドレスへ公開範囲を限定する場合は、次のようにホストIPまで指定できます。

ports:
    - '127.0.0.1:83:80'

localhostからアクセスできることと、localhostだけに公開されていることは別です。
上記は公開範囲を限定する場合の構成例であり、#157で実際に採用した設定とは区別しています。

参考:Docker Docs - Compose services / ports
公式の説明:“Docker binds to all interfaces (0.0.0.0)”


セッションCookieを分離する

通常LaravelとE2E Laravelで同じセッションCookie名を使うと、同じブラウザからアクセスした際に認証状態が混ざる可能性があります。

そこでE2E serviceでは、

SESSION_COOKIE: review_app_e2e_session

を設定しました。

これにより、

通常Laravelのセッション
≠
Playwright E2Eのセッション

として扱えます。

実機確認でも、E2E側のsession cookieがreview_app_e2e_sessionになっていることを確認しました。


DBはあえて接続不能にしておく

#157では、まだE2E専用DBを作りません。

しかし、Laravelアプリケーションを起動するとDBへ接続できる設定が存在します。

ここで通常開発DBの設定へそのままfallbackすると、将来Playwrightテストが誤って通常DBを書き換える危険があります。

そこで、この段階ではE2E serviceの通常mysql接続を意図的に無効化しました。

DB_HOST: e2e-database-not-configured
DB_DATABASE: e2e_testing
DB_USERNAME: e2e_not_configured
DB_PASSWORD: e2e_not_configured

つまり、

E2E専用DBはまだない
↓
DBへアクセスするページは失敗してよい
↓
通常開発DBへfallbackするより安全

という考え方です。

この「安全側に失敗する」考え方は、後続のE2E DB構築でも重要になりました。


Viteのhot fileを分離する

ここで別の問題がありました。

通常開発環境ではVite dev serverを起動しており、Laravelでは次のhot fileが使われます。

public/hot

通常LaravelとE2E Laravelは同じプロジェクトディレクトリをbind mountしています。

そのため、通常環境で作成されたpublic/hotをE2E Laravelからも参照すると、E2E側まで通常のVite dev serverへ接続してしまいます。

今回のE2Eでは、

npm run build
↓
build済みasset
↓
Laravel

という状態を確認したかったため、E2E環境だけhot fileの参照先を変更しました。

AppServiceProviderで、E2E環境の場合のみ専用hot fileを使用します。

use Illuminate\Support\Facades\Vite;

public function boot(): void
{
    if ($this->app->environment('e2e')) {
        Vite::useHotFile(storage_path('vite.e2e.hot'));
    }
}

E2E専用の参照先は、

storage/vite.e2e.hot

です。

Laravel公式でも、Vite::useHotFile()を使ってhot fileの場所を変更できます。

ただし、Vite::useHotFile()はhot fileの参照先を変更するだけで、build済みassetの利用を強制するメソッドではありません。

この構成でbuild済みassetを利用するには、E2E専用のstorage/vite.e2e.hotが存在しないことに加え、npm run buildで生成したmanifestとassetが用意されている必要があります。

E2E専用hot fileは作成せず、存在しない状態を前提にします。通常環境にpublic/hotが存在していても、E2E側はそちらを参照しません。

通常Laravel
→ public/hot が存在
→ Vite dev server

E2E Laravel
→ storage/vite.e2e.hot を参照
→ E2E専用hot fileは存在しない
→ 通常のpublic/hotには反応しない
→ build manifestを参照
→ 用意したbuild済みassetを利用

補足

storage/vite.e2e.hotが存在すると、E2E側もそのhot fileに記載されたdev serverを参照します。
また、専用hot fileが存在しなくても、必要なbuild成果物がなければ正常に表示できません。

Laravel公式ソースのuseHotFile()の説明は“Set the Vite "hot" file path.”です。isRunningHot()is_file($this->hotFile())で、参照先ファイルの存在を判定しています。
参考:Laravel Framework 13.x - Vite.php

この条件を満たすことで、通常のVite dev serverが動いているかどうかに依存せず、E2E側でbuild済みassetを参照できます。


artisan serveで環境変数が引き継がれなかった

E2E serviceを追加したあと、少し厄介な問題が起きました。

コンテナ内でArtisanから設定を確認すると、

APP_ENV
SESSION_COOKIE
DB_HOST

などはE2E用の値になっていました。

しかし、ブラウザからHTTPアクセスすると、一部の設定が期待したE2E用の値になりませんでした。

つまり、

docker compose environment
↓
コンテナ内のArtisan
→ E2E設定が見える

docker compose environment
↓
artisan serve
↓
HTTPリクエスト
→ 一部のE2E設定が期待どおり引き継がれない

という状態です。

調査すると、Laravelのartisan serveの実装が関係していました。

Laravel 13のServeCommandでは、.envが存在していてreloadが有効な場合、PHP開発サーバーの子processへ渡す環境変数を限定しています。

Laravel 13のソースでは概ね、

--no-reloadあり
→ $_ENVをそのまま子processへ渡す

reload有効 + .envあり
→ passthrough対象の環境変数だけを維持
→ その他は子processへそのまま渡さない

という処理になっています。

APP_ENVはpassthrough対象ですが、E2E用にComposeから渡していたすべての変数が対象になるわけではありません。

このため、「コンテナ内で確認したLaravel設定」と「artisan serve経由のHTTPリクエスト」が食い違う原因になっていました。


--no-reloadでE2E環境変数を引き継ぐ

最終的に、E2E serviceではartisan serveのreloadを無効化しました。

SUPERVISOR_PHP_COMMAND: '/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80 --no-reload'

Laravel 13のServeCommandでは、--no-reloadが指定されている場合、環境変数をPHP server processへそのまま渡す分岐があります。

--no-reload自体の公式な意味は、

.envファイル変更時にdevelopment serverを再起動しない

です。

今回はreload機能を不要とし、同時にComposeで指定したE2E用environmentを子PHP serverへ引き継ぐために利用しました。

この修正後、

APP_ENV
→ e2e

SESSION_COOKIE
→ review_app_e2e_session

など、HTTPリクエスト側でも期待したE2E設定になっていることを確認できました。

今回特に勉強になったのは、

コンテナに環境変数がある
≠
そのコンテナ内で起動した子processへ必ず同じ形で渡る

という点でした。


DB非依存のSmoke Testを作る

#157ではE2E専用DBがまだありません。

そのため、最初のSmoke TestにはDBアクセスを必要としないログイン画面を選びました。

e2e/smoke.spec.ts

import { test, expect } from '@playwright/test';

test('ログイン画面を表示できる', async ({ page }) => {
    await page.goto('/login');

    await expect(page.getByRole('button', { name: 'ログイン' })).toBeVisible();
});

baseURLを設定しているため、

page.goto('/login');

は、

http://localhost:83/login

へアクセスします。

このSmoke Testで確認したかったのは、

Playwright
↓
Chromium
↓
localhost:83
↓
laravel.e2e
↓
Laravel
↓
Blade
↓
build済みasset

という基本経路です。

一方、

/
 /items
 /items/{item}

などDBアクセスを伴う画面は、この段階ではSmoke Test対象にしませんでした。


Playwright成果物をGit管理外にする

Playwrightはテスト失敗時などに、report・screenshot・trace等を生成します。

また、将来storageStateを利用した場合には認証状態をファイルへ保存する可能性があります。

そこで.gitignoreへ次を追加しました。

/playwright-report/
/test-results/
/playwright/.auth/

目的は、

playwright-report/
→ HTML report

test-results/
→ screenshot / trace等

playwright/.auth/
→ 将来の認証状態

をGit管理しないことです。

特に認証状態を含む可能性のあるファイルは、リポジトリへ入れない前提にしました。


最終的な構成

#157完了時点では、おおよそ次の構成になりました。

review-app-laravel/
├─ app/
│  └─ Providers/
│     └─ AppServiceProvider.php
├─ e2e/
│  ├─ tsconfig.json
│  └─ smoke.spec.ts
├─ tests/
│  └─ PHPUnit
├─ compose.yaml
├─ package.json
├─ playwright.config.ts
└─ vite.config.js

実行経路は、

WSL
│
├─ Playwright Test
├─ TypeScript
└─ Chromium
      │
      │ http://localhost:83
      ▼
Docker / Laravel Sail
└─ laravel.e2e
   ├─ APP_ENV=e2e
   ├─ 専用session cookie
   ├─ 通常DBへ接続不能
   ├─ --no-reload
   └─ build済みVite asset

となりました。


今回理解できたこと

今回のE2E Laravel環境構築では、単に「PlaywrightからLaravelへアクセスできればよい」という話ではありませんでした。

特に重要だったのは次の点です。

baseURL
→ Playwrightの接続先を固定する

laravel.e2e
→ 通常LaravelとE2E Laravelを分離する

port 83
→ 通常開発環境と接続先を分離する

SESSION_COOKIE
→ 認証状態を混線させない

無効なDB設定
→ E2E DB完成前に通常DBへfallbackさせない

Vite::useHotFile()
→ E2E専用hot fileへ参照先を変更
→ 専用hot fileが存在せず、build成果物がある状態でbuild済みassetを利用

--no-reload
→ ComposeのE2E environmentをPHP server processへ引き継ぐ

特にartisan serveの件では、

Compose
↓
Laravel container
↓
artisan serve
↓
PHP development server
↓
HTTP request

とprocessを1段ずつ追わないと原因を見つけられませんでした。


まとめ

今回は、PlaywrightからアクセスするためのE2E専用Laravel環境をLaravel Sail上へ構築しました。

Playwright / Chromium
→ WSL側

Laravel
→ laravel.e2e

通常Laravel
→ localhost:82

E2E Laravel
→ localhost:83

さらに、

専用session cookie
DB fail-closed
Vite hot file分離
artisan serve --no-reload
DB非依存Smoke Test

まで整備し、PlaywrightからE2E Laravelへ安全にアクセスするための土台を作りました。

次回は、このlaravel.e2eから利用するE2E専用データベース・MySQLユーザー・reset CLI・Factory / Seeder・fixture基盤を構築します。


公式一次情報

この記事の内容は、次の公式ドキュメント・公式ソースを確認しながら整理しています。

参照日: 2026-09-06
Playwright、Laravel、Dockerの仕様は更新されるため、実際に構築する際は最新版の公式一次情報も確認してください。


関連記事

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?