Implementing WPBrowser bootstrap interactive mode 01

The first step to put in place an interactive bootstrap process for WPBrowser.

It’s complicated

I’ll admit to myself wp-browser bootstrap and following configuration could prove challenging for some users.
To cope with that and nicely integrate with wp-cli I’m putting in place an interactive bootstrap process.
“Interactive” is still a term that applies to a CLI command, the wpcept bootstrap one, and that could help first time users to get up and running with WPBrowser and Codeception.
The interactive mode will be available running the command with the interactive option like

wpcept bootstrap --interactive

the option comes in the -i short form too.

So what do you want to ask me?

To make an example of how this interactive mode could help this is the drill I usually go through when scaffolding tests for a new WordPress plugin or theme:

  • run wpcept bootstrap
  • check some path and memory settings in codeception.yml file, this is usually good to go out of the box
  • edit the acceptance.suite.yml file to match my local setup
  • edit the functional.suite.yml file to match my local setup
  • edit the integration.suite.yml file to match my local setup
  • check the unit.suite.yml, this file too is usually good to go out of the box

The difficult might rise while trying to configure one of the more complex configuration files like functional.suite.yml:

class_name: FunctionalTester
modules:
    enabled:
          - FunctionalHelper
          - Filesystem
          - WPDb
          - WordPress
    config:
        WPDb:
            dsn: 'mysql:host=mysql;dbname=wpFuncTests'
            user: root
            password: ''
            dump: tests/_data/dump.sql
            populate: true
            cleanup: true
            url: 'http://wp.dev'
            tablePrefix: wp_
        WordPress:
            depends: WPDb
            wpRootFolder: /Users/Luca/Sites/wp
            adminUsername: admin
            adminPassword: admin

In order I edit:

  • the database host to mysql
  • the database name to wpFuncTests
  • the url to http://wp.dev
  • the WordPress installation root folder to /Users/Luca/Sites/wp
  • the administrator username and password

I will go through the same drill, with variations, for acceptance and integration suites.
That’s not all though: under the hood I’ve created new databases and tested some connection settings; the database creation especially seems to be a point escaping to many wp-browser users that expect the suite to “do it all”.
While I won’t move in that direction I think some help and guidance along the way could help and make things clear.

Starting with a test

The wp-browser is a module library for Codeception and adding code without tests would do it a bad service.
I’ve added some unit testing for the suite config file template generation but the interesting part is the one where the whole bootstrap command is put under test: I’m checking on its side effects of the file structure.

<?php
use Codeception\Command\WPBootstrap;
use Ofbeaton\Console\Tester\QuestionTester;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Component\Yaml\Yaml;

class WPBootstrapTest extends \Codeception\Test\Unit
{
    use QuestionTester;

    protected static $path;
    protected static $cwdBackup;

    /**
     * @var Application
     */
    public $application;

    /**
     * @var OutputInterface
     */
    protected $outputInterface;

    /**
     * @var InputInterface
     */
    protected $inputInterface;

    /**
     * @var \IntegrationTester
     */
    protected $tester;


    public static function setUpBeforeClass()
    {
        self::$cwdBackup = getcwd();
        self::$path = codecept_data_dir('folder-structures/wpbootstrap-test-root');
    }

    protected function _before()
    {
        self::clean();
    }

    public static function tearDownAfterClass()
    {
        self::clean();
        chdir(self::$cwdBackup);
    }

    protected function testDir($relative = '')
    {
        $frag = $relative ? '/' . ltrim($relative, '/') : '';
        return self::$path . $frag;
    }

    protected static function clean()
    {
        rrmdir(self::$path . '/tests');
        foreach (glob(self::$path . '/*.*') as $file) {
            unlink($file);
        }
    }

    // removed some tests for brevity

    /**
     * @param $command
     * @param $questionsAndAnswers
     */
    protected function mockAnswers($command, $questionsAndAnswers)
    {
        $this->mockQuestionHelper($command, function ($text, $order, Question $question) use ($questionsAndAnswers) {
            foreach ($questionsAndAnswers as $key => $value) {
                if (strpos($text, $key) !== false) {
                    return $value;
                }
            }

            // no question matched, fail
            throw new PHPUnit_Framework_AssertionFailedError();
        });
    }


    /**
     * @test
     * it should allow user to specify params through question for functional suite
     */
    public function it_should_allow_user_to_specify_params_through_question_for_functional_suite()
    {
        $app = new Application();
        $app->add(new WPBootstrap('bootstrap'));
        $command = $app->find('bootstrap');
        $commandTester = new CommandTester($command);

        $questionsAndAnswers = [
            'MySQL database host?' => 'mysql'
        ];

        $this->mockAnswers($command, $questionsAndAnswers);

        $commandTester->execute([
            'command' => $command->getName(),
            'path' => $this->testDir(),
            '--no-build' => true,
            '--interactive' => true
        ]);

        $file = $this->testDir('tests/functional.suite.yml');

        $this->assertFileExists($file);

        $fileContents = file_get_contents($file);

        $this->assertNotEmpty($fileContents);

        $decoded = Yaml::parse($fileContents);

        $this->assertNotEmpty($decoded['modules']['config']['WPDb']['dsn']);
        $this->assertContains('mysql:host=mysql', $decoded['modules']['config']['WPDb']['dsn']);
    }

}

Some things to note here:

  • Codeception is a Symfony Console command and as such I’m testing it the same way the Symfony site shows; not much difference here but the application and command setup might be confusing
  • the use QuestionTester statement uses a trait defined in the console-tester package
  • the mockAnswers method “mocks” the user answers setting those up as I need
  • I’m using the path argument of the bootstrap command to avoid Codeception overwriting its own configuration during the tests; given the possibility I would not use the real filesystem and would rather rely on vfsStream but Codeception use of the chdir function makes it impossible; as such I’m working in the tests/_data folder.

Next

I’ve been able to pass the test method above and will move on to cover all the fields a user might need to reasonably change and could use help with; so far a smooth ride.