Browser test: use Laravel Dusk for browser test


brief introduction

Laravel Dusk provides an elegant, easy-to-use browser automated testing API. By default, Dusk does not force you to install JDK or Selenium on the machine. Instead, Dusk is based on independently installed ChromeDriver However, you can use any other Selenium compatible driver.

Note: Selenium It is a set of web application automatic test system, running Selenium test is just like you operate in the browser; WebDriver provides a more streamlined programming interface to solve some limitations in Selenium RC API. The goal of WebDriver is to provide a set of well-designed, object-oriented APIs to better support the testing of modern advanced Web applications. Selenium 2.0 has integrated the WebDriver API since its inception, but you can still use the WebDriver API independently. The ChromeDriver is maintained by the Chromium team and is provided and implemented for Chrome based browsers WebDriver wire protocol After installing the Dusk expansion pack, it will come with its own ChromeDriver, which does not need to be manually installed.

install

Before starting, you need to add Composer dependencies laravel/dusk To project:

 composer require --dev laravel/dusk:^2.0

After installing Dusk, you need to register the service provider Laravel\Dusk\DuskServiceProvider Usually, this will be done automatically through Larravel's automatic service provider registration service.

Note: If you manually register the Dusk service provider, never register it to the production environment, which will result in all users being able to log in to the application.

Next, run the Artisan command dusk:install

 php artisan dusk:install

After running the command tests Create a new one in the directory Browser Directory and contains a test sample. Next, in .env Setting environment variables in the file APP_URL , the variable value needs to match the URL you use to access the application in the browser.

To run a test, use the Artisan command dusk dusk Command receiving any phpunit Supported parameters:

 php artisan dusk

Use another browser

By default, Dusk uses Google Chrome and separately installed ChromeDriver To run the browser test (included in the Dusk expansion pack), however, you can also start your Selenium server and test it in the browser.

