7 steps to running Laravel Dusk 3.0 tests in Docker environments

Jake Harris
6 min readMar 16, 2018

If you’ve followed the excellent Shipping Docker series by the wonderful Chris Fidao of Servers For Hackers fame you may have noticed he skipped setting up Laravel Dusk tests during the testing portions of the series.

This post will detail how to get set up running headless Dusk tests in the environment detailed in the Shipping Docker course. It may well work using Vessel as well though I haven’t tested, nor do I know if these steps are even necessary using that tool.

There are a few caveats listed towards the end of this article, but with a few minor modifications, we’ll be able to run a Laravel Dusk test suite in headless ChromeDriver mode via Docker containers.

We’ll be elaborating on a few guides, which you can find linked at the end of the article. Let’s get started!

Step 1: add a Selenium container to docker-compose.yml

Add the following entry entry to the services section in your docker-compose.yml file:

selenium:
image: selenium/standalone-chrome:3.11.0-antimony
depends_on:
- app
links:
- app:sdnet.test
volumes:
- /dev/shm:/dev/shm
networks:
- sdnet

If you’ve yet to dive too deeply into the Docker ecosystem as I have, it may be helpful to break this down in detail. If you’re a battle-scarred container veteran, feel free to skip to the next section.

selenium:
image: selenium/standalone-chrome:3.11.0-antimony

This names the service into which we’re going to build Selenium and tells Docker to use the selenium/standalone-chrome image. This will pull in everything you need to get a Selenium grid and hub up and running, as well as all the necessary tooling for ChromeDriver.

I’ve opted to pin the Dockerfile to the 3.11.0-antimony tag here as that’s the most up-to-date at the time I write this. You may want to use a more recent tag if you’re reading this in the future, or even use latest if you’re feeling frisky.

depends_on:
- app
links:
- app:sdnet.test

This tells Docker we’re dependent upon our app image. If you renamed your main application container (the one running NGINX & PHP-FPM) you’ll need to change app to whatever name you used. We’ll cover the links section later on, but this will help our Selenium container access our application across the Docker network.

volumes:
- /dev/shm:/dev/shm

From the docker-selenium README:

❗️ When executing docker run for an image with Chrome or Firefox please either mount -v /dev/shm:/dev/shm or use the flag --shm-size=2g to use the host's shared memory.

You can read more about why this is necessary here.

networks:
- sdnet

This adds our new container into the Docker network, accessible by the service name (selenium). If you renamed the network from sdnet, make sure you use the correct network name here.

Step 3: Add Dusk dependencies to the app container

If you haven’t already, you’ll need to add php-zip to the list of installed packages. This php extension is a dependency of later versions of facebook/webdriver which is itself a nested Dusk dependency. Without the php-zip extension installed, Composer took the liberty of installing an ancient version (~1.2) of the WebDriver library, and I was getting the following error message:

Error: Call to undefined method Facebook\Webdriver\Remote\RemoteWebDriver::getCapabilities()

If you’ve installed and tried to run Dusk and encountered this message, it’s likely your facebook/webdriver needs an update. Add php-zip to your apt-get install line in the app Dockerfile, rebuild and then run composer update facebook/webdriver. You’ll need >= 1.3 as per an older Dusk issue on Github. At the time of writing, it’s up to 1.5.

Step 2: Install Dusk

If you haven’t yet, follow the official docs to install Dusk. Follow the guide up to the “Using Other Browsers” section. This guide assumes we’re modifying a “stock” Dusk installation.

Note: At the time of writing, the latest Laravel version is 5.6 and Dusk is 3.0.

Step 3: Configure ChromeDriver

I’ve found the best results with this combination of options set in the tests/DuskTestCase::driver() method. It’s admittedly just a Frankensteinian mish-mash of found answers from random posts and message boards, so I won’t pretend to be a ChromeDriver expert. Feel free to add or remove options as you see fit.

