| name | dusk |
| description | End-to-end browser testing with Laravel Dusk. Used exclusively for full user flows requiring a real Chrome browser — JavaScript interactions, multi-step forms, and visual assertions. |
| compatible_agents | ["test","refactor","review"] |
Dusk (Browser Testing)
When to Use
- End-to-end flows requiring a real browser and JavaScript execution.
- Multi-step forms, modal interactions, and client-side validation states.
- Regression testing for key UI journeys before release.
When Not to Use
- Unit-level business logic validation (use unit tests).
- Controller/API contract validation without browser behavior (use feature/API tests).
- Fast feedback checks where browser startup cost is unnecessary.
Preconditions
- Laravel Dusk is installed and browser tests run from
tests/Browser/.
- Chrome/Chromium and compatible driver are available on the test environment.
.env.dusk.local is configured with isolated test DB and app URL.
- Test data setup strategy uses
DatabaseTruncation or DatabaseMigrations, never RefreshDatabase.
Process Checklist
Rules
- Browser tests live in
tests/Browser/.
- Use
DatabaseTruncation (preferred) or DatabaseMigrations; never RefreshDatabase.
- Use only
dusk="" selectors for test targeting.
- Do not use
pause(); wait for explicit UI state transitions.
Examples
uses(DatabaseTruncation::class);
it('user can submit an invoice', function () {
$user = User::factory()->create();
$this->browse(function (Browser $browser) use ($user) {
$browser->loginAs($user)
->visit('/invoices/create')
->assertNoJavaScriptErrors()
->type('@amount-input', '100')
->press('@submit-button')
->waitForText('Invoice created')
->assertSee('Invoice created');
});
});
<button dusk="submit-invoice">Submit</button>
<input dusk="amount-input" type="number" name="amount">
class InvoicePage extends Page
{
public function __construct(private readonly int $invoiceId) {}
public function url(): string { return "/invoices/{$this->invoiceId}"; }
public function assert(Browser $browser): void
{
$browser->assertPathIs($this->url());
}
public function elements(): array
{
return [
'@mark-paid-button' => '[dusk="mark-paid-button"]',
];
}
public function markAsPaid(Browser $browser): void
{
$browser->click('@mark-paid-button')
->waitForText('Invoice marked as paid');
}
public function fillForm(Browser $browser, string $amount): void
{
$browser->type('@amount-input', $amount);
}
public function submitForm(Browser $browser): void
{
$browser->press('@submit-button');
}
public function assertSuccess(Browser $browser): void
{
$browser->waitForText('Invoice created')
->assertSee('Invoice created');
}
}
$browser->waitFor('@invoice-table');
$browser->waitForText('Invoice created');
$browser->waitUntilMissing('@loading-spinner');
Testing Guidance
- Run all browser tests:
php artisan dusk.
- Run a specific file:
php artisan dusk tests/Browser/InvoiceFlowTest.php.
- Use CI-friendly headless Chrome settings and isolate Dusk database from local dev DB.
Anti-Patterns
- Using
RefreshDatabase in Dusk tests (transactions invisible across HTTP processes)
- Using
pause(3000) — arbitrary sleeps make tests slow and brittle
- Using CSS classes or IDs as selectors instead of
dusk="" attributes
- Testing pure business logic or JSON API responses with Dusk (use feature tests instead)
- Not calling
assertNoJavaScriptErrors() after visit()
- Repeating navigation and selector logic across test files instead of using Page Objects
References
- Laravel Dusk
- Related:
PestTesting/SKILL.md — Pest syntax used for Dusk tests
- Related:
PHPUnit/SKILL.md — feature tests for non-browser scenarios