laravel/dusk
Laravel Dusk is a browser automation and end-to-end testing tool for Laravel. It provides an expressive API for driving real browsers, ships with a standalone Chromedriver (no Selenium/JDK required by default), and can use other Selenium drivers.
## Getting Started
### Minimal Setup
1. **Installation**:
```bash
composer require --dev laravel/dusk
Run the Dusk installer:
php artisan dusk:install
This sets up ChromeDriver, configures PHPUnit, and generates a base test class.
First Test:
Create a test in tests/Browser/ExampleTest.php:
use Laravel\Dusk\TestCase;
class ExampleTest extends TestCase
{
/** @test */
public function it_loads_the_main_page()
{
$this->browse(function ($browser) {
$browser->visit('/')
->assertSee('Welcome');
});
}
}
Run tests with:
php artisan dusk
Key Files:
tests/Browser/ – Store all Dusk tests here.dusk.config.php – Configure drivers, browsers, and environments.phpunit.xml – PHPUnit configuration for Dusk.Browser Interaction:
Use the $browser instance inside browse() to chain actions:
$browser->visit('/dashboard')
->click('@login-button')
->type('@email-input', 'user@example.com')
->press('Login')
->assertPathIs('/dashboard');
Element Selection:
Use Dusk selectors (e.g., @id, .class, #element) or custom CSS selectors:
$browser->click('@submit-button'); // Uses data-dusk attribute
$browser->assertSee('Success')->click('button[type="submit"]'); // CSS selector
Pages & Components:
Create reusable page classes (e.g., LoginPage.php) to encapsulate logic:
namespace Tests\Browser\Pages;
use Laravel\Dusk\Page;
class LoginPage extends Page
{
public function __construct()
{
$this->url = '/login';
}
public function submit($email, $password)
{
$this->browse(function ($browser) use ($email, $password) {
$browser->type('@email', $email)
->type('@password', $password)
->click('@login-button');
});
}
}
Use in tests:
$this->browse(function ($browser) {
$loginPage = new LoginPage();
$loginPage->submit('user@example.com', 'password');
});
Assertions: Leverage built-in assertions for UI validation:
$browser->assertSee('Welcome')
->assertDontSee('Error')
->assertPathIs('/dashboard')
->assertAttribute('value', 'expected', '@input-field');
Data-Driven Tests: Use PHPUnit data providers for parameterized tests:
public function test_login_with_credentials()
{
$credentials = [
['user@example.com', 'password', true],
['wrong@example.com', 'wrong', false],
];
foreach ($credentials as [$email, $password, $shouldSucceed]) {
$this->browse(function ($browser) use ($email, $password, $shouldSucceed) {
$browser->visit('/login')
->type('@email', $email)
->type('@password', $password)
->click('@login-button');
if ($shouldSucceed) {
$browser->assertPathIs('/dashboard');
} else {
$browser->assertSee('Invalid credentials');
}
});
}
}
Debugging Tools:
$browser->screenshot()).$browser->consoleLog().dd($browser->driver->getCurrentUrl()) or dump($browser->driver->getPageSource()).Headless Mode:
Configure in dusk.config.php:
'headless' => [
'chrome' => true,
'firefox' => false,
],
Custom Drivers:
Extend Laravel\Dusk\Browser or use DUSK_DRIVER_URL to point to a remote Selenium grid.
Element Not Found:
data-dusk attributes (e.g., data-dusk="login-button").waitFor() to handle dynamic content:
$browser->waitForText('Loading...')->assertDontSee('Loading...');
$browser->driver->getPageSource() to inspect the DOM.Flaky Tests:
$browser->pause(1000); // 1-second pause
waitFor() or waitForElement() to avoid race conditions.ChromeDriver Issues:
php artisan dusk:chrome-driver
Assertion Failures:
assertSeeIn() or assertDontSeeIn() for partial text matches:
$browser->assertSeeIn('body', 'partial text');
assertSeeIn() with str_contains() if needed.Environment Configuration:
APP_ENV=testing and DB_CONNECTION=sqlite_testing in .env.DUSK_DRIVER=chrome or DUSK_DRIVER=firefox to override defaults.Performance:
DuskTestCase:
protected function tearDown(): void
{
$this->browse(function ($browser) {
$browser->driver->executeScript("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})");
});
parent::tearDown();
}
Custom Selectors: Extend Dusk’s selector engine by adding a custom resolver:
$browser->addCustomSelector('custom', function ($selector) {
return '.custom-class[data-custom="' . $selector . '"]';
});
Usage:
$browser->click('custom:user-button');
Component Testing:
Use component() to test Blade components in isolation:
$browser->component('alert', ['message' => 'Test'])
->assertSee('Test');
Parallel Testing:
Configure PHPUnit for parallel runs in phpunit.xml:
<phpunit ...>
<parallel>
<processes>4</processes>
</parallel>
</phpunit>
CI Optimization:
DUSK_HEADLESS=true to skip GUI rendering.Debugging Helpers:
$browser->screenshotElement('@element')->saveAs('screenshot.png');
$browser->consoleLog()->saveAs('logs.txt');
Laravel-Specific Tips:
actingAs() for authenticated tests:
$this->browse(function ($browser) {
$browser->actingAs(User::first())
->visit('/dashboard');
});
public function tearDown(): void
{
Artisan::call('migrate:fresh --env=testing');
parent::tearDown();
}
Pest Integration: Dusk works seamlessly with Pest. Example:
it('loads the homepage', function () {
$this->browse(function ($browser) {
$browser->visit('/')
->assertSee('Welcome');
});
});
Custom Commands:
Extend Laravel\Dusk\Browser to add domain-specific methods:
namespace Tests\Browser;
use Laravel\Dusk\Browser;
class CustomBrowser extends Browser
{
public function loginAsAdmin()
{
$this->type('@email', 'admin@example.com')
->type('@password', 'admin')
->click('@login-button');
}
}
Usage in DuskTestCase:
protected function createBrowser()
{
return new CustomBrowser();
}
Handling Iframes: Switch to an iframe using:
$browser->driver->switchToFrame('iframe-id');
Return to the parent frame with
How can I help you explore Laravel packages today?