Laravel Dusk Automation

Yesterday, I described getting started with Laravel Dusk, and finished off with the suggestion that one of my next steps would be to get Dusk running on Codeship. I thought that this might be relatively straightforward, but there were a few twists and turns before I got my tests to green.

First Attempt

The first thing to do was to set up a second pipeline on codeship where the browser tests could take place. I would also need to ensure that a local server would be running that the browser tests could connect to. So the first attempt at the pipeline looked like this:

nohup bash -c "php artisan serve --env=dusk.local 2>&1 &" && sleep 4
php artisan dusk

I am specifying the environnent file to be used to ensure consistency with the testing requests. Unfortunately, this did not get me to green.

The correct version of PHP

The first snag I hit was even getting the test process to run. The error I got was that I was using the wrong version of PHP. Codeship has pretty clear documentation on the various versions of PHP that are available, and how to use them. I already had

phpenv local 7.0

in place for the testing I was already doing, but for some reason the Dusk command was managing to pick up the default box version (5.5.9) rather than the shim that this command sets up. Honestly, I don’t actually know why /laravel/dusk/src/Console/DuskCommand was picking this up instead, but I did find the PHP_BINARY constant, which “specifies the PHP binary path during script execution” (http://php.net/manual/en/reserved.constants.php)

This seemed like the perfect solution to me, so I have submitted a pull request, and am just waiting for it to be merged in (I may have been a bit slapdash with my spacing the first time around).

update: this has now been merged into the master branch of Dusk (thanks Taylor) so I’ve removed the link to my fork.

Talking to Chrome

So having got past issues with the version of PHP, I then hit the next snag, which was that the request to chromedriver was simply timing out for each attempt to run the test:

Facebook\WebDriver\Exception\WebDriverCurlException: Curl error thrown for http POST to /session with params: {"desiredCapabilities":{"browserName":"chrome","platform":"ANY"}}
Operation timed out after 30001 milliseconds with 0 bytes received

I spent a fair bit of time wrestling with this, before hitting on the simple idea of just turning off the chromedriver startup in the Dusk process, and manually running it. My goal here was to have a look at what errors were being generated, but to my surprise, it actually started to work. I am not sure what the problem is with Dusk approach, as it doesn’t appear to be doing anything particularly fancy:

static::$chromeProcess = new Process('./chromedriver-linux', realpath(__DIR__.'/../bin'), ['DISPLAY' => ':0'], null, null);

I tried removing the DISPLAY argument, but that made no difference. But having got to a point of actually having tests working, I didn’t really want to spend much more time trying to resolve this.

Sundry Items

As part of my fiddling trying to get the chromedriver to work, I came across a couple of other tidbits that I put in place prior to getting everything working. The main thing was wanting to specify the location of the chrome browser, as Codeship doesn’t have it in the standard linux location. A suggestion that also came out of the research I did was to prevent Chrome running as though it were the first time (i.e. all the gubbins Chrome does to introduce itself to the user). To resolve these, I expanded the driver method of the base DuskTestCase class:

protected function driver()
{
$chromeOptions = new ChromeOptions();
$chromeOptions->setBinary('/usr/bin/chromium-browser');
$chromeOptions->addArguments(['no-first-run']);
$capabilities = DesiredCapabilities::chrome();
$capabilities->setCapability(ChromeOptions::CAPABILITY, $chromeOptions);

return RemoteWebDriver::create(
'http://localhost:9515',
$capabilities
);
}

Clean Running

These changes were all very well, but I didn’t want these changes to be having an impact on my local dev environment, where I already had the Dusk tests running nicely. So I decided to use some environment variables to control things a bit more cleanly.

First, I wanted to be able to configure whether Dusk would be in charge of running chromedriver or not:

public static function prepare()
{
if (env('DUSK_START_CHROMEDRIVER', true)) {
static::startChromeDriver();
}
}

And then I improved the prepare method:

protected function driver()
{
$chromeOptions = new ChromeOptions();
if ($binary = env('DUSK_CHROME_BINARY')) {
$chromeOptions->setBinary($binary);
}
$chromeOptions->addArguments(['no-first-run']);
$capabilities = DesiredCapabilities::chrome();
$capabilities->setCapability(ChromeOptions::CAPABILITY, $chromeOptions);

return RemoteWebDriver::create(
'http://localhost:9515',
$capabilities
);
}

Then I updated my Codeship test script to setup my enviroment correctly:

phpenv local 7.0
export DUSK_START_CHROMEDRIVER=false
export DUSK_CHROME_BINARY=/usr/bin/chromium-browser
nohup bash -c "./vendor/laravel/dusk/bin/chromedriver-linux 2>&1 &"
nohup bash -c "php artisan serve --env=dusk.local 2>&1 &" && sleep 4
php artisan dusk

And finally, I got everything to green:

The all important green ticks for my tests

Conclusion

In essence, setting this up was not all that complicated, it just took some time for me to work out what the actual problems were. Dusk has motivated me to get my browser testing automated, which is definitely a good thing (tm).

Now, I can finally start expanding my browser test suite with Dusk, and move my app forward again.