1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

docker環境(LEMP)のlaravel 11でのブラウザテストの環境構築 & 実行方法&トラブルシューティング

Last updated at Posted at 2025-06-19

前置き

初投稿です。
ハマったので備忘録的に記事を書きます。
同様の現象だったり、他にもこんなエラーが起きたよって人がいれば解決策の共有をしてもらえると助かります。

誤解を生みそうな部分や、間違えている部分のご指摘いただけると助かります。

前提

  • ホストはUbuntu mate25.x
  • WindowsからVirtualBoxでLinux起動
  • Docker環境構築済みかつLaravelが動いている
  • LEMP環境かつseleniumがWebアプリサーバとは別
  • Laravel 11以降
  • Laravel Dusk使用
  • Vite使用

結論

先に結論というか、重要なポイントを簡単にピックアップします。
ハマってる人にはこれで伝わるはず。

  • Laravel 11でのdusk実行時の挙動
    過去のLaravel(laravel 10以前)では dusk実行時、 .env.env.dusk.localの内容で上書きをしていた。
    laravel 11(dusk)では上書きが行われない。
    明示的に上書きをする必要がある。
    shellを作成の上、実行すると事故が無いです。後述(CSRFトークン不一致の項)

  • viteサーバ
    hot/public(ホットリロード)がある場合、セレニウムからcss/jsが読み込めない。
    npm run devをしている場合、dusk実行時は停止する必要がある。

  • CSRFトークン不一致
    Laravel 11でのdusk実行時の挙動のせいで.envAPP_URLが一致していないと起きる。
    これはサーバーとして正しい仕様ですが、cookieの取得ができなくて初めて気がつくと思います

  • seleniumでアクセスするURLはサービス名に合わせる


以下詳細です。
長くなりすぎたので適宜開閉できるようにしました。
必要なら開いて確認してください。

構成図

アプリケーション構成を表示
項目 バージョン例 説明
Laravel 11.x PHPフレームワーク
PHP 8.3.2.1 Laravel実行用
MySQL 8.0.36 データベース
Nginx 1.24 Webサーバー
Node.js v18.20.8 フロントエンドビルド
Composer 2.8.9 PHPパッケージ管理
OS Ubuntu 25.02 開発ベース環境
Docker 28.1.1 コンテナ実行環境
Docker Compose 2.34 コンテナオーケストレーション
システム構成を表示

ChatGPTくんに丸投げ


Docker関連

必要なら展開していってください。
docker-compose.ymlは重要です。

Docker関連のファイル例を表示

dockerFile

必要パッケージを入れたり、作業用のユーザーを作っています。
本来ならユーザーの存在チェックがあったほうがいいですが、書き忘れてます。
イメージ作り直す度にユーザーも作り直されるので問題は基本起きないはずです。

展開
FROM php:8.3-fpm

# UID/GIDを引数で受け取る(デフォルト1000)
ARG UID=1000
ARG GID=1000
ARG USERNAME=appuser
ARG GROUPNAME=appuser

# 必要パッケージのインストール
RUN apt-get update && apt-get install -y \
    git \
    unzip \
    curl \
    vim \
    libzip-dev \
    mariadb-client \
    zlib1g-dev \
    sudo \
    gnupg

# wait-for-it.sh をコンテナ内にコピー
# COPY ../../wait-for-it.sh /usr/local/bin/wait-for-it.sh
# RUN chmod +x /usr/local/bin/wait-for-it.sh

# PHP拡張モジュールのインストール
RUN docker-php-ext-install zip pdo_mysql

# Node.js 18.x をインストール
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
 && apt-get install -y nodejs \
 && npm install -g npm vite vue

# Laravel用ユーザー作成 + sudo権限付与
RUN groupadd -g ${GID} ${GROUPNAME} \
 && useradd -u ${UID} -g ${GROUPNAME} -m ${USERNAME} \
 && echo "${USERNAME} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers

# Composer インストール
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \
 && php composer-setup.php \
 && php -r "unlink('composer-setup.php');" \
 && mv composer.phar /usr/local/bin/composer

# 作業ディレクトリ作成と権限調整
WORKDIR /var/www/laravel-project
RUN chown -R ${USERNAME}:${GROUPNAME} /var/www

