At iThemes we use Codeception / WP-Browser to run our automated tests. I’ve described our setup in more detail in an earlier blog post, but the important bit to know for this post is that we use the Official WordPress Docker image as the base of our tests.
The WordPress docker image only creates tags when new versions are officially released which made running our tests on beta releases difficult. To solve this, we need to figure out a way to upgrade the installed WordPress version before our tests run. There are a couple of ways we could go about this.
- Add our own
trunk
Docker image. This is probably the “best” solution in terms of performance and doing things the “right” way. But maintaining our own Docker image that is refreshed nightly is a bunch of work I didn’t want to get into. - Right now, the only volume we mount is our plugin into the plugins directory. We could download a copy of WordPress and then mount WordPress as a volume inside the Docker container. But I think this will also get complicated to deal with installation and
wp-config.php
generation. - Use WP-CLI to upgrade WordPress to trunk. This is as simple as running
wp core update --version=nightly
and is officially supported.
Originally, I tried running this in the Bitbucket Pipelines but that of course won’t work since we have to be in the Docker container. Annoyingly, the Official WordPress image doesn’t include WP-CLI in the WordPress container, you have to use a separate image for it. Again, another mess I didn’t want to deal with.
All is not lost, though. WP-Browser includes WP-CLI as a dependency, so we can use that instead by making a Codeception module!
Building the module
To do this, we create a new class that extends the \Codeception\Module
class. We can name it whatever we want, and use whatever namespace are project is using. I ended up creating a class called WpVersion
in the \Helper
namespace. Typically, you’ll want to put this file in the tests/_support/Helper
directory.
namespace Helper;
class WpVersion extends \Codeception\Module {
}
WP-Browser helpfully defines a WithWpCli
trait that makes interacting with WP-CLI simple. We just need to provide it with the WordPress root directory and it takes care of the rest.
class WpVersion extends \Codeception\Module {
use \tad\WPBrowser\Traits\WithWpCli;
public function _initialize() {
parent::_initialize();
$this->setUpWpCli( $this->config['path'] );
}
}
This will take the path
that we provide when configuring the module and pass it to the setUpWpCli
method from WithWpCli
.
Next let’s define a runCommand
method that encapsulates the logic for running a WP CLI command and printing debug output. This method will take a list of command arguments and return the Symfony Process
instance that WP-Browser uses to run the command.
protected function runCommand( ...$user_command ) {
$options = [];
if ( ! empty( $this->config['allow-root'] ) ) {
$options[] = '--allow-root';
}
$command = array_merge( $options, $user_command );
$this->debugSection( 'WpVersion', $command );
$process = $this->executeWpCliCommand( $command, null );
$out = $process->getOutput() ?: $process->getErrorOutput();
$this->debugSection( 'WpVersion', $out );
return $process;
}
This method adds support for specifying allow-root
in the Module’s configuration. Running WP-CLI as root
in a real WordPress site is a bad idea as it gives all code in your WordPress install root
access to your server. However, the WordPress Docker image doesn’t play nicely when not running as root
and since we trust our own plugin, I’m not too concerned about it.
Finally we log the command we’re running, execute the command, and log the output.
Now let’s write a method that installs the requested WordPress version called install
.
protected function install( $version ) {
$this->debugSection( 'WpVersion', sprintf(
'Installing WordPress %s', $version
) );
try {
$process = $this->runCommand(
'core',
'update',
'--version=' . $version
);
if ( ! $process->isSuccessful() ) {
throw new \Codeception\Exception\ModuleException(
$this,
sprintf(
'Failed to install WordPress %s: %s',
$version,
$process->getErrorOutput() ?: $process->getOutput()
)
);
}
echo sprintf( 'Installed WordPress: %s', $version ) . PHP_EOL;
} catch ( \Exception $e ) {
throw new \Codeception\Exception\ModuleException(
$this,
$e->getMessage()
);
}
}
The $version
parameter can be any version that we can pass to WP-CLI. For example nightly
or 5.5
. First we build our command arguments, excluding the leading wp
. By passing the command as an array, the Symfony Process
class can automatically escape each argument, so we don’t need to manually call escapeshellarg
.
We add some additional debug output and throw a ModuleException
if the command wasn’t successful. Lastly, we echo
some output directly to the screen indicating what WordPress version we installed.
To put it all together we need to listen for the suite.before
Codeception event. This runs once immediately before a Codeception suite starts to run tests. Helpfully, Codeception Modules get easy access to this event by implementing the _beforeSuite
method.
public function _beforeSuite( $settings = [] ) {
if ( ! empty( $this->config['version'] ) ) {
$this->install( trim(
$this->config['version'],
" \t\n\r\0\x0B'\""
) );
}
}
This checks for a version
configuration flag, and if one is present we pass it to the install
method we wrote earlier. We also apply trim
to the defined version to get rid of any whitespace or quotes.
Configuring the module
To use and configure our module we need to add it to our suite’s .yml
file. For instance your acceptance.yml
file might look something like this.
actor: AcceptanceTester
modules:
enabled:
- WPDb
config:
WPDb:
url: '%TEST_SITE_WP_URL%'
urlReplacement: true
tablePrefix: '%TEST_SITE_TABLE_PREFIX%'
To add our custom module we need to the list the fully-qualified class name to the enabled
list.
actor: AcceptanceTester
modules:
enabled:
- WPDb
- \Helper\WpVersion
And then we can specify our configuration in the config
section.
actor: AcceptanceTester
modules:
enabled:
- WPDb
- \Helper\WpVersion
config:
WPDb:
url: '%TEST_SITE_WP_URL%'
urlReplacement: true
tablePrefix: '%TEST_SITE_TABLE_PREFIX%'
\Helper\WpVersion:
version: '%WP_VERSION%'
path: '%WP_ROOT_FOLDER%'
allow-root: true
I’ve defined our configuration to utilize two environment variables that we define in our codeception.env
file. This let’s us run sed -i -e 's/WP_VERSION=""/WP_VERSION="nightly"/g' tests/codeception.env
in our bitbucket-pipelines.yml
script to have a step that is setup to run Trunk.
I hope you’ve found this useful. You can checkout the Gist to see the completed Module.
Leave a Reply