Zaher Ghaibeh
PHP Backend developer
I've experience in a few PHP Frameworks, such as Laravel, Lumen and Slim (The last two are used for building Microservices/API services).
Execute your Laravel Dusk tests on Docker HUB CI - Part 1
Published at Thursday, January 10, 2019 , Categorized under: php, laravel, development, testing, docker

Running your tests before merging or even building Docker images are not complicated any more, we have many companies providing you with a CI service that you can use, but am not going to talk about them all, I am just going to talk about Docker Hub.

Recently, Docker the company, merged Docker Hub and Docker Cloud, they used to be a different product with a different set of features. Also, I've talked in one of my previous posts how you can test your Laravel application on Docker Hub on the same day they have announced the change.

In my post today I'll talk about how you can run your Laravel Dusk tests on Docker Hub moreover, I'll explain one of two different way: a simple way, and an advanced way.

What is Laravel Dusk

From the documentation:

Laravel Dusk provides an expressive, easy-to-use browser automation and testing API. By default, Dusk does not require you to install JDK or Selenium on your machine. Instead, Dusk uses a standalone ChromeDriver installation. However, you are free to utilize any other Selenium compatible driver you wish.

So basically it will help you testing your UI using ChromeDriver:

WebDriver is an open source tool for automated testing of webapps across many browsers. It provides capabilities for navigating to web pages, user input, JavaScript execution, and more. ChromeDriver is a standalone server which implements WebDriver's wire protocol for Chromium.

While you are running your Laravel Dusk tests, you'll see a browser opened automatically and closed automatically, and this won't work on the CI out of the box.

To make the story short, I built a new Docker image based on my older Docker image which was intended to work with Bitbucket Pipelines, meanwhile the new image should work everywhere.

Requirements

I'll assume that you already familiar with Docker plus you have an account with Docker Hub also, you know the basics of Linux (for sure).

Also, I'll assume that you already have a Laravel application with some tests written for Laravel Dusk to execute.

Dockerizing your Application

As everyone knows, there are many ways to Dockerize your application, and I'll use a simple structure here, maybe we can enhance it later.

Firstly, We need to create two new files Dockerfile and Dockerfiles.testing:

├── Dockerfile
└── Dockerfile.testing

Where Dockerfile is used for production, while Dockerfile.testing is used to test our code. We can have as much files as we want, all we need is to specify the correct file when building our image.

Second, We also can create a new bin directory which contains only one file, run_tests which we use to execute everything we need to test our code

bin
└── run_tests

Finally, We create a new file called docker-compose.test.yml which contains a small instruction for Docker Hub CI to run our tests.

Now let's dive into each file content.

Dockerfile content

The content of this file like any regular file, it is different from company to company, but in general, I've created a simple one to initiate the build on Docker Hub.


FROM zaherg/php-and-nginx:latest

ENV APP_NAME ${APP_NAME:-laravel} \
    APP_ENV ${APP_ENV:-production} \
    APP_KEY ${APP_KEY} \
    APP_DEBUG ${APP_DEBUG:-false} \
    APP_URL ${APP_URL:-"http://localhost"} \
    LOG_CHANNEL ${LOG_CHANNEL:-stack} \
    APP_TIMEZONE ${APP_TIMEZONE:-UTC} \
    DB_CONNECTION ${DB_CONNECTION:-mysql} \
    DB_HOST ${DB_HOST:-database} \
    DB_PORT ${DB_PORT:-3306} \
    DB_DATABASE ${DB_DATABASE:-docker} \
    DB_USERNAME ${DB_USERNAME:-docker} \
    DB_PASSWORD ${DB_PASSWORD:-secret} \
    DB_COLLATION ${DB_COLLATION:-utf8mb4_unicode_ci} \
    DB_CHARSET ${DB_CHARSET:-utf8mb4} \
    BROADCAST_DRIVER ${BROADCAST_DRIVER:-log} \
    CACHE_DRIVER ${CACHE_DRIVER:-file} \
    QUEUE_CONNECTION ${QUEUE_CONNECTION:-sync} \
    SESSION_DRIVER ${SESSION_DRIVER:-file} \
    SESSION_LIFETIME ${SESSION_LIFETIME:-120} \
    REDIS_HOST ${REDIS_HOST:-"127.0.0.1"} \
    REDIS_PORT ${REDIS_PORT:-6379} \
    REDIS_PASSWORD ${REDIS_PASSWORD:-null} \
    MAIL_DRIVER ${MAIL_DRIVER:-smtp} \
    MAIL_HOST ${MAIL_DRIVER:-smtp.mailtrap.io} \
    MAIL_PORT ${MAIL_PORT:-2525} \
    MAIL_USERNAME ${MAIL_USERNAME:-null} \
    MAIL_PASSWORD ${MAIL_PASSWORD:-null} \
    MAIL_ENCRYPTION ${MAIL_ENCRYPTION:-null} \
    PUSHER_APP_ID ${PUSHER_APP_ID} \
    PUSHER_APP_KEY ${PUSHER_APP_KEY} \
    PUSHER_APP_SECRET ${PUSHER_APP_SECRET} \
    PUSHER_APP_CLUSTER ${PUSHER_APP_CLUSTER} \
    MIX_PUSHER_APP_KEY ${PUSHER_APP_KEY:-""} \
    MIX_PUSHER_APP_CLUSTER ${PUSHER_APP_CLUSTER:-""}

USER root

ADD ./ /var/www