# 実行ユーザー切替
USER ${USERNAME}

docker-compose.yml

ポイントはサービス名です。(db,appといった)
サービス名とアクセスする時の名前は一致している必要があります。
あと色々とヘルスチェックしていますが、これは基本やらなくていいです。
起動(up)の整合性を取る場合は記載してください。

展開

services:
  db:
    image: mysql:8.0.36
    container_name: db
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    volumes:
      - ./docker/db/data:/var/lib/mysql
      - ./docker/db/my.cnf:/etc/mysql/conf.d/my.cnf
      - ./docker/db/init:/docker-entrypoint-initdb.d
    ports:
      - "${MYSQL_PORT}:3306"

  app:
    container_name: app_laravel
    build:
      context: .
      dockerfile: docker/php/Dockerfile
      args:
        UID: ${UID}
        GID: ${GID}
    user: "${UID}:${GID}" 
    volumes:
      - .:/var/www
      - ~/.docker_bash_history:/appuser/.bash_history
      - ./wait-for-it.sh:/usr/local/bin/wait-for-it.sh:ro
    command: ["/usr/local/bin/wait-for-it.sh", "db:3306", "--", "php-fpm"]
    depends_on:
      - db
    ports:
      - 5173:5173

  nginx:
    image: nginx
    container_name: nginx
    volumes:
      - .:/var/www
      - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
      - ./wait-for-it.sh:/usr/local/bin/wait-for-it.sh:ro
    working_dir: /var/www
    command: ["/usr/local/bin/wait-for-it.sh", "app:9000", "--", "nginx", "-g", "daemon off;"]
    depends_on:
      - app
    ports:
      - "${APP_PORT}:80"

  phpmyadmin:
    container_name: test_phpmyadmin
    image: phpmyadmin
    environment:
      PMA_USER: ${PMA_USER}
      PMA_PASSWORD: ${PMA_PASSWORD}
    depends_on:
      - db
    ports:
      - "${PMA_PORT}:80"

  selenium:
    image: selenium/standalone-chrome:latest
    container_name: selenium
    depends_on:
      - nginx
    command: ["/usr/local/bin/wait-for-it.sh", "nginx:80", "--", "/opt/bin/entry_point.sh"]
    ports:
      - 4444:4444
      - 7900:7900
      - 5900:5900
    privileged: true
    volumes:
      - ./wait-for-it.sh:/usr/local/bin/wait-for-it.sh:ro
      - /dev/shm:/dev/shm
    shm_size: 2g

default.conf

展開

server {
  listen 80;
  # server_name nginx localhost 127.0.0.1;
  root /var/www/laravel-project/public;
  index index.php;
  location / {
    try_files $uri $uri/ /index.php?$query_string;
  }
  location ~ \.php$ {
    try_files $uri =404;
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    fastcgi_pass app:9000; 
    fastcgi_index index.php;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param PATH_INFO $fastcgi_path_info;
    fastcgi_param HTTP_HOST $host; 

    # HostヘッダーをLaravelに渡す
    # fastcgi_param HTTP_HOST $host;
    # fastcgi_param HTTP_HOST $server_name; # または $host

    # Cookieを渡す
    # fastcgi_param HTTP_COOKIE $http_cookie;
  }
}


インストール手順

すでにlaravelは動いていて、composeで入れるだけの前提

インストール手順を表示
  • docker
    seleniumを追加 追加方法は default.confseleniumをコピー
    一度dockerを停止の上再度立ち上げ
docker compose down
docker compose up --build
>UID=1000 GID=1000 docker compose --env-file .env up -d --build

念の為dockerの立ち上げ確認

docker ps
docker compose container ls
# seleniumが追加されていることを確認する。

appサーバに入る
docker compose exec app bash

appサーバ内で以下コマンド(php artisanが通る場所)

composer require --dev laravel/dusk
php artisan dusk:install

# tests/Browser ディレクトリと、DuskTestCase.php が作成される。

apt-getで必要なパッケージを入れる。
頻繁に環境を作り直す場合は、dockerfileに移動したり、シェルにしたほうがいいです。
私はシェルにしています。

#!/bin/bash

