Laravel Dusk – Intuitive and Easy Browser Testing for All!

Viraj Khatavkar
Share

This article was peer reviewed by Younes Rafie. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!


End to end testing for JavaScript applications, particularly single-page-apps, has always been a challenge. To that end, Laravel released its 5.4 version recently with a new testing library: Dusk.

Laravel Dusk Logo

With the release of Dusk, Laravel hopes to give its users a common API for browser testing. It ships with the default ChromeDriver, and if we need support for other browsers, we can use Selenium. It will still have this common testing API to cater to our needs.

The tutorial will assume you’re starting a new Laravel 5.4 app.

Installation

composer require laravel/dusk

This will install the most recent stable version of the package via Composer.

Next, we need to register DuskServiceProvider within our application. We can do it in a couple of ways:

Approach 1

We can include it in the providers array of our config/app.php file.

...

App\Providers\RouteServiceProvider::class,
Laravel\Dusk\DuskServiceProvider::class,
...

The problem with this approach is that DuskServiceProvider will be registered in our application for all the environments. We don’t need Dusk to be available and registered in our production environment. We can avoid this with the second approach.

Approach 2

Register DuskServiceProvider in the AppServiceProvider class for specific environments:

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Laravel\Dusk\DuskServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        if ($this->app->environment('local', 'testing', 'staging')) {
           $this->app->register(DuskServiceProvider::class);
        }
    }
}

Next, to complete the installation process:

php artisan dusk:install

Dusk will provide a basic scaffolding of classes and directories. If we open the tests directory, we can see a new Browser directory with the necessary scaffolding for Dusk tests.

Our First Test

First, we will scaffold an authentication workflow using Laravel’s pre-built authentication mechanism.

php artisan make:auth

Let us now create our first Dusk test:

php artisan dusk:make LoginTest

The above command will create a LoginTest class in our Browser directory.

class LoginTest extends DuskTestCase
{
    /**
     * A Dusk test example.
     *
     * @return void
     */
    public function test_I_can_login_successfully()
    {
        $this->browse(function ($browser) {
            $browser->visit('/login')
                    ->type('email', 'viraj@virajkhatavkar.com')
                    ->type('password', 'secret')
                    ->press('Login')
                    ->assertSee('You are logged in!');
        });
    }
}

In the above test case, we check whether a user can successfully log into the system and see a home page with a welcome message.

Note: To make this test succeed, we need to have an actual user in the database. For this demonstration, we have already configured a user in the database with the above credentials.

Let us now execute our Dusk tests:

php artisan dusk

If you have a user entry in the database with the correct credentials, you can see the below output:

PHPUnit 5.7.6 by Sebastian Bergmann and contributors.

..                                                                  2 / 2 (100%)

Time: 4.71 seconds, Memory: 10.00MB

OK (2 tests, 2 assertions)

Failed Tests

When tests fail, PHPUnit throws some errors at us. We have to interpret what went wrong.

Dusk has a few notable additions in place to cater to this use-case.

We will first modify our test to deliberately fail on execution:

public function test_I_can_login_successfully()
{
    $this->browse(function ($browser) {
        $browser->visit('/login')
                ->type('email', 'viraj2438@gmail.com')
                ->type('password', 'secret')
                ->press('Login')
                ->assertSee('You are logged in!');
    });
}

In the above test case, we will try to log in with a user who is not present in the database. Let us now run our Dusk tests and see what happens:

Failed-Test

If you look carefully, a browser opens there just before the test fails.

Behind the scenes, Dusk takes a screenshot of the page where the error was triggered, and saves it in the (automatically git-ignored) screenshots directory:

Screenshot-Dusk-Test-Failed

This gives us a visual representation of why our test fails. We can see that the credentials were not matching any records in the database. With visual feedback like this, Dusk makes sure you can quickly pinpoint the problems.

Testing AJAX Calls

Dusk is meant for end to end browser testing of modern JavaScript applications. In such apps, a typical use-case is waiting for the response of AJAX requests.

We will test the Create Task feature from a demo app – please clone it to follow along:

Dusk - Create Task

We use an AJAX request to create a new task and then redirect the user to the task list page. This is a perfect testing use-case.

php artisan dusk:make CreateTaskTest

The above command will create a CreateTaskTest class in our Browser directory. Let us now write the test:

class CreateTaskTest extends DuskTestCase
{
    /**
     * A Dusk test example.
     *
     * @return void
     */
    public function test_I_can_create_task_successfully()
    {
        $this->browse(function ($browser) {

            $browser->visit('/tasks/create')
                    ->type('title', 'My Task')
                    ->press('Add Task')
                    ->pause(5000)
                    ->assertPathIs('/tasks');
        });
    }
}

