Updated June 2026. Tested on Laravel 13 and PHP 8.4.
Plain Laravel feature tests are fast, but they do not run JavaScript. If your page relies on a front end framework or any client side behaviour, those tests cannot see it. Dusk fills that gap. It drives a real Chrome browser, so anything a user can do, your test can do, JavaScript included.
Dusk uses ChromeDriver under the hood, so you do not have to set up Selenium.
Install
Pull in the package and run the installer.
composer require --dev laravel/dusk
php artisan dusk:install
dusk:install creates a tests/Browser directory with an example test, and a screenshots folder where Dusk drops a picture of the page whenever a test fails, which is gold for debugging.
Dusk can do things you never want in production, like logging in as any user, so make sure you only ever run it in local and testing environments. Never install or register it for production.
Your first test
The example test visits the homepage and checks for some text.
namespace Tests\Browser;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
class ExampleTest extends DuskTestCase
{
public function test_basic_example(): void
{
$this->browse(function (Browser $browser) {
$browser->visit('/')
->assertSee('Laravel');
});
}
}
Run it.
php artisan dusk
browse hands you a Browser object. visit loads a URL, and assertSee checks the text is on the rendered page. If you change 'Laravel' to something that is not there, the test fails and Dusk saves a screenshot of what the page actually looked like.
Testing a form: registration
The real value is interacting with the page. First get auth pages in place. The old make:auth command is gone, so install a starter kit like Laravel Breeze.
composer require laravel/breeze --dev
php artisan breeze:install blade
php artisan migrate
Now write a test that fills in the registration form and confirms it worked.
php artisan dusk:make RegisterTest
public function test_a_user_can_register(): void
{
$this->browse(function (Browser $browser) {
$browser->visit('/register')
->type('name', 'Joe Perera')
->type('email', 'joe@example.com')
->type('password', 'password')
->type('password_confirmation', 'password')
->press('Register')
->assertPathIs('/dashboard')
->assertSee('Joe Perera');
});
}
Read that top to bottom and it is exactly the steps a person would take. type fills a field by its name, press clicks a button by its label, assertPathIs checks where you landed, and assertSee checks what is on the page.
Waiting for the page
Because Dusk runs a real browser with real timing, it gives you wait helpers so a test does not race ahead of the page. This is the part you cannot do with non browser tests.
$browser->waitForText('Loading complete')
->waitFor('.user-list')
->click('@save-button');
That @save-button is a Dusk selector. Add dusk="save-button" to an element and you can target it without coupling the test to CSS classes that might change.
Dusk makes it practical to test the parts of your app that only come alive in a browser. Start with a couple of happy path flows like login and a key form, and grow from there. Questions welcome in the comments.
All comments ()
No comments yet
Be the first to leave a comment on this post.