# sudoで実行

apt-get update && apt-get install -y \
    libglib2.0-0 \
    libnss3 \
    libgconf-2-4 \
    libfontconfig1 \
    libxss1 \
    libappindicator1 \
    libasound2 \
    fonts-liberation \
    xdg-utils \
    wget \
    unzip \
    iputils-ping \
    net-tools # debug

ファイルの修正

.env.dusk.local

新規作成
.envをコピーして、以下必要な箇所だけ書き換えする。
viteの変更は結果的に無駄だったのでいらないです。
ポイントはコメントがついているサービス名です。
これを、docker-compose.ymlのサービス名と合わせます。

# -------------------
# dusk起動に必要な最小構成
APP_ENV=dusk.local
APP_URL=http://nginx  # nginxコンテナ名
# APP_URL=http://app  # appコンテナ名
DB_HOST=db            # dbコンテナ名
DB_PORT=3306
DB_DATABASE=laravel_db
DB_USERNAME=root
DB_PASSWORD=password

# SESSION_DOMAIN=null 
SESSION_DOMAIN=null
SESSION_DRIVER=file
SESSION_SECURE_COOKIE=false

# DuskがリモートSelenium使う設定
DUSK_DRIVER=remote

VITE_APP_NAME=vite
# 開発時のviteサーバ
#VITE_DEV_SERVER_URL=http://app:5173
FRONTEND_URL=http://app:3000

# sanctum
# SANCTUM_STATEFUL_DOMAINS=nginx:8000,nginx:3000,nginx
SANCTUM_STATEFUL_DOMAINS="localhost,127.0.0.1,nginx"

# ------------------

以下略

作成後、.env.dusk.localを.envに上書きしますが、バックアップを取った上で上書きしてください。
何かあった時に戻せるようにしておけば大丈夫です。

DuskTestCase.php

以下を修正


    ~~~~~~~~~~~~~

    #[BeforeClass]
    public static function prepare(): void
    {
        if (! static::runningInSail()) {
            // static::startChromeDriver(['--port=9515']); // これを削除
        }
    }

    protected function driver(): RemoteWebDriver
    {
        $options = (new ChromeOptions)->addArguments([
            '--window-size=1920,1080',
            '--disable-search-engine-choice-screen',
            '--disable-smooth-scrolling',
            '--no-sandbox', // 追記
            '--disable-gpu', // 追記
            '--headless=new', // 追記 古いセレニウムの場合は=newでエラーになる可能性あり
        ]);

        return RemoteWebDriver::create(
            'http://selenium:4444/wd/hub', // ここをseleniumのサービス名/wd/hubにする
            DesiredCapabilities::chrome()->setCapability(
                ChromeOptions::CAPABILITY, $options
            )
        );
    }

    ~~~~~~~~~~~~~

    

動作確認

app,nginx,seleniumでcurl等で疎通ができることを確認する。

app

念の為コンテナに入るところから始めます。他も同様

# コンテナに入る
docker compose exec app
# nginx
curl -i http://nginx
# selenium
curl -i http://selenium:4444

nginx
# コンテナに入る
docker compose exec nginx
# app 
curl -i http://app:9000
# selenium
curl -i http://selenium:4444

selenium

docker compose exec selenium
# nginx
curl -i http://nginx
# app
curl -i http://app:9000

テスト実施

スクリーンショットは必ず取ったほうがいいです。
とりあえず実行方法についてだけ記載します。

# 全テストの実行
php artisan dusk --env=dusk.local
# 特定のテストの実行
php artisan dusk tests/Browser/ExampleTest.php --env=dusk.local
# 特定のメソッドのみ実行
php artisan dusk --filter testBasicExample --env=dusk.local
# .env.dusk.stagingを作った場合
php artisan dusk --env=dusk.staging

実行前にキャッシュクリアやseederの実行をしておきます。


発生した問題とその対応

地の文多めです。後で直すかも。

  • css,jsがseleniumから読み込めない
展開

