Laravel Dusk
Introduction
Laravel Dusk provides an expressive, easy-to-use browser automation and testing API. By default, Dusk does not require you to install JDK or Selenium on your local computer. Instead, Dusk uses a standalone ChromeDriver installation. However, you are free to utilize any other Selenium compatible driver you wish.
Installation
To get started, you should install Google Chrome and add the laravel/dusk Composer dependency to your project:
composer require laravel/dusk --dev
If you are manually registering Dusk's service provider, you should never register it in your production environment, as doing so could lead to arbitrary users being able to authenticate with your application.
After installing the Dusk package, execute the dusk:install Artisan command. The dusk:install command will create a tests/Browser directory, an example Dusk test, and install the Chrome Driver binary for your operating system:
php artisan dusk:install
Next, set the APP_URL environment variable in your application's .env file. This value should match the URL you use to access your application in a browser.
If you are using Laravel Sail to manage your local development environment, please also consult the Sail documentation on configuring and running Dusk tests.
Managing ChromeDriver Installations
If you would like to install a different version of ChromeDriver than what is installed by Laravel Dusk via the dusk:install command, you may use the dusk:chrome-driver command:
# Install the latest version of ChromeDriver for your OS...
php artisan dusk:chrome-driver
# Install a given version of ChromeDriver for your OS...
php artisan dusk:chrome-driver 86
# Install a given version of ChromeDriver for all supported OSs...
php artisan dusk:chrome-driver --all
# Install the version of ChromeDriver that matches the detected version of Chrome / Chromium for your OS...
php artisan dusk:chrome-driver --detect
Dusk requires the chromedriver binaries to be executable. If you're having problems running Dusk, you should ensure the binaries are executable using the following command: chmod -R 0755 vendor/laravel/dusk/bin/.
Using Other Browsers
By default, Dusk uses Google Chrome and a standalone ChromeDriver installation to run your browser tests. However, you may start your own Selenium server and run your tests against any browser you wish.
To get started, open your tests/DuskTestCase.php file, which is the base Dusk test case for your application. Within this file, you can remove the call to the startChromeDriver method. This will stop Dusk from automatically starting the ChromeDriver:
/**
* Prepare for Dusk test execution.
*
* @beforeClass
*/
public static function prepare(): void
{
// static::startChromeDriver();
}
Next, you may modify the driver method to connect to the URL and port of your choice. In addition, you may modify the "desired capabilities" that should be passed to the WebDriver:
use Facebook\WebDriver\Remote\RemoteWebDriver;
/**
* Create the RemoteWebDriver instance.
*/
protected function driver(): RemoteWebDriver
{
return RemoteWebDriver::create(
'http://localhost:4444/wd/hub', DesiredCapabilities::phantomjs()
);
}
Getting Started
Generating Tests
To generate a Dusk test, use the dusk:make Artisan command. The generated test will be placed in the tests/Browser directory:
php artisan dusk:make LoginTest
Resetting the Database After Each Test
Most of the tests you write will interact with pages that retrieve data from your application's database; however, your Dusk tests should never use the RefreshDatabase trait. The RefreshDatabase trait leverages database transactions which will not be applicable or available across HTTP requests. Instead, you have two options: the DatabaseMigrations trait and the DatabaseTruncation trait.
Using Database Migrations
The DatabaseMigrations trait will run your database migrations before each test. However, dropping and re-creating your database tables for each test is typically slower than truncating the tables:
<?php
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
uses(DatabaseMigrations::class);
//
<?php
namespace Tests\Browser;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
class ExampleTest extends DuskTestCase
{
use DatabaseMigrations;
//
}
SQLite in-memory databases may not be used when executing Dusk tests. Since the browser executes within its own process, it will not be able to access the in-memory databases of other processes.
Using Database Truncation
The DatabaseTruncation trait will migrate your database on the first test in order to ensure your database tables have been properly created. However, on subsequent tests, the database's tables will simply be truncated - providing a speed boost over re-running all of your database migrations:
<?php
use Illuminate\Foundation\Testing\DatabaseTruncation;
use Laravel\Dusk\Browser;
uses(DatabaseTruncation::class);
//
<?php
namespace Tests\Browser;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseTruncation;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
class ExampleTest extends DuskTestCase
{
use DatabaseTruncation;
//
}
By default, this trait will truncate all tables except the migrations table. If you would like to customize the tables that should be truncated, you may define a $tablesToTruncate property on your test class:
If you are using Pest, you should define properties or methods on the base DuskTestCase class or on any class your test file extends.
/**
* Indicates which tables should be truncated.
*
* @var array
*/
protected $tablesToTruncate = ['users'];
Alternatively, you may define an $exceptTables property on your test class to specify which tables should be excluded from truncation:
/**
* Indicates which tables should be excluded from truncation.
*
* @var array
*/
protected $exceptTables = ['users'];
To specify the database connections that should have their tables truncated, you may define a $connectionsToTruncate property on your test class:
/**
* Indicates which connections should have their tables truncated.
*
* @var array
*/
protected $connectionsToTruncate = ['mysql'];
If you would like to execute code before or after database truncation is performed, you may define beforeTruncatingDatabase or afterTruncatingDatabase methods on your test class:
/**
* Perform any work that should take place before the database has started truncating.
*/
protected function beforeTruncatingDatabase(): void
{
//
}
/**
* Perform any work that should take place after the database has finished truncating.
*/
protected function afterTruncatingDatabase(): void
{
//
}
Running Tests
To run your browser tests, execute the dusk Artisan command:
php artisan dusk
If you had test failures the last time you ran the dusk command, you may save time by re-running the failing tests first using the dusk:fails command:
php artisan dusk:fails
The dusk command accepts any argument that is normally accepted by the Pest / PHPUnit test runner, such as allowing you to only run the tests for a given group:
php artisan dusk --group=foo
If you are using Laravel Sail to manage your local development environment, please consult the Sail documentation on configuring and running Dusk tests.
Manually Starting ChromeDriver
By default, Dusk will automatically attempt to start ChromeDriver. If this does not work for your particular system, you may manually start ChromeDriver before running the dusk command. If you choose to start ChromeDriver manually, you should comment out the following line of your tests/DuskTestCase.php file:
/**
* Prepare for Dusk test execution.
*
* @beforeClass
*/
public static function prepare(): void
{
// static::startChromeDriver();
}
In addition, if you start ChromeDriver on a port other than 9515, you should modify the driver method of the same class to reflect the correct port:
use Facebook\WebDriver\Remote\RemoteWebDriver;
/**
* Create the RemoteWebDriver instance.
*/
protected function driver(): RemoteWebDriver
{
return RemoteWebDriver::create(
'http://localhost:9515', DesiredCapabilities::chrome()
);
}
Environment Handling
To force Dusk to use its own environment file when running tests, create a .env.dusk.{environment} file in the root of your project. For example, if you will be initiating the dusk command from your local environment, you should create a .env.dusk.local file.
When running tests, Dusk will back-up your .env file and rename your Dusk environment to .env. Once the tests have completed, your .env file will be restored.
Browser Basics
Creating Browsers
To get started, let's write a test that verifies we can log into our application. After generating a test, we can modify it to navigate to the login page, enter some credentials, and click the "Login" button. To create a browser instance, you may call the browse method from within your Dusk test:
<?php
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
uses(DatabaseMigrations::class);
test('basic example', function () {
$user = User::factory()->create([
'email' => 'taylor@laravel.com',
]);
$this->browse(function (Browser $browser) use ($user) {
$browser->visit('/login')
->type('email', $user->email)
->type('password', 'password')
->press('Login')
->assertPathIs('/home');
});
});
<?php
namespace Tests\Browser;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
class ExampleTest extends DuskTestCase
{
use DatabaseMigrations;
/**
* A basic browser test example.
*/
public function test_basic_example(): void
{
$user = User::factory()->create([
'email' => 'taylor@laravel.com',
]);
$this->browse(function (Browser $browser) use ($user) {
$browser->visit('/login')
->type('email', $user->email)
->type('password', 'password')
->press('Login')
->assertPathIs('/home');
});
}
}
As you can see in the example above, the browse method accepts a closure. A browser instance will automatically be passed to this closure by Dusk and is the main object used to interact with and make assertions against your application.
Creating Multiple Browsers
Sometimes you may need multiple browsers in order to properly carry out a test. For example, multiple browsers may be needed to test a chat screen that interacts with websockets. To create multiple browsers, simply add more browser arguments to the signature of the closure given to the browse method:
$this->browse(function (Browser $first, Browser $second) {
$first->loginAs(User::find(1))
->visit('/home')
->waitForText('Message');
$second->loginAs(User::find(2))
->visit('/home')
->waitForText('Message')
->type('message', 'Hey Taylor')
->press('Send');
$first->waitForText('Hey Taylor')
->assertSee('Jeffrey Way');
});
Navigation
The visit method may be used to navigate to a given URI within your application:
$browser->visit('/login');
You may use the visitRoute method to navigate to a named route:
$browser->visitRoute($routeName, $parameters);
You may navigate "back" and "forward" using the back and forward methods:
$browser->back();
$browser->forward();
You may use the refresh method to refresh the page:
$browser->refresh();
Resizing Browser Windows
You may use the resize method to adjust the size of the browser window:
$browser->resize(1920, 1080);
The maximize method may be used to maximize the browser window:
$browser->maximize();
The fitContent method will resize the browser window to match the size of its content:
$browser->fitContent();
When a test fails, Dusk will automatically resize the browser to fit the content prior to taking a screenshot. You may disable this feature by calling the disableFitOnFailure method within your test:
$browser->disableFitOnFailure();
You may use the move method to move the browser window to a different position on your screen:
$browser->move($x = 100, $y = 100);
Browser Macros
If you would like to define a custom browser method that you can re-use in a variety of your tests, you may use the macro method on the Browser class. Typically, you should call this method from a service provider's boot method:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Laravel\Dusk\Browser;
class DuskServiceProvider extends ServiceProvider
{
/**
* Register Dusk's browser macros.
*/
public function boot(): void
{
Browser::macro('scrollToElement', function (string $element = null) {
$this->script("$('html, body').animate({ scrollTop: $('$element').offset().top }, 0);");
return $this;
});
}
}
The macro function accepts a name as its first argument, and a closure as its second. The macro's closure will be executed when calling the macro as a method on a Browser instance:
$this->browse(function (Browser $browser) use ($user) {
$browser->visit('/pay')
->scrollToElement('#credit-card-details')
->assertSee('Enter Credit Card Details');
});
Authentication
Often, you will be testing pages that require authentication. You can use Dusk's loginAs method in order to avoid interacting with your application's login screen during every test. The loginAs method accepts a primary key associated with your authenticatable model or an authenticatable model instance:
use App\Models\User;
use Laravel\Dusk\Browser;
$this->browse(function (Browser $browser) {
$browser->loginAs(User::find(1))
->visit('/home');
});
After using the loginAs method, the user session will be maintained for all tests within the file.
Cookies
You may use the cookie method to get or set an encrypted cookie's value. By default, all of the cookies created by Laravel are encrypted:
$browser->cookie('name');
$browser->cookie('name', 'Taylor');
You may use the plainCookie method to get or set an unencrypted cookie's value:
$browser->plainCookie('name');
$browser->plainCookie('name', 'Taylor');
You may use the deleteCookie method to delete the given cookie:
$browser->deleteCookie('name');
Executing JavaScript
You may use the script method to execute arbitrary JavaScript statements within the browser:
$browser->script('document.documentElement.scrollTop = 0');
$browser->script([
'document.body.scrollTop = 0',
'document.documentElement.scrollTop = 0',
]);
$output = $browser->script('return window.location.pathname');
Taking a Screenshot
You may use the screenshot method to take a screenshot and store it with the given filename. All screenshots will be stored within the tests/Browser/screenshots directory:
$browser->screenshot('filename');
The responsiveScreenshots method may be used to take a series of screenshots at various breakpoints:
$browser->responsiveScreenshots('filename');
The screenshotElement method may be used to take a screenshot of a specific element on the page:
$browser->screenshotElement('#selector', 'filename');
Storing Console Output to Disk
You may use the storeConsoleLog method to write the current browser's console output to disk with the given filename. Console output will be stored within the tests/Browser/console directory:
$browser->storeConsoleLog('filename');
Storing Page Source to Disk
You may use the storeSource method to write the current page's source to disk with the given filename. The page source will be stored within the tests/Browser/source directory:
$browser->storeSource('filename');
Interacting With Elements
Dusk Selectors
Choosing good CSS selectors for interacting with elements is one of the hardest parts of writing Dusk tests. Over time, frontend changes can cause CSS selectors like the following to break your tests:
// HTML...
<button>Login</button>
// Test...
$browser->click('.login-page .container div > button');
Dusk selectors allow you to focus on writing effective tests rather than remembering CSS selectors. To define a selector, add a dusk attribute to your HTML element. Then, when interacting with a Dusk browser, prefix the selector with @ to manipulate the attached element within your test:
// HTML...
<button dusk="login-button">Login</button>
// Test...
$browser->click('@login-button');
If desired, you may customize the HTML attribute that the Dusk selector utilizes via the selectorHtmlAttribute method. Typically, this method should be called from the boot method of your application's AppServiceProvider:
use Laravel\Dusk\Dusk;
Dusk::selectorHtmlAttribute('data-dusk');
Text, Values, and Attributes
Retrieving and Setting Values
Dusk provides several methods for interacting with the current value, display text, and attributes of elements on the page. For example, to get the "value" of an element that matches a given CSS or Dusk selector, use the value method:
// Retrieve the value...
$value = $browser->value('selector');
// Set the value...
$browser->value('selector', 'value');
You may use the inputValue method to get the "value" of an input element that has a given field name:
$value = $browser->inputValue('field');
Retrieving Text
The text method may be used to retrieve the display text of an element that matches the given selector:
$text = $browser->text('selector');
Retrieving Attributes
Finally, the attribute method may be used to retrieve the value of an attribute of an element matching the given selector:
$attribute = $browser->attribute('selector', 'value');
Interacting With Forms
Typing Values
Dusk provides a variety of methods for interacting with forms and input elements. First, let's take a look at an example of typing text into an input field:
$browser->type('email', 'taylor@laravel.com');
Note that, although the method accepts one if necessary, we are not required to pass a CSS selector into the type method. If a CSS selector is not provided, Dusk will search for an input or textarea field with the given name attribute.
To append text to a field without clearing its content, you may use the append method:
$browser->type('tags', 'foo')
->append('tags', ', bar, baz');
You may clear the value of an input using the clear method:
$browser->clear('email');
You can instruct Dusk to type slowly using the typeSlowly method. By default, Dusk will pause for 100 milliseconds between key presses. To customize the amount of time between key presses, you may pass the appropriate number of milliseconds as the third argument to the method:
$browser->typeSlowly('mobile', '+1 (202) 555-5555');
$browser->typeSlowly('mobile', '+1 (202) 555-5555', 300);
You may use the appendSlowly method to append text slowly:
$browser->type('tags', 'foo')
->appendSlowly('tags', ', bar, baz');
Dropdowns
To select a value available on a select element, you may use the select method. Like the type method, the select method does not require a full CSS selector. When passing a value to the select method, you should pass the underlying option value instead of the display text:
$browser->select('size', 'Large');