WORKDIR /var/www

RUN composer global require hirak/prestissimo && \
    composer install --no-progress --no-suggest --prefer-dist --optimize-autoloader --no-dev

Basically, we define some environments, add our files to the image, and then run composer install.

Dockerfile.testing content

The content of this file is the same as the first one, except it is going to install the development dependency packages, and it is going to use zaherg/laravel-dusk image that I've built as the base image.

FROM zaherg/laravel-dusk:7.2

LABEL Maintainer="Zaher Ghaibeh <z@zah.me>"

ENV APP_NAME ${APP_NAME:-laravel} \
    APP_ENV ${APP_ENV:-local} \
    APP_KEY ${APP_KEY} \
    APP_DEBUG ${APP_DEBUG:-false} \
    APP_URL ${APP_URL:-"http://localhost"} \
    LOG_CHANNEL ${LOG_CHANNEL:-stack} \
    APP_TIMEZONE ${APP_TIMEZONE:-UTC} \
    DB_CONNECTION ${DB_CONNECTION:-mysql} \
    DB_HOST ${DB_HOST:-database} \
    DB_PORT ${DB_PORT:-3306} \
    DB_DATABASE ${DB_DATABASE:-docker} \
    DB_USERNAME ${DB_USERNAME:-docker} \
    DB_PASSWORD ${DB_PASSWORD:-secret} \
    DB_COLLATION ${DB_COLLATION:-utf8mb4_unicode_ci} \
    DB_CHARSET ${DB_CHARSET:-utf8mb4} \
    BROADCAST_DRIVER ${BROADCAST_DRIVER:-log} \
    CACHE_DRIVER ${CACHE_DRIVER:-file} \
    QUEUE_CONNECTION ${QUEUE_CONNECTION:-sync} \
    SESSION_DRIVER ${SESSION_DRIVER:-file} \
    SESSION_LIFETIME ${SESSION_LIFETIME:-120} \
    REDIS_HOST ${REDIS_HOST:-"127.0.0.1"} \
    REDIS_PORT ${REDIS_PORT:-6379} \
    REDIS_PASSWORD ${REDIS_PASSWORD:-null} \
    MAIL_DRIVER ${MAIL_DRIVER:-smtp} \
    MAIL_HOST ${MAIL_DRIVER:-smtp.mailtrap.io} \
    MAIL_PORT ${MAIL_PORT:-2525} \
    MAIL_USERNAME ${MAIL_USERNAME:-null} \
    MAIL_PASSWORD ${MAIL_PASSWORD:-null} \
    MAIL_ENCRYPTION ${MAIL_ENCRYPTION:-null} \
    PUSHER_APP_ID ${PUSHER_APP_ID} \
    PUSHER_APP_KEY ${PUSHER_APP_KEY} \
    PUSHER_APP_SECRET ${PUSHER_APP_SECRET} \
    PUSHER_APP_CLUSTER ${PUSHER_APP_CLUSTER} \
    MIX_PUSHER_APP_KEY ${PUSHER_APP_KEY:-""} \
    MIX_PUSHER_APP_CLUSTER ${PUSHER_APP_CLUSTER:-""}

USER root

ADD ./ /var/www
ADD ./bin/run_tests /var/test/run_tests

WORKDIR /var/www

RUN composer global require hirak/prestissimo \
    && cp .env.example .env \
    && composer install --no-progress --no-suggest --prefer-dist --optimize-autoloader \
    && php artisan key:generate

The content is similar small changes as we create a new .env file from .env.example and generate a new APP_KEY, also, we move bin/run_tests file to /var/test/run_tests.

bin/run_tests content

This file is a simple shell file, which contains a few lines that we need our test image to execute

#!/usr/bin/env sh

set -e

echo "Running tests"

php vendor/bin/phpunit --testdox --colors=never

php artisan serve &

php artisan dusk

We run the following steps:

  1. PHPUnit.
  2. Run artisan serve.
  3. Run our dusk tests.

Let's see the content of our last file docker-compose.test.yml

docker-compose.test.yml content

You should be familiar with the content of this file from my previous post, but just in case you forget about it, this is a regular docker-compose file which Docker Hub CI will run, and it'll only execute the content of the sut service.

version: "3.6"

services:
  sut:
    command: /var/test/run_tests
    build:
      context: ./
      dockerfile: Dockerfile.testing
    environment:
      - "APP_URL=http://127.0.0.1:8000"

As you notice, we only overwrite the APP_URL environment value, since artisan serve will run on port 8000.

Final Step

Before we commit our code to git and let Docker Hub pickup and build our image, we need to do a small modification to our tests/DuskTestCase.php as the documentation for the test image said we should add the following flag --no-sandbox to the $options argument so it should look like:

        $options = (new ChromeOptions)->addArguments([
            '--disable-gpu',
            '--headless',
            '--window-size=1920,1080',
            '--no-sandbox',
        ]);

Running the tests

Now that we have everything in place, we are ready to go. Commit your code, let Docker Hub pickup, build our image and run the tests. So, every time you pushed a new code Docker Hub will execute the tests and only tag the successful build.

Final words

Remember that Docker Hub has its way to manage the auto build for the images, for example, it won't build any image in a branch or tag you didn't configure a rule for it on Docker Hub, also, they don't have any way for you to store artifacts which sometimes make it hard to figure out what the browser sees.

There is a way to fix that, not an official way sadly, but I'll leave it for later.

Share It via: