Installing and setting up WordPress and Nginx for Travis CI tests.

You should probably set up Continuous Integration system

I could go at length about why setting up a “Continuous Something” flow is something you should do, but I’d rather defer the theory and the introduction to this article on Nutcache blog as it provides a clear high-level view of the matter while also providing good starting points for each touched subject.
In a way not dissimilar to what happens with testing the greatest barrier to the adoption and implementation of a CI system is the perceived difficulty.
In the same way that setting up to test code does not mean code coverage has now to be 100% or nothing, setting up to use continuous integration does not mean that everything has to be automated and/or managed by automatic tools.
Continuous integration usually involves running some kind of testing as a first phase; in this post I will focus on how to set up a Nginx web-server on Travis CI to run acceptance and functional like tests.
I will then specialize the guide for a WordPress-based application (e.g. a plugin, a theme or a whole site) and provide two examples.

Installing Nginx on Travis CI

I’ve been using Travis CI, in its free and paid version, for a while now.
The support team proved to be, for private and open-source projects as well, crazy fast in dealing with any request or issue and tutorials, should one need help beyond Travis CI documentation, are abundant on the web.
In the past I’ve relied on an Apache-based setup to scaffold test WordPress installations; that’s a collection of scripts that configure Apache web-server, PHP and install WordPress on a Ubuntu box (the machine provided by Travis CI) to be able to run acceptance tests using a “pretty” URL, like http://wp.dev.
Due to recent changes to the base Travis CI box, the script is currently not working; out of frustration I tried a different approach using Nginx to see how that would work.
Installing and configuring Nginx proved to be rather easy and is the first step to a fully automated WordPress testing environment.
In the following example I will assume the site to be tested is the repository itself; if this is not true the second, WordPress-specific, example will handle a different case.
Below is a Travis configuration file (.travis.yml), with copious inline comments, to get me started:

# the setup I'm using requires the use of `sudo`
# to run some commands as super-user, as such I
# require it at the top of the configuration file
sudo: required

# this tells Travis to set up the box with some common tools
# used in PHP projects; while one can start with a "blank"
# box I find this convenient
language: php

# no email notifications for me; this is personal choice
notifications:
  email: false

# I want my project to be tested with the following PHP versions
php:
  - '7.0'
  - '7.1'
  - nightly

matrix:
  fast_finish: true

  # if the tests fail on the "nightly" version of PHP that's fine
  allow_failures:
    - php: nightly

# I will need to use a database in my tests so, in place of installing and
# setting up a MySQL server I simply require it; this will make the `mysql`
# binary available
services:
  - mysql

cache:
  apt: true
  directories:
    - vendor
    - $HOME/.composer/cache/files

addons:

  # instead of adding a line, in the `before_install` or `install`
  # sections to install packages using `apt-get install...` I simply
  # delegate the task to Travis
  apt:
    packages:

    # I will need PHP in its FPM version, MySQL PHP extensions and Nginx
    # itself
      - php5-fpm
      - php5-mysql
      - nginx

  # if I need to resolve pretty URLs to the local machine in place
  # of adding a line that tries to modify the `/etc/hosts` file I can
  # specify all the modifications I'd like to do here.
  # there is no `*.site.localhost` option so I need to add an entry for
  # each subdomain I want to use
  hosts:
    - site.localhost
    - test1.site.localhost

env:
  # here I define some environment variables I will be able to use
  # in scripts and in the PHP application itself
  # e.g `$SITE_FOLDER` will return `/tmp/site` in a bash script
  # while `$siteFolder = getenv('SITE_FOLDER');` will return the same
  # value in a PHP script
  global:
    - SITE_URL="http://site.localhost"
    - SITE_DOMAIN="site.localhost"
    - DB_NAME="test"

before_install:
  # create the databases that will be used by the site using `mysql` binary
  # user is `root`, password is empty and host is `localhost`
  - mysql -e "create database IF NOT EXISTS $DB_NAME;" -uroot