seleniumとの疎通確認後、スクショをみてファッっとなったやつ
原因は npm run devをしているときに php artisan duskを実行したため。
仕様で,public/hotがある場合はそっちを見に行く。
つまり、 app側からは見えるけど、selenium側からは見えないよ!って状態になる。
npm run dev中でも動かせるよう解決しようとしたけど、実現が難しそうなのでduskを実行時はnpm run buildをして、publicのcss,jsを参照するようにした。
問題点として、ホットリロードが効かないので、ブラウザテストで問題があって修正をする場合、手動でビルドが必要になる。逆に言うとそれくらい。

  • CSRFトークン不一致
展開

formのテストを作った時に発生。
原因はapp側のserver nameとseleniumから要求する際のserver nameが違っていたため。
テスト実行時、.env.dusk.localartisan dusk起動で自動的に読み込まれる。(はずだが、明示的に--env=dusk.localを指定したほうがいい)
appからseleniumに対しては.env.dusk.localの設定が使用される。
seleniumからnginxにアクセスする際は.envが使われるため、.env.env.dusk.localAPP_NAMEは一致している必要がある。

流れとしてはこう。
app→selenium .env.dusk.localを使う
selenium→nginx .envを使う

.env.dusk.localはなんのための設定?って思うよね。
ワイトもそう思います。
実際の運用時はコピー用として使うことにしました。
.envはlocalhost:8000で開発している人が多そうだから頻出しそうな問題。

対応策として、.envを.env.dusk.localで上書きする。

laravel-projec直下に以下のシェルを用意するといいです。
作成後、実行権限を付与してください。(chmod +x ****.sh)

ブラウザテスト前に.envの複製をとって、.env.dusk.localで.envを上書きする。
prepare_dusk_env.sh

展開
#!/bin/bash

# プロジェクトのルートディレクトリに移動
cd /var/www/laravel-project/

# .env ファイルのバックアップパス
ENV_BACKUP=".env.bak"
# テスト用の .env ファイルパス
ENV_DUSK=".env.dusk.local"
# 実際の .env ファイルパス
ENV_CURRENT=".env"

echo "Duskテスト用の.envファイルを準備します..."

# 1. .env ファイルをバックアップ
if [ -f "$ENV_CURRENT" ]; then
    echo "既存の .env を $ENV_BACKUP にバックアップします。"
    cp "$ENV_CURRENT" "$ENV_BACKUP"
else
    echo "既存の .env ファイルが見つかりません。バックアップはスキップします。"
fi

# 2. .env.dusk.local を .env にコピー
if [ -f "$ENV_DUSK" ]; then
    echo "$ENV_DUSK$ENV_CURRENT にコピーします。"
    cp "$ENV_DUSK" "$ENV_CURRENT"
    # SANCTUM_STATEFUL_DOMAINS の警告を避けるため、空文字列に置き換え
    # sed -i はmacOSとLinuxでオプションが異なる場合があるため、互換性を考慮
    if [[ "$OSTYPE" == "darwin"* ]]; then
      # macOS
      sed -i '' 's/^SANCTUM_STATEFUL_DOMAINS=null$/SANCTUM_STATEFUL_DOMAINS=""/g' "$ENV_CURRENT"
    else
      # Linux (GNU sed)
      sed -i 's/^SANCTUM_STATEFUL_DOMAINS=null$/SANCTUM_STATEFUL_DOMAINS=""/g' "$ENV_CURRENT"
    fi
else
    echo "エラー: $ENV_DUSK が見つかりません。中断します。"
    exit 1
fi

# 3. Laravelの設定キャッシュをクリア
echo "Laravelの設定キャッシュをクリアします..."
php artisan config:clear
php artisan cache:clear
php artisan route:clear
php artisan view:clear

echo "Duskテスト用の.envファイルの準備が完了しました。"

ブラウザテスト後に.envをもとに戻す

展開
#!/bin/bash

# プロジェクトのルートディレクトリに移動
cd /var/www/laravel-project/

# .env ファイルのバックアップパス
ENV_BACKUP=".env.bak"
# 実際の .env ファイルパス
ENV_CURRENT=".env"

echo "Duskテスト後の.envファイルをクリーンアップします..."

# 1. .env ファイルを元に戻す
if [ -f "$ENV_BACKUP" ]; then
    echo "バックアップした .env を元に戻します。"
    mv "$ENV_BACKUP" "$ENV_CURRENT"
    # 元に戻した後のキャッシュクリアも重要
    echo "Laravelの設定キャッシュを再度クリアします..."
    php artisan config:clear
    php artisan cache:clear