$options = (new ChromeOptions)->addArguments([
'--disable-gpu',
'--headless',
'--no-sandbox',
'--ignore-ssl-errors',
'--whitelisted-ips=""'
]);

Step 4: Configure Selenium

Now we need to tell Dusk to use Selenium. We’ll do this in a way that should future-proof ourselves should we ever go back to the cave-drawing past of Vagrant, Homestead or Valet. Also, for project collaborators, it won’t force them to use docker to run our test suite.

First, replace the RemoteWebDriver::create() section with the following code:

if (env('USE_SELENIUM', 'false') == 'true') {
return RemoteWebDriver::create(
'http://selenium:4444/wd/hub', DesiredCapabilities::chrome()->setCapability(
ChromeOptions::CAPABILITY, $options
));
} else {
return RemoteWebDriver::create(
'http://localhost:9515', DesiredCapabilities::chrome()->setCapability(
ChromeOptions::CAPABILITY, $options
));
}

Notice we added a new env variable. We can add this value to our .dusk.env.local. Create this file if you don’t have one already, and add the variable like so:

USE_SELENIUM=TRUE

Step 5: Set the APPLICATION_URL env variable

We’ll now we need to tell Selenium/ChromeDriver how to find our application. When I initially tried to use app (the container alias in the Docker network) and localhost as the app’s url, Dusk was only able to find empty pages, and couldn’t seem to locate the application. Assigning a DNS-name and an explicit link to the app running in the container was how I found my way around this.

Speaking of — remember the link we added to our docker-compose.yml file?

depends_on:
- app
links:
- app:sdnet.test

Notice the binding or destination is to the app container, and the domain is sdnet.test. If you’ve renamed your app container, you’ll need to update this section, and of course you’re free to use any URL you wish, though it’ll never be seen so the value is pretty irrelevant. Just make sure you don’t use a .dev domain!

Now we just need to update our.dusk.env.local file to match:

APP_URL=http://sdnet.test

Okay, that’s all the set-up we need to do. We’re just about done. Now that we’re all configured and installed, it’s time to re-build the containers and run our tests.

Step 6: Rebuild your containers

Now that we’ve added Selenium to our docker-compose.yml file, you’ll need to rebuild your docker images if you haven’t yet. From the directory where docker-compose.yml lives:

➜ ./develop down # if your containers are running this will shut them down
➜ ./develop build # this should force rebuild your images

Again, I’m no Docker pro, so if there’s a more comprehensive way you know of, have at it.

Note: the ./develop script is a utility covered in Shipping Docker and emulated in Vessel. It simply exports a few environment variables and passes commands to various utilities used to control and utilize the Docker container. In this case, it's a pass-through for docker-compose but in further examples from this article, we’ll use the art alias to use Dusk, which runs php artisan {COMMAND} within the Docker container.

Step 7: Run the Dusk suite

Dusk comes with a single example test out of the box which visits the root path (/)of your app and looks for the word “Laravel” on the screen. If you’re testing an existing app, chances are you’ve modified the default root path. Make sure you update that test to search for a string on your root path.

➜ ./develop up -d
Creating network "monolith_sdnet" with driver "bridge"
Creating monolith_node_1 ... done
Creating monolith_app_1 ... done
Creating monolith_selenium_1 ... done
Creating monolith_mysql_1 ... done
➜ ./develop art duskPHPUnit 7.0.2 by Sebastian Bergmann and contributors.. 1 / 1 (100%)Time: 9.2 seconds, Memory: 14.00MBOK (1 test, 1 assertion)

Note: I have yet to figure out how to get the tests to run without the environment running (./develop up -d) so this solution isn’t without it’s quirks and isn’t quite CI (Continuous Integration) friendly yet.

Summary

With a few minor, and flexible modifications, we were able to get Laravel Dusk tests running locally in the Shipping Docker-driven local development environments. With some further tweaks, this system should be able to accommodate more sophisticated CI workflows. When I manage to get that working, I’ll share a similar article.

--

--