Before you start, open tests/DuskTestCase.php File, which is the base class of the Dusk test case of the application. In this file, you can remove startChromeDriver Method to prevent Dusk from automatically starting the ChromeDriver:

 /** * Prepare for Dusk test execution. * * @beforeClass * @return void */ public static function prepare() { // static::startChromeDriver(); }

Next, you can edit driver Method to connect to the URL and interface you select. In addition, you can edit the "desired function" that needs to be passed to WebDriver:

 /** *Create a RemoteWebDriver instance * * @return \Facebook\WebDriver\Remote\RemoteWebDriver */ protected function driver() { return RemoteWebDriver::create( ' http://localhost:4444/wd/hub ', DesiredCapabilities::phantomjs() ); }

quick get start

Generate Test

To generate a Dusk test, use the Artisan command dusk:make , the generated test is located in tests/Browser catalog:

 php artisan dusk:make LoginTest

Run Test

To run a browser test, use the Artisan command dusk

 php artisan dusk

dusk The command receives any parameter supported by PHPUnit, so you can give group Run test:

 php artisan dusk --group=foo

Manually start ChromeDriver

By default, Dusk will automatically try to start the ChromeDriver. If it does not work on your system, you can run dusk Start the ChromeDriver manually before the command. If you choose to manually start the ChromeDriver, you need to comment out tests/DuskTestCase.php The following line of code in the file:

 /** * Prepare for Dusk test execution. * * @beforeClass * @return void */ public static function prepare() { // static::startChromeDriver(); }

In addition, if you do not start the ChromeDriver on port 9515, you need to edit it in the same class driver method:

 /** * Create the RemoteWebDriver instance. * * @return \Facebook\WebDriver\Remote\RemoteWebDriver */ protected function driver() { return RemoteWebDriver::create( ' http://localhost:9515 ', DesiredCapabilities::chrome() ); }

Environmental treatment

To force Dusk to use its own environment file when running tests, you can create a .env.dusk. {environment} Files, for example, if you are local Start in environment dusk Command, need to create .env.dusk.local File.

When running the test, Dusk will back up .env File and rename the Dusk environment file to .env Once the test is completed, .env The file will be restored.

Create Browser

As a starting point, we write a test to verify that you can log in to the application. After generating the test, edit the test class to navigate it to the login page, enter the authentication information, and click the login button. To create a browser instance, call browse method:

 <? php namespace Tests\Browser; use App\User; use Tests\DuskTestCase; use Laravel\Dusk\Chrome; use Illuminate\Foundation\Testing\DatabaseMigrations; class LoginTest 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', 'secret') ->press('Login') ->assertPathIs('/home'); }); } }

As you can see in the above example, browse Method receives a callback, and the browser instance will automatically pass it to the callback through Dusk, interact with the application as the main object, and generate assertions.

After writing the test case, we run php artisan dusk Perform test:

Note: This test can be used to test whether Artisan commands are passed make:auth Generated landing page.

Create Multiple Browsers

Sometimes you need more than one browser to complete the test. For example, you need more than one browser when testing a chat room page that interacts through a Web socket. To create multiple browsers, simply call browse When the method is used, specify the required browser in the callback ID:

 $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 ',' Hello College ') ->press('Send'); $first ->waitForText ('Hello College ') ->AssertSee ('Academician '); });

Resize browser window

You can use resize Method to resize the browser window:

 $browser->resize(1920, 1080);

maximize Method can be used to maximize the browser window:

 $browser->maximize();

authentication

We often test the pages that need authentication. You can use Dusk's loginAs Method is used to avoid interaction with the login page in each test, loginAs Method receives user ID or user model instance:

 $this->browse(function ($first, $second) { $first->loginAs(User::find(1)) ->visit('/home'); });
Note: Use loginAs Method, the user session is maintained by all tests in a file.

Database migration

When migration is required for testing, such as the above authentication example, do not use RefreshDatabase trait, RefreshDatabase Traits will affect database transactions that cannot be applied across requests. Instead, we use 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; }

Interact with elements

Dusk selector

One of the most difficult parts of writing a Dusk test is to select a good CSS selector for element interaction. Over time, changes in the front end may cause the CSS selector to interrupt the test as follows:

 // HTML... <button>Login</button> // Test... $browser->click('.login-page .container div > button');

Dusk selectors allow you to focus on writing efficient tests rather than memorizing CSS selectors. To define a selector, add dusk Attribute to the HTML element, and then, by adding @ Prefix operates elements in Dusk test:

 // HTML... <button dusk="login-button">Login</button> // Test... $browser->click('@login-button');

Click the link

To click the link, you can use the clickLink method, clickLink The method will click the link containing the given text:

 $browser->clickLink($linkText);
Note: This method interacts through jQuery. If jQuery is invalid on the page, Dusk will automatically inject it into the page to take effect during the test.

Text, Value&Attributes

Get&Set Value

Dusk provides several methods to interact with the currently displayed text, values and attributes of page elements. For example, to obtain the element value matching a given selector, use value method:

 //Get Values $value = $browser->value('selector'); //Set Values $browser->value('selector', 'value');

Get Text

text Method can be used to obtain the element display text that matches the given selector:

 $text = $browser->text('selector');

get attribute

last, attribute Method can be used to get the element attributes that match the given selector:

 $attribute = $browser->attribute('selector', 'value');

Using Forms

Input value

Dusk provides several methods to interact with forms and input elements. First, let's look at an example of entering text into an input field:

 $browser->type('email', ' xueyuanjun@laravelacademy.org ');

Note that although type Methods can receive when needed, but we don't have to pass CSS selectors to this method. If no CSS selector is provided, Dusk will search for name The input field of the attribute. Finally, Dusk will try to find name Attribute textarea

To append text to a field without clearing its value, use the append method:

 $browser->type('tags', 'foo') ->append('tags', ', bar, baz');

You can use clear The value entered by the method Clear:

 $browser->clear('email');

Drop down box

To select a value in the drop-down selection box, use the select method. and type In the same way, select Method does not require a complete CSS selector. When the value is passed to select Method, you need to pass the underlying option value instead of displaying the text:

 $browser->select('size', 'Large');

You can select a random option by omitting the second parameter:

 $browser->select('size');

check box

To "select" the check field, you can use the check Method. Like other input related methods, it is not necessary to pass in a complete CSS selector. If an exact matching selector cannot be found, Dusk will match it through a name Property to search the check box:

 $browser->check('terms'); $browser->uncheck('terms');

radio button

To "select" a radio button option, use the radio Method, like other input related methods, does not need a complete CSS selector. If an exact matching selector cannot be found, Dusk will search for a match name and value Attribute radio box:

 $browser->radio('version', 'php7');

Upload file

attach Method can be used to upload files to file The input element, like other input related methods, does not need a complete CSS selector. If the exact matching selector cannot be found, Dusk will pass name Attribute search matching file input:

 $browser->attach('photo', __DIR__.'/photos/me.png');

Using the keyboard

keys The method allows you to provide more than type Methods more complex input sequences to a given element. For example, you can save edit keys and corresponding input values. In this example, shift The key will be stored taylor Is entered into an element that matches the given selector. input taylor After, otwell It will be input without any edit key:

 $browser->keys('selector', ['{shift}', 'taylor'], 'otwell');

You can even send "hotkeys" to the main CSS selector containing the application:

 $browser->keys('.app', ['{command}', 'j']);
Note: All edit keys are wrapped in {} And match the definitions in Facebook\WebDriver\WebDriverKeys Constants in the class, these constants Can be found on GitHub

Using the mouse

Click Element

click Method can be used to click an element that matches a given selector:

 $browser->click('.selector');

Mouse over

mouseover Method can be used when you hover over an element that matches a given selector:

 $browser->mouseover('.selector');

Drag

drag Method can be used to drag and drop an element that matches a given selector to another element:

 $browser->drag('.from-selector', '.to-selector');

Alternatively, you can drag and drop elements in a single direction:

 $browser->dragLeft('.selector', 10); $browser->dragRight('.selector', 10); $browser->dragUp('.selector', 10); $browser->dragDown('.selector', 10);

Selector Scope

Sometimes you may only want to perform a series of operations on a given selector. For example, you may want to assert whether the text exists in a table and then click the button in the same table. You can use with Method to complete this function, pass the with All operations executed in the callback of the method are restricted to the given selector:

 $browser->with('.table', function ($table) { $table->assertSee('Hello World') ->clickLink('Delete'); });

Waiting element

When testing widely used JavaScript applications, you often need to wait for specific elements or data to be loaded before testing. Dusk makes this test easy. By using various methods, you can wait on the page for elements to become accessible, even until the given JavaScript expression value is true

Pause Test

If you need to pause the test for a specified number of milliseconds, you can use pause method:

 $browser->pause(1000);

Waiting selector

waitFor Method can be used to pause test execution until elements matching a given CSS selector are displayed on the page. By default, this will pause the test for up to 5 seconds before throwing an exception. If necessary, you can pass a custom timeout as the second parameter of this method:

 //Wait for selector for 5s at most $browser->waitFor('.selector'); //Wait for selector 1s at most $browser->waitFor('.selector', 1);

You can also wait until the given selector disappears on the page:

 $browser->waitUntilMissing('.selector'); $browser->waitUntilMissing('.selector', 1);

Selector scope (if valid)

In some cases, you may want to wait for a given selector and then interact with elements that match a given selector. For example, you may want to wait until the modal window is valid and then press the "OK" button in that window. whenAvailable The method can be used in such cases. All element operations in a given callback are restricted to a specific selector:

 $browser->whenAvailable('.modal', function ($modal) { $modal->assertSee('Hello World') ->press('OK'); });

Waiting for text

waitForText Method can be used to wait until the given text is displayed on the page:

 //Wait for text for 5s at most $browser->waitForText('Hello World'); //Wait for text 1s at most $browser->waitForText('Hello World', 1);

Waiting for link

waitForLink Method can be used to wait until the given link text is displayed on the page:

 //Wait up to 5s for the link $browser->waitForLink('Create'); //Wait up to 1s for the link $browser->waitForLink('Create', 1);

Waiting for the page to load

When making page assertions such as $browser->assertPathIs('/home') If window.location.pathname If it is updated asynchronously, the assertion will fail. You can use waitForLocation Method Wait location Become a given value:

 $browser->waitForLocation('/secret');

Waiting for page reload

If you need to assert after page reload, you can use waitForReload method:

 $browser->click('.some-action') ->waitForReload() ->assertSee('something');

Wait for JavaScript expression

Sometimes you may want to test temporarily until the JavaScript expression value is true You can simply waitUntil Method to complete this function. When the expression is paused, it is not necessary to include return Keyword or ending semicolon:

 // 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 with callback

Many "wait" methods in Dusk depend on the underlying waitUsing method. You can directly use this method to wait for the return of a given callback true waitUsing The method receives the maximum number of seconds to wait, the time interval for closure execution, closures, and optional failure information:

 $browser->waitUsing(10, 1, function () use ($something) { return $something->isReady(); }, "Something wasn't ready in time.");

Vue Assertions

Dusk even allows you to assert the status of Vue component data. For example, suppose your application contains the following Vue components:

 // HTML... <profile dusk="profile-component"></profile> // Component Definition... Vue.component('profile', { template: '<div>{{ user.name }}</div>', data: function () { return { user: { Name: 'Scholar' } }; } });

You can assert Vue component status like this:

 /** * A basic Vue test example. * * @return void */ public function testVue() { $this->browse(function (Browser $browser) { $browser->visit('/') ->AssertVue ('user. name ',' Scholar ',' @ profile component '); }); }

Valid Assertion Methods

Dusk provides a variety of assertions for application testing. All valid assertions are listed below:

Assertions describe
$browser->assertTitle($title) Asserting that the page title matches the given text
$browser->assertTitleContains($title) Asserting that the page title contains the given text
$browser->assertUrlIs($url) Asserting that the current URL (excluding the query string) matches the given string
$browser->assertPathBeginsWith($path) Asserting that the current path starts with the given path
$browser->assertPathIs('/home') Asserting that the current path matches the given path
$browser->assertPathIsNot('/home') Asserting that the current path does not match the given path
$browser->assertRouteIs($name, $parameters) Asserting that the current URL matches the given named route URL
$browser->assertQueryStringHas($name, $value) Asserting that the given query string parameter exists and contains the given value
$browser->assertQueryStringMissing($name) Asserting that the given query string parameter is missing
$browser->assertHasQueryStringParameter($name) Asserting that the given query string exists
$browser->assertHasCookie($name) Asserting that the given cookie exists
$browser->assertCookieMissing($name) Asserting that the given cookie does not exist
$browser->assertCookieValue($name, $value) Asserting that the cookie contains the given value
$browser->assertPlainCookieValue($name, $value) Asserting that an unencrypted cookie contains the given value
$browser->assertSee($text) Asserting that the given text exists on the page
$browser->assertDontSee($text) Asserting that the given text does not exist on the page
$browser->assertSeeIn($selector, $text) Asserting that the given text exists in the specified selector
$browser->assertDontSeeIn($selector, $text) Asserting that the given text does not exist in the specified selector
$browser->assertSourceHas($code) Asserting that the given source code exists in the page
$browser->assertSourceMissing($code) Asserting that the given source code does not exist in the page
$browser->assertSeeLink($linkText) Asserting that the given link exists in the page
$browser->assertDontSeeLink($linkText) Asserting that the given link does not exist in the page
$browser->assertInputValue($field, $value) Asserting that the given input field contains the given value
$browser->assertInputValueIsNot($field, $value) Asserting that the given input field does not contain the given value
$browser->assertChecked($field) Assertion given check box is selected
$browser->assertNotChecked($field) Assert that the given check box is not selected
$browser->assertRadioSelected($field, $value) Asserting that the given radio box is selected
$browser->assertRadioNotSelected($field, $value) Asserting that the given radio box is not selected
$browser->assertSelected($field, $value) Asserting that the given drop-down box contains the given selection value
$browser->assertNotSelected($field, $value) Asserting that the given drop-down box does not contain the given selection value
$browser->assertSelectHasOptions($field, $values) Asserting that the given numeric array can be selected
$browser->assertSelectMissingOptions($field, $values) Asserting that the given numeric array cannot be selected
$browser->assertSelectHasOption($field, $value) Asserting that the given value can be selected
$browser->assertValue($selector, $value) Asserting that the element matching the given selector contains the given value
$browser->assertVisible($selector) Asserting that elements matching a given selector are visible
$browser->assertMissing($selector) Asserting that elements matching the given selector are not visible
$browser->assertDialogOpened($message) The JavaScript dialog box for asserting the given string has been opened
$browser->assertVue($property, $value, $component) Asserting that the given Vue component data matches the given value
$browser->assertVueIsNot($property, $value, $component) Asserting that the given Vue component data does not match the given value

page

Sometimes, the test requires multiple complex actions to be executed in a sequence, which makes the test code difficult to read and understand. The page allows you to use a single method to define elegant actions that can be performed on a given page. The page also allows you to define shortcuts to general selectors that point to applications or a single page.

Generate Page

To generate a page object, use the Artisan command dusk:page OK. All page objects are located in tests/Browser/Pages catalog:

 php artisan dusk:page Login

Configuration page

By default, the page has three methods: url assert and elements , let's discuss url and assert , as for elements , we will Detailed discussion in selector shorthand

Url method

url Method will return the URL path representing the page. Dusk will use this URL when navigating to the page in the browser:

 /** * Get the URL for the page. * * @return string */ public function url() { return '/login'; }

Assert method

assert Method will generate necessary assertions to verify that the browser is on a given page. It is not necessary to complete this method; However, you can generate these assertions when needed. These assertions will run automatically when you navigate to the page:

 /** * Assert that the browser is on the page. * * @return void */ public function assert(Browser $browser) { $browser->assertPathIs($this->url()); }

Navigate to page

After the page is configured, you can use the visit Method to navigate to the page:

 use Tests\Browser\Pages\Login; $browser->visit(new Login);

Sometimes, you may already be on a given page and need to "load" the page selector and method to the current test context, which is very common when clicking a button and redirecting to a given page. In this example, we can use the on method:

 use Tests\Browser\Pages\CreatePlaylist; $browser->visit('/dashboard') ->clickLink('Create Playlist') ->on(new CreatePlaylist) ->assertSee('@create');

Shorthand selector

Of the page elements Method allows you to define a quick and easy to remember shortcut for the page's CSS selector. For example, let's define a shortcut for the "email" input of the application login page:

 /** * Get the element shortcuts for the page. * * @return array */ public function elements() { return [ '@email' => 'input[name=email]', ]; }

Now, you can use shorthand selectors anywhere you want to try out a full CSS selector:

 $browser->type('@email', ' xueyuanjun@laravelacademy.org ');

Global shorthand selector

After installing Dusk, a basic Page Class will be generated to tests/Browser/Pages Directory. This class contains a siteElement Method. The so-called global shorthand selector refers to the shorthand selector that takes effect in the entire application:

 /** * Get the global element shortcuts for the site. * * @return array */ public static function siteElements() { return [ '@element' => '#selector', ]; }

Page method

In addition to the default methods defined by the page, you can also define additional methods for testing. For example, if we are building a music management application, the common function of the application page is to create a playlist. We don't need to repeat the logic of creating playlists every time we test, just define a createPlaylist Method:

 <? 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'); } }

After the method is defined, it can be used in any test using the page, and the browser instance will be automatically passed to the page method:

 use Tests\Browser\Pages\Dashboard; $browser->visit(new Dashboard) ->createPlaylist('My Playlist') ->assertSee('My Playlist');

assembly

The component is similar to Dusk's "page object", but it is used as a reusable UI and functional fragment in the whole application, such as navigation bar or notification window. Therefore, the component is not bound to a specific URL.

Build Component

To generate components, use the Artisan command dusk:component , the newly generated component is located in test/Browser/Components Under the directory:

 php artisan dusk:component DatePicker

As shown above, the Date Selector is an example component that can be effective on different pages of the entire application. It would be unwieldy to manually write browser automatic test logic to select the date in each test of the test suite. Therefore, we can define a Dusk component to represent the date selector, so that we can implement the corresponding business logic in a single reusable 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  $year * @return void */ public function selectDate($browser, $month, $year) { $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

After defining the component, you can easily select the date in any test through the date selector. Another advantage of using the component is that if the necessary logic for selecting the date has changed, just 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

Travis CI

To pass Travis CI To run the Dusk test, you need to have sudo Permission. Since Travis CI is not a graphical environment, we need some additional steps to start Chrome browser. In addition, we will use php artisan serve To start PHP's own Web server:
 sudo: required dist: trusty addons: chrome: stable install: - cp .env.testing .env - travis_retry composer install --no-interaction --prefer-dist --no-suggest before_script: - google-chrome-stable --headless --disable-gpu --remote-debugging-port=9222  http://localhost  & - php artisan serve & script: - php artisan dusk

Circle CI

CircleCI 1.0

If you want to pass Circle CI To run the Dusk test, you can use this configuration file as a starting point. Like Travis CI, we use php artisan serve The command starts the Web server that comes with PHP:

 dependencies: pre: - curl -L -o google-chrome.deb  https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb - sudo dpkg -i google-chrome.deb - sudo sed -i 's|HERE/chrome\"|HERE/chrome\" --disable-setuid-sandbox|g' /opt/google/chrome/google-chrome - rm google-chrome.deb test: pre: - "./vendor/laravel/dusk/bin/chromedriver-linux": background: true - cp .env.testing .env - "php artisan serve": background: true override: - php artisan dusk

CircleCI 2.0

If you use CircleCI 2.0, you can click build Add the following steps to the section:

 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

Want to Codeship Run the Dusk test in and add the following commands to the Codeship project. Of course, these commands are a starting example. If necessary, you can add more additional commands:

 phpenv local 7.1 cp .env.testing .env composer install --no-interaction nohup bash -c "./ vendor/laravel/dusk/bin/chromedriver-linux 2>&1 &" nohup bash -c "php artisan serve 2>&1 &" && sleep 5 php artisan dusk

give the thumbs-up Cancel Like Collection Cancel Collection

<<Previous: HTTP test: how to test HTTP requests and responses

>>Next: Database testing: model factory generation and use