else
    echo "バックアップした .env ファイルが見つかりません。"
    # 元々.envファイルが存在しなかったがDusk用に作成された場合
    if [ -f "$ENV_CURRENT" ]; then
        echo "Dusk用に作成された .env を削除します。"
        rm "$ENV_CURRENT"
    fi
fi

echo "Duskテスト後の.envファイルのクリーンアップが完了しました。"

テスト用に、ドメインを確認するルートを追加したりで確認できます。
CSRF mismatchが発生した時は仕込んで確認してみてください。

web.php

    Route::get('/debug-domain', function () {
    // return response('Domain: ' . var_export(config('session.domain'), true));
        return response('Session Domain: ' . var_export(config('session.domain'), true) . ' | App URL: ' . config('app.url'));
    });
    Route::get('/debug-sanctum-domains', function () {
        return response()->json(config('sanctum.stateful'));
    });
    Route::get('/debug-app-url', function () {
    return response()->json([
        'app_url_env' => env('APP_URL'),
        'parsed_app_url_host' => parse_url(env('APP_URL'), PHP_URL_HOST),
        'base_url_config' => config('app.url'),
    ]);

テスト例

    public function testSunctumDomainCheck(): void
    {
        $this->browse(function (Browser $browser) {
            \Log::info('[Dusk] 現在のURL: ' . $browser->driver->getCurrentURL()); // ログ例
            $browser->visit('http://nginx/debug-sanctum-domains'); // http://nginx は環境に合わせてください

            // ページが完全にロードされるのを待つ (重要)
            $browser->pause(1000); // 1秒待機 (または waitForText など)

            // ページの生のソースコードをダンプ
            $pageSource = $browser->driver->getPageSource();
            dump($pageSource);

            // 必要であれば、JSONとしてパースし、'nginx'が含まれているか確認
            // ただし、生のJSON文字列がページソースとして取得されるため、
            // parse_urlで得られるホスト名が配列に含まれているか確認する方が確実です
            $jsonResponse = json_decode(strip_tags($pageSource), true); // HTMLタグを除去してからJSONパース
            dump($jsonResponse);

            // Nginxドメインが配列に含まれていることをアサート
            $this->assertContains('nginx', $jsonResponse);

            $browser->screenshot($this->getScreenShotPath('sanctum-domain-check', 1));
        });
    }

  • ログイン確認機能において、ログイン済みとして扱われる
展開

仕様らしいです。
browse() はクロージャごとにブラウザインスタンスを使い回す(ブラウザの新規起動ではない)
→ 前のセッションを維持したまま次のクロージャが動く

ログイン系のテスト前にログアウト処理を入れて対応しました。
laravel breezeを使ってる人なら/logoutにpostすればOK.
もしくはsessionを強制的に削除したりと対応は色々可能

  • seleniumのイメージが古い
展開

極端に古い場合、通るはずのテストが通らない可能性があります。
selenium/standalone-chrome:latestを使うか、新しめのバージョンを明示的に指定しましょう。
ログを仕込んでみて、原因不明なエラーだったり挙動をした場合は更新するに限ります。

直接関係のない雑備忘録

  • selenium導入後にURLのポートが消える
展開

 例: localhost:8000→localhostになる。
明確な原因がわかっていないが、今までLaravel,nginxがよろしく解釈してくれていた部分がしてくれなくなったぽい。

対応は強制的に.envAPP_URLを使用するようにした。

# Providers/AppServiceProvider.php

    public function boot(): void
    {
        // ポートが欠落することがある為対応
        if (config('app.url')) {
            URL::forceRootUrl(config('app.url')); // これを追加
        }
    }

  • .env.dusk.localについて
展開

.env.dusk.local はartisan dusk起動で自動で読み込まれる。
個別に指定する場合は以下

# .env.dusk.stagingを作った場合
php artisan dusk --env=dusk.staging

github

専用の環境ではないので確認しにくいかもです。
https://github.com/git-syuu2449/laravel_docker

以上です。

1
0
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?