Pest 4 Browser Testing: пишем e2e-тесты для Laravel на Playwright в синтаксисе Pest — PROG-TIME

Pest 4 Browser Testing: пишем e2e-тесты для Laravel на Playwright в синтаксисе Pest

30.05.2026

Pest долго был фреймворком «как PHPUnit, только красивее». В версии 4 он стал чем-то заметно большим: теперь это инструмент, который позволяет писать честные end-to-end тесты в реальном браузере — на Chromium, Firefox или Safari — в том же лёгком синтаксисе, что и unit-тесты. Под капотом — Playwright, сверху — полная интеграция с Laravel: фабрики, RefreshDatabase, Event::fake(), assertAuthenticated() работают прямо внутри браузерного теста. В этой статье разберём, как поднять Pest 4 Browser Testing на Laravel-проекте, как писать практичные сценарии и как ускорить прогон в CI через шардирование.

Что нового в Pest 4

Главная фича релиза — браузерное тестирование. Pest 4 запускает реальный браузер, делает в нём клики, ввод текста, проверки DOM, ловит JS-ошибки и сообщения консоли. Тесты выглядят как обычный Pest-код: всё та же функция it(), всё те же expectations, плюс новый объект страницы.

Помимо браузера в релизе появились:

  • Visual regression testing — сравнение скриншотов с эталоном через assertScreenshotMatches().
  • Test sharding — разбиение прогона на части (--shard=1/4), удобно для CI.
  • Новый движок Type Coverage — в 2 раза быстрее на первом запуске, мгновенно — на последующих.
  • Профанити-чекер (pest-plugin-profanity) и проверка подозрительных юникод-символов.
  • Тесты на PHPUnit 12.

Дальше — про самое полезное: браузерные тесты.

Установка плагина и Playwright

На свежий Laravel-проект с Pest 4 ставим браузерный плагин и сам Playwright:

composer require pestphp/pest-plugin-browser --dev

npm install playwright@latest
npx playwright install

Команда npx playwright install скачает бинарники Chromium, Firefox и WebKit (для Safari) — это десятки мегабайт, разумно кэшировать их в CI. После этого в проекте можно использовать функцию visit() внутри любого теста.

Полезно сразу добавить в .gitignore папку со скриншотами от упавших тестов:

tests/Browser/Screenshots

Первый рабочий тест: вход в систему

Покажем, как выглядит реалистичный сценарий: пользователь логинится через форму, попадает на дашборд, в системе диспатчится событие. Pest 4 позволяет описать это в одном коротком тесте — без отдельной инфраструктуры Dusk, без ручного управления драйвером и без серверов в фоне.

<?php

use App\Events\UserLoggedIn;
use App\Models\User;
use Illuminate\Support\Facades\Event;

it('логинит пользователя через форму', function () {
    Event::fake();

    User::factory()->create([
        'email' => 'shirley@example.com',
        'password' => bcrypt('secret-password'),
    ]);

    $page = visit('/')->on()->mobile()->firefox();

    $page->click('Войти')
         ->assertUrlIs(config('app.url') . '/login')
         ->assertSee('Вход в аккаунт')
         ->fill('email', 'shirley@example.com')
         ->fill('password', 'secret-password')
         ->click('Отправить')
         ->assertSee('Панель управления');

    $this->assertAuthenticated();
    Event::assertDispatched(UserLoggedIn::class);
});

Обратите внимание на три вещи. Во-первых, тест работает с фабрикой и в transactional базе — для этого подключите RefreshDatabase в tests/Pest.php. Во-вторых, прямо в одном тесте смешиваются браузерные ассерты (assertSee, assertUrlIs) и серверные (assertAuthenticated, Event::assertDispatched). В-третьих, цепочка ->on()->mobile()->firefox() переключает viewport и движок без отдельной конфигурации.

Локаторы, ввод, ожидания

Pest 4 умеет искать элементы по тексту, CSS-селекторам, id, классам и data-атрибутам. Достаточно одной строки:

$page->click('Сохранить');      // ссылка/кнопка с таким текстом
$page->click('.btn-primary');    // CSS-класс
$page->click('@save-button');    // data-test="save-button"
$page->click('#submit');         // id

Ввод данных, выбор, чекбоксы, файлы, drag-and-drop — всё через тот же объект страницы:

$page->type('email', 'shirley@example.com')
     ->typeSlowly('search', 'laravel', delay: 80)
     ->select('country', 'RU')
     ->check('terms')
     ->attach('avatar', storage_path('app/fixtures/avatar.jpg'))
     ->press('Зарегистрироваться')
     ->assertPathIs('/welcome');

По умолчанию Pest ждёт элементы до 5 секунд — достаточно для большинства SPA. Если приложение медленное, таймаут поднимается в tests/Pest.php:

pest()->browser()->timeout(10000); // 10 секунд

Smoke-тесты и проверка JS-ошибок

Самый дешёвый способ закрыть «ничего не сломалось при деплое» — пройтись браузером по основным страницам и убедиться, что нет JS-ошибок и логов в консоли. Pest 4 умеет это в две строки:

it('главные страницы не падают', function () {
    $routes = ['/', '/about', '/pricing', '/docs', '/blog'];

    visit($routes)->assertNoSmoke();
});

Метод assertNoSmoke() — это сокращение для assertNoJavaScriptErrors() + assertNoConsoleLogs(). Для проверок доступности есть assertNoAccessibilityIssues() с настраиваемым уровнем строгости — от critical до minor.

Визуальная регрессия

Если в проекте дизайн-критичные страницы (лендинги, отчёты, документация), визуальная регрессия отлавливает то, что не ловят функциональные тесты — съехавший отступ, потерянную иконку, изменившийся цвет. Pest 4 делает это так:

it('лендинги выглядят как ожидается', function () {
    $pages = visit(['/', '/about', '/pricing']);

    $pages->assertScreenshotMatches();
});

При первом запуске Pest сохранит эталонные скриншоты. На последующих прогонах сравнит новые с эталоном и упадёт при отличии. Эталоны хранятся рядом с тестами и коммитятся в репозиторий.

Параллельный запуск и шардирование в CI

Браузерные тесты — самые медленные в проекте, поэтому ускорение прогона критично. Pest 4 поддерживает обе стратегии параллельности.

Локально и в одном CI-джобе работает --parallel — тесты бегут в нескольких процессах на одной машине:

./vendor/bin/pest --parallel

На CI с матрицей джобов помогает шардирование: тестовый набор делится на N частей, каждый шард прогоняется на своей машине параллельно. В GitHub Actions это выглядит так:

strategy:
  matrix:
    shard: [1, 2, 3, 4]

steps:
  - uses: actions/checkout@v4

  - uses: actions/setup-node@v4
    with:
      node-version: lts/*

  - name: Install Playwright Browsers
    run: npx playwright install --with-deps

  - name: Run Pest
    run: ./vendor/bin/pest --parallel --shard=${{ matrix.shard }}/4

Так браузерный сьют, который локально идёт 8 минут, в CI укладывается в 2-3 минуты — без аренды более жирных раннеров.

Отладка падающих тестов

Когда тест падает, главное — увидеть, что было на странице. Pest 4 предлагает несколько инструментов. Флаг --debug при падении открывает реальный браузер и ставит тест на паузу, чтобы можно было потыкать DOM руками:

./vendor/bin/pest --debug

Внутри теста доступны точечные хелперы:

$page->screenshot(fullPage: true);    // снимок всей страницы
$page->screenshotElement('#chart');   // снимок отдельного элемента
$page->debug();                       // пауза + headed-режим
$page->tinker();                      // Tinker-сессия в контексте страницы

Метод tinker() — почти магия: пока браузер на паузе, в терминале открывается полноценный Laravel Tinker, через который можно дёрнуть модели, кэш, очереди — и сразу увидеть результат на странице.

Итоги

Pest 4 закрывает старую боль PHP-стека: e2e-тестирование больше не требует отдельной инфраструктуры. Один composer require, один npx playwright install — и в проекте появляются полноценные браузерные тесты, которые умеют работать с фабриками, базой, событиями и очередями Laravel так же, как обычные unit-тесты.

Если в команде давно лежит идея «надо бы покрыть критичные сценарии браузером, но Dusk страшно поднимать» — Pest 4 это та точка, в которой её можно реализовать за день. Начать имеет смысл с трёх простых шагов: smoke-теста по основным маршрутам, одного полного сценария логина или оплаты и шардированного CI на 4 джоба. Дальше — по мере роста проекта — добавлять визуальную регрессию и тесты на разных viewport’ах.