There is a need to be able to test build a Drupal site. This is a long-time need and is now possible.
Documentation here: https://www.drupal.org/docs/8/phpunit/build-testing
Why Build Testing?
The bash functionality of drupalci.yml is intended to do minor setup work. It is *not* intended to be a bash testing framework or anything resembling that. It is not desirable to place bash scripts into drupalci.yml and then rely on the exit codes of those scripts as evidence of build success or failure.
It is preferable to have these bash scripts execute within the context of phpunit, which frames them as a standard test, and give the results in a standard way, and is runnable in environments *other than* drupalci.
Additionally, we want any developer to be able to write and execute these tests locally, and running phpunit is typical. Setting up a local drupalci, is not very typical.
Build Testing
Here's an example, functioning test that uses the quickstart CLI to build a site and test that it worked. Note that it uses a data provider for three different profiles, and that the test is annotated to require the existence of the composer command before the test will run.
/**
* Test whether we can install a Drupal site using the quickstart CLI.
*
* @group Build
* @group Command
* @group QuickStart
*/
class InstallTest extends BuildTestBase {
public function providerProfile() {
return [
'standard' => ['standard'],
'minimal' => ['minimal'],
'demo_umami' => ['demo_umami'],
];
}
/**
* @dataProvider providerProfile
* @requires externalCommand composer
*/
public function testInstall($profile) {
$adminUsername = '';
$adminPassword = '';
// Copy the codebase to our workspace.
$this->copyCodebase();
// Execute composer install.
$this->executeCommand('composer install --no-dev --no-interaction');
// Command assertions operate on the last command executed.
$this->assertCommandSuccessful();
// Composer tells you stuff in error output.
$this->assertErrorOutputContains('Generating autoload files');
$finder = new PhpExecutableFinder();
// You can keep the process object for later assertions or manipulations.
$process = $this->executeCommand(
$finder->find() . ' ./core/scripts/drupal install ' . $profile
);
$this->assertCommandSuccessful();
$this->assertCommandOutputContains('Username:');
preg_match('/Username: (.+)\vPassword: (.+)/', $process->getOutput(), $matches);
$this->assertNotEmpty($adminUsername = $matches[1]);
$this->assertNotEmpty($adminPassword = $matches[2]);
// The visit() method returns a Mink object, and we can perform assertions
// against that.
$assert = $this->visit()->assertSession();
$assert->elementExists('css', 'html');
// We also have a built-in assertion to check if the generator meta on the
// last request says this is a Drupal site.
$this->assertDrupalVisit();
// Do we get 404 for a path that does not exist?
$this->visit('/does-not-exist')->assertSession()->statusCodeEquals(404);
// Can we log in and be an administrator?
$this->visit('/admin');
$assert->statusCodeEquals(403);
// Use the form to log in.
$mink = $this->visit('/user/login');
$assert = $mink->assertSession();
$assert->statusCodeEquals(200);
$assert->fieldExists('edit-name')->setValue($adminUsername);
$assert->fieldExists('edit-pass')->setValue($adminPassword);
$mink->getSession()->getPage()->findButton('Log in')->submit();
// Visit the admin page as an administrator.
$this->visit('/admin');
$assert->statusCodeEquals(200);
// Can we try to be an admin even though we logged out?
$this->visit('/user/logout')->assertSession()->statusCodeEquals(200);
$this->visit('/admin');
$assert->statusCodeEquals(403);
}
}