coolify/.claude/skills/pest-testing/SKILL.md
Andras Bacsai 47a3f2e2cd test: add Pest browser testing with SQLite :memory: schema
Set up end-to-end browser testing using Pest Browser Plugin + Playwright.
New v4 test suite uses SQLite :memory: database with pre-generated schema dump
(database/schema/testing-schema.sql) instead of running migrations, enabling
faster test startup.

- Add pestphp/pest-plugin-browser dependency
- Create GenerateTestingSchema command to export PostgreSQL schema to SQLite
- Add .env.testing configuration for isolated test environment
- Implement v4 test directory structure (Feature, Browser, Unit tests)
- Update Pest skill documentation with browser testing patterns, API reference,
  debugging techniques, and common pitfalls
- Configure phpunit.xml and tests/Pest.php for v4 suite
- Update package.json and docker-compose.dev.yml for testing dependencies
2026-02-11 15:25:47 +01:00

6.2 KiB

name description
pest-testing Tests applications using the Pest 4 PHP framework. Activates when writing tests, creating unit or feature tests, adding assertions, testing Livewire components, browser testing, debugging test failures, working with datasets or mocking; or when the user mentions test, spec, TDD, expects, assertion, coverage, or needs to verify functionality works.

Pest Testing 4

When to Apply

Activate this skill when:

  • Creating new tests (unit, feature, or browser)
  • Modifying existing tests
  • Debugging test failures
  • Working with browser testing or smoke testing
  • Writing architecture tests or visual regression tests

Documentation

Use search-docs for detailed Pest 4 patterns and documentation.

Test Directory Structure

  • tests/Feature/ and tests/Unit/ — Legacy tests (keep, don't delete)
  • tests/v4/Feature/ — New feature tests (SQLite :memory: database)
  • tests/v4/Browser/ — Browser tests (Pest Browser Plugin + Playwright)
  • tests/Browser/ — Legacy Dusk browser tests (keep, don't delete)

New tests go in tests/v4/. The v4 suite uses SQLite :memory: with a schema dump (database/schema/testing-schema.sql) instead of running migrations.

Do NOT remove tests without approval.

Running Tests

  • All v4 tests: php artisan test --compact tests/v4/
  • Browser tests: php artisan test --compact tests/v4/Browser/
  • Feature tests: php artisan test --compact tests/v4/Feature/
  • Specific file: php artisan test --compact tests/v4/Browser/LoginTest.php
  • Filter: php artisan test --compact --filter=testName
  • Headed (see browser): ./vendor/bin/pest tests/v4/Browser/ --headed
  • Debug (pause on failure): ./vendor/bin/pest tests/v4/Browser/ --debug

Basic Test Structure

it('is true', function () { expect(true)->toBeTrue(); });

Assertions

Use specific assertions (assertSuccessful(), assertNotFound()) instead of assertStatus():

Use Instead of
assertSuccessful() assertStatus(200)
assertNotFound() assertStatus(404)
assertForbidden() assertStatus(403)

Mocking

Import mock function before use: use function Pest\Laravel\mock;

Datasets

Use datasets for repetitive tests:

it('has emails', function (string $email) { expect($email)->not->toBeEmpty(); })->with([ 'james' => 'james@laravel.com', 'taylor' => 'taylor@laravel.com', ]);

Browser Testing (Pest Browser Plugin + Playwright)

Browser tests use pestphp/pest-plugin-browser with Playwright. They run outside Docker — the plugin starts an in-process HTTP server and Playwright browser automatically.

Key Rules

  1. Always use RefreshDatabase — the in-process server uses SQLite :memory:
  2. Always seed InstanceSettings::create(['id' => 0]) in beforeEach — most pages crash without it
  3. Use User::factory() for auth tests — create users with id => 0 for root user
  4. No Dusk, no Selenium — use visit(), fill(), click(), assertSee() from the Pest Browser API
  5. Place tests in tests/v4/Browser/
  6. Views with bare function declarations will crash on the second request in the same process — wrap with function_exists() guard if you encounter this

Browser Test Template

use App\Models\InstanceSettings; use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

beforeEach(function () { InstanceSettings::create(['id' => 0]); });

it('can visit the page', function () { $page = visit('/login');

$page->assertSee('Login');

});

Browser Test with Form Interaction

it('fails login with invalid credentials', function () { User::factory()->create([ 'id' => 0, 'email' => 'test@example.com', 'password' => Hash::make('password'), ]);
$page = visit('/login');

$page->fill('email', 'random@email.com')
    ->fill('password', 'wrongpassword123')
    ->click('Login')
    ->assertSee('These credentials do not match our records');

});

Browser API Reference

Method Purpose
visit('/path') Navigate to a page
->fill('field', 'value') Fill an input by name
->click('Button Text') Click a button/link by text
->assertSee('text') Assert visible text
->assertDontSee('text') Assert text is not visible
->assertPathIs('/path') Assert current URL path
->assertSeeIn('.selector', 'text') Assert text in element
->screenshot() Capture screenshot
->debug() Pause test, keep browser open
->wait(seconds) Wait N seconds

Debugging

  • Screenshots auto-saved to tests/Browser/Screenshots/ on failure
  • ->debug() pauses and keeps browser open (press Enter to continue)
  • ->screenshot() captures state at any point
  • --headed flag shows browser, --debug pauses on failure

SQLite Testing Setup

v4 tests use SQLite :memory: instead of PostgreSQL. Schema loaded from database/schema/testing-schema.sql.

Regenerating the Schema

When migrations change, regenerate from the running PostgreSQL database:

docker exec coolify php artisan schema:generate-testing

Architecture Testing

arch('controllers') ->expect('App\Http\Controllers') ->toExtendNothing() ->toHaveSuffix('Controller');

Common Pitfalls

  • Not importing use function Pest\Laravel\mock; before using mock
  • Using assertStatus(200) instead of assertSuccessful()
  • Forgetting datasets for repetitive validation tests
  • Deleting tests without approval
  • Forgetting assertNoJavaScriptErrors() in browser tests
  • Browser tests: forgetting InstanceSettings::create(['id' => 0]) — most pages crash without it
  • Browser tests: forgetting RefreshDatabase — SQLite :memory: starts empty
  • Browser tests: views with bare function declarations crash on second request — wrap with function_exists() guard