ÿØÿà JFIF    ÿÛ „  ( %!1"%)+...383-7(-.+  -%%--------+------------/----------------------------ÿÀ  ¶" ÿÄ     ÿÄ @    !1AQaq‘¡"±ÁÑð2’BRráS‚ñ3b¢CT²Ò #cÿÄ    ÿÄ '   1!AQ2a"‘±#ÿÚ   ? ò(/p‘—–¢·,M|¯G»R£üešT. ôŸEoCFñ‡ 4ÔkåóxR|µÁ¨ÓDà!Ç´µ˜‡p ÍQ‹§!þž^õWsŒy  åÁ§$á«%ºq˜™JyyË0y«^Ϗ -3ÃÏËÈ£Æ\P’ä„s&•‹Üs üô'àWt¹pñCp$bÚF`·7.^)¾!)ÒÙ˜–ÅÏš^h¾ø³·ìe±ŒaeÖ—Í,e=B}Jéߦ$$½—šÙÜCC¯¶è8β–Åkèwx LÉngT±æ¹£Ócœ¿Í/©Éÿ ‰ÚCÒ­mq;Tt¶–}0"zÖPcd1š­¤tˆ„ÐÓâqú[™öUCŠ2޿د­É(¸ßúçšmW,Ò ºf‹Ån™ÄLêÁ8üÁkÁ€L¥ÍuJ1œôrÂSŒÓöt!7EUíïb)AËi¬â-ÙHk8òÁiXYwÅ)O^"ÇØriÛô{»wRUKÙÏÚlÓ¬€‚’שT ] ªb—ÊîKC^×M“xùäñz¬zJ× {4[¹M'IÆeH1LCWÕ]œûº WDæà°OÅ‚¹c²ÞuA–rL`HšpCez‡V’— Ž‚Ë„–ñ­'µR0ÕÖ=À:±G»C\nÆÉ5*¢ †3šhQ4ýÒi$Õ1"Ü]¢p-q3zcQ$ªö¨¥æf›‘»´ÝÚHá„e²JÇ–iÊ:·à¬Øc5Åk»Laª*wi [0Ówk ¦a¦0Õ²ÅÅ‚T¸’³q:Æ<¹ú Ò˜Æ}2VE•Ѐp¡Ô3Úº …h³^LJºçì¥Ánó|˜ÖˆÎˆ+« ¦dø‘Mº–d\ªDðÛ„Ì×LÝ——Ð9Ç|•FèçVm™¨€™3õS–9Äå¢<̉Ìm¢±`·º˜&Z²ž Ë®D«¶»6™‰©­HÈ Àá«h­i³04æ’hxœi3ï¨(ÓL·†‹¶ËpŒ ƒÜÒ? :õdsÀ –3æÓ·Üjâ¬9—]8n¼&]I F£¼…ì–—Œ0v°LǐÅfÛ2TÖ’aµ˜N@ÖBSÏ^à´4-±°b×nÈÈœÆD=KdŒø…ÄÈK,›ÖR§fjü$^ÔÎ!„6^ s:Üun\ôkCÞò÷<—Lúl Í›G¹ÀPá¼Nc¡šÒ…ÙWͳœ±vÊáЄ×9‹P‰cEØ‘™›µŸP<×g1 º©Ø¬wiÁ^l5× I/';Ë+´Z‡ÓÄFÕp[Èð’ HÄ ó 9¬Dl4“é±Ï”V=^Hð;žLädÉa‚°Ë,#/Ž~²BÔÛ QãñQtIeórVB,ÏÂf‹4#Μc‹É3ZIÖ Œ¢¥·üxa‡Ãçè„jŠ[5 Ä!U?,3žÎ×…ðdÓ÷háŠA‰ìÄ4÷ÃÜZÃE{‰®+W\ZÍE[‰»µk»MÝ­`¢©†˜ÃV‹-f¢¡†¢a«eŠ%«Y¨©q%jâd,Ôq`) ¤¦…šˆ ¦E>0Ùb²t–Ÿ  ÷jsςҒKÈcø(é›;k(’#ôŒQ ™C> õ³M9óºÆµ¤çâ;uÀ)5>RÔ¸fÓ~Ȧ—’-$fe«"FâUˆÊwµPÏu w+"åÉó¨6s™Û9(À¨ù©hE°½†n`m~“@©›p!Vkhµ Á1šbEqqÊ@ºèl›2 –Yã%‘‚hÛ{a¶‡ÅÞM²¬çL34®Çl]®‹ŽØŒm'RÜe35æöˆ­Á­ cP9šeÁköo´L³Ì=p8]”‡B¶,šº|ɏdzXˆØk*ÇÚ{#ñ‰pã(€¶‡9ý'šÜ³½¯kƒ†Â’ëSOƒ•Å®H6+a¢µˆ‹Y¨b˜b+XˆÖ!°u!¢"µˆˆn2ˆ Å0Ä`Å Ô®c(ÓÜGº•Ä»Œ âWî%q.áÐÄ÷î%umÍ£+ÜQ,Vn¦-GphU¸¢X­&,Gt ·K¢ÅÅ·¥RÄÊÉbKn N­R2g$VµÕó Qr#–ÓZUÏð0ÎtÙÀœz ëöo“Îk¯n„T°"SÞ+³/…€½"ižãO%Í%&Δâ‘ËØ´IˆÓ°Ë‰ÞM3.™yçÁzit›`4C`k¢¼á!álÄï sÂ[×!¤l¾æo"ƒIÔ'@ªu)3›ô®“@Z à¾] üœ'‡4¸PÞkÁ-qÄH‰ÓÍBd´ÿ —¹°ÄA&ÊUC„öz‚‚RæŠtf¬>˜ÑidšEçÑL÷áý¡V±h§­c¿¥¨ƒ@g¼…Ýö^ÞËc.ž„æ“I[ ª+·zÐÒÚ,½Ð»¶€C›7jk$๖€Ž¾ÅÙpp¶ÍþéΤ ½ØÎŒq ñpæžÅ¥#Øâ49ÃÂK^MYV“,¶Éz_òæ:…;Õ«¦I3âJ£¤û5$7Èxün\â]Êe¯(Ÿ&Ž‹¶2<6Ćf× æ¢ƱqWı[(ÙÂŽç‚T7¨@ÈÉÍß‚ïÙ"&3TYlGށ†)†#5ŠA©^T2ÆÁ†)©†©†¤yGXÁ†©"†)©¼ÅV WS†#©]R–r‹q5ÕbâWþBh¯u+ªÅÄ×ü„nÑ^êkªÁj‰jeœWˆÕÕ`µDµ2Ì+Ä µDµµ5Ô{¤Þ0RF’H÷AÛ<ÊÁ§ ƈæ0Ñ£ê4 öÚµ[¬/1²€â<-¬šö²r×0o×w ´xÐÒË„ g01¬„åEM蛇Ã\6µE›‘fÙO k÷CºnÓ1`iîÒ² áÂ!ÑLÄÿ ³¬\õ*:M=Ç»c‹2[‰ØsÕLò\ŒK L^™üÄN\¶#» Ç\……ßïIœÉ$¸ÎñÍ®ÔHÇ Ûí/´>Li”„†g^$ 7NÍÑÑ2M2ªNg=k½Ð}Ÿl6‡ —JWˆ”ÞéT·+µ¦üÂFè¢Vs6ýÆ4»2+2IÝ9ô9®ÇGÙÇ1+Ä8;*Õ§ž½D*îÑWÚçá3Ma¤·ˆ¥t}š²Ü…Q+Æüµ8HíS”üxGéÁˆ¦Ël„éÈ 5 µÄƒ6äÜ(kIš¯S†fÖ&¸®×v|Äyˆ*禌Éù@7-®Ä[]ÎÖ¿ê`º…=:…žJVnݳvðÖKE¼‚ »mà ÷f®:Ë ™–4rð¡4` ܹçž~‹C=œ…¾Ïj|VDtK.ƒ"9 Ï ­–éXá³6WÒïIL-Ї Ç BehatExtension/EventDispatcher/Tester/MoodleEventDispatchingStepTester.php000064400000011772152267215570023201 0ustar00. namespace Moodle\BehatExtension\EventDispatcher\Tester; // phpcs:disable moodle.NamingConventions.ValidFunctionName.LowercaseMethod use Behat\Behat\EventDispatcher\Event\AfterStepSetup; use Behat\Behat\EventDispatcher\Event\AfterStepTested; use Behat\Behat\EventDispatcher\Event\BeforeStepTeardown; use Behat\Behat\EventDispatcher\Event\BeforeStepTested; use Behat\Behat\Tester\Result\StepResult; use Behat\Behat\Tester\StepTester; use Behat\Gherkin\Node\FeatureNode; use Behat\Gherkin\Node\StepNode; use Behat\Testwork\Environment\Environment; use Behat\Testwork\EventDispatcher\TestworkEventDispatcher; use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** * Step tester dispatching BEFORE/AFTER events during tests. * * @package core * @copyright 2016 Rajesh Taneja * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class MoodleEventDispatchingStepTester implements StepTester { /** @var StepTester */ private $basetester; /** @var EventDispatcherInterface */ private $eventdispatcher; /** * Initializes tester. * * @param StepTester $basetester * @param EventDispatcherInterface $eventdispatcher */ public function __construct(StepTester $basetester, EventDispatcherInterface $eventdispatcher) { $this->basetester = $basetester; $this->eventdispatcher = $eventdispatcher; } /** * Sets up step for a test. * * @param Environment $env * @param FeatureNode $feature * @param StepNode $step * @param bool $skip * * @return Setup */ public function setUp(Environment $env, FeatureNode $feature, StepNode $step, $skip) { $event = new BeforeStepTested($env, $feature, $step); if (TestworkEventDispatcher::DISPATCHER_VERSION === 2) { // Symfony 4.3 and up. $this->eventdispatcher->dispatch($event, $event::BEFORE); } else { // TODO: Remove when our min supported version is >= 4.3. $this->eventdispatcher->dispatch($event::BEFORE, $event); } $setup = $this->basetester->setUp($env, $feature, $step, $skip); $this->basetester->setEventDispatcher($this->eventdispatcher); $event = new AfterStepSetup($env, $feature, $step, $setup); if (TestworkEventDispatcher::DISPATCHER_VERSION === 2) { // Symfony 4.3 and up. $this->eventdispatcher->dispatch($event, $event::AFTER_SETUP); } else { // TODO: Remove when our min supported version is >= 4.3. $this->eventdispatcher->dispatch($event::AFTER_SETUP, $event); } return $setup; } /** * Tests step. * * @param Environment $env * @param FeatureNode $feature * @param StepNode $step * @param bool $skip * @return StepResult */ public function test(Environment $env, FeatureNode $feature, StepNode $step, $skip) { return $this->basetester->test($env, $feature, $step, $skip); } /** * Tears down step after a test. * * @param Environment $env * @param FeatureNode $feature * @param StepNode $step * @param bool $skip * @param StepResult $result * @return Teardown */ public function tearDown(Environment $env, FeatureNode $feature, StepNode $step, $skip, StepResult $result) { $event = new BeforeStepTeardown($env, $feature, $step, $result); if (TestworkEventDispatcher::DISPATCHER_VERSION === 2) { // Symfony 4.3 and up. $this->eventdispatcher->dispatch($event, $event::BEFORE_TEARDOWN); } else { // TODO: Remove when our min supported version is >= 4.3. $this->eventdispatcher->dispatch($event::BEFORE_TEARDOWN, $event); } $teardown = $this->basetester->tearDown($env, $feature, $step, $skip, $result); $event = new AfterStepTested($env, $feature, $step, $result, $teardown); if (TestworkEventDispatcher::DISPATCHER_VERSION === 2) { // Symfony 4.3 and up. $this->eventdispatcher->dispatch($event, $event::AFTER); } else { // TODO: Remove when our min supported version is >= 4.3. $this->eventdispatcher->dispatch($event::AFTER, $event); } return $teardown; } } BehatExtension/EventDispatcher/Tester/ChainedStepTester.php000064400000023060152267215570020126 0ustar00. namespace Moodle\BehatExtension\EventDispatcher\Tester; use Behat\Behat\EventDispatcher\Event\AfterStepSetup; use Behat\Behat\EventDispatcher\Event\AfterStepTested; use Behat\Behat\EventDispatcher\Event\BeforeStepTeardown; use Behat\Behat\EventDispatcher\Event\BeforeStepTested; use Behat\Behat\Tester\Result\ExecutedStepResult; use Behat\Behat\Tester\Result\SkippedStepResult; use Behat\Behat\Tester\Result\StepResult; use Behat\Behat\Tester\Result\UndefinedStepResult; use Behat\Behat\Tester\StepTester; use Behat\Gherkin\Node\FeatureNode; use Behat\Gherkin\Node\StepNode; use Behat\Testwork\Call\CallResult; use Behat\Testwork\Environment\Environment; use Behat\Testwork\EventDispatcher\TestworkEventDispatcher; use Moodle\BehatExtension\Context\Step\ChainedStep; use Moodle\BehatExtension\Exception\SkippedException; use Symfony\Component\EventDispatcher\EventDispatcherInterface; // phpcs:disable moodle.NamingConventions.ValidFunctionName.LowercaseMethod /** * Override step tester to ensure chained steps gets executed. * * @package core * @copyright 2016 Rajesh Taneja * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class ChainedStepTester implements StepTester { /** * The text of the step to look for exceptions / debugging messages. */ const EXCEPTIONS_STEP_TEXT = 'I look for exceptions'; /** * @var StepTester Base step tester. */ private $singlesteptester; /** * @var EventDispatcher keep step event dispatcher. */ private $eventdispatcher; /** * Keep status of chained steps if used. * @var bool */ protected static $chainedstepused = false; /** * Constructor. * * @param StepTester $steptester single step tester. */ public function __construct(StepTester $steptester) { $this->singlesteptester = $steptester; } /** * Set event dispatcher to use for events. * * @param EventDispatcherInterface $eventdispatcher */ public function setEventDispatcher(EventDispatcherInterface $eventdispatcher) { $this->eventdispatcher = $eventdispatcher; } /** * Sets up step for a test. * * @param Environment $env * @param FeatureNode $feature * @param StepNode $step * @param bool $skip * * @return Setup */ public function setUp(Environment $env, FeatureNode $feature, StepNode $step, $skip) { return $this->singlesteptester->setUp($env, $feature, $step, $skip); } /** * Tests step. * * @param Environment $env * @param FeatureNode $feature * @param StepNode $step * @param bool $skip * @return StepResult */ public function test(Environment $env, FeatureNode $feature, StepNode $step, $skip) { $result = $this->singlesteptester->test($env, $feature, $step, $skip); if (!($result instanceof ExecutedStepResult) || !$this->supportsResult($result->getCallResult())) { $result = $this->checkSkipResult($result); // If undefined step then don't continue chained steps. if ($result instanceof UndefinedStepResult) { return $result; } // If exception caught, then don't continue chained steps. if (($result instanceof ExecutedStepResult) && $result->hasException()) { return $result; } // If step is skipped, then return. no need to continue chain steps. if ($result instanceof SkippedStepResult) { return $result; } // Check for exceptions. // Extra step, looking for a moodle exception, a debugging() message or a PHP debug message. $checkingstep = new StepNode('Given', self::EXCEPTIONS_STEP_TEXT, [], $step->getLine()); $afterexceptioncheckingevent = $this->singlesteptester->test($env, $feature, $checkingstep, $skip); $exceptioncheckresult = $this->checkSkipResult($afterexceptioncheckingevent); if (!$exceptioncheckresult->isPassed()) { return $exceptioncheckresult; } return $result; } return $this->runChainedSteps($env, $feature, $result, $skip); } /** * Tears down step after a test. * * @param Environment $env * @param FeatureNode $feature * @param StepNode $step * @param bool $skip * @param StepResult $result * @return Teardown */ public function tearDown(Environment $env, FeatureNode $feature, StepNode $step, $skip, StepResult $result) { return $this->singlesteptester->tearDown($env, $feature, $step, $skip, $result); } /** * Check if results supported. * * @param CallResult $result * @return bool */ private function supportsResult(CallResult $result) { $return = $result->getReturn(); if ($return instanceof ChainedStep) { return true; } if (!is_array($return) || empty($return)) { return false; } foreach ($return as $value) { if (!$value instanceof ChainedStep) { return false; } } return true; } /** * Run chained steps. * * @param Environment $env * @param FeatureNode $feature * @param ExecutedStepResult $result * @param bool $skip * @return ExecutedStepResult|StepResult */ private function runChainedSteps(Environment $env, FeatureNode $feature, ExecutedStepResult $result, $skip) { // Set chained setp is used, so it can be used by formatter to o/p. self::$chainedstepused = true; $callresult = $result->getCallResult(); $steps = $callresult->getReturn(); if (!is_array($steps)) { // Test it, no need to dispatch events for single chain. $stepresult = $this->test($env, $feature, $steps, $skip); return $this->checkSkipResult($stepresult); } // Test all steps. foreach ($steps as $step) { // Setup new step. $event = new BeforeStepTested($env, $feature, $step); if (TestworkEventDispatcher::DISPATCHER_VERSION === 2) { // Symfony 4.3 and up. $this->eventdispatcher->dispatch($event, $event::BEFORE); } else { // TODO: Remove when our min supported version is >= 4.3. $this->eventdispatcher->dispatch($event::BEFORE, $event); } $setup = $this->setUp($env, $feature, $step, $skip); $event = new AfterStepSetup($env, $feature, $step, $setup); if (TestworkEventDispatcher::DISPATCHER_VERSION === 2) { // Symfony 4.3 and up. $this->eventdispatcher->dispatch($event, $event::AFTER_SETUP); } else { // TODO: Remove when our min supported version is >= 4.3. $this->eventdispatcher->dispatch($event::AFTER_SETUP, $event); } // Test it. $stepresult = $this->test($env, $feature, $step, $skip); // Tear down. $event = new BeforeStepTeardown($env, $feature, $step, $result); if (TestworkEventDispatcher::DISPATCHER_VERSION === 2) { // Symfony 4.3 and up. $this->eventdispatcher->dispatch($event, $event::BEFORE_TEARDOWN); } else { // TODO: Remove when our min supported version is >= 4.3. $this->eventdispatcher->dispatch($event::BEFORE_TEARDOWN, $event); } $teardown = $this->tearDown($env, $feature, $step, $skip, $result); $event = new AfterStepTested($env, $feature, $step, $result, $teardown); if (TestworkEventDispatcher::DISPATCHER_VERSION === 2) { // Symfony 4.3 and up. $this->eventdispatcher->dispatch($event, $event::AFTER); } else { // TODO: Remove when our min supported version is >= 4.3. $this->eventdispatcher->dispatch($event::AFTER, $event); } if (!$stepresult->isPassed()) { return $this->checkSkipResult($stepresult); } } return $this->checkSkipResult($stepresult); } /** * Handle skip exception. * * @param StepResult $result * * @return ExecutedStepResult|SkippedStepResult */ private function checkSkipResult(StepResult $result) { if ((method_exists($result, 'getException')) && ($result->getException() instanceof SkippedException)) { return new SkippedStepResult($result->getSearchResult()); } else { return $result; } } /** * Returns if cahined steps are used. * @return bool. */ public static function is_chained_step_used() { return self::$chainedstepused; } } BehatExtension/ServiceContainer/services/core.xml000064400000002517152267215570016245 0ustar00 Moodle\BehatExtension\Context\Initializer\MoodleAwareInitializer Moodle\BehatExtension\Context\ContextClass\ClassResolver Behat\Mink\Selector\SelectorsHandler %behat.moodle.parameters% %behat.moodle.parameters% BehatExtension/ServiceContainer/BehatExtension.php000064400000026274152267215570016407 0ustar00. namespace Moodle\BehatExtension\ServiceContainer; use Behat\Behat\Definition\ServiceContainer\DefinitionExtension; use Behat\Behat\EventDispatcher\ServiceContainer\EventDispatcherExtension; use Behat\Behat\Gherkin\ServiceContainer\GherkinExtension; use Behat\Behat\Tester\ServiceContainer\TesterExtension; use Behat\Testwork\Cli\ServiceContainer\CliExtension; use Behat\Testwork\Output\ServiceContainer\OutputExtension; use Behat\Testwork\ServiceContainer\Extension as ExtensionInterface; use Behat\Testwork\ServiceContainer\ExtensionManager; use Behat\Testwork\ServiceContainer\ServiceProcessor; use Behat\Testwork\Suite\ServiceContainer\SuiteExtension; use Moodle\BehatExtension\Driver\WebDriverFactory; use Moodle\BehatExtension\Output\Formatter\MoodleProgressFormatterFactory; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\Reference; // phpcs:disable moodle.NamingConventions.ValidFunctionName.LowercaseMethod /** * Behat extension for moodle * * Provides multiple features directory loading (Gherkin\Loader\MoodleFeaturesSuiteLoader * * @package core * @copyright 2016 Rajesh Taneja * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class BehatExtension implements ExtensionInterface { /** @var string Extension configuration ID */ const MOODLE_ID = 'moodle'; /** @var ServiceProcessor */ private $processor; /** * Initializes compiler pass. * * @param null|ServiceProcessor $processor */ public function __construct(?ServiceProcessor $processor = null) { $this->processor = $processor ? : new ServiceProcessor(); } /** * Loads moodle specific configuration. * * @param ContainerBuilder $container ContainerBuilder instance * @param array $config Extension configuration hash (from behat.yml) */ public function load(ContainerBuilder $container, array $config) { $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/services')); $loader->load('core.xml'); // Getting the extension parameters. $container->setParameter('behat.moodle.parameters', $config); // Load moodle progress formatter. $moodleprogressformatter = new MoodleProgressFormatterFactory(); $moodleprogressformatter->buildFormatter($container); // Load custom step tester event dispatcher. $this->loadEventDispatchingStepTester($container); // Load chained step tester. $this->loadChainedStepTester($container); // Load step count formatter. $this->loadMoodleListFormatter($container); // Load step count formatter. $this->loadMoodleStepcountFormatter($container); // Load screenshot formatter. $this->loadMoodleScreenshotFormatter($container); // Load namespace alias. $this->alias_old_namespaces(); } /** * Loads moodle List formatter. * * @param ContainerBuilder $container */ protected function loadMoodleListFormatter(ContainerBuilder $container) { $definition = new Definition('Moodle\BehatExtension\Output\Formatter\MoodleListFormatter', [ 'moodle_list', 'List all scenarios. Use with --dry-run', ['stepcount' => false], $this->createOutputPrinterDefinition() ]); $definition->addTag(OutputExtension::FORMATTER_TAG, ['priority' => 101]); $container->setDefinition(OutputExtension::FORMATTER_TAG . '.moodle_list', $definition); } /** * Loads moodle Step count formatter. * * @param ContainerBuilder $container */ protected function loadMoodleStepcountFormatter(ContainerBuilder $container) { $definition = new Definition('Moodle\BehatExtension\Output\Formatter\MoodleStepcountFormatter', [ 'moodle_stepcount', 'Count steps in feature files. Use with --dry-run', ['stepcount' => false], $this->createOutputPrinterDefinition() ]); $definition->addTag(OutputExtension::FORMATTER_TAG, ['priority' => 101]); $container->setDefinition(OutputExtension::FORMATTER_TAG . '.moodle_stepcount', $definition); } /** * Loads moodle screenshot formatter. * * @param ContainerBuilder $container */ protected function loadMoodleScreenshotFormatter(ContainerBuilder $container) { $definition = new Definition('Moodle\BehatExtension\Output\Formatter\MoodleScreenshotFormatter', [ 'moodle_screenshot', // phpcs:ignore Generic.Files.LineLength.TooLong 'Take screenshot of all steps. Use --format-settings \'{"formats": "html,image"}\' to get specific o/p type', ['formats' => 'html,image'], $this->createOutputPrinterDefinition() ]); $definition->addTag(OutputExtension::FORMATTER_TAG, ['priority' => 102]); $container->setDefinition(OutputExtension::FORMATTER_TAG . '.moodle_screenshot', $definition); } /** * Creates output printer definition. * * @return Definition */ protected function createOutputPrinterDefinition() { return new Definition('Behat\Testwork\Output\Printer\StreamOutputPrinter', [ new Definition('Behat\Behat\Output\Printer\ConsoleOutputFactory'), ]); } /** * Loads definition printers. * * @param ContainerBuilder $container */ private function loadDefinitionPrinters(ContainerBuilder $container) { $definition = new Definition('Moodle\BehatExtension\Definition\Printer\ConsoleDefinitionInformationPrinter', [ new Reference(CliExtension::OUTPUT_ID), new Reference(DefinitionExtension::PATTERN_TRANSFORMER_ID), new Reference(DefinitionExtension::DEFINITION_TRANSLATOR_ID), new Reference(GherkinExtension::KEYWORDS_ID) ]); $container->removeDefinition('definition.information_printer'); $container->setDefinition('definition.information_printer', $definition); } /** * Loads definition controller. * * @param ContainerBuilder $container */ private function loadController(ContainerBuilder $container) { $definition = new Definition('Moodle\BehatExtension\Definition\Cli\AvailableDefinitionsController', [ new Reference(SuiteExtension::REGISTRY_ID), new Reference(DefinitionExtension::WRITER_ID), new Reference('definition.list_printer'), new Reference('definition.information_printer') ]); $container->removeDefinition(CliExtension::CONTROLLER_TAG . '.available_definitions'); $container->setDefinition(CliExtension::CONTROLLER_TAG . '.available_definitions', $definition); } /** * Loads chained step tester. * * @param ContainerBuilder $container */ protected function loadChainedStepTester(ContainerBuilder $container) { // Chained steps. $definition = new Definition('Moodle\BehatExtension\EventDispatcher\Tester\ChainedStepTester', [ new Reference(TesterExtension::STEP_TESTER_ID), ]); $definition->addTag(TesterExtension::STEP_TESTER_WRAPPER_TAG, ['priority' => 100]); $container->setDefinition(TesterExtension::STEP_TESTER_WRAPPER_TAG . '.substep', $definition); } /** * Loads event-dispatching step tester. * * @param ContainerBuilder $container */ protected function loadEventDispatchingStepTester(ContainerBuilder $container) { $definition = new Definition('Moodle\BehatExtension\EventDispatcher\Tester\MoodleEventDispatchingStepTester', [ new Reference(TesterExtension::STEP_TESTER_ID), new Reference(EventDispatcherExtension::DISPATCHER_ID) ]); $definition->addTag(TesterExtension::STEP_TESTER_WRAPPER_TAG, ['priority' => -9999]); $container->setDefinition(TesterExtension::STEP_TESTER_WRAPPER_TAG . '.event_dispatching', $definition); } /** * Setups configuration for current extension. * * @param ArrayNodeDefinition $builder */ public function configure(ArrayNodeDefinition $builder) { // phpcs:disable PEAR.WhiteSpace.ObjectOperatorIndent.Incorrect $builder->children() ->arrayNode('capabilities') ->useAttributeAsKey('key') ->prototype('variable')->end() ->end() ->arrayNode('steps_definitions') ->useAttributeAsKey('key') ->prototype('variable')->end() ->end() ->scalarNode('moodledirroot') ->defaultNull() ->end() ->end() ->end(); // phpcs:enable PEAR.WhiteSpace.ObjectOperatorIndent.Incorrect } /** * Returns the extension config key. * * @return string */ public function getConfigKey() { return self::MOODLE_ID; } /** * Initializes other extensions. * * This method is called immediately after all extensions are activated but * before any extension `configure()` method is called. This allows extensions * to hook into the configuration of other extensions providing such an * extension point. * * @param ExtensionManager $extensionmanager */ public function initialize(ExtensionManager $extensionmanager) { if (null !== $minkextension = $extensionmanager->getExtension('mink')) { $minkextension->registerDriverFactory(new WebDriverFactory()); } } /** * You can modify the container here before it is dumped to PHP code. * * @param ContainerBuilder $container */ public function process(ContainerBuilder $container) { // Load controller for definition printing. $this->loadDefinitionPrinters($container); $this->loadController($container); } /** * Alias old namespace of given. when and then for BC. */ private function alias_old_namespaces() { class_alias('Moodle\\BehatExtension\\Context\\Step\\Given', 'Behat\\Behat\\Context\\Step\\Given', true); class_alias('Moodle\\BehatExtension\\Context\\Step\\When', 'Behat\\Behat\\Context\\Step\\When', true); class_alias('Moodle\\BehatExtension\\Context\\Step\\Then', 'Behat\\Behat\\Context\\Step\\Then', true); } } BehatExtension/Exception/SkippedException.php000064400000001601152267215570015423 0ustar00. namespace Moodle\BehatExtension\Exception; /** * Skipped exception (throw this to mark step as "skipped"). * * @package core * @author Jerome Mouneyrac */ class SkippedException extends \Exception { } BehatExtension/Output/Formatter/MoodleListFormatter.php000064400000007644152267215570017426 0ustar00. namespace Moodle\BehatExtension\Output\Formatter; use Behat\Behat\EventDispatcher\Event\AfterOutlineTested; use Behat\Behat\EventDispatcher\Event\AfterScenarioTested; use Behat\Testwork\Output\Formatter; use Behat\Testwork\Output\Printer\OutputPrinter; // phpcs:disable moodle.NamingConventions.ValidFunctionName.LowercaseMethod /** * Feature step counter for distributing features between parallel runs. * * Use it with --dry-run (and any other selectors combination) to * get the results quickly. * * @package core * @copyright 2015 onwards Rajesh Taneja * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class MoodleListFormatter implements Formatter { /** @var OutputPrinter */ private $printer; /** @var array */ private $parameters; /** @var string */ private $name; /** @var string */ private $description; /** * Initializes formatter. * * @param string $name * @param string $description * @param array $parameters * @param OutputPrinter $printer */ public function __construct($name, $description, array $parameters, OutputPrinter $printer) { $this->name = $name; $this->description = $description; $this->parameters = $parameters; $this->printer = $printer; } /** * Returns an array of event names this subscriber wants to listen to. * * @return array The event names to listen to */ public static function getSubscribedEvents() { return [ 'tester.scenario_tested.after' => 'afterScenario', 'tester.outline_tested.after' => 'afterOutlineExample', ]; } /** * Returns formatter name. * * @return string */ public function getName() { return $this->name; } /** * Returns formatter description. * * @return string */ public function getDescription() { return $this->description; } /** * Returns formatter output printer. * * @return OutputPrinter */ public function getOutputPrinter() { return $this->printer; } /** * Sets formatter parameter. * * @param string $name * @param mixed $value */ public function setParameter($name, $value) { $this->parameters[$name] = $value; } /** * Returns parameter name. * * @param string $name * * @return mixed */ public function getParameter($name) { return isset($this->parameters[$name]) ? $this->parameters[$name] : null; } /** * Listens to "scenario.after" event. * * @param AfterScenarioTested $event */ public function afterScenario(AfterScenarioTested $event) { $scenario = $event->getScenario(); $this->printer->writeln($event->getFeature()->getFile() . ':' . $scenario->getLine()); } /** * Listens to "outline.example.after" event. * * @param AfterOutlineTested $event */ public function afterOutlineExample(AfterOutlineTested $event) { $outline = $event->getOutline(); $line = $outline->getLine(); $this->printer->writeln($event->getFeature()->getFile() . ':' . $line); } } BehatExtension/Output/Formatter/MoodleProgressFormatterFactory.php000064400000020702152267215570021635 0ustar00. namespace Moodle\BehatExtension\Output\Formatter; use Behat\Testwork\Exception\ServiceContainer\ExceptionExtension; use Behat\Testwork\Output\ServiceContainer\Formatter\FormatterFactory; use Behat\Testwork\Output\ServiceContainer\OutputExtension; use Behat\Testwork\ServiceContainer\ServiceProcessor; use Behat\Testwork\Translator\ServiceContainer\TranslatorExtension; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; // phpcs:disable moodle.NamingConventions.ValidFunctionName.LowercaseMethod /** * Moodle behat context class resolver. * * @package core * @copyright 2016 Rajesh Taneja * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class MoodleProgressFormatterFactory implements FormatterFactory { /** @var ServiceProcessor */ private $processor; /** @var string moodle progress ID */ const ROOT_LISTENER_ID_MOODLE = 'output.node.listener.moodleprogress'; /** @var string moodle printer ID */ const RESULT_TO_STRING_CONVERTER_ID_MOODLE = 'output.node.printer.result_to_string'; /** @var string Available extension points */ const ROOT_LISTENER_WRAPPER_TAG_MOODLE = 'output.node.listener.moodleprogress.wrapper'; /** * Initializes extension. * * @param null|ServiceProcessor $processor */ public function __construct(?ServiceProcessor $processor = null) { $this->processor = $processor ? : new ServiceProcessor(); } /** * Builds formatter configuration. * * @param ContainerBuilder $container */ public function buildFormatter(ContainerBuilder $container) { $this->loadRootNodeListener($container); $this->loadCorePrinters($container); $this->loadPrinterHelpers($container); $this->loadFormatter($container); } /** * Processes formatter configuration. * * @param ContainerBuilder $container */ public function processFormatter(ContainerBuilder $container) { $this->processListenerWrappers($container); } /** * Loads progress formatter node event listener. * * @param ContainerBuilder $container */ protected function loadRootNodeListener(ContainerBuilder $container) { $definition = new Definition('Behat\Behat\Output\Node\EventListener\AST\StepListener', [ new Reference('output.node.printer.moodleprogress.step') ]); $container->setDefinition(self::ROOT_LISTENER_ID_MOODLE, $definition); } /** * Loads formatter itself. * * @param ContainerBuilder $container */ protected function loadFormatter(ContainerBuilder $container) { $definition = new Definition('Behat\Behat\Output\Statistics\TotalStatistics'); $container->setDefinition('output.moodleprogress.statistics', $definition); $moodleconfig = $container->getParameter('behat.moodle.parameters'); $definition = new Definition( 'Moodle\BehatExtension\Output\Printer\MoodleProgressPrinter', [$moodleconfig['moodledirroot']] ); $container->setDefinition('moodle.output.node.printer.moodleprogress.printer', $definition); $definition = new Definition('Behat\Testwork\Output\NodeEventListeningFormatter', [ 'moodle_progress', 'Prints information about then run followed by one character per step.', [ 'timer' => true ], $this->createOutputPrinterDefinition(), new Definition('Behat\Testwork\Output\Node\EventListener\ChainEventListener', [ [ new Reference(self::ROOT_LISTENER_ID_MOODLE), new Definition('Behat\Behat\Output\Node\EventListener\Statistics\StatisticsListener', [ new Reference('output.moodleprogress.statistics'), new Reference('output.node.printer.moodleprogress.statistics') ]), new Definition('Behat\Behat\Output\Node\EventListener\Statistics\ScenarioStatsListener', [ new Reference('output.moodleprogress.statistics') ]), new Definition('Behat\Behat\Output\Node\EventListener\Statistics\StepStatsListener', [ new Reference('output.moodleprogress.statistics'), new Reference(ExceptionExtension::PRESENTER_ID) ]), new Definition('Behat\Behat\Output\Node\EventListener\Statistics\HookStatsListener', [ new Reference('output.moodleprogress.statistics'), new Reference(ExceptionExtension::PRESENTER_ID) ]), new Definition('Behat\Behat\Output\Node\EventListener\AST\SuiteListener', [ new Reference('moodle.output.node.printer.moodleprogress.printer') ]) ] ]) ]); $definition->addTag(OutputExtension::FORMATTER_TAG, ['priority' => 1]); $container->setDefinition(OutputExtension::FORMATTER_TAG . '.moodleprogress', $definition); } /** * Loads printer helpers. * * @param ContainerBuilder $container */ protected function loadPrinterHelpers(ContainerBuilder $container) { $definition = new Definition('Behat\Behat\Output\Node\Printer\Helper\ResultToStringConverter'); $container->setDefinition(self::RESULT_TO_STRING_CONVERTER_ID_MOODLE, $definition); } /** * Loads feature, scenario and step printers. * * @param ContainerBuilder $container */ protected function loadCorePrinters(ContainerBuilder $container) { $definition = new Definition('Behat\Behat\Output\Node\Printer\CounterPrinter', [ new Reference(self::RESULT_TO_STRING_CONVERTER_ID_MOODLE), new Reference(TranslatorExtension::TRANSLATOR_ID), ]); $container->setDefinition('output.node.moodle.printer.counter', $definition); $definition = new Definition('Behat\Behat\Output\Node\Printer\ListPrinter', [ new Reference(self::RESULT_TO_STRING_CONVERTER_ID_MOODLE), new Reference(ExceptionExtension::PRESENTER_ID), new Reference(TranslatorExtension::TRANSLATOR_ID), '%paths.base%' ]); $container->setDefinition('output.node.moodle.printer.list', $definition); $definition = new Definition('Behat\Behat\Output\Node\Printer\Progress\ProgressStepPrinter', [ new Reference(self::RESULT_TO_STRING_CONVERTER_ID_MOODLE) ]); $container->setDefinition('output.node.printer.moodleprogress.step', $definition); $definition = new Definition('Behat\Behat\Output\Node\Printer\Progress\ProgressStatisticsPrinter', [ new Reference('output.node.moodle.printer.counter'), new Reference('output.node.moodle.printer.list') ]); $container->setDefinition('output.node.printer.moodleprogress.statistics', $definition); } /** * Creates output printer definition. * * @return Definition */ protected function createOutputPrinterDefinition() { return new Definition('Behat\Testwork\Output\Printer\StreamOutputPrinter', [ new Definition('Behat\Behat\Output\Printer\ConsoleOutputFactory'), ]); } /** * Processes all registered pretty formatter node listener wrappers. * * @param ContainerBuilder $container */ protected function processListenerWrappers(ContainerBuilder $container) { $this->processor->processWrapperServices( $container, self::ROOT_LISTENER_ID_MOODLE, self::ROOT_LISTENER_WRAPPER_TAG_MOODLE ); } } BehatExtension/Output/Formatter/MoodleScreenshotFormatter.php000064400000022537152267215570020626 0ustar00. namespace Moodle\BehatExtension\Output\Formatter; use Behat\Behat\EventDispatcher\Event\AfterStepTested; use Behat\Behat\EventDispatcher\Event\BeforeScenarioTested; use Behat\Behat\EventDispatcher\Event\BeforeStepTested; use Behat\Testwork\Output\Formatter; use Behat\Testwork\Output\Printer\OutputPrinter; // phpcs:disable moodle.NamingConventions.ValidFunctionName.LowercaseMethod /** * Feature step counter for distributing features between parallel runs. * * Use it with --dry-run (and any other selectors combination) to * get the results quickly. * * @package core * @copyright 2016 onwards Rajesh Taneja * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class MoodleScreenshotFormatter implements Formatter { /** @var OutputPrinter */ private $printer; /** @var array */ private $parameters; /** @var string */ private $name; /** @var string */ private $description; /** @var int The scenario count */ protected static $currentscenariocount = 0; /** @var int The step count within the current scenario */ protected static $currentscenariostepcount = 0; /** * If we are saving any kind of dump on failure we should use the same parent dir during a run. * * @var The parent dir name */ protected static $faildumpdirname = false; /** * Initializes formatter. * * @param string $name * @param string $description * @param array $parameters * @param OutputPrinter $printer */ public function __construct($name, $description, array $parameters, OutputPrinter $printer) { $this->name = $name; $this->description = $description; $this->parameters = $parameters; $this->printer = $printer; } /** * Returns an array of event names this subscriber wants to listen to. * * @return array The event names to listen to */ public static function getSubscribedEvents() { return [ 'tester.scenario_tested.before' => 'beforeScenario', 'tester.step_tested.before' => 'beforeStep', 'tester.step_tested.after' => 'afterStep', ]; } /** * Returns formatter name. * * @return string */ public function getName() { return $this->name; } /** * Returns formatter description. * * @return string */ public function getDescription() { return $this->description; } /** * Returns formatter output printer. * * @return OutputPrinter */ public function getOutputPrinter() { return $this->printer; } /** * Sets formatter parameter. * * @param string $name * @param mixed $value */ public function setParameter($name, $value) { $this->parameters[$name] = $value; } /** * Returns parameter name. * * @param string $name * @return mixed */ public function getParameter($name) { return isset($this->parameters[$name]) ? $this->parameters[$name] : null; } /** * Reset currentscenariostepcount * * @param BeforeScenarioTested $event */ public function beforeScenario(BeforeScenarioTested $event) { self::$currentscenariostepcount = 0; self::$currentscenariocount++; } /** * Increment currentscenariostepcount * * @param BeforeStepTested $event */ public function beforeStep(BeforeStepTested $event) { self::$currentscenariostepcount++; } /** * Take screenshot after step is executed. Behat\Behat\Event\html * * @param AfterStepTested $event */ public function afterStep(AfterStepTested $event) { $behathookcontext = $event->getEnvironment()->getContext('behat_hooks'); $formats = $this->getParameter('formats'); $formats = explode(',', $formats); // Take screenshot. if (in_array('image', $formats)) { $this->take_screenshot($event, $behathookcontext); } // Save html content. if (in_array('html', $formats)) { $this->take_contentdump($event, $behathookcontext); } } /** * Return screenshot directory where all screenshots will be saved. * * @return string */ protected function get_run_screenshot_dir() { global $CFG; if (self::$faildumpdirname) { return self::$faildumpdirname; } // If output_path is set then use output_path else use faildump_path. if ($this->getOutputPrinter()->getOutputPath()) { $screenshotpath = $this->getOutputPrinter()->getOutputPath(); } else if ($CFG->behat_faildump_path) { $screenshotpath = $CFG->behat_faildump_path; } else { // It should never reach here. throw new FormatterException('You should specify --out "SOME/PATH" for moodle_screenshot format'); } if ($this->getParameter('dir_permissions')) { $dirpermissions = $this->getParameter('dir_permissions'); } else { $dirpermissions = 0777; } // All the screenshot dumps should be in the same parent dir. self::$faildumpdirname = $screenshotpath . DIRECTORY_SEPARATOR . date('Ymd_His'); if (!is_dir(self::$faildumpdirname) && !mkdir(self::$faildumpdirname, $dirpermissions, true)) { // It shouldn't, we already checked that the directory is writable. throw new FormatterException(sprintf( 'No directories can be created inside %s, check the directory permissions.', $screenshotpath )); } return self::$faildumpdirname; } /** * Take screenshot when a step fails. * * @throws Exception * @param AfterStepTested $event * @param Context $context */ protected function take_screenshot(AfterStepTested $event, $context) { // BrowserKit can't save screenshots. if ($context->getMink()->isSessionStarted($context->getMink()->getDefaultSessionName())) { if (get_class($context->getMink()->getSession()->getDriver()) === 'Behat\Mink\Driver\BrowserKitDriver') { return false; } list ($dir, $filename) = $this->get_faildump_filename($event, 'png'); $context->saveScreenshot($filename, $dir); } } /** * Take a dump of the page content when a step fails. * * @throws Exception * @param AfterStepTested $event * @param \Behat\Context\Context\Context $context */ protected function take_contentdump(AfterStepTested $event, $context) { list ($dir, $filename) = $this->get_faildump_filename($event, 'html'); $fh = fopen($dir . DIRECTORY_SEPARATOR . $filename, 'w'); fwrite($fh, $context->getMink()->getSession()->getPage()->getContent()); fclose($fh); } /** * Determine the full pathname to store a failure-related dump. * * This is used for content such as the DOM, and screenshots. * * @param AfterStepTested $event * @param String $filetype The file suffix to use. Limited to 4 chars. */ protected function get_faildump_filename(AfterStepTested $event, $filetype) { // Make a directory for the scenario. $featurename = $event->getFeature()->getTitle(); $featurename = preg_replace('/([^a-zA-Z0-9\_]+)/', '-', $featurename); if ($this->getParameter('dir_permissions')) { $dirpermissions = $this->getParameter('dir_permissions'); } else { $dirpermissions = 0777; } $dir = $this->get_run_screenshot_dir(); // We want a i-am-the-scenario-title format. $dir = $dir . DIRECTORY_SEPARATOR . self::$currentscenariocount . '-' . $featurename; if (!is_dir($dir) && !mkdir($dir, $dirpermissions, true)) { // We already checked that the directory is writable. This should not fail. throw new FormatterException(sprintf( 'No directories can be created inside %s, check the directory permissions.', $dir )); } // The failed step text. // We want a stepno-i-am-the-failed-step.$filetype format. $filename = $event->getStep()->getText(); $filename = preg_replace('/([^a-zA-Z0-9\_]+)/', '-', $filename); $filename = self::$currentscenariostepcount . '-' . $filename; // File name limited to 255 characters. Leaving 4 chars for the file // extension as we allow .png for images and .html for DOM contents. $filename = substr($filename, 0, 250) . '.' . $filetype; return [$dir, $filename]; } } BehatExtension/Output/Formatter/MoodleStepcountFormatter.php000064400000010161152267215570020463 0ustar00. namespace Moodle\BehatExtension\Output\Formatter; use Behat\Behat\EventDispatcher\Event\AfterFeatureTested; use Behat\Behat\EventDispatcher\Event\AfterStepTested; use Behat\Behat\EventDispatcher\Event\BeforeFeatureTested; use Behat\Testwork\Output\Formatter; use Behat\Testwork\Output\Printer\OutputPrinter; // phpcs:disable moodle.NamingConventions.ValidFunctionName.LowercaseMethod /** * Feature step counter for distributing features between parallel runs. * * Use it with --dry-run (and any other selectors combination) to * get the results quickly. * * @package core * @copyright 2016 onwards Rajesh Taneja * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class MoodleStepcountFormatter implements Formatter { /** @var int Number of steps executed in feature file. */ private static $stepcount = 0; /** @var OutputPrinter */ private $printer; /** @var array */ private $parameters; /** @var string */ private $name; /** @var string */ private $description; /** * Initializes formatter. * * @param string $name * @param string $description * @param array $parameters * @param OutputPrinter $printer */ public function __construct($name, $description, array $parameters, OutputPrinter $printer) { $this->name = $name; $this->description = $description; $this->parameters = $parameters; $this->printer = $printer; } /** * Returns an array of event names this subscriber wants to listen to. * * @return array The event names to listen to */ public static function getSubscribedEvents() { return [ 'tester.feature_tested.before' => 'beforeFeature', 'tester.feature_tested.after' => 'afterFeature', 'tester.step_tested.after' => 'afterStep', ]; } /** * Returns formatter name. * * @return string */ public function getName() { return $this->name; } /** * Returns formatter description. * * @return string */ public function getDescription() { return $this->description; } /** * Returns formatter output printer. * * @return OutputPrinter */ public function getOutputPrinter() { return $this->printer; } /** * Sets formatter parameter. * * @param string $name * @param mixed $value */ public function setParameter($name, $value) { $this->parameters[$name] = $value; } /** * Returns parameter name. * * @param string $name * @return mixed */ public function getParameter($name) { return isset($this->parameters[$name]) ? $this->parameters[$name] : null; } /** * Listens to "feature.before" event. * * @param BeforeFeatureTested $event */ public function beforeFeature(BeforeFeatureTested $event) { self::$stepcount = 0; } /** * Listens to "feature.after" event. * * @param AfterFeatureTested $event */ public function afterFeature(AfterFeatureTested $event) { $this->printer->writeln($event->getFeature()->getFile() . '::' . self::$stepcount); } /** * Listens to "step.after" event. * * @param AfterStepTested $event */ public function afterStep(AfterStepTested $event) { self::$stepcount++; } } BehatExtension/Output/Printer/MoodleProgressPrinter.php000064400000010244152267215570017445 0ustar00. namespace Moodle\BehatExtension\Output\Printer; use Behat\Behat\Output\Node\Printer\SetupPrinter; use Behat\Testwork\Call\CallResult; use Behat\Testwork\Hook\Tester\Setup\HookedTeardown; use Behat\Testwork\Output\Formatter; use Behat\Testwork\Output\Printer\OutputPrinter; use Behat\Testwork\Tester\Setup\Setup; use Behat\Testwork\Tester\Setup\Teardown; use Moodle\BehatExtension\Driver\WebDriver; // phpcs:disable moodle.NamingConventions.ValidFunctionName.LowercaseMethod /** * Prints hooks in a pretty fashion. * * @package core * @copyright 2016 Rajesh Taneja * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class MoodleProgressPrinter implements SetupPrinter { /** * @var string Moodle directory root. */ private $moodledirroot; /** * @var bool true if output is displayed. */ private static $outputdisplayed; /** * Constructor. * * @param string $moodledirroot Moodle dir root. */ public function __construct($moodledirroot) { $this->moodledirroot = $moodledirroot; } /** * Prints setup state. * * @param Formatter $formatter * @param Setup $setup */ public function printSetup(Formatter $formatter, Setup $setup) { if (empty(self::$outputdisplayed)) { $this->printMoodleInfo($formatter->getOutputPrinter()); self::$outputdisplayed = true; } } /** * Prints teardown state. * * @param Formatter $formatter * @param Teardown $teardown */ public function printTeardown(Formatter $formatter, Teardown $teardown) { if (!$teardown instanceof HookedTeardown) { return; } foreach ($teardown->getHookCallResults() as $callresult) { $this->printTeardownHookCallResult($formatter->getOutputPrinter(), $callresult); } } /** * We print the site info + driver used and OS. * * @param Printer $printer * @return void */ public function printMoodleInfo($printer) { require_once($this->moodledirroot . '/lib/behat/classes/util.php'); $browser = WebDriver::getBrowserName(); // Calling all directly from here as we avoid more behat framework extensions. $runinfo = \behat_util::get_site_info(); $runinfo .= 'Server OS "' . PHP_OS . '"' . ', Browser: "' . $browser . '"' . PHP_EOL; $runinfo .= 'Started at ' . date('d-m-Y, H:i', time()); $printer->writeln($runinfo); } /** * Prints teardown hook call result. * * @param OutputPrinter $printer * @param CallResult $callresult */ private function printTeardownHookCallResult(OutputPrinter $printer, CallResult $callresult) { // Notify dev that chained step is being used. if (\Moodle\BehatExtension\EventDispatcher\Tester\ChainedStepTester::is_chained_step_used()) { $printer->writeln(); $printer->write( "{+failed}Chained steps are deprecated. " . "See https://moodledev.io/general/development/tools/behat/" . "Migrating_from_Behat_2.5_to_3.x_in_Moodle#Changes_required_in_context_file{-failed}" ); } if (!$callresult->hasStdOut() && !$callresult->hasException()) { return; } $hook = $callresult->getCall()->getCallee(); $path = $hook->getPath(); $printer->writeln($hook); $printer->writeln($path); } } BehatExtension/Context/MoodleContext.php000064400000002705152267215570014425 0ustar00. namespace Moodle\BehatExtension\Context; use Behat\MinkExtension\Context\RawMinkContext; // phpcs:disable moodle.NamingConventions.ValidFunctionName.LowercaseMethod /** * Moodle contexts loader * * It gathers all the available steps definitions reading the * Moodle configuration file * * @package core * @copyright 2012 David Monllaó * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class MoodleContext extends RawMinkContext { /** @var array Moodle features and steps definitions list */ protected $moodleconfig; /** * Includes all the specified Moodle subcontexts. * * @param array $parameters */ public function setMoodleConfig(array $parameters): void { $this->moodleconfig = $parameters; } } BehatExtension/Context/Initializer/MoodleAwareInitializer.php000064400000003311152267215570020521 0ustar00. namespace Moodle\BehatExtension\Context\Initializer; use Behat\Behat\Context\Context; use Behat\Behat\Context\Initializer\ContextInitializer; use Moodle\BehatExtension\Context\MoodleContext; // phpcs:disable moodle.NamingConventions.ValidFunctionName.LowercaseMethod /** * MoodleContext initializer * * @package core * @author David Monllaó * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class MoodleAwareInitializer implements ContextInitializer { /** @var array The list of parameters */ private $parameters; /** * Initializes initializer. * * @param array $parameters */ public function __construct(array $parameters) { $this->parameters = $parameters; } /** * Initializes provided context. * * @param Context $context */ public function initializeContext(Context $context) { if ($context instanceof MoodleContext) { $context->setMoodleConfig($this->parameters); } } } BehatExtension/Context/Step/ChainedStep.php000064400000003700152267215570014737 0ustar00. namespace Moodle\BehatExtension\Context\Step; use Behat\Gherkin\Node\StepNode; // phpcs:disable moodle.NamingConventions.ValidFunctionName.LowercaseMethod /** * Chained Step base class. * * @package core * @copyright 2016 Rajesh Taneja * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ abstract class ChainedStep extends StepNode { /** * @var string */ private $language; // phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod.Found /** * Initializes ChainedStep. * * @param string $keyword * @param string $text * @param array $arguments * @param int $line * @param string $keywordtype */ public function __construct($keyword, $text, array $arguments, $line = 0, $keywordtype = 'Given') { parent::__construct($keyword, $text, $arguments, $line, $keywordtype); } // phpcs:enable Generic.CodeAnalysis.UselessOverridingMethod.Found /** * Sets language. * * @param string $language */ public function setLanguage($language) { $this->language = $language; } /** * Returns language. * * @return string */ public function getLanguage() { return $this->language; } } BehatExtension/Context/Step/Then.php000064400000002245152267215570013451 0ustar00. namespace Moodle\BehatExtension\Context\Step; /** * Chained `Then` ChainedStep. * * @package core * @copyright 2016 Rajesh Taneja * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class Then extends ChainedStep { /** * Initializes `Then` sub-step. */ public function __construct() { $arguments = func_get_args(); $text = array_shift($arguments); parent::__construct('Then', $text, $arguments); } } BehatExtension/Context/Step/Given.php000064400000002242152267215570013620 0ustar00. namespace Moodle\BehatExtension\Context\Step; /** * Chained `Given` step. * * @package core * @copyright 2016 Rajesh Taneja * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class Given extends ChainedStep { /** * Initializes `Given` sub-step. */ public function __construct() { $arguments = func_get_args(); $text = array_shift($arguments); parent::__construct('Given', $text, $arguments); } } BehatExtension/Context/Step/When.php000064400000002236152267215570013454 0ustar00. namespace Moodle\BehatExtension\Context\Step; /** * Chained `When` step. * * @package core * @copyright 2016 Rajesh Taneja * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class When extends ChainedStep { /** * Initializes `When` sub-step. */ public function __construct() { $arguments = func_get_args(); $text = array_shift($arguments); parent::__construct('When', $text, $arguments); } } BehatExtension/Context/ContextClass/ClassResolver.php000064400000005211152267215570017035 0ustar00. namespace Moodle\BehatExtension\Context\ContextClass; use Behat\Behat\Context\ContextClass\ClassResolver as Resolver; // phpcs:disable moodle.NamingConventions.ValidFunctionName.LowercaseMethod /** * Moodle behat context class resolver. * * Resolves arbitrary context strings into a context classes. * * @see ContextEnvironmentHandler * * @package core * @copyright 2104 Rajesh Taneja * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class ClassResolver implements Resolver { /** @var array keep list of all behat contexts in moodle. */ private $moodlebehatcontexts = null; /** * Constructor for ClassResolver class. * * @param array $parameters list of params provided to moodle. */ public function __construct($parameters) { $this->moodlebehatcontexts = $parameters['steps_definitions']; } /** * Checks if resolvers supports provided class. * Moodle behat context class starts with behat_ * * @param string $contextstring * @return Boolean */ public function supportsClass($contextstring) { return (strpos($contextstring, 'behat_') === 0); } /** * Resolves context class. * * @param string $contextclass * @return string context class. */ public function resolveClass($contextclass) { if (!is_array($this->moodlebehatcontexts)) { throw new \RuntimeException('There are no Moodle context with steps definitions'); } // Using the key as context identifier load context class. if ( !empty($this->moodlebehatcontexts[$contextclass]) && (file_exists($this->moodlebehatcontexts[$contextclass])) ) { require_once($this->moodlebehatcontexts[$contextclass]); } else { throw new \RuntimeException('Moodle behat context "' . $contextclass . '" not found'); } return $contextclass; } } BehatExtension/Definition/Printer/ConsoleDefinitionInformationPrinter.php000064400000010405152267215570023111 0ustar00. namespace Moodle\BehatExtension\Definition\Printer; use Behat\Behat\Definition\Printer\ConsoleDefinitionPrinter; use Behat\Behat\Definition\Printer\UnusedDefinitionPrinter; use Behat\Testwork\Suite\Suite; // phpcs:disable moodle.NamingConventions.ValidFunctionName.LowercaseMethod /** * Moodle console definition information printer. * * Used in moodle for definition printing. * * @package core * @copyright Rajesh Taneja * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class ConsoleDefinitionInformationPrinter extends ConsoleDefinitionPrinter implements UnusedDefinitionPrinter { /** @var null|string */ private $searchcriterion; /** * Sets search criterion. * * @param string $criterion */ public function setSearchCriterion($criterion) { $this->searchcriterion = $criterion; } #[\Override] public function printDefinitions(Suite $suite, $definitions) { $this->print_definitions_with_optional_suite($definitions, $suite); } #[\Override] public function printUnusedDefinitions(array $definitions): void { $unusedtext = $this->translateInfoText( 'unused_definitions', ['%count%' => count($definitions)] ); $this->write('--- ' . $unusedtext, true); if (count($definitions) !== 0) { $this->print_definitions_with_optional_suite($definitions); } } /** * Print definitions with optional suite. * * @param array $definitions * @param null|Suite $suite */ protected function print_definitions_with_optional_suite( array $definitions, ?Suite $suite = null ): void { $template = <<
{description}
{type}{regex}
{apipath}
TPL; $search = $this->searchcriterion; // If there is a specific type (given, when or then) required. if ($search && strpos($search, '&&') !== false) { [$search, $type] = explode('&&', $search); } foreach ($definitions as $definition) { if ($suite) { $definition = $this->translateDefinition($suite, $definition); } if (!empty($type) && strtolower($definition->getType()) != $type) { continue; } $pattern = $definition->getPattern(); if ($search && !preg_match('/' . str_replace(' ', '.*', preg_quote($search, '/') . '/'), $pattern)) { continue; } $description = $definition->getDescription(); // Removing beginning and end. $pattern = substr($pattern, 2, strlen($pattern) - 4); // Replacing inline regex for expected info string. $pattern = preg_replace_callback( '/"\(\?P<([^>]*)>(.*?)"( |$)/', fn ($matches): string => '"' . strtoupper($matches[1]) . '" ', $pattern ); $definitiontoprint[] = strtr($template, [ '{regex}' => $pattern, '{type}' => str_pad($definition->getType(), 5, ' ', STR_PAD_LEFT), '{description}' => $description ? $description : '', '{apipath}' => $definition->getPath(), ]); $this->write(implode("\n", $definitiontoprint)); unset($definitiontoprint); } } } BehatExtension/Definition/Cli/AvailableDefinitionsController.php000064400000010222152267215570021125 0ustar00. namespace Moodle\BehatExtension\Definition\Cli; use Behat\Behat\Definition\DefinitionWriter; use Behat\Behat\Definition\Printer\ConsoleDefinitionListPrinter; use Behat\Testwork\Cli\Controller; use Behat\Testwork\Suite\SuiteRepository; use Moodle\BehatExtension\Definition\Printer\ConsoleDefinitionInformationPrinter; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; /** * Available definition controller, for calling moodle information printer. * * @package core * @copyright 2016 Rajesh Taneja * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class AvailableDefinitionsController implements Controller { /** @var SuiteRepository */ private $suiterepository; /** @var DefinitionWriter */ private $writer; /** @var ConsoleDefinitionListPrinter */ private $listprinter; /** @var ConsoleDefinitionInformationPrinter */ private $infoprinter; /** * Initializes controller. * * @param SuiteRepository $suiterepository * @param DefinitionWriter $writer * @param ConsoleDefinitionListPrinter $listprinter * @param ConsoleDefinitionInformationPrinter $infoprinter */ public function __construct( SuiteRepository $suiterepository, DefinitionWriter $writer, ConsoleDefinitionListPrinter $listprinter, ConsoleDefinitionInformationPrinter $infoprinter ) { $this->suiterepository = $suiterepository; $this->writer = $writer; $this->listprinter = $listprinter; $this->infoprinter = $infoprinter; } /** * Configures command to be executable by the controller. * * @param Command $command */ public function configure(Command $command) { $command->addOption('--definitions', '-d', InputOption::VALUE_REQUIRED, "Print all available step definitions:" . PHP_EOL . "- use --definitions l to just list definition expressions." . PHP_EOL . "- use --definitions i to show definitions with extended info." . PHP_EOL . "- use --definitions 'needle' to find specific definitions." . PHP_EOL . "Use --lang to see definitions in specific language." ); } /** * Executes controller. * * @param InputInterface $input * @param OutputInterface $output * * @return null|integer */ public function execute(InputInterface $input, OutputInterface $output) { if (null === $argument = $input->getOption('definitions')) { return null; } $printer = $this->getdefinitionPrinter($argument); foreach ($this->suiterepository->getSuites() as $suite) { $this->writer->printSuiteDefinitions($printer, $suite); } return 0; } /** * Returns definition printer for provided option argument. * * @param string $argument * * @return \Behat\Behat\Definition\Printer\DefinitionPrinter */ private function getdefinitionprinter($argument) { if ('l' === $argument) { return $this->listprinter; } if ('i' !== $argument) { $this->infoprinter->setSearchCriterion($argument); } return $this->infoprinter; } } BehatExtension/Driver/WebDriverFactory.php000064400000006043152267215570014670 0ustar00. namespace Moodle\BehatExtension\Driver; use Behat\MinkExtension\ServiceContainer\Driver\DriverFactory; use OAndreyev\Mink\Driver\WebDriverFactory as UpstreamFactory; use Symfony\Component\DependencyInjection\Definition; // phpcs:disable moodle.NamingConventions.ValidFunctionName.LowercaseMethod /** * Driver factory for the Moodle WebDriver. * * @package core * @copyright 2020 Andrew Nicols * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class WebDriverFactory extends UpstreamFactory implements DriverFactory { /** * Builds the service definition for the driver. * * @param array $config * @return Definition */ public function buildDriver(array $config) { // Merge capabilities. $extracapabilities = $config['capabilities']['extra_capabilities']; unset($config['capabilities']['extra_capabilities']); // Normalise the Edge browser name. if ($config['browser'] === 'edge') { $config['browser'] = 'MicrosoftEdge'; } // Ensure that the capabilites.browserName is set correctly. $config['capabilities']['browserName'] = $config['browser']; $capabilities = array_replace($extracapabilities, $config['capabilities']); // Incorrect top level capabilities lead to invalid Selenium browser selection. // See https://github.com/SeleniumHQ/selenium/issues/10410 for more information. // If any of these settings are mentioned then additional empty Capability options are created and a random // browser is chosen. $filteredcapabilities = [ 'tags', 'ignoreZoomSetting', 'marionette', 'browser', 'name', ]; foreach ($filteredcapabilities as $capabilityname) { unset($capabilities[$capabilityname]); } // Build driver definition. return new Definition(WebDriver::class, [ $config['browser'], $capabilities, $config['wd_host'], ]); } /** * Get the CapabilitiesNode. * * @return Node */ protected function getCapabilitiesNode() { $node = parent::getCapabilitiesNode(); // Specify chrome as the default browser. $node->find('browser')->defaultValue('chrome'); return $node; } } BehatExtension/Driver/WebDriver.php000064400000005637152267215570013350 0ustar00. namespace Moodle\BehatExtension\Driver; use Behat\Mink\Exception\DriverException; use OAndreyev\Mink\Driver\WebDriver as UpstreamDriver; // phpcs:disable moodle.NamingConventions.ValidFunctionName.LowercaseMethod /** * WebDriver Driver to allow extra selenium capabilities required by Moodle. * * @package core * @copyright 2016 onwards Rajesh Taneja * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class WebDriver extends UpstreamDriver { /** * Dirty attribute to get the browser name; $browserName is private * @var string */ protected static $browser; /** * Instantiates the driver. * * @param string $browsername Browser name * @param array $desiredcapabilities The desired capabilities * @param string $wdhost The WebDriver host * @param array $moodleparameters Moodle parameters including our non-behat-friendly selenium capabilities */ public function __construct( $browsername = 'chrome', $desiredcapabilities = null, $wdhost = 'http://localhost:4444/wd/hub', $moodleparameters = [] ) { parent::__construct($browsername, $desiredcapabilities, $wdhost); // This class is instantiated by the dependencies injection system so prior to all of beforeSuite subscribers // which will call getBrowser*(). self::$browser = $browsername; } /** * Returns the browser being used. * * We need to know it: * - To show info about the run. * - In case there are differences between browsers in the steps. * * @return string */ public static function getBrowserName() { return self::$browser; } /** * Post key on specified xpath. * * @param string $key * @param string $xpath */ public function post_key($key, $xpath) { throw new \Exception('No longer used - please use keyDown and keyUp'); } #[\Override] public function stop(): void { try { parent::stop(); } catch (DriverException $e) { error_log($e->getMessage()); $rcp = new \ReflectionProperty(parent::class, 'webDriver'); $rcp->setValue($this, null); } } }