Pest 4は、PHPのテストフレームワークPestの4系で、PlaywrightによるブラウザテストをPHPのテストコードから書けるようにした版です。公式のサポートポリシーでは初回リリースが2025年8月21日です。2026年10月3日時点で4.x系の最新リリースは2026年8月3日公開の4.7.8、現行のメジャー版はPest 5系(5.3.0)で、Pestそのものの基本と最新版の新機能はPestPHPの解説記事(Pest 5対応)にまとめています。本記事はPest 4で入った機能、特にpest-plugin-browserによるブラウザテストに絞り、2026年10月3日にmacOS Intel、PHP 8.5.8、Laravel Framework 13.34.0で検証した結果をもとに説明します。実行時間はこの環境での参考値です。
まとめ:Pest 4のブラウザテストの要点
- 導入は
composer require pestphp/pest-plugin-browser --dev、npm install playwright@latest、npx playwright installの3コマンドです。 - プラグインはPlaywrightの最低版を内部で固定しており、4.3.1では1.59.1未満だと「Playwright is outdated」で全テストが落ちます。
- Firefoxを入れずに
--browser firefoxを指定しても同じ「outdated」が出ます。原因はブラウザ本体の未導入です。 - ビジュアル回帰の初回は基準画像を作って「incomplete」で終わり、終了コードは0です。基準画像をコミットしないとCIでは毎回素通りします。
- Laravel 13の標準スケルトンはPHPUnit ^12.5.12を指定しているため、バージョンを付けずに入れるとPest 5ではなく4.7.8が入ります。
- Pest 4のバグ修正は2027年7月28日まで続きます。PHP 8.3の環境ではPest 4に留まるのが正解です。
以下、導入、書き方、各アサーションの挙動、CIでの分割実行、Pest 5へ上げる判断の順に説明します。
Pest 4で追加された機能とサポート期限
Pest 4.0の公式告知に並んだ新機能
公式の告知記事「Pest v4 Is Here — Now with Browser Testing」が挙げた主な追加は次のとおりです。中心はブラウザテストで、他はその周辺を固める機能です。
| 機能 | 使い方 | 要点 |
|---|---|---|
| ブラウザテスト | visit('/') |
Playwrightで実ブラウザを操作 |
| スモークテスト | assertNoSmoke() |
JSエラーとコンソール出力を一括検査 |
| ビジュアル回帰 | assertScreenshotMatches() |
基準画像と画面を比較 |
| シャーディング | --shard=1/4 |
テストを複数ジョブへ分割 |
| 型カバレッジ高速化 | --type-coverage |
初回2倍速・別プラグインが必要 |
| 不適切語チェック | --profanity |
本体の依存に同梱 |
| 環境別スキップ | skipLocally() / skipOnCi() |
ローカルかCIかで実行を分ける |
型カバレッジはpest-plugin-type-coverageを別に入れて使います。不適切語チェックは告知では別途導入と書かれていますが、4.0.0の時点で本体のcomposer.jsonが依存に含めており、Pest 4を入れれば --profanity がそのまま使えます。土台はPHPUnit 12です。このほかアーキテクチャテストの not->toHaveSuspiciousCharacters()(intl拡張が必要)と、文字列がスラッグ形式かを見る toBeSlug が追加されました。Pest 3からの移行で影響が大きいのはスナップショット名の付け方の変更で、公式アップグレードガイドは --update-snapshots で作り直すよう指示しています。watchとfakerのプラグインは4系で外れました。
Pest 4.7.8のPHP・PHPUnit要件とサポート期限
Packagistの公開記録とPest公式のサポートポリシーを並べると、版ごとの要件は次のようになります。
| 版 | PHP | PHPUnit | 初回リリース | バグ修正期限 |
|---|---|---|---|---|
| Pest 3 | 8.2以上 | 11 | 2024-09-09 | 2026-08-21 |
| Pest 4 | 8.3以上 | 12(4.7.8は^12.5.33) | 2025-08-21 | 2027-07-28 |
| Pest 5 | 8.4以上 | 13(5.3.0は^13.3.6) | 2026-07-28 | 未定 |
4.0.0の公開から4.7.8の公開までは約1年で、Pest 5の公開後も8月に4.7.7と4.7.8が出ています。サポートポリシーは「旧版のバグ修正は最新版リリースから12か月」と定めているため、Pest 4はPest 5の初回リリースから12か月後の2027年7月28日まで修正対象です。
pest-plugin-browserの導入とPlaywrightの版
composerとnpmで入れる3つのコマンド
以下はPest 4を導入・初期化済みのプロジェクトに、別パッケージのpest-plugin-browserを追加する手順です。Playwright本体はnpmで入れるため、PHPのプロジェクトでもNode.jsが必要です。
composer require pestphp/pest-plugin-browser --dev
npm install playwright@latest
npx playwright install
公式ドキュメントは、テスト失敗時に保存されるスクリーンショットの置き場 tests/Browser/Screenshots を .gitignore に加えるよう求めています。ビジュアル回帰の基準画像は別の場所(tests/.pest/snapshots)に保存されるので、こちらはコミットします。理由は後述します。
プラグインはテスト開始時に ./node_modules/.bin/playwright run-server を起動して接続します。プロジェクト直下に node_modules が無いと、次の例外ですべてのテストが落ちます。
Pest\Browser\Exceptions\PlaywrightNotInstalledException
Playwright is not installed. Please run [npm install playwright && npx playwright install] in the root directory of your project.
Playwrightの導入で止まった場合の切り分けはPlaywrightのインストール方法とできない時の対処で扱っています。
プラグイン版ごとに上がるPlaywrightの最低版
プラグインのソース(PlaywrightNpmServer.php)には定数 PLAYWRIGHT_VERSION があり、起動時に playwright run-server --version の結果と比べています。GitHubのタグごとのソースで値を確認すると、パッチ版でも下限が上がっていました。公開日はPackagistの記録です。
| pest-plugin-browser | 公開日 | 対応Pest | Playwright下限 |
|---|---|---|---|
| 4.0.0〜4.3.0 | 2025-08-20〜2026-02-17 | 4系 | 1.54.1 |
| 4.3.1(検証使用版) | 2026-04-08 | 4系 | 1.59.1 |
| 5.0.0 | 2026-07-21 | 5系 | 1.61.1 |
| 5.1.2 | 2026-10-02 | 5系 | 1.63.0 |
Playwright 1.58.2を入れた状態でPest 4.7.8とプラグイン4.3.1を動かすと、テストは1件も実行されずに次のように落ちました。
FAILED Tests\Browser\SearchTest > it 検索結… PlaywrightOutdatedException
Playwright is outdated. Please run [npm install playwright@latest && npx playwright install] in the root directory of your project.
package-lock.jsonでPlaywrightを固定していると、composer update でプラグインのパッチ版が上がっただけでCIが赤くなります。composer.lockとpackage-lock.jsonは同じプルリクエストで更新してください。Playwrightの現行版と更新手順はPlaywrightの最新バージョンの解説で追っています。
Firefox・Safari指定で「outdated」が出る原因
既定のブラウザはChromeで、--browser firefox や --browser safari で切り替えられます。ここで npx playwright install chromium だけ実行した環境に --browser firefox を付けると、Playwright 1.63.0を入れていても上と同じ PlaywrightOutdatedException が出ました。
プラグインの Client.php は、Playwrightから返るエラー文に「Playwright was just installed or updated」が含まれると、それを一律に「outdated」の例外へ置き換えます。ブラウザ本体が無いときのPlaywrightのエラーにもこの文が含まれるため、実際の原因は版ではなくFirefoxの未導入です。この表示が出てPlaywrightの版が下限を満たしているなら、npx playwright install firefox を実行してください。
ブラウザテストの書き方とLaravelでの使い方
素のPHPアプリに対する最小のテスト
Laravel以外のアプリでも、URLを完全な形で渡せばテストできます。次の例は、php -S 127.0.0.1:8099 で起動した検索フォームに文字を入れ、結果とJavaScriptエラーの有無を確かめます。
<?php
// tests/Browser/SearchTest.php
const BASE = 'http://127.0.0.1:8099';
it('検索結果に在庫数が出る', function () {
visit(BASE.'/')
->assertSee('在庫検索')
->type('q', 'SKU-1')
->press('検索')
->assertSee('「SKU-1」の在庫: 5')
->assertNoJavaScriptErrors();
});
type() の第1引数はinputのname属性、press() はボタンの表示文字列です。./vendor/bin/pest で実行すると、ヘッドレスのChromiumが起動して約1秒で通りました。画面を見ながら確かめたいときは --headed、失敗したテストの最後で止めて調べたいときは --debug を付けます。
LaravelでactingAsとファクトリを使う例
Laravelでは visit('/dashboard') のように相対パスで書けます。プラグインがLaravelアプリを同じPHPプロセス内のHTTPサーバーで動かすため、actingAs() のログイン状態や、SQLiteのインメモリDBに作ったファクトリのデータがそのままブラウザから見えます。
<?php
// tests/Browser/DashboardTest.php
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
it('ログイン済みユーザーにダッシュボードが表示される', function () {
$user = User::factory()->create(['name' => '山田']);
$this->actingAs($user);
visit('/dashboard')
->assertSee('ようこそ 山田 さん')
->assertNoJavaScriptErrors();
});
it('未ログインならログイン画面へ送られる', function () {
visit('/dashboard')->assertPathIs('/login');
});
検証では、認証必須のdashboardルート、ユーザー名を表示する画面、loginという名前のルートを用意したうえで、Laravel Framework 13.34.0のプロジェクトに次の2か所を設定しました。./vendor/bin/pest --init が作る tests/Pest.php は pest()->extend(TestCase::class)->in('Feature') になっているので、->in('Feature', 'Browser') に広げます。もう1つは phpunit.xml で、tests/Browser を指すtestsuiteを足します。どちらも漏れると $this->actingAs() が使えないか、テスト自体が見つかりません。
サーバー側で例外が起きると、その内容が失敗メッセージに併記されます。最初の検証ではloginという名前のルートが無く、assertPathIs の失敗に続けて「The following exception was thrown by the HTTP server: RouteNotFoundException: Route [login] not defined.」と表示されました。画面だけでは原因が分からない失敗を、サーバーのスタックトレースで追えます。
端末・ダークモード・タイムアウトの指定
画面サイズは visit('/')->on()->mobile() でモバイル表示に、on()->iPhone14Pro() のように機種名でも指定できます。既定はライトモードで、inDarkMode() を付けるとダークモードで描画します。要素が出るまでの待ち時間の既定は5秒で、全テスト共通の値は tests/Pest.php に pest()->browser()->timeout(10000) のようにミリ秒で書きます。この値は次の章で見るとおり、失敗するテストの所要時間にそのまま響きます。
スモーク・ビジュアル回帰・a11y検査の実際の挙動
assertNoSmoke失敗時のタイムアウトと実行時間
visit() にURLの配列を渡して assertNoSmoke() を付けると、全ページのJavaScriptエラーとコンソール出力をまとめて検査します。公式は assertNoJavaScriptErrors() と assertNoConsoleLogs() をまとめたものと説明しています。
it('全ページでJSエラーが出ない', function () {
visit([BASE.'/', BASE.'/broken.php'])->assertNoSmoke();
});
未定義の関数を呼ぶページを混ぜると、エラー文(Uncaught ReferenceError: undefinedFunction is not defined)とURLを示して失敗します。注意したいのは所要時間です。既定のタイムアウト5秒では5.95秒、timeout(10000) を設定すると10.54〜10.60秒かかりました。成功する検査は1秒前後で終わるので、失敗のたびにタイムアウトの秒数がそのまま上乗せされます。タイムアウトを大きくするのは遅いページだけに留め、スモークテストを全ルートへ広げるときは既定の5秒のままにするのが無難です。スモークテストを置く場所の考え方はスモークテストのCI/CDへの組み込み方を参照してください。
assertScreenshotMatchesの初回は終了コード0
assertScreenshotMatches() は、初めて実行したときに基準画像を作るだけで比較はしません。結果は「Snapshot created at [tests/.pest/snapshots/…]」と表示されたincomplete扱いで、終了コードは0でした。今回の検索フォームで作成された基準画像は、PNGをBase64にした約23KBの .snap ファイルでした。
この設定のまま基準画像をリポジトリに入れずにCIで回すと、ジョブのたびに新しい基準画像が作られ、比較しないまま終了コード0になります。見た目が崩れても気づけません。運用では次の順に進めます。
- 手元で1回実行して
tests/.pest/snapshotsを作り、コミットする。 - 見出しの色を変えるなど画面が変わると「Expect failed」で失敗し、その時の画面が
tests/Browser/Screenshotsに保存される。 - 意図した変更なら
./vendor/bin/pest --update-snapshotsで基準画像を更新してコミットする。
差分画像も見たい場合は --diff を付けるか、assertScreenshotMatches(true, true)(ページ全体・差分表示)と書きます。フォントの描画がOSで変わるため、基準画像はCIと同じOSで作るほうが誤検知が減ります。
assertNoAccessibilityIssuesの検出水準
assertNoAccessibilityIssues() はaxe-coreでページを検査し、既定ではレベル1(serious)以上の問題があると失敗します。ラベルの無い検索欄を置いたページでは、次のように重大度・ルール・該当要素が並びました。
1 Accessibility issues found
- [critical] Form elements must have labels https://dequeuniversity.com/rules/axe/4.10/label?application=axeAPI
Selector: #q
HTML: <input name="q" id="q" value="">
<label for="q">商品コード</label> を足すと通りました。レベルは0(critical)から3(minor)まで選べます。既存サイトにいきなり3を当てると細かな指摘で埋まるため、0か1から始めて段階的に上げるのが現実的です。
CIでの並列実行とシャーディング
--parallelによる少数テストの実行時間比較
公式ドキュメントはブラウザテストを --parallel で走らせるよう勧めています。ただしブラウザテスト3件で比べると、逐次実行が2.5〜3.4秒、16プロセスの並列実行が3.3秒前後で、差は出ませんでした。この測定だけでは原因を特定できませんが、プロセスやブラウザの起動負荷が、並列化による短縮効果を相殺した可能性があります。件数が数十件を超えて1回の実行が数分かかるようになってから並列化を検討しても遅くはありません。なお --parallel を付けると --exclude-filter は「option does not exist」で受け付けられませんでした。並列実行の内部はparatestで、使えるオプションが逐次実行と異なります。
--shardのファイル単位分割と時間配分
--shard=1/2 と --shard=2/2 で実行すると、それぞれ「1 file ran, out of 2.」と表示されました。分割の単位はテストの1件ではなくテストファイルです。重いテストを1ファイルに詰め込むと、そのジョブだけが遅れます。
4.6.0(2026年4月14日公開)からは --update-shards が加わり、テストクラスごとの所要時間を tests/.pest/shards.json に記録して、時間が均等になるように振り分けられます。ソースでは終了コードが0のときにだけこのファイルを書く作りで、実際に失敗テストを含む実行ではファイルが作られず、全件が成功した実行で初めて作られました。記録が古くなると「The [tests/.pest/shards.json] file is out of date.」と警告が出ます。
GitHub Actionsでは、公式ドキュメントの2つの手順(Playwrightブラウザの導入とmatrixでの分割)を組み合わせて次のように書けます。
jobs:
browser-tests:
runs-on: ubuntu-latest
strategy:
matrix:
shard: [1, 2, 3, 4]
name: Tests (Shard ${{ matrix.shard }}/4)
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
- uses: actions/setup-node@v4
with:
node-version: lts/*
- run: composer install --no-interaction
- run: npm ci
- run: npx playwright install --with-deps
- run: ./vendor/bin/pest --parallel --shard=${{ matrix.shard }}/4
--with-deps はブラウザ本体に加えて、ブラウザが必要とするOSのライブラリも入れるオプションです。Pest公式のCI手順もこれを指定しています。ワークフロー全体の組み立てはGitHub Actionsでビルド・自動テストを設定する方法を参照してください。
Pest 4に留まるかPest 5へ上げるかの判断
Laravel 13のPHPUnit依存制約とPestの選択結果
検証で使用したスケルトン(laravel/laravel v13.10.1)は、開発用依存に "phpunit/phpunit": "^12.5.12" を持っています。ここで composer require pestphp/pest pestphp/pest-plugin-browser --dev -W と版を付けずに入れると、Composerは「Using version ^4.7」を選び、Pest 4.7.8とプラグイン4.3.1が入りました。PHPが8.5でも同じです。Pest 5はPHPUnit 13を要求するため、スケルトンのPHPUnit 12と両立しません。
実際に "pestphp/pest:^5.0" だけを指定すると「conflicts with your root composer.json require (^12.5.12)」で解決に失敗しました。公式のアップグレードガイドはPestと各プラグインを ^5.0 に上げる手順しか書いていませんが、Laravelのプロジェクトでは phpunit/phpunit も同時に ^13 へ上げる必要があります。
Pest 5へ上げるときに変える3か所
次のように3つを同時に指定すると、Pest 5.3.0、プラグイン5.1.2、PHPUnit 13.3.6が入りました。
composer require "pestphp/pest:^5.0" "pestphp/pest-plugin-browser:^5.0" "phpunit/phpunit:^13.3" --dev -W
npm install -D playwright@latest
npx playwright install
前の章のLaravelのブラウザテスト2件は、テストコードを変えずにそのまま通りました。ただしプラグイン5.1.2のPlaywright下限は1.63.0なので、npm側も上げます。上げるかどうかは次の基準で決めます。
- 本番のPHPが8.3:Pest 4に留める。Pest 5はPHP 8.4以上が必須で、Pest 4は2027年7月28日までバグ修正を受けられる。
- PHP 8.4以上で、PHPUnit 12固有の拡張やプラグインに依存していない:Pest 5へ上げる。Pest 5で追加された機能(テスト影響分析など)はPestPHPの解説記事で扱っている。
- PHP 8.4以上でも、PHPUnit 12向けのサードパーティ拡張を使っている:その拡張がPHPUnit 13に対応するまでPest 4に留める。
Laravel DuskからPest 4へ移るかの判断
LaravelのDusk公式ドキュメントは冒頭で「Pest 4 now includes automated browser testing which offers significant performance and usability improvements compared to Laravel Dusk. For new projects, we recommend using Pest for browser testing.」と書き、新規プロジェクトにはPestのブラウザテストを勧めています。DuskはChromeDriverでブラウザを操作し、Pestのブラウザテストは上で見たとおりPlaywrightを使います。
新規はPestで始めるのが妥当です。一方、Duskのテストが大量に動いている既存プロジェクトで一括移行する必要はありません。ただし両者を同じプロジェクトに置くときは、テストのディレクトリを分けてください。Pestを入れたプロジェクトで php artisan dusk:install を実行すると、tests/Pest.php の先頭に pest()->extend(Tests\DuskTestCase::class)->in('Browser') が追記され、Duskのサンプルが tests/Browser に置かれます。Pestのブラウザテストを同じ tests/Browser に置いていると、./vendor/bin/pest がテストを1件も実行せずに「Test case [Tests\TestCase] can not be used. The folder […] already uses the test case [Tests\DuskTestCase].」で止まりました。Pestのブラウザテストを tests/E2E に移し、tests/Pest.php と phpunit.xml の指定を合わせると、./vendor/bin/pest と php artisan dusk の両方が通りました。そのうえで、新しく書く画面のテストからPestに寄せていく進め方が安全です。Duskの導入とCI設定はLaravelのテスト自動化をDuskで実装する方法、E2Eテスト全体のツール選びはE2Eテストのベストプラクティスで解説しています。
よくある質問
Pest 4とPest 5の違いは何ですか?
最大の違いは要件です。Pest 4はPHP 8.3以上・PHPUnit 12、Pest 5はPHP 8.4以上・PHPUnit 13を必要とします。ブラウザテストの書き方は共通で、今回の検証ではLaravelのブラウザテストがコードを変えずに両方で通りました。ブラウザテストのプラグインも5系に分かれ、5.1.2はPlaywright 1.63.0以上を要求します。Pest 5で追加された機能はPestPHPの解説記事を参照してください。
Pest 4のサポートはいつまでですか?
Pest公式のサポートポリシーでは、Pest 4のバグ修正期限は2027年7月28日です。「旧版のバグ修正は最新版のリリースから12か月」という規定に基づき、Pest 5の初回リリース(2026年7月28日)から12か月後に設定されています。2026年10月3日時点の4.x系の最新リリースは、2026年8月3日公開の4.7.8です。
Laravel DuskからPest 4のブラウザテストへ移行すべきですか?
新規プロジェクトならPestを選びます。Laravel公式のDuskドキュメントも、性能と使いやすさを理由に新規ではPestのブラウザテストを推奨しています。既存のDuskテストは、動いているなら書き直しを急ぐ必要はありません。同じプロジェクトに置く場合は、Duskが使う tests/Browser とは別のディレクトリ(例:tests/E2E)にPestのブラウザテストを置けば両方とも動きます。新規に書くテストからPestへ寄せるのが現実的です。
toHaveCountとassertCountの違いは何ですか?
expect($items)->toHaveCount(3) は、配列やCountableを実装したオブジェクトの要素数を確かめるPest本体のexpectationです。assertCount('.item', 5) はブラウザテスト用のアサーションで、表示中のページにCSSセレクタに当たる要素がいくつあるかを確かめます。PHPの値を数えるか、画面上の要素を数えるかの違いです。
PHPUnitで書いた既存のテストはPest 4でも動きますか?
動きます。PestはPHPUnitの上で動くため、PHPUnit\Framework\TestCase を継承したクラス形式のテストも ./vendor/bin/pest でそのまま実行されます。Laravel 13の新規プロジェクトでは、標準のクラス形式のテスト2件とPestで書いたブラウザテスト2件が同じ実行で4件とも通りました。関数形式への書き換えは必須ではありません。