Laravel (5.7) Browser Testing (Laravel Dusk)
Laravel Dusk is the default for browser testing in laravel, this tutorial will give you an in depth explanation of what it is, and how you can use it to perform various tests on your browser.
Introduction
Laravel Dusk offers an expressive, easy-to-use browser testing and automation API. By default, Laravel Dusk doesn't require you to install JDK or Selenium on your machine. Rather, Dusk makes use of a standalone ChromeDriver installation. However, you can also use any other Selenium compatible driver that you wish.
Installation
To get started with Dusk, add the laravel/dusk composer dependency to your project:
composer require --dev laravel/dusk
Once you are done installing the Dusk package, you should run dusk:install Artisan command:
php artisan dusk:install
A Browser directory is created within your tests directory and contains an example test. Next, you should set the APP_URL environment variable in your .env file. This value should match the URL that you use to access your application in a browser.
If you want to run your tests, you should use the dusk Artisan command. The dusk command will accept any argument that is also accepted by the phpunit command:
php artisan dusk
If you had test failures the last time that you ran the dusk command, you can save time by re-running the failing tests first using the dusk:fails command:
php artisan dusk:fails
Using Other Browsers
Dusk uses Google Chrome by default and a standalone ChromeDriver installation to run all your browser tests. However, you can start your own Selenium server and then run your tests against any browser you wish.
First thing you need to get started, is to open your tests/DuskTestCase.php file, which is the base Dusk test case of your application. Within the file, you can remove the call to startChromeDriver method. This stops Dusk from automatically starting the ChromeDriver:
/**
* Prepare for Dusk test execution.
*
* @beforeClass
* @return void
*/
public static function prepare()
{
// static::startChromeDriver();
}
Next, you can modify the driver method to connect to the URL and the port of your choice. Additionally, you can 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
If you to generate a Dusk test, you should use the dusk:make Artisan command. The generated test is placed in the tests/Browser directory:
php artisan dusk:make LoginTest
Running Tests
If you want to run your browser tests, you should use the dusk Artisan command:
php artisan dusk
If you experienced test failures the last time you ran the dusk command, you can save time by re-running the failing tests first using the dusk:fails command:
php artisan dusk:fails
The dusk command will accept any argument that is normally accepted by the PHPUnit test runner, this allows you to only run the tests for a given group, etc:
php artisan dusk --group=foo
Manually Starting ChromeDriver
By default, Dusk automatically attempts to start ChromeDriver. If this doesn't work for your particular system, you can manually start ChromeDriver before running the dusk command. Int the case where you choose to start ChromeDriver manually, 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();
}
Additionally, if you start ChromeDriver on a port aside 9515, you have to 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
If you want to force Dusk to use its own environment file when running tests, then create a .env.dusk.{environment} file in the root of your project. For instance, if you are initiating the dusk command from your local environment, create a .env.dusk.local file.
When you are running tests, Dusk backs-up your .env file and renames your Dusk environment to .env. Once the tests complete, your .env file will is restored.
Creating Browsers
To get started with this, let us write a test that verifies we can log into our application. After we generate a test, we can now modify it to navigate to the login page, enter some login credentials, and then click the "Login" button. We create a browser instance by calling the browse method:
<?php
namespace Tests\Browser;
use App\User;
use Tests\DuskTestCase;
use Laravel\Dusk\Chrome;
use Illuminate\Foundation\Testing\DatabaseMigrations;
class ExampleTest extends DuskTestCase
{
use DatabaseMigrations;
/**
* A basic browser test example.
*
* @return void
*/
public function testBasicExample()
{
$user = factory(User::class)->create([
'email' => '[email protected]',
]);
$this->browse(function ($browser) use ($user) {
$browser->visit('/login')
->type('email', $user->email)
->type('password', 'secret')
->press('Login')
->assertPathIs('/home');
});
}
}
As seen in the example above, the browse method will accept a callback. A browser instance is automatically passed to this callback by Dusk and is the main object that is used to interact with and will make assertions against your application.
Creating Multiple Browsers
There are times when you need multiple browsers in order to properly carry out a test. For instance, you may need multiple browsers to test a chat screen that interacts with websockets. For you to create multiple browsers, "ask" for more than one browser in the signature of the callback that is 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 can use the resize method to adjust the size of the browser window:
$browser->resize(1920, 1080);
The maximize method that can be used to maximize the browser window:
$browser->maximize();
Browser Macros
If you want to define a custom browser method that you can re-use in a variety of your tests, you can use the macro method on the Browser class. Typically, you have to call this method from a service provider's boot method:
<?php
namespace App\Providers;
use Laravel\Dusk\Browser;
use Illuminate\Support\ServiceProvider;
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 will accept a name as its first argument, and a Closure as its second. The macro's Closure is 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
Most often, the pages you will be testing will require authentication. You can make use of Dusk's loginAs method in order to avoid interacting with the login screen during every test. The loginAs method will accept a user ID or user model instance:
$this->browse(function ($first, $second) {
$first->loginAs(User::find(1))
->visit('/home');
});
Database Migrations
In the case where your test requires migrations, such as the authentication example above, never use the RefreshDatabase trait. The RefreshDatabase trait will leverage database transactions which will not be applicable across HTTP requests. Rather, you should use the DatabaseMigrations trait:
<?php
namespace Tests\Browser;
use App\User;
use Tests\DuskTestCase;
use Laravel\Dusk\Chrome;
use Illuminate\Foundation\Testing\DatabaseMigrations;
class ExampleTest extends DuskTestCase
{
use DatabaseMigrations;
}
Interacting With Elements
Dusk Selectors
One of the hardest parts of writing Dusk tests is choosing good CSS selectors for interacting with elements. Over time, frontend changes can cause CSS selectors such as the following to break your tests:
// HTML...
<button>Login</button>
// Test...
$browser->click('.login-page .container div > button');
The Dusk selectors allow you to focus on writing effective tests rather than remembering CSS selectors. If you want to define a selector, you should 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
If your test involves clicking a link, use the clickLink method on the browser instance. The clickLink method clicks the link that has the given display text:
$browser->clickLink($linkText);
Text, Values, & Attributes
Retrieving & Setting Values
Laravel Dusk provides us with several methods for interacting with the current display text, value, and attributes of elements on the page. For instance, to get the "value" of an element that matches a given selector, you should use the value method:
// Retrieve the value...
$value = $browser->value('selector');
// Set the value...
$browser->value('selector', 'value');
Retrieving Text
The text method can 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 can be used to retrieve an attribute of an element matching the given selector:
$attribute = $browser->attribute('selector', 'value');
Using Forms
Typing Values
Dusk provides us with a variety of methods for interacting with forms and input elements. First, let us take consider an example of typing text into an input field:
$browser->type('email', '[email protected]');
Note, although the method will accept one if necessary, we aren't required to pass a CSS selector into the type method. In the case where a CSS selector is not provided, Dusk will search for an input field that has the given name attribute. Finally, Dusk attempts to find a textarea with the given name attribute.
If you want to append text to a field without clearing its content, you can use the append method:
$browser->type('tags', 'foo')
->append('tags', ', bar, baz');
You can clear the value of an input using the clear method:
$browser->clear('email');
Dropdowns
To select a value that is in a dropdown selection box, you can use the select method. Just like the type method, the select method doesn't require a full CSS selector. When you are passing a value to the select method, you should pass the underlying option value rather than the display text:
$browser->select('size', 'Large');
You can select a random option by omitting the second parameter:
$browser->select('size');
Checkboxes
If you want to "check" a checkbox field, you can use the check method. Just like many other input related methods, a full CSS selector isn't required. If an exact selector match cannot be found, Dusk will search for a checkbox that has a matching name attribute:
$browser->check('terms');
$browser->uncheck('terms');
Radio Buttons
If you want to "select" a radio button option, you can use the radio method. Just like many other input related methods, a full CSS selector isn't required. If an exact selector match cannot be found, Dusk will search for a radio that has matching name and value attributes:
$browser->radio('version', 'php7');
Attaching Files
The attach method can be used to attach a file to a file input element. Just like many other input related methods, a full CSS selector isn't required. If an exact selector match cannot be found, Dusk will search for a file input that has matching name attribute:
$browser->attach('photo', __DIR__.'/photos/me.png');
Using The Keyboard
The keys method will allow you to provide more complex input sequences to a given element than normally allowed by the type method. For instance, you can hold modifier keys entering values. In this case, the shift key will be held while aryan is entered into the element matching the given selector. After aryan is typed, mishal will be typed without any modifier keys:
$browser->keys('selector', ['{shift}', 'aryan'], 'mishal');
You can even send a "hot key" to the primary CSS selector that contains your application:
$browser->keys('.app', ['{command}', 'j']);
Every modifier keys is wrapped in {} characters, and match they all match the constants defined in the Facebook\WebDriver\WebDriverKeys class, which can be found on GitHub.
Using The Mouse
Clicking On Elements
The click method can be used to "click" on an element matching the given selector:
$browser->click('.selector');
Mouseover
The mouseover method can be used when you need to move the mouse over an element matching the given selector:
$browser->mouseover('.selector');
Drag & Drop
The drag method can be used to drag an element matching the given selector to another element:
$browser->drag('.from-selector', '.to-selector');
Or, you can drag an element in a single direction:
$browser->dragLeft('.selector', 10);
$browser->dragRight('.selector', 10);
$browser->dragUp('.selector', 10);
$browser->dragDown('.selector', 10);
JavaScript Dialogs
Dusk provides us with various methods to interact with JavaScript Dialogs:
// Wait for a dialog to appear:
$browser->waitForDialog($seconds = null);
// Assert that a dialog has been displayed and that its message matches the given value:
$browser->assertDialogOpened('value');
// Type the given value in an open JavaScript prompt dialog:
$browser->typeInDialog('Hello World');
If you want to close an opened JavaScript Dialog, by clicking the OK button:
$browser->acceptDialog();
If you want to close an opened JavaScript Dialog, clicking the Cancel button (for a confirmation dialog only):
$browser->dismissDialog();
Scoping Selectors
Sometimes you can wish to perform several operations while scoping all of the operations within a given selector. For instance, you can wish to assert that some text exists only within a table and then click a button within that table. You can use the with method to accomplish this. All operations performed within the callback given to the with method will be scoped to the original selector:
$browser->with('.table', function ($table) {
$table->assertSee('Hello World')
->clickLink('Delete');
});
Waiting For Elements
When you are testing applications that use JavaScript extensively, sometimes it becomes necessary to "wait" for certain elements or data to be available before proceeding with a test. Laravel Dusk makes this a cinch. Using a variety of methods, you can wait for elements to be visible on the page or even wait until a given JavaScript expression evaluates to true.
Waiting
In the case where you need to pause the test for a given number ofmilliseconds, use the pause method:
$browser->pause(1000);
Waiting For Selectors
The waitFor method can be used to pause the execution of the test until the element matching the given CSS selector is displayed on the page. By default, this pauses the test for a maximum of five seconds before throwing an exception. Where necessary, you can pass a custom timeout threshold as the second argument to the method:
// Waits a maximum of five seconds for the selector
$browser->waitFor('.selector');
// Waits a maximum of one second for the selector...
$browser->waitFor('.selector', 1);
You can also wait until the given selector is missing from the page:
$browser->waitUntilMissing('.selector');
$browser->waitUntilMissing('.selector', 1);
Scoping Selectors When Available
Occasionally, you may want to wait for a given selector and then interact with the element matching the selector. For instance, you may want to wait until a modal window is available and then press the "OK" button within the modal. The whenAvailable method can be used in this case. All element operations that are performed within the given callback will be scoped to the original selector:
$browser->whenAvailable('.modal', function ($modal) {
$modal->assertSee('Hello World')
->press('OK');
});
Waiting For Text
The waitForText method can be used to wait until the given text is displayed on the page:
// Wait a maximum of five seconds for the text...
$browser->waitForText('Hello World');
// Wait a maximum of one second for the text...
$browser->waitForText('Hello World', 1);
// Waiting For Links
// The waitForLink method may be used to wait until the given link text is displayed on the page:
// Wait a maximum of five seconds for the link...
$browser->waitForLink('Create');
// Wait a maximum of one second for the link...
$browser->waitForLink('Create', 1);
Waiting On The Page Location
Whenever you are making a path assertion such as $browser->assertPathIs('/home'), the assertion may fail if window.location.pathname is being updated asynchronously. You can use the waitForLocation method to wait for the location to be a given value:
$browser->waitForLocation('/secret');
You can also wait for a named route's location:
$browser->waitForRoute($routeName, $parameters);
Waiting for Page Reloads
In cases when you need to make assertions after a page has been reloaded, you should use the waitForReload method:
$browser->click('.some-action')
->waitForReload()
->assertSee('something');
Waiting On JavaScript Expressions
Sometimes you may want to pause the execution of a test until a given JavaScript expression evaluates to true. You can easily accomplish this using the waitUntil method. When you are passing an expression to this method, you don't need to include the return keyword or an ending semi-colon:
// Wait a maximum of five seconds for the expression to be true...
$browser->waitUntil('App.dataLoaded');
$browser->waitUntil('App.data.servers.length > 0');
// Wait a maximum of one second for the expression to be true...
$browser->waitUntil('App.data.servers.length > 0', 1);
Waiting On Vue Expressions
The following methods can be used to wait until a given Vue component attribute has a given value:
// Wait until the component attribute contains the given value...
$browser->waitUntilVue('user.name', 'Aryan', '@user');
// Wait until the component attribute doesn't contain the given value...
$browser->waitUntilVueIsNot('user.name', null, '@user');
Waiting With A Callback
Many of the "wait" methods that are in Dusk rely on the underlying waitUsing method. You can use this method directly to wait for a given callback to return true. The waitUsing method will accept the maximum number of seconds to wait, the interval at which the Closure should be evaluated, the Closure, and an optional failure message:
$browser->waitUsing(10, 1, function () use ($something) {
return $something->isReady();
}, "Something wasn't ready in time.");
Making Vue Assertions
Dusk even allows us to make assertions on the state of Vue component data. For instance, imagine our application contains the following Vue component:
// HTML...
<profile dusk="profile-component"></profile>
// Component Definition...
Vue.component('profile', {
template: '<div>{{ user.name }}</div>',
data: function () {
return {
user: {
name: 'Taylor'
}
};
}
});
we may assert on the state of the Vue component like so:
/**
* A basic Vue test example.
*
* @return void
*/
public function testVue()
{
$this->browse(function (Browser $browser) {
$browser->visit('/')
->assertVue('user.name', 'Taylor', '@profile-component');
});
}
Available Assertions
Dusk provides you with a variety of assertions that you may make against your application. Below is a documented list of all the possible assertions:
Name | Description | Code |
---|---|---|
assertTitle | This asserts that the page title matches the given text: | $browser->assertTitle($title); |
assertTitleContains | This asserts that the page title contains the given text: | $browser->assertTitleContains($title); |
assertUrlIs | This asserts that the current URL (without the query string) matches the given string: | $browser->assertUrlIs($url); |
assertSchemeIs | This asserts that the current URL scheme matches the given scheme: | $browser->assertSchemeIs($scheme); |
assertSchemeIsNot | This asserts that the current URL scheme matches the given scheme: | $browser->assertSchemeIsNot($scheme); |
assertHostIs | This asserts that the current URL host matches the given host: | $browser->assertHostIs($host); |
assertHostIsNot | This asserts that the current URL host does not match the given host: | $browser->assertHostIsNot($host); |
assertPortIs | This asserts that the current URL port matches the given port: | $browser->assertPortIs($port); |
assertPortIsNot | This asserts that the current URL port does not match the given port | $browser->assertPortIsNot($port); |
assertPathBeginsWith | This asserts that the current URL path begins with the given path | $browser->assertPathBeginsWith($path); |
assertPathIs | This asserts that the current path matches the given path: | $browser->assertPathIs('/home'); |
assertPathIsNot | This asserts that the current path does not match the given path: | $browser->assertPathIsNot('/home'); |
assertRouteIs | This asserts that the current URL matches the given named route's URL: | $browser->assertRouteIs($name, $parameters); |
assertQueryStringHas | This asserts that the given query string parameter is present: This asserts that the given query string parameter is present and has a given value: |
$browser->assertQueryStringHas($name); $browser->assertQueryStringHas($name, $value); |
assertQueryStringMissing | This asserts that the given query string parameter is missing: | $browser->assertQueryStringMissing($name); |
assertFragmentIs | This asserts that the current fragment matches the given fragment: | $browser->assertFragmentIs('anchor'); |
assertFragmentBeginsWith | This asserts that the current fragment begins with the given fragment: | $browser->assertFragmentBeginsWith('anchor'); |
assertFragmentIsNot | This asserts that the current fragment does not match the given fragment: | $browser->assertFragmentIsNot('anchor'); |
assertHasCookie | This asserts that the given cookie is present: | $browser->assertHasCookie($name); |
assertCookieMissing | This asserts that the given cookie is not present: | $browser->assertCookieMissing($name); |
assertCookieValue | This asserts that a cookie has a given value: | $browser->assertCookieValue($name, $value); |
assertPlainCookieValue | This asserts that an unencrypted cookie has a given value: | $browser->assertPlainCookieValue($name, $value); |
assertSee | This asserts that the given text is present on the page: | $browser->assertSee($text); |
assertDontSee | This asserts that the given text is not present on the page: | $browser->assertDontSee($text); |
assertSeeIn | This asserts that the given text is present within the selector: | $browser->assertSeeIn($selector, $text); |
assertDontSeeIn | This asserts that the given text is not present within the selector: | $browser->assertDontSeeIn($selector, $text); |
assertSourceHas | This asserts that the given source code is present on the page: | $browser->assertSourceHas($code); |
assertSourceMissing | This asserts that the given source code is not present on the page: | $browser->assertSourceMissing($code); |
assertSeeLink | This asserts that the given link is present on the page: | $browser->assertSeeLink($linkText); |
assertDontSeeLink | asserts that the given link is not present on the page: | $browser->assertDontSeeLink($linkText); |
assertInputValue | asserts that the given input field has the given value: | $browser->assertInputValue($field, $value); |
assertInputValueIsNot | asserts that the given input field does not have the given value: | $browser->assertInputValueIsNot($field, $value); |
assertChecked | asserts that the given checkbox is checked: | $browser->assertChecked($field); |
assertNotChecked | asserts that the given checkbox is not checked: | $browser->assertNotChecked($field); |
assertRadioSelected | asserts that the given radio field is selected: | $browser->assertRadioSelected($field, $value); |
assertRadioNotSelected | asserts that the given radio field is not selected: | $browser->assertRadioNotSelected($field, $value); |
assertSelected | asserts that the given dropdown has the given value selected: | $browser->assertSelected($field, $value); |
assertNotSelected | asserts that the given dropdown does not have the given value selected: | $browser->assertNotSelected($field, $value); |
assertSelectHasOptions | asserts that the given array of values are available to be selected: | $browser->assertSelectHasOptions($field, $values); |
assertSelectMissingOptions | asserts that the given array of values are not available to be selected: | $browser->assertSelectMissingOptions($field, $values); |
assertSelectHasOption | asserts that the given value is available to be selected on the given field: | $browser->assertSelectHasOption($field, $value); |
assertValue | asserts that the element matching the given selector has the given value: | $browser->assertValue($selector, $value); |
assertPresent | asserts that the element matching the given selector is present: | $browser->assertPresent($selector); |
assertMissing | asserts that the element matching the given selector is not visible: | $browser->assertMissing($selector); |
assertDialogOpened | asserts that a JavaScript dialog with the given message has been opened: | $browser->assertDialogOpened($message); |
assertEnabled | asserts that the given field is enabled: | $browser->assertEnabled($field); |
assertDisabled | asserts that the given field is disabled: | $browser->assertDisabled($field); |
assertFocused | asserts that the given field is focused: | $browser->assertFocused($field); |
assertNotFocused | asserts that the given field is not focused: | $browser->assertNotFocused($field); |
assertVue | asserts that a given Vue component data property matches the given value: | $browser->$browser->assertVue($property, $value, $componentSelector = null); |
assertVueIsNot | asserts that a given Vue component data property does not match the given value: | $browser->assertVueIsNot($property, $value, $componentSelector = null); |
assertVueContains | asserts that a given Vue component data property is an array and contains the given value: | $browser->assertVueContains($property, $value, $componentSelector = null); |
assertVueDoesNotContain | asserts that a given Vue component data property is an array and does not contain the given value: | $browser->assertVueDoesNotContain($property, $value, $componentSelector = null); |
Pages
Sometimes, tests will require several complicated actions to be performed in sequence. This make our tests harder to read and understand. Pages allow us to define expressive actions that may then be performed on a given page using a single method. Pages also allow us to define short-cuts to common selectors for our application or a single page.
Generating Pages
If you want to generate a page object, you should use the dusk:page Artisan command. All the page objects will be placed in the tests/Browser/Pages directory:
php artisan dusk:page Login
Configuring Pages
Pages have three methods: url, assert, and elements by default. We will discuss the assert and url methods now. The elements method is discussed below in more detail.
The url Method
The url method returns the path of the URL that represents the page. Dusk will use this URL when it navigates to the page in the browser:
/**
* Get the URL for the page.
*
* @return string
*/
public function url()
{
return '/login';
}
The assert Method
The assert method can make any assertions necessary to verify that the browser is actually on the given page. It is not necessary to complete this method; however, we are free to make these assertions if we wish. These assertions are run automatically when navigating to the page:
/**
* Asserts that the browser is on the page.
*
* @return void
*/
public function assert(Browser $browser)
{
$browser->assertPathIs($this->url());
}
Navigating To Pages
Once a page is configured, we may navigate to it using the visit method:
use Tests\Browser\Pages\Login;
$browser->visit(new Login);
Sometimes we may already be on a given page and have to "load" the page's selectors and methods into the current test context. This is common when we press a button and are redirected to a given page without explicitly navigating to it. In such case, we may use the on method to load the page:
use Tests\Browser\Pages\CreatePlaylist;
$browser->visit('/dashboard')
->clickLink('Create Playlist')
->on(new CreatePlaylist)
->assertSee('@create');
Shorthand Selectors
The elements method of pages allows us to define quick, easy-to-remember shortcuts for any CSS selector on our page. For instance, let us define a shortcut for the "email" input field of the application's login page:
/**
* Get the element shortcuts for the page.
*
* @return array
*/
public function elements()
{
return [
'@email' => 'input[name=email]',
];
}
Now, we may use this shorthand selector anywhere we would use a full CSS selector:
$browser->type('@email', '[email protected]');
Global Shorthand Selectors
After you install Dusk, a base Page class is placed in your tests/Browser/Pages directory. This class contains a siteElements method which can be used to define global shorthand selectors that should be available on every page throughout your application:
/**
* Gets the global element shortcuts for the site.
*
* @return array
*/
public static function siteElements()
{
return [
'@element' => '#selector',
];
}
Page Methods
Added to the default methods defined on pages, you can define additional methods which may be used throughout your tests. For instance, let us imagine we are building a music management application. A common action for one of the pages of the application might be to create a playlist. Rather than re-writing the logic to create a playlist in each test, you can define a createPlaylist method on a page class:
<?php
namespace Tests\Browser\Pages;
use Laravel\Dusk\Browser;
class Dashboard extends Page
{
// Other page methods...
/**
* Create a new playlist.
*
* @param \Laravel\Dusk\Browser $browser
* @param string $name
* @return void
*/
public function createPlaylist(Browser $browser, $name)
{
$browser->type('name', $name)
->check('share')
->press('Create Playlist');
}
}
Once the method is defined, you can use it within any test that utilizes the page. The browser instance is automatically passed to the page method:
use Tests\Browser\Pages\Dashboard;
$browser->visit(new Dashboard)
->createPlaylist('My Playlist')
->assertSee('My Playlist');
Components
Components are very similar to Dusk's "page objects", but they are intended for pieces of UI and functionality that are re-used throughout our application, such as a navigation bar or notification window. Hence, components are not bound to specific URLs.
Generating Components
If you want to generate a component, you should use the dusk:component Artisan command. New Dusk components are placed in the test/Browser/Components directory:
php artisan dusk:component DatePicker
As you can see in the example above, a "date picker" is an instance of a component that might exist throughout your application on a variety of pages. It could become cumbersome to manually write the browser automation logic to select a date in dozens of tests throughout your test suite. Rather, you can define a Dusk component to represent the date picker, allowing you to encapsulate that logic within the component:
<?php
namespace Tests\Browser\Components;
use Laravel\Dusk\Browser;
use Laravel\Dusk\Component as BaseComponent;
class DatePicker extends BaseComponent
{
/**
* Get the root selector for the component.
*
* @return string
*/
public function selector()
{
return '.date-picker';
}
/**
* Assert that the browser page contains the component.
*
* @param Browser $browser
* @return void
*/
public function assert(Browser $browser)
{
$browser->assertVisible($this->selector());
}
/**
* Get the element shortcuts for the component.
*
* @return array
*/
public function elements()
{
return [
'@date-field' => 'input.datepicker-input',
'@month-list' => 'div > div.datepicker-months',
'@day-list' => 'div > div.datepicker-days',
];
}
/**
* Select the given date.
*
* @param \Laravel\Dusk\Browser $browser
* @param int $month
* @param int $day
* @return void
*/
public function selectDate($browser, $month, $day)
{
$browser->click('@date-field')
->within('@month-list', function ($browser) use ($month) {
$browser->click($month);
})
->within('@day-list', function ($browser) use ($day) {
$browser->click($day);
});
}
}
Using Components
Once you have been defined the component, you can then easily select a date within the date picker from any test. And, if the logic necessary to select a date is changed, you only need to update the component:
<?php
namespace Tests\Browser;
use Tests\DuskTestCase;
use Laravel\Dusk\Browser;
use Tests\Browser\Components\DatePicker;
use Illuminate\Foundation\Testing\DatabaseMigrations;
class ExampleTest extends DuskTestCase
{
/**
* A basic component test example.
*
* @return void
*/
public function testBasicExample()
{
$this->browse(function (Browser $browser) {
$browser->visit('/')
->within(new DatePicker, function ($browser) {
$browser->selectDate(1, 2018);
})
->assertSee('January');
});
}
}
Continuous Integration
CircleCI
I the case where you are using CircleCI to run your Dusk tests, you can use this configuration file as a starting point. Just like TravisCI, you will use the php artisan serve command to launch PHP's built-in web server:
version: 2
jobs:
build:
steps:
- run: sudo apt-get install -y libsqlite3-dev
- run: cp .env.testing .env
- run: composer install -n --ignore-platform-reqs
- run: npm install
- run: npm run production
- run: vendor/bin/phpunit
- run:
name: Start Chrome Driver
command: ./vendor/laravel/dusk/bin/chromedriver-linux
background: true
- run:
name: Run Laravel Server
command: php artisan serve
background: true
- run:
name: Run Laravel Dusk Tests
command: php artisan dusk
Codeship
If you want to run Dusk tests on Codeship, ou need to add the following commands to your Codeship project. These commands that are listed below are just a starting point and you have the freedom to add additional commands as needed:
phpenv local 7.2
cp .env.testing .env
mkdir -p ./bootstrap/cache
composer install --no-interaction --prefer-dist
php artisan key:generate
nohup bash -c "php artisan serve 2>&1 &" && sleep 5
php artisan dusk
Heroku CI
If you want to use Heroku CI to run Dusk tests, you need to add the following Google Chrome buildpack and scripts to your Heroku app.json file:
{
"environments": {
"test": {
"buildpacks": [
{ "url": "heroku/php" },
{ "url": "https://github.com/heroku/heroku-buildpack-google-chrome" }
],
"scripts": {
"test-setup": "cp .env.testing .env",
"test": "nohup bash -c './vendor/laravel/dusk/bin/chromedriver-linux > /dev/null 2>&1 &' && nohup bash -c 'php artisan serve > /dev/null 2>&1 &' && php artisan dusk"
}
}
}
}
Travis CI
If you want to run your Dusk tests on Travis CI, you should use the following .travis.yml configuration. Because Travis CI is not a graphical environment, you will need to take some extra steps in order to launch a Chrome browser. Additionally, you will use php artisan serve to launch PHP's built-in web server:
language: php
php:
- 7.3
addons:
chrome: stable
install:
- cp .env.testing .env
- travis_retry composer install --no-interaction --prefer-dist --no-suggest
- php artisan key:generate
before_script:
- google-chrome-stable --headless --disable-gpu --remote-debugging-port=9222 http://localhost &
- php artisan serve &
script:
- php artisan dusk
Adjust the value of APP_URL in your .env.testing file:
APP_URL=http://127.0.0.1:8000
Previous:
Laravel (5.7) Database Testing
Next:
Laravel (5.7) Mocking
It will be nice if you may share this link in any developer community or anywhere else, from where other developers may find this content. Thanks.
https://w3resource.com/laravel/browser-testing.php
- Weekly Trends and Language Statistics
- Weekly Trends and Language Statistics