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 machine. 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 add the laravel/dusk
Composer dependency to your project:
composer require --dev laravel/dusk
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, run the dusk:install
Artisan command:
php artisan dusk:install
A Browser
directory will be created within your tests
directory and will contain an example test. Next, set the APP_URL
environment variable in your .env
file. This value should match the URL you use to access your application in a browser.
To run your tests, use the dusk
Artisan command. The dusk
command accepts any argument that is also accepted by the phpunit
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
Managing ChromeDriver Installations
If you would like to install a different version of ChromeDriver than what is included with Laravel Dusk, 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 74
# Install a given version of ChromeDriver for all supported OSs...
php artisan dusk:chrome-driver --all
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
* @return void
*/
public static function prepare()
{
// 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:
/**
* Create the RemoteWebDriver instance.
*
* @return \Facebook\WebDriver\Remote\RemoteWebDriver
*/
protected function driver()
{
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
Running Tests
To run your browser tests, use 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 PHPUnit test runner, allowing you to only run the tests for a given group, etc:
php artisan dusk --group=foo
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
* @return void
*/
public static function prepare()
{
// static::startChromeDriver();
}
In addition, if you start ChromeDriver on a port other than 9515, you should modify the driver
method of the same class:
/**
* Create the RemoteWebDriver instance.
*
* @return \Facebook\WebDriver\Remote\RemoteWebDriver
*/
protected function driver()
{
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.
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, call the browse
method:
<?php
namespace Tests\Browser;
use App\User;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Chrome;
use Tests\DuskTestCase;
class ExampleTest extends DuskTestCase
{
use DatabaseMigrations;
/**
* A basic browser test example.
*
* @return void
*/
public function testBasicExample()
{
$user = factory(User::class)->create([
'email' => 'taylor@laravel.com',
]);
$this->browse(function ($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 callback. A browser instance will automatically be passed to this callback 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, "ask" for more than one browser in the signature of the callback given to the browse
method:
$this->browse(function ($first, $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');
});
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 the 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();
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 the Dusk's browser macros.
*
* @return void
*/
public function boot()
{
Browser::macro('scrollToElement', function ($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
implementation:
$this->browse(function ($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 the login screen during every test. The loginAs
method accepts a user ID or user model instance:
$this->browse(function ($first, $second) {
$first->loginAs(User::find(1))
->visit('/home');
});
After using the loginAs
method, the user session will be maintained for all tests within the file.
Database Migrations
When your test requires migrations, like the authentication example above, you should never use the RefreshDatabase
trait. The RefreshDatabase
trait leverages database transactions which will not be applicable across HTTP requests. Instead, use the DatabaseMigrations
trait:
<?php
namespace Tests\Browser;
use App\User;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Chrome;
use Tests\DuskTestCase;
class ExampleTest extends DuskTestCase
{
use DatabaseMigrations;
}
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, prefix the selector with @
to manipulate the attached element within a Dusk test:
// HTML...
<button dusk="login-button">Login</button>
// Test...
$browser->click('@login-button');
Clicking Links
To click a link, you may use the clickLink
method on the browser instance. The clickLink
method will click the link that has the given display text:
$browser->clickLink($linkText);
This method interacts with jQuery. If jQuery is not available on the page, Dusk will automatically inject it into the page so it is available for the test's duration.
Text, Values, & Attributes
Retrieving & Setting Values
Dusk provides several methods for interacting with the current display text, value, and attributes of elements on the page. For example, to get the "value" of an element that matches a given selector, use the value
method:
// Retrieve the value...
$value = $browser->value('selector');
// Set the value...
$browser->value('selector', 'value');
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 an attribute of an element matching the given selector:
$attribute = $browser->attribute('selector', 'value');
Using 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 field with the given name
attribute. Finally, Dusk will attempt to find a textarea
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');