install:
  # install Composer dependencies
  - composer install --prefer-dist

  # copy the Nginx configuration file for the site among the available ones from 
  # the `build` folder; the content of the Nginx configuration file I'm using is
  # specific to WordPress (see below) so use whatever you need to use.
  # Given the current environment configuration it will be copied to `/etc/nginx/sites-available/site.localhost`
  - sudo cp build/travis-nginx-conf /etc/nginx/sites-available/$SITE_DOMAIN

  # using sed replace the placeholder text in the Nginx configuration file to use the
  # correct site folder path and domain
  - sudo sed -e "s?%TRAVIS_BUILD_DIR%?$TRAVIS_BUILD_DIR?g" --in-place /etc/nginx/sites-available/$SITE_DOMAIN
  - sudo sed -e "s?%SITE_DOMAIN%?$SITE_DOMAIN?g" --in-place /etc/nginx/sites-available/$SITE_DOMAIN

  # enable the site by creating a symbolic link in the `sites-enabled` folder
  - sudo ln -s /etc/nginx/sites-available/$SITE_DOMAIN /etc/nginx/sites-enabled/

before_script:

  # restart Nginx and PHP-FPM services to make sure the new site is loaded
  - sudo service php5-fpm restart
  - sudo service nginx restart

script:
  # finally run the tests...
  - curl http://site.localhost

In the file above I refer to a build/travis-nginx-conf file; trying to keep this example removed from WordPress any template found on the web will do; I’m reporting here one where I’ve laid out the base for the sed replacements and some sane testing defaults:

server {
    listen 80;

    server_name %SITE_DOMAIN% *.%SITE_DOMAIN%;

    root %TRAVIS_BUILD_DIR%;

    index index.php;

    client_body_in_file_only clean;
    client_body_buffer_size 32K;

    client_max_body_size 300M;

    sendfile on;
    send_timeout 300s;

    location / {
        try_files $uri $uri/ /index.php?$args ;
    }

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_pass unix:/var/run/php5-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
    }

    # Expire images, icons, video, audio, HTC in 1 hour, *pretty* ok in development
    location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ {
      expires 1h;
      access_log off;
      add_header Cache-Control "public";
    }

    # Do not cache CSS and Javascript files at all
    location ~* \.(?:css|js)$ {
      # set this to `off` to avoid issues with the virtual machine
      sendfile off;
      expires -1;
      access_log off;
      add_header Cache-Control "public";
    }
}

The WordPress specific example

There are two main differences between the example above and what I use for WordPress projects: 1. I’m not testing the site as a whole but just a plugin or a theme, as such the TRAVIS_BUILD_DIR folder will contain the plugin or theme code, not the whole site; the site will live in a separate folder 2. I’m using Codeception and wp-browser to run the tests

Here is the modified .travis.yml template file with more WordPress specific comments:

sudo: required

language: php

notifications:
  email: false

php:
  - '7.0'
  - '7.1'
  - nightly

matrix:
  fast_finish: true
  allow_failures:
    - php: nightly
    - env: WP_VERSION=nightly

services:
  - mysql

cache:
  apt: true
  directories:
    - vendor
    - $HOME/.composer/cache/files

addons:
  apt:
    packages:
      # required by WordPress
      - libjpeg-dev
      - libpng12-dev
      - php5-fpm
      - php5-mysql
      - nginx
  hosts:
    - wp.localhost
    - test1.wp.localhost
    - test2.wp.localhost

env:
  global:
    - WP_FOLDER="/tmp/wordpress"
    - WP_URL="http://wp.localhost"
    - WP_DOMAIN="wp.localhost"
    - DB_NAME="test"
    - TEST_DB_NAME="wploader"
    - WP_TABLE_PREFIX="wp_"
    - WP_ADMIN_USERNAME="admin"
    - WP_ADMIN_PASSWORD="admin"
    - WP_SUBDOMAIN_1="test1"
    - WP_SUBDOMAIN_1_TITLE="Test Subdomain 1"
    - WP_SUBDOMAIN_2="test2"
    - WP_SUBDOMAIN_2_TITLE="Test Subdomain 2"
  matrix:
    - WP_VERSION=latest
    - WP_VERSION=nightly

before_install:
  # create the databases that will be used in the tests
  - mysql -e "create database IF NOT EXISTS $DB_NAME;" -uroot
  - mysql -e "create database IF NOT EXISTS $TEST_DB_NAME;" -uroot
  # set up folders
  - mkdir -p $WP_FOLDER
  - mkdir tools
  # install wp-cli in the `tools` folder
  - wget https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar -P $(pwd)/tools/
  - chmod +x tools/wp-cli.phar && mv tools/wp-cli.phar tools/wp
  # append the `tools` folder to the PATH
  - export PATH=$PATH:$(pwd)/tools
  # prepend the `vendor/bin` folder the PATH
  - export PATH=vendor/bin:$PATH

install:
  - composer install --prefer-dist

  # install WordPress in the `wordpress` folder
  - cd $WP_FOLDER
  - wp core download --version=$WP_VERSION
  - wp config create --dbname="$DB_NAME" --dbuser="root" --dbpass="" --dbhost="127.0.0.1" --dbprefix="$WP_TABLE_PREFIX"
  - wp core multisite-install --url="$WP_URL" --base="/" --subdomains --title="Test" --admin_user="$WP_ADMIN_USERNAME" --admin_password="$WP_ADMIN_PASSWORD" --admin_email="admin@$WP_DOMAIN" --skip-email

  # flush rewrite rules to use pretty permalinks
  - wp rewrite structure '/%postname%/' --hard

  # create the two subdomain sites I need
  - wp site create --slug="$WP_SUBDOMAIN_1" --title="$WP_SUBDOMAIN_1_TITLE"
  - wp site create --slug="$WP_SUBDOMAIN_2" --title="$WP_SUBDOMAIN_2_TITLE"

  # update WordPress database to avoid prompts
  - wp core update-db --network

  # export a dump of the just installed database to the _data folder
  - wp db export $TRAVIS_BUILD_DIR/tests/_data/dump.sql

  # get back to the build folder
  - cd $TRAVIS_BUILD_DIR

  # copy the Nginx configuration file to the default site and replace the path and domain
  - sudo cp build/travis-nginx-conf /etc/nginx/sites-available/$WP_DOMAIN
  - sudo sed -e "s?%WP_FOLDER%?$WP_FOLDER?g" --in-place /etc/nginx/sites-available/$WP_DOMAIN
  - sudo sed -e "s?%WP_DOMAIN%?$WP_DOMAIN?g" --in-place /etc/nginx/sites-available/$WP_DOMAIN

  # enable the WordPress installation
  - sudo ln -s /etc/nginx/sites-available/$WP_DOMAIN /etc/nginx/sites-enabled/

before_script:
  - sudo service php5-fpm restart
  - sudo service nginx restart

  # symbolically link the plugin root folder to the WordPress installation folder
  # if you are using wp-browser use the Symlinker extension and skip this
  - ln -s $TRAVIS_BUILD_DIR $WP_FOLDER/wp-content/plugins

  # build Codeception modules
  - codecept build

script:
  - codecept run acceptance
  - codecept run functional
  - codecept run integration
  - codecept run unit

The build/travis-nginx-conf file I use for WordPress projects is this:

map $http_host $blogid {
    default       -999;
    #Ref: http://wordpress.org/extend/plugins/nginx-helper/
    #include %WP_FOLDER%/wp-content/plugins/nginx-helper/map.conf;
}

server {
    listen 80;

    server_name %WP_DOMAIN% *.%WP_DOMAIN%;

    root %WP_FOLDER%;
    index index.php;

    client_body_in_file_only clean;
    client_body_buffer_size 32K;

    client_max_body_size 300M;

    sendfile on;
    send_timeout 300s;

    location / {
        try_files $uri $uri/ /index.php?$args ;
    }

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_pass unix:/var/run/php5-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
    }

    #WPMU Files
    location ~ ^/files/(.*)$ {
            try_files /wp-content/blogs.dir/$blogid/$uri /wp-includes/ms-files.php?file=$1 ;
            access_log off;
            log_not_found off;
            expires max;
    }

    #WPMU x-sendfile to avoid php readfile()
    location ^~ /blogs.dir {
        internal;
        alias %WP_FOLDER%/wp-content/blogs.dir;
        access_log off;
        log_not_found off;
        expires max;
    }

    # Expire images, icons, video, audio, HTC in 1 hour, *pretty* ok in development
    location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ {
      expires 1h;
      access_log off;
      add_header Cache-Control "public";
    }

    # Do not cache CSS and Javascript files at all
    location ~* \.(?:css|js)$ {
      # set this to `off` to avoid issues with the virtual machine
      sendfile off;
      expires -1;
      access_log off;
      add_header Cache-Control "public";
    }
}

