Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 401 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  namespace Moodle\BehatExtension\ServiceContainer;
  18  
  19  use Behat\Behat\Definition\ServiceContainer\DefinitionExtension;
  20  use Behat\Behat\EventDispatcher\ServiceContainer\EventDispatcherExtension;
  21  use Behat\Behat\Gherkin\ServiceContainer\GherkinExtension;
  22  use Behat\Behat\Tester\ServiceContainer\TesterExtension;
  23  use Behat\Testwork\Cli\ServiceContainer\CliExtension;
  24  use Behat\Testwork\Output\ServiceContainer\OutputExtension;
  25  use Behat\Testwork\ServiceContainer\Extension as ExtensionInterface;
  26  use Behat\Testwork\ServiceContainer\ExtensionManager;
  27  use Behat\Testwork\ServiceContainer\ServiceProcessor;
  28  use Behat\Testwork\Specification\ServiceContainer\SpecificationExtension;
  29  use Behat\Testwork\Suite\ServiceContainer\SuiteExtension;
  30  use Moodle\BehatExtension\Driver\WebDriverFactory;
  31  use Moodle\BehatExtension\Output\Formatter\MoodleProgressFormatterFactory;
  32  use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
  33  use Symfony\Component\Config\FileLocator;
  34  use Symfony\Component\DependencyInjection\ContainerBuilder;
  35  use Symfony\Component\DependencyInjection\Definition;
  36  use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
  37  use Symfony\Component\DependencyInjection\Reference;
  38  
  39  // phpcs:disable moodle.NamingConventions.ValidFunctionName.LowercaseMethod
  40  
  41  /**
  42   * Behat extension for moodle
  43   *
  44   * Provides multiple features directory loading (Gherkin\Loader\MoodleFeaturesSuiteLoader
  45   *
  46   * @package core
  47   * @copyright 2016 Rajesh Taneja <rajesh@moodle.com>
  48   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  49   */
  50  class BehatExtension implements ExtensionInterface {
  51      /** @var string Extension configuration ID */
  52      const MOODLE_ID = 'moodle';
  53  
  54      /** @var string Gherkin ID */
  55      const GHERKIN_ID = 'gherkin';
  56  
  57      /** @var ServiceProcessor */
  58      private $processor;
  59  
  60      /**
  61       * Initializes compiler pass.
  62       *
  63       * @param null|ServiceProcessor $processor
  64       */
  65      public function __construct(ServiceProcessor $processor = null) {
  66          $this->processor = $processor ? : new ServiceProcessor();
  67      }
  68  
  69      /**
  70       * Loads moodle specific configuration.
  71       *
  72       * @param ContainerBuilder $container ContainerBuilder instance
  73       * @param array            $config    Extension configuration hash (from behat.yml)
  74       */
  75      public function load(ContainerBuilder $container, array $config) {
  76          $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/services'));
  77          $loader->load('core.xml');
  78  
  79          // Getting the extension parameters.
  80          $container->setParameter('behat.moodle.parameters', $config);
  81  
  82          // Load moodle progress formatter.
  83          $moodleprogressformatter = new MoodleProgressFormatterFactory();
  84          $moodleprogressformatter->buildFormatter($container);
  85  
  86          // Load custom step tester event dispatcher.
  87          $this->loadEventDispatchingStepTester($container);
  88  
  89          // Load chained step tester.
  90          $this->loadChainedStepTester($container);
  91  
  92          // Load step count formatter.
  93          $this->loadMoodleListFormatter($container);
  94  
  95          // Load step count formatter.
  96          $this->loadMoodleStepcountFormatter($container);
  97  
  98          // Load screenshot formatter.
  99          $this->loadMoodleScreenshotFormatter($container);
 100  
 101          // Load namespace alias.
 102          $this->alias_old_namespaces();
 103  
 104          // Load skip passed controller and list locator.
 105          $this->loadSkipPassedController($container, $config['passed_cache']);
 106          $this->loadFilesystemSkipPassedScenariosListLocator($container);
 107      }
 108  
 109      /**
 110       * Loads moodle List formatter.
 111       *
 112       * @param ContainerBuilder $container
 113       */
 114      protected function loadMoodleListFormatter(ContainerBuilder $container) {
 115          $definition = new Definition('Moodle\BehatExtension\Output\Formatter\MoodleListFormatter', [
 116              'moodle_list',
 117              'List all scenarios. Use with --dry-run',
 118              ['stepcount' => false],
 119              $this->createOutputPrinterDefinition()
 120          ]);
 121          $definition->addTag(OutputExtension::FORMATTER_TAG, ['priority' => 101]);
 122          $container->setDefinition(OutputExtension::FORMATTER_TAG . '.moodle_list', $definition);
 123      }
 124  
 125      /**
 126       * Loads moodle Step count formatter.
 127       *
 128       * @param ContainerBuilder $container
 129       */
 130      protected function loadMoodleStepcountFormatter(ContainerBuilder $container) {
 131          $definition = new Definition('Moodle\BehatExtension\Output\Formatter\MoodleStepcountFormatter', [
 132              'moodle_stepcount',
 133              'Count steps in feature files. Use with --dry-run',
 134              ['stepcount' => false],
 135              $this->createOutputPrinterDefinition()
 136          ]);
 137          $definition->addTag(OutputExtension::FORMATTER_TAG, ['priority' => 101]);
 138          $container->setDefinition(OutputExtension::FORMATTER_TAG . '.moodle_stepcount', $definition);
 139      }
 140  
 141      /**
 142       * Loads moodle screenshot formatter.
 143       *
 144       * @param ContainerBuilder $container
 145       */
 146      protected function loadMoodleScreenshotFormatter(ContainerBuilder $container) {
 147          $definition = new Definition('Moodle\BehatExtension\Output\Formatter\MoodleScreenshotFormatter', [
 148              'moodle_screenshot',
 149              // phpcs:ignore Generic.Files.LineLength.TooLong
 150              'Take screenshot of all steps. Use --format-settings \'{"formats": "html,image"}\' to get specific o/p type',
 151              ['formats' => 'html,image'],
 152              $this->createOutputPrinterDefinition()
 153          ]);
 154          $definition->addTag(OutputExtension::FORMATTER_TAG, ['priority' => 102]);
 155          $container->setDefinition(OutputExtension::FORMATTER_TAG . '.moodle_screenshot', $definition);
 156      }
 157  
 158      /**
 159       * Creates output printer definition.
 160       *
 161       * @return Definition
 162       */
 163      protected function createOutputPrinterDefinition() {
 164          return new Definition('Behat\Testwork\Output\Printer\StreamOutputPrinter', [
 165              new Definition('Behat\Behat\Output\Printer\ConsoleOutputFactory'),
 166          ]);
 167      }
 168  
 169      /**
 170       * Loads skip passed controller.
 171       *
 172       * @param ContainerBuilder $container
 173       * @param null|string      $cachepath
 174       */
 175      protected function loadSkipPassedController(ContainerBuilder $container, $cachepath) {
 176          $definition = new Definition('Moodle\BehatExtension\Tester\Cli\SkipPassedController', [
 177              new Reference(EventDispatcherExtension::DISPATCHER_ID),
 178              $cachepath,
 179              $container->getParameter('paths.base')
 180          ]);
 181          $definition->addTag(CliExtension::CONTROLLER_TAG, ['priority' => 200]);
 182          $container->setDefinition(CliExtension::CONTROLLER_TAG . '.passed', $definition);
 183      }
 184  
 185      /**
 186       * Loads filesystem passed scenarios list locator.
 187       *
 188       * @param ContainerBuilder $container
 189       */
 190      private function loadFilesystemSkipPassedScenariosListLocator(ContainerBuilder $container) {
 191          $definition = new Definition('Moodle\BehatExtension\Locator\FilesystemSkipPassedListLocator', [
 192              new Reference(self::GHERKIN_ID)
 193          ]);
 194          $definition->addTag(SpecificationExtension::LOCATOR_TAG, ['priority' => 50]);
 195          $container->setDefinition(
 196              SpecificationExtension::LOCATOR_TAG . '.filesystem_skip_passed_scenarios_list',
 197              $definition
 198          );
 199      }
 200  
 201      /**
 202       * Loads definition printers.
 203       *
 204       * @param ContainerBuilder $container
 205       */
 206      private function loadDefinitionPrinters(ContainerBuilder $container) {
 207          $definition = new Definition('Moodle\BehatExtension\Definition\Printer\ConsoleDefinitionInformationPrinter', [
 208              new Reference(CliExtension::OUTPUT_ID),
 209              new Reference(DefinitionExtension::PATTERN_TRANSFORMER_ID),
 210              new Reference(DefinitionExtension::DEFINITION_TRANSLATOR_ID),
 211              new Reference(GherkinExtension::KEYWORDS_ID)
 212          ]);
 213          $container->removeDefinition('definition.information_printer');
 214          $container->setDefinition('definition.information_printer', $definition);
 215      }
 216  
 217      /**
 218       * Loads definition controller.
 219       *
 220       * @param ContainerBuilder $container
 221       */
 222      private function loadController(ContainerBuilder $container) {
 223          $definition = new Definition('Moodle\BehatExtension\Definition\Cli\AvailableDefinitionsController', [
 224              new Reference(SuiteExtension::REGISTRY_ID),
 225              new Reference(DefinitionExtension::WRITER_ID),
 226              new Reference('definition.list_printer'),
 227              new Reference('definition.information_printer')
 228          ]);
 229          $container->removeDefinition(CliExtension::CONTROLLER_TAG . '.available_definitions');
 230          $container->setDefinition(CliExtension::CONTROLLER_TAG . '.available_definitions', $definition);
 231      }
 232  
 233      /**
 234       * Loads chained step tester.
 235       *
 236       * @param ContainerBuilder $container
 237       */
 238      protected function loadChainedStepTester(ContainerBuilder $container) {
 239          // Chained steps.
 240          $definition = new Definition('Moodle\BehatExtension\EventDispatcher\Tester\ChainedStepTester', [
 241              new Reference(TesterExtension::STEP_TESTER_ID),
 242          ]);
 243          $definition->addTag(TesterExtension::STEP_TESTER_WRAPPER_TAG, ['priority' => 100]);
 244          $container->setDefinition(TesterExtension::STEP_TESTER_WRAPPER_TAG . '.substep', $definition);
 245      }
 246  
 247      /**
 248       * Loads event-dispatching step tester.
 249       *
 250       * @param ContainerBuilder $container
 251       */
 252      protected function loadEventDispatchingStepTester(ContainerBuilder $container) {
 253          $definition = new Definition('Moodle\BehatExtension\EventDispatcher\Tester\MoodleEventDispatchingStepTester', [
 254              new Reference(TesterExtension::STEP_TESTER_ID),
 255              new Reference(EventDispatcherExtension::DISPATCHER_ID)
 256          ]);
 257          $definition->addTag(TesterExtension::STEP_TESTER_WRAPPER_TAG, ['priority' => -9999]);
 258          $container->setDefinition(TesterExtension::STEP_TESTER_WRAPPER_TAG . '.event_dispatching', $definition);
 259      }
 260  
 261      /**
 262       * Setups configuration for current extension.
 263       *
 264       * @param ArrayNodeDefinition $builder
 265       */
 266      public function configure(ArrayNodeDefinition $builder) {
 267          // phpcs:disable PEAR.WhiteSpace.ObjectOperatorIndent.Incorrect
 268          $builder->children()
 269              ->arrayNode('capabilities')
 270                  ->useAttributeAsKey('key')
 271                  ->prototype('variable')->end()
 272                  ->end()
 273              ->arrayNode('steps_definitions')
 274                  ->useAttributeAsKey('key')
 275                  ->prototype('variable')->end()
 276                  ->end()
 277              ->scalarNode('moodledirroot')
 278                  ->defaultNull()
 279                  ->end()
 280                  ->scalarNode('passed_cache')
 281              ->info('Sets the passed cache path')
 282                  ->defaultValue(
 283                      is_writable(sys_get_temp_dir())
 284                          ? sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'behat_passed_cache'
 285                          : null
 286                  )
 287                  ->end()
 288              ->end()
 289          ->end();
 290          // phpcs:enable PEAR.WhiteSpace.ObjectOperatorIndent.Incorrect
 291      }
 292  
 293      /**
 294       * Returns the extension config key.
 295       *
 296       * @return string
 297       */
 298      public function getConfigKey() {
 299          return self::MOODLE_ID;
 300      }
 301  
 302      /**
 303       * Initializes other extensions.
 304       *
 305       * This method is called immediately after all extensions are activated but
 306       * before any extension `configure()` method is called. This allows extensions
 307       * to hook into the configuration of other extensions providing such an
 308       * extension point.
 309       *
 310       * @param ExtensionManager $extensionmanager
 311       */
 312      public function initialize(ExtensionManager $extensionmanager) {
 313          if (null !== $minkextension = $extensionmanager->getExtension('mink')) {
 314              $minkextension->registerDriverFactory(new WebDriverFactory());
 315          }
 316      }
 317  
 318      /**
 319       * You can modify the container here before it is dumped to PHP code.
 320       *
 321       * @param ContainerBuilder $container
 322       */
 323      public function process(ContainerBuilder $container) {
 324          // Load controller for definition printing.
 325          $this->loadDefinitionPrinters($container);
 326          $this->loadController($container);
 327      }
 328  
 329      /**
 330       * Alias old namespace of given. when and then for BC.
 331       */
 332      private function alias_old_namespaces() {
 333          class_alias('Moodle\\BehatExtension\\Context\\Step\\Given', 'Behat\\Behat\\Context\\Step\\Given', true);
 334          class_alias('Moodle\\BehatExtension\\Context\\Step\\When', 'Behat\\Behat\\Context\\Step\\When', true);
 335          class_alias('Moodle\\BehatExtension\\Context\\Step\\Then', 'Behat\\Behat\\Context\\Step\\Then', true);
 336      }
 337  }