In the above test, we try to add a new test using the AJAX form:

  1. Enter the title
  2. Click the Add Task button
  3. Wait for 5 seconds
  4. Assert that we are redirected to the tasks page

Let us now run our Dusk tests and see the result:

AJAX Test Passed Dusk

As you can see, the above test passed.

We can also use the waitUntilMissing method of Dusk’s API to test the flow above:

<?php

namespace Tests\Browser;

use Tests\DuskTestCase;
use Illuminate\Foundation\Testing\DatabaseMigrations;

class CreateTaskTest extends DuskTestCase
{
    /**
     * A Dusk test example.
     *
     * @return void
     */
    public function test_I_can_create_task_successfully()
    {
        $this->browse(function ($browser) {

            $browser->visit('/tasks/create')
                    ->type('title', 'My Task')
                    ->press('Add Task')
                    ->waitUntilMissing('.btn-primary')
                    ->assertPathIs('/tasks');
        });
    }
}

You can refer to the official documentation for learning about other waiting elements available in the API.

A More Advanced Example

Our app has a menu item, wherein if we click on the Support Email link, a modal pops up with a form for sending a support email:

Open-Support-Modal

We will write a test case to test the following scenario:

  1. Log in
  2. See Support Email
  3. Click on Support Email
  4. Assert that modal opens and has user’s email ID in the text box

In the above gif, we interact with our UI using a mouse and open the modal. Let us re-create the above flow in our Dusk test case.

First, we will create a new test class:

php artisan dusk:make SupportEmailsTest

Then, the test:

class SupportEmailsTest extends DuskTestCase
{
    /**
     * A Dusk test example.
     *
     * @return void
     */
    public function test_I_can_open_modal_for_support_emails()
    {
        $this->browse(function ($browser) {

            $user = factory(User::class)->create();

            $browser->loginAs($user)
                    ->visit('/tasks')
                    ->clickLink('Support Email')
                    ->whenAvailable('#modal-support', function ($modal) use($user) {
                        $modal->assertInputValue('#support-from', $user->email);
                    });
        });
    }
}

Let’s run it:

php artisan dusk tests/Browser/SupportEmailsTest.php

We can see that our test passes:

PHPUnit 5.7.13 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 3.63 seconds, Memory: 12.00MB

OK (1 test, 1 assertion)

For all other available assertions, you can refer to the official documentation.

Pages

There is a concept of Pages in Dusk. They are nothing more than powerful, reusable test classes.

Let’s refactor CreateTaskTest using Pages.

Let us first create a new Page:

php artisan dusk:page CreateTaskPage

The above command will create a new class in the Pages directory. Let us examine each method and modify it to better suit our Create Task test case:

public function url()
{
    return '/tasks/create';
}

url method defines the page url. Whenever this page is invoked, Dusk will navigate to this url.

public function assert(Browser $browser)
{
    $browser->assertPathIs($this->url());
}

assert defines the assertions for this page. Whenever CreateTaskPage is invoked, all the assertions defined in the assert method will execute.

In the above example, we are simply asserting that the active page’s url is appropriate.

public function elements()
{
    return [
        '@addTask' => '.btn-primary',
    ];
}

The elements method can have pre-defined selectors. We can define human readable names for selectors and reuse them for this page in different test cases. In the above example, I have defined a selector for the Add Task button.

Let us now modify the CreateTaskTest class and use the selector:

class CreateTaskTest extends DuskTestCase
{
    /**
     * A Dusk test example.
     *
     * @return void
     */
    public function test_I_can_create_task_successfully()
    {
        $this->browse(function ($browser) {

            $user = factory(User::class)->create();

            $browser->loginAs($user)
                    ->visit(new CreateTaskPage)
                    ->type('title', 'My Task')
                    ->click('@addTask')
                    ->waitUntilMissing('@addTask')
                    ->assertPathIs('/tasks');
        });
    }
}

We modified our class to use the CreateTaskPage. Let us now re-run our test and see if everything works fine:

PHPUnit 5.7.13 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 2.76 seconds, Memory: 12.00MB

OK (1 test, 2 assertions)

All good!

You can even define custom methods for performing some reusable actions on a certain page. You can read more about it in the official documentation.

Conclusion

In this article, we introduced Laravel Dusk, a Laravel package which provides an alternative for end to end JavaScript testing. We have covered the configuration options necessary to get started, and the examples above should help you fill in the gaps and give an overview of some of the other configuration options available to you.

How do you use Dusk? Can you think of any advanced use cases for this library? What are they? Let us know in the comments!