When Behat does not find features or bootstrap...

Submitted by Frederic Marand on

The issue

I was checking a bunch of Behat features left by former developers on a project, and noticed that the Rake rule to run them looked like:

$ behat -c config/behat.yml features

But this looked suspiciously like defaut options. How about running them more simply like just $ behat ? Alas, this would throw something like:


  [InvalidArgumentException]                         
  Can't find applicable feature loader               
  Maybe you've forgot to create `features/` folder?

Specifying the Behat configuration file but not the features directory like this didn't change a thing:

$ behat -c config/behat.yml


  [InvalidArgumentException]                         
  Can't find applicable feature loader               
  Maybe you've forgot to create `features/` folder?

And yet the behat.yml appeared to look sane:

default:
    paths:
        features: '%behat.paths.base%/features'
        bootstrap: '%behat.paths.features%/bootstrap'
    extensions:
        Behat\MinkExtension\Extension:
            goutte: ~
            selenium2: ~
            base_url: http://some.site
        Drupal\DrupalExtension\Extension:
            blackbox: ~
            api_driver: 'drupal'
            drupal: 
                drupal_root: 'public/drupal'            
            drush:
                alias: 'somealias'
            region_map:
                content: "#content"
                'left header': '#header-left'
                'right header': '#header-right'
                'right sidebar': '#column-right'
                footer: "#footer-region"
            selectors:
                message_selector: '.messages'
                error_message_selector: '.messages.messages-error'
                success_message_selector: '.messages.messages-status'

The features were indeed in the (project dir)/features, the bootstrap was indeed from (project dir)/features/bootstrap. Modifying the bootstrap line showed that it was being used correctly. And yet the features line never worked. I tried multiple variants like these:

# As per the Behat docs at http://docs.behat.org/guides/7.config.html#paths :
default:
    paths:
        features: features

# Trying to specify the base path :
default:
    paths:
        features: %behat.paths.base%/features

# Trying to get rid of the IDE YAML parser warnings :
default:
    paths:
        features: '%behat.paths.base%/features'

All of which didn't change a thing. What could be going wrong ?

The diagnostic

I dug into the LocatorProcessor::process() function, and decided to dump what was going on. The function looks like this:

<?php
   
public function process(InputInterface $input, OutputInterface $output)
    {
       
$this->container->get('behat.console.command')->setFeaturesPaths(
            array(
$input->getArgument('features'))
        );

        if (
is_dir($bootstrapPath = $this->container->getParameter('behat.paths.bootstrap'))) {
           
$this->loadBootstrapScripts($bootstrapPath);
        }
    }
?>

Some data printing easily showed that the setFeaturesPaths() call received an empty string as the value of the features argument, while the loadboostrapScripts() received a well-formed value from the behat.paths.bootstrap parameter.

Well, that made sense: when a Symfony2 Command like the Behat console is called without an argument, it does not default to the matching parameter. But then, how about looking into the behat.paths.features parameter ?

That turned out to be the missing link: the value of the features parameter was actually something like (project dir)/config/features, not (project dir)/features as expected. In other words, that parameter is relative to the Behat config file, not to the current working directory. Which of course, the Behat documentation had been explaining all along:

%behat.paths.base% - current working dir or configuration path (if configuration file exists and loaded).

The solution

After Reading The Fine Manual armed with the results of this diagnostic, the solution was painfully obvious. Since the config file was in config/behat.yml, just climb back one level from it:

default:
    paths:
        features: '%behat.paths.base%/../features'
        bootstrap: '%behat.paths.features%/bootstrap'
# ...snip...

And, of course, the test suite now ran with just a simple:

$ behat