It will work for single and subdomain-based multisite installations; a good starting point to write a different one is the Codex and Nginx documentation itself.

Bonus - dynamic Codeception configuration

Codeception allows configuring its suites using dynamic configuration parameters pulled from an environment file or the environment itself.
I’ve taken the habit to create only distribution version of Codeception configuration files (e.g. integration.suite.dist.yml in place of integration.suite.yml) to simplify the maintenance and leverage this possibility.
These are the steps to configure Codeception to work with dynamic configuration parameters:

  • first require the vlucas/phpdotenv library: composer require --dev vlucas/phpdotenv.
  • modify each configuration file to use placeholders in the %PLACEHOLDER% format, e.g. wp-browser own acceptance.suite.dist.yml is this:
    class_name: AcceptanceTester
    modules:
      enabled:
          - WPBrowser
          - WPDb
          - AcceptanceHelper
          - WPFilesystem
      config:
          WPBrowser:
              url: '%WP_URL%'
              adminUsername: 'admin'
              adminPassword: 'admin'
              adminPath: '/wp-admin'
          WPDb:
              dsn: 'mysql:host=%DB_HOST%;dbname=%DB_NAME%'
              user: %DB_USER%
              password: %DB_PASSWORD%
              dump: 'tests/_data/dump.sql'
              populate: true
              cleanup: true
              reconnect: false
              url: '%WP_URL%'
              tablePrefix: 'wp_'
          WPFilesystem:
              wpRootFolder: '%WP_ROOT_FOLDER%'
              themes: '/wp-content/themes'
              plugins: '/wp-content/plugins'
              mu-plugins: '/wp-content/mu-plugins'
              uploads: '/wp-content/uploads'
    
    
  • in the distribution version of the main Codeception configuration file, codecept.dist.yml specify that dynamic parameters shoudld be used:
    actor: Tester
    paths:
      tests: tests
      log: tests/_output
      data: tests/_data
      helpers: tests/_support
    settings:
    # ...
    wpFolder: '%WP_ROOT_FOLDER%'
    extensions:
    # ...
    params:
      - .env
    
    
  • override just the params parameter in the local Codeception configuration file, codecpetion.yml (which is not pushed to the remote repository):
    params:
      - .env.local
    
    
  • create the .env and .env.local files to configure the suites; the .env. file will be used by the CI host while the .env.local file will be used locally; as an example here are mines for wp-browser; the .env one first:
    WP_DOMAIN="wp.localhost"
    WP_URL="http://wp.localhost"
    DB_HOST="localhost"
    DB_NAME="test"
    DB_USER="root"
    DB_PASSWORD=""
    WP_ROOT_FOLDER="/tmp/wordpress"
    TEST_DB_NAME="wploader"
    PHANTOMJS="/usr/local/phantomjs/phantomjs"
    wpSubdomain1="test1"
    wpSubdomain1Title="Test Subdomain 1"
    wpSubdomain2="test2"
    wpSubdomain2Title="Test Subdomain 2"
    
    
  • and the .env.local one:
    WP_DOMAIN="wp.localhost"
    WP_URL="http://wp.localhost"
    DB_HOST="db.localhost"
    DB_NAME="wp"
    TEST_DB_NAME="wpTests"
    DB_USER="root"
    DB_PASSWORD="root"
    WP_ROOT_FOLDER="/Users/luca/Repos/wp-browser/docker/www"
    PHANTOMJS="/usr/local/bin/phantomjs"
    wpSubdomain1="test1"
    wpSubdomain1Title="Test1"
    wpSubdomain2="test2"
    wpSubdomain2Title="Test2"
    
    

See it in action

I’ve shown, in the post, working code but wp-browser configuration file is available for inspection; since that contains a number of “meta” concepts (testing a WordPress testing tool) a clearer example might be the one provided by the “I’d like this” plugin.