├── src └── Moodle │ └── BehatExtension │ ├── Exception │ └── SkippedException.php │ ├── Driver │ ├── Selenium2 │ │ ├── README.md │ │ └── moodle_syn-min.js │ ├── MoodleSelenium2Factory.php │ └── MoodleSelenium2Driver.php │ ├── Context │ ├── MoodleContext.php │ ├── Initializer │ │ └── MoodleAwareInitializer.php │ ├── Step │ │ ├── When.php │ │ ├── Given.php │ │ ├── Then.php │ │ └── ChainedStep.php │ └── ContextClass │ │ └── ClassResolver.php │ ├── ServiceContainer │ ├── services │ │ └── core.xml │ └── BehatExtension.php │ ├── Locator │ └── FilesystemSkipPassedListLocator.php │ ├── Definition │ ├── Printer │ │ └── ConsoleDefinitionInformationPrinter.php │ └── Cli │ │ └── AvailableDefinitionsController.php │ ├── EventDispatcher │ └── Tester │ │ ├── MoodleEventDispatchingStepTester.php │ │ └── ChainedStepTester.php │ ├── Output │ ├── Printer │ │ └── MoodleProgressPrinter.php │ └── Formatter │ │ ├── MoodleListFormatter.php │ │ ├── MoodleStepcountFormatter.php │ │ ├── MoodleProgressFormatterFactory.php │ │ └── MoodleScreenshotFormatter.php │ └── Tester │ └── Cli │ └── SkipPassedController.php ├── init.php ├── composer.json └── README.md /src/Moodle/BehatExtension/Exception/SkippedException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | spl_autoload_register(function($class) { 13 | if (false !== strpos($class, 'Moodle\\BehatExtension')) { 14 | require_once(__DIR__.'/src/'.str_replace('\\', '/', $class).'.php'); 15 | return true; 16 | } 17 | if (false !== strpos($class, 'Behat\\Mink\\Driver\\Selenium2Driver')) { 18 | require_once(__DIR__.'/src/Moodle/BehatExtension/Driver/MoodleSelenium2Driver.php'); 19 | return true; 20 | } 21 | }, true, false); 22 | 23 | return new Moodle\BehatExtension\ServiceContainer\BehatExtension; 24 | -------------------------------------------------------------------------------- /src/Moodle/BehatExtension/Context/MoodleContext.php: -------------------------------------------------------------------------------- 1 | moodleConfig = $parameters; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "moodlehq/behat-extension", 3 | "description": "Moodle behat extension", 4 | "keywords": ["Moodle", "BDD", "Behat"], 5 | "type": "library", 6 | "license": "GPLv3", 7 | "authors": [ 8 | { 9 | "name": "David Monllaó", 10 | "email": "david.monllao@gmail.com", 11 | "homepage": "http://moodle.com", 12 | "role": "Developer" 13 | } 14 | ], 15 | "require": { 16 | "php": ">=5.4.4", 17 | "behat/mink": "~1.7", 18 | "behat/mink-extension": "~2.2", 19 | "behat/mink-goutte-driver": "~1.2", 20 | "behat/mink-selenium2-driver": "~1.3", 21 | "symfony/process": "2.8.*", 22 | "behat/behat": "3.3.*", 23 | "guzzlehttp/guzzle": "^6.3" 24 | }, 25 | "autoload": { 26 | "psr-0": { 27 | "Moodle\\BehatExtension": "src/" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Moodle/BehatExtension/Context/Initializer/MoodleAwareInitializer.php: -------------------------------------------------------------------------------- 1 | 14 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 15 | */ 16 | class MoodleAwareInitializer implements ContextInitializer 17 | { 18 | private $parameters; 19 | 20 | 21 | /** 22 | * Initializes initializer. 23 | * 24 | * @param Mink $mink 25 | * @param array $parameters 26 | */ 27 | public function __construct(array $parameters) { 28 | $this->parameters = $parameters; 29 | } 30 | 31 | /** 32 | * Initializes provided context. 33 | * 34 | * @param Context $context 35 | */ 36 | public function initializeContext(Context $context) { 37 | if (method_exists($context, 'setMoodleConfig')) { 38 | $context->setMoodleConfig($this->parameters); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Moodle/BehatExtension/Context/Step/When.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Override step tester to ensure chained steps gets executed. 19 | * 20 | * @package behat 21 | * @copyright 2016 Rajesh Taneja 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | namespace Moodle\BehatExtension\Context\Step; 25 | /** 26 | * `When` ChainedStep. 27 | */ 28 | class When extends ChainedStep { 29 | /** 30 | * Initializes `When` sub-step. 31 | */ 32 | public function __construct() { 33 | $arguments = func_get_args(); 34 | $text = array_shift($arguments); 35 | parent::__construct('When', $text, $arguments); 36 | } 37 | } -------------------------------------------------------------------------------- /src/Moodle/BehatExtension/Context/Step/Given.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Override step tester to ensure chained steps gets executed. 19 | * 20 | * @package behat 21 | * @copyright 2016 Rajesh Taneja 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace Moodle\BehatExtension\Context\Step; 26 | /** 27 | * Given sub-step. 28 | */ 29 | class Given extends ChainedStep { 30 | /** 31 | * Initializes `Given` sub-step. 32 | */ 33 | public function __construct() { 34 | $arguments = func_get_args(); 35 | $text = array_shift($arguments); 36 | parent::__construct('Given', $text, $arguments); 37 | } 38 | } -------------------------------------------------------------------------------- /src/Moodle/BehatExtension/Context/Step/Then.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Override step tester to ensure chained steps gets executed. 19 | * 20 | * @package behat 21 | * @copyright 2016 Rajesh Taneja 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | namespace Moodle\BehatExtension\Context\Step; 25 | 26 | /** 27 | * `Then` ChainedStep. 28 | */ 29 | class Then extends ChainedStep { 30 | /** 31 | * Initializes `Then` sub-step. 32 | */ 33 | public function __construct() { 34 | $arguments = func_get_args(); 35 | $text = array_shift($arguments); 36 | parent::__construct('Then', $text, $arguments); 37 | } 38 | } -------------------------------------------------------------------------------- /src/Moodle/BehatExtension/ServiceContainer/services/core.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | Moodle\BehatExtension\Context\Initializer\MoodleAwareInitializer 9 | Moodle\BehatExtension\Context\ContextClass\ClassResolver 10 | Behat\Mink\Selector\SelectorsHandler 11 | 12 | 13 | 14 | 15 | 16 | %behat.moodle.parameters% 17 | 18 | 19 | 20 | 21 | 22 | %behat.moodle.parameters% 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | moodle-behat-extension 2 | ====================== 3 | 4 | Behat extension for Moodle to get features and steps definitions from different moodle components; it basically allows multiple features folders and helps with the contexts spreads across components of an external app. 5 | 6 | Following custom formats are supported. 7 | ====================================== 8 | * **moodle_progress**: Prints Moodle branch information and dots for each step. 9 | * **moodle_list**: List all scenarios. 10 | * **moodle_stepcount**: List all features with total steps in each feature file. Used for parallel run. 11 | * **moodle_screenshot**: Take screenshot and core dump of each step. With following options you can dump either or both. 12 | * **--format-settings '{"formats": "image"}'**: will dump image only 13 | * **--format-settings '{"formats": "html"}'**: will dump html only. 14 | * **--format-settings '{"formats": "html,image"}'**: will dump both. 15 | * **--format-settings '{"formats": "html", "dir_permissions": "0777"}'** 16 | 17 | Contributing 18 | ============ 19 | 20 | http://docs.moodle.org/dev/Acceptance_testing/Contributing_to_Moodle_behat_extension 21 | 22 | Upgrade from moodle-behat-extension 1.31.x to 3.31.0 23 | ==================================================== 24 | * Chained steps are not natively supported by behat 3. 25 | * You should either replace Behat\Behat\Context\Step\Given with Behat\Behat\Context\Step\Given; 26 | * or use behat_context_helper::get('BEHAT_CONTEXT_CLASS'); and call api to execute the step. 27 | * named selectors are deprecated, use named_exact or named_partial instead. 28 | * Failed steps are cached for rerun and doesn't require an empty file to save failed scenarios. 29 | -------------------------------------------------------------------------------- /src/Moodle/BehatExtension/Context/Step/ChainedStep.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Override step tester to ensure chained steps gets executed. 19 | * 20 | * @package behat 21 | * @copyright 2016 Rajesh Taneja 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | namespace Moodle\BehatExtension\Context\Step; 25 | 26 | use Behat\Gherkin\Node\StepNode; 27 | /** 28 | * Base ChainedStep class. 29 | */ 30 | abstract class ChainedStep extends StepNode { 31 | /** 32 | * @var string 33 | */ 34 | private $language; 35 | 36 | /** 37 | * Initializes ChainedStep. 38 | * 39 | * @param string $type 40 | * @param string $text 41 | * @param array $arguments 42 | */ 43 | public function __construct($keyword, $text, array $arguments, $line = 0, $keywordType = 'Given') { 44 | parent::__construct($keyword, $text, $arguments, $line, $keywordType); 45 | } 46 | 47 | /** 48 | * Sets language. 49 | * 50 | * @param string $language 51 | */ 52 | public function setLanguage($language) { 53 | $this->language = $language; 54 | } 55 | 56 | /** 57 | * Returns language. 58 | * 59 | * @return string 60 | */ 61 | public function getLanguage() { 62 | return $this->language; 63 | } 64 | } -------------------------------------------------------------------------------- /src/Moodle/BehatExtension/Context/ContextClass/ClassResolver.php: -------------------------------------------------------------------------------- 1 | . 17 | 18 | /** 19 | * Moodle behat context class resolver. 20 | * 21 | * @package behat 22 | * @copyright 2104 Rajesh Taneja 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | namespace Moodle\BehatExtension\Context\ContextClass; 26 | 27 | use Behat\Behat\Context\Environment\Handler\ContextEnvironmentHandler; 28 | use Behat\Behat\Context\ContextClass\ClassResolver as Resolver; 29 | 30 | /** 31 | * Resolves arbitrary context strings into a context classes. 32 | * 33 | * @see ContextEnvironmentHandler 34 | * 35 | * @author Konstantin Kudryashov 36 | */ 37 | final class ClassResolver implements Resolver { 38 | 39 | /** 40 | * @var array keep list of all behat contexts in moodle. 41 | */ 42 | private $moodlebehatcontexts = null; 43 | 44 | /** 45 | * @param $parameters array list of params provided to moodle. 46 | */ 47 | public function __construct($parameters) { 48 | $this->moodlebehatcontexts = $parameters['steps_definitions']; 49 | } 50 | /** 51 | * Checks if resolvers supports provided class. 52 | * Moodle behat context class starts with behat_ 53 | * 54 | * @param string $contextString 55 | * 56 | * @return Boolean 57 | */ 58 | public function supportsClass($contextString) { 59 | return (strpos($contextString, 'behat_') === 0); 60 | } 61 | 62 | /** 63 | * Resolves context class. 64 | * 65 | * @param string $contexclass 66 | * 67 | * @return string context class. 68 | */ 69 | public function resolveClass($contextclass) { 70 | if (!is_array($this->moodlebehatcontexts)) { 71 | throw new \RuntimeException('There are no Moodle context with steps definitions'); 72 | } 73 | 74 | // Using the key as context identifier load context class. 75 | if (!empty($this->moodlebehatcontexts[$contextclass]) && 76 | (file_exists($this->moodlebehatcontexts[$contextclass]))) { 77 | require_once($this->moodlebehatcontexts[$contextclass]); 78 | } else { 79 | throw new \RuntimeException('Moodle behat context "'.$contextclass.'" not found'); 80 | } 81 | return $contextclass; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Moodle/BehatExtension/Locator/FilesystemSkipPassedListLocator.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Skips gherkin features using a file with the list of scenarios. 19 | * 20 | * @copyright 2016 onwards Rajesh Taneja 21 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 22 | */ 23 | 24 | namespace Moodle\BehatExtension\Locator; 25 | 26 | use Behat\Behat\Gherkin\Specification\LazyFeatureIterator; 27 | use Behat\Gherkin\Gherkin; 28 | use Behat\Testwork\Specification\Locator\SpecificationLocator; 29 | use Behat\Testwork\Specification\NoSpecificationsIterator; 30 | use Behat\Testwork\Suite\Suite; 31 | 32 | /** 33 | * Skips gherkin features using a file with the list of scenarios. 34 | * 35 | * @copyright 2016 onwards Rajesh Taneja 36 | */ 37 | final class FilesystemSkipPassedListLocator implements SpecificationLocator { 38 | /** 39 | * @var Gherkin 40 | */ 41 | private $gherkin; 42 | 43 | /** 44 | * Initializes locator. 45 | * 46 | * @param Gherkin $gherkin 47 | */ 48 | public function __construct(Gherkin $gherkin) { 49 | $this->gherkin = $gherkin; 50 | } 51 | 52 | /** 53 | * {@inheritdoc} 54 | */ 55 | public function getLocatorExamples() { 56 | return array(); 57 | } 58 | 59 | /** 60 | * {@inheritdoc} 61 | */ 62 | public function locateSpecifications(Suite $suite, $locator) { 63 | if (!is_file($locator) || 'passed' !== pathinfo($locator, PATHINFO_EXTENSION)) { 64 | return new NoSpecificationsIterator($suite); 65 | } 66 | 67 | $scenarios = json_decode(trim(file_get_contents($locator)), true); 68 | if (empty($scenarios) || empty($scenarios[$suite->getName()])) { 69 | return new NoSpecificationsIterator($suite); 70 | } 71 | 72 | $suitepaths = $this->getSuitePaths($suite); 73 | 74 | $scenarios = array_diff($suitepaths, array_values($scenarios[$suite->getName()])); 75 | 76 | return new LazyFeatureIterator($suite, $this->gherkin, $scenarios); 77 | } 78 | 79 | /** 80 | * Returns array of feature paths configured for the provided suite. 81 | * 82 | * @param Suite $suite 83 | * 84 | * @return string[] 85 | * 86 | * @throws SuiteConfigurationException If `paths` setting is not an array 87 | */ 88 | private function getSuitePaths(Suite $suite) { 89 | if (!is_array($suite->getSetting('paths'))) { 90 | throw new SuiteConfigurationException( 91 | sprintf('`paths` setting of the "%s" suite is expected to be an array, %s given.', 92 | $suite->getName(), 93 | gettype($suite->getSetting('paths')) 94 | ), 95 | $suite->getName() 96 | ); 97 | } 98 | 99 | return $suite->getSetting('paths'); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/Moodle/BehatExtension/Definition/Printer/ConsoleDefinitionInformationPrinter.php: -------------------------------------------------------------------------------- 1 | . 17 | 18 | namespace Moodle\BehatExtension\Definition\Printer; 19 | 20 | use Behat\Behat\Definition\Definition; 21 | use Behat\Testwork\Suite\Suite; 22 | use Behat\Behat\Definition\Printer\ConsoleDefinitionPrinter; 23 | 24 | /** 25 | * Moodle console definition information printer. 26 | * Used in moodle for definition printing. 27 | * 28 | * @package behat 29 | * @copyright 2016 Rajesh Taneja 30 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 31 | */ 32 | final class ConsoleDefinitionInformationPrinter extends ConsoleDefinitionPrinter { 33 | /** 34 | * @var null|string 35 | */ 36 | private $searchCriterion; 37 | 38 | /** 39 | * Sets search criterion. 40 | * 41 | * @param string $criterion 42 | */ 43 | public function setSearchCriterion($criterion) { 44 | $this->searchCriterion = $criterion; 45 | } 46 | 47 | /** 48 | * {@inheritdoc} 49 | */ 50 | public function printDefinitions(Suite $suite, $definitions) { 51 | $template = <<
{description}
53 |
{type}{regex}
54 |
{apipath}
55 | 56 | TPL; 57 | 58 | $search = $this->searchCriterion; 59 | 60 | // If there is a specific type (given, when or then) required. 61 | if (strpos($search, '&&') !== false) { 62 | list($search, $type) = explode('&&', $search); 63 | } 64 | 65 | foreach ($definitions as $definition) { 66 | $definition = $this->translateDefinition($suite, $definition); 67 | 68 | if (!empty($type) && strtolower($definition->getType()) != $type) { 69 | continue; 70 | } 71 | 72 | $pattern = $definition->getPattern(); 73 | 74 | if ($search && !preg_match('/'.str_replace(' ', '.*', preg_quote($search, '/').'/'), $pattern)) { 75 | continue; 76 | } 77 | 78 | $description = $definition->getDescription(); 79 | 80 | // Removing beginning and end. 81 | $pattern = substr($pattern, 2, strlen($pattern) - 4); 82 | 83 | // Replacing inline regex for expected info string. 84 | $pattern = preg_replace_callback( 85 | '/"\(\?P<([^>]*)>(.*?)"( |$)/', 86 | function ($matches) { 87 | return '"' . strtoupper($matches[1]) . '" '; 88 | }, $pattern); 89 | 90 | $definitiontoprint[] = strtr($template, array( 91 | '{regex}' => $pattern, 92 | '{type}' => str_pad($definition->getType(), 5, ' ', STR_PAD_LEFT), 93 | '{description}' => $description ? $description : '', 94 | '{apipath}' => $definition->getPath() 95 | )); 96 | 97 | $this->write(implode("\n", $definitiontoprint)); 98 | unset($definitiontoprint); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/Moodle/BehatExtension/EventDispatcher/Tester/MoodleEventDispatchingStepTester.php: -------------------------------------------------------------------------------- 1 | . 17 | 18 | /** 19 | * Override step tester to ensure chained steps gets executed. 20 | * 21 | * @package behat 22 | * @copyright 2016 Rajesh Taneja 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | namespace Moodle\BehatExtension\EventDispatcher\Tester; 27 | 28 | use Behat\Behat\EventDispatcher\Event\AfterStepSetup; 29 | use Behat\Behat\EventDispatcher\Event\AfterStepTested; 30 | use Behat\Behat\EventDispatcher\Event\BeforeStepTeardown; 31 | use Behat\Behat\EventDispatcher\Event\BeforeStepTested; 32 | use Behat\Behat\Tester\Result\StepResult; 33 | use Behat\Behat\Tester\StepTester; 34 | use Behat\Gherkin\Node\FeatureNode; 35 | use Behat\Gherkin\Node\StepNode; 36 | use Behat\Testwork\Environment\Environment; 37 | use Symfony\Component\EventDispatcher\EventDispatcherInterface; 38 | 39 | /** 40 | * Step tester dispatching BEFORE/AFTER events during tests. 41 | * 42 | * @package behat 43 | * @copyright 2016 Rajesh Taneja 44 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 45 | */ 46 | final class MoodleEventDispatchingStepTester implements StepTester 47 | { 48 | /** 49 | * @var StepTester 50 | */ 51 | private $baseTester; 52 | /** 53 | * @var EventDispatcherInterface 54 | */ 55 | private $eventDispatcher; 56 | 57 | /** 58 | * Initializes tester. 59 | * 60 | * @param StepTester $baseTester 61 | * @param EventDispatcherInterface $eventDispatcher 62 | */ 63 | public function __construct(StepTester $baseTester, EventDispatcherInterface $eventDispatcher) { 64 | $this->baseTester = $baseTester; 65 | $this->eventDispatcher = $eventDispatcher; 66 | } 67 | 68 | /** 69 | * {@inheritdoc} 70 | */ 71 | public function setUp(Environment $env, FeatureNode $feature, StepNode $step, $skip) { 72 | $event = new BeforeStepTested($env, $feature, $step); 73 | $this->eventDispatcher->dispatch($event::BEFORE, $event); 74 | 75 | $setup = $this->baseTester->setUp($env, $feature, $step, $skip); 76 | $this->baseTester->setEventDispatcher($this->eventDispatcher); 77 | 78 | $event = new AfterStepSetup($env, $feature, $step, $setup); 79 | $this->eventDispatcher->dispatch($event::AFTER_SETUP, $event); 80 | 81 | return $setup; 82 | } 83 | 84 | /** 85 | * {@inheritdoc} 86 | */ 87 | public function test(Environment $env, FeatureNode $feature, StepNode $step, $skip) { 88 | return $this->baseTester->test($env, $feature, $step, $skip); 89 | } 90 | 91 | /** 92 | * {@inheritdoc} 93 | */ 94 | public function tearDown(Environment $env, FeatureNode $feature, StepNode $step, $skip, StepResult $result) { 95 | $event = new BeforeStepTeardown($env, $feature, $step, $result); 96 | $this->eventDispatcher->dispatch($event::BEFORE_TEARDOWN, $event); 97 | 98 | $teardown = $this->baseTester->tearDown($env, $feature, $step, $skip, $result); 99 | 100 | $event = new AfterStepTested($env, $feature, $step, $result, $teardown); 101 | $this->eventDispatcher->dispatch($event::AFTER, $event); 102 | 103 | return $teardown; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/Moodle/BehatExtension/Definition/Cli/AvailableDefinitionsController.php: -------------------------------------------------------------------------------- 1 | . 17 | 18 | namespace Moodle\BehatExtension\Definition\Cli; 19 | 20 | use Behat\Behat\Definition\DefinitionWriter; 21 | use Moodle\BehatExtension\Definition\Printer\ConsoleDefinitionInformationPrinter; 22 | use Behat\Behat\Definition\Printer\ConsoleDefinitionListPrinter; 23 | use Behat\Behat\Definition\Printer\DefinitionPrinter; 24 | use Behat\Testwork\Cli\Controller; 25 | use Behat\Testwork\Suite\SuiteRepository; 26 | use Symfony\Component\Console\Command\Command; 27 | use Symfony\Component\Console\Input\InputInterface; 28 | use Symfony\Component\Console\Input\InputOption; 29 | use Symfony\Component\Console\Output\OutputInterface; 30 | 31 | /** 32 | * Available definition controller, for calling moodle information printer. 33 | * 34 | * @package behat 35 | * @copyright 2016 Rajesh Taneja 36 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 37 | */ 38 | final class AvailableDefinitionsController implements Controller { 39 | /** 40 | * @var SuiteRepository 41 | */ 42 | private $suiteRepository; 43 | /** 44 | * @var DefinitionWriter 45 | */ 46 | private $writer; 47 | /** 48 | * @var ConsoleDefinitionListPrinter 49 | */ 50 | private $listPrinter; 51 | /** 52 | * @var ConsoleDefinitionInformationPrinter 53 | */ 54 | private $infoPrinter; 55 | 56 | /** 57 | * Initializes controller. 58 | * 59 | * @param SuiteRepository $suiteRepository 60 | * @param DefinitionWriter $writer 61 | * @param ConsoleDefinitionListPrinter $listPrinter 62 | * @param ConsoleDefinitionInformationPrinter $infoPrinter 63 | */ 64 | public function __construct( 65 | SuiteRepository $suiteRepository, 66 | DefinitionWriter $writer, 67 | ConsoleDefinitionListPrinter $listPrinter, 68 | ConsoleDefinitionInformationPrinter $infoPrinter 69 | ) { 70 | $this->suiteRepository = $suiteRepository; 71 | $this->writer = $writer; 72 | $this->listPrinter = $listPrinter; 73 | $this->infoPrinter = $infoPrinter; 74 | } 75 | 76 | /** 77 | * {@inheritdoc} 78 | */ 79 | public function configure(Command $command) { 80 | $command->addOption('--definitions', '-d', InputOption::VALUE_REQUIRED, 81 | "Print all available step definitions:" . PHP_EOL . 82 | "- use --definitions l to just list definition expressions." . PHP_EOL . 83 | "- use --definitions i to show definitions with extended info." . PHP_EOL . 84 | "- use --definitions 'needle' to find specific definitions." . PHP_EOL . 85 | "Use --lang to see definitions in specific language." 86 | ); 87 | } 88 | 89 | /** 90 | * {@inheritdoc} 91 | */ 92 | public function execute(InputInterface $input, OutputInterface $output) { 93 | if (null === $argument = $input->getOption('definitions')) { 94 | return null; 95 | } 96 | 97 | $printer = $this->getDefinitionPrinter($argument); 98 | foreach ($this->suiteRepository->getSuites() as $suite) { 99 | $this->writer->printSuiteDefinitions($printer, $suite); 100 | } 101 | 102 | return 0; 103 | } 104 | 105 | /** 106 | * Returns definition printer for provided option argument. 107 | * 108 | * @param string $argument 109 | * 110 | * @return DefinitionPrinter 111 | */ 112 | private function getDefinitionPrinter($argument) { 113 | if ('l' === $argument) { 114 | return $this->listPrinter; 115 | } 116 | 117 | if ('i' !== $argument) { 118 | $this->infoPrinter->setSearchCriterion($argument); 119 | } 120 | 121 | return $this->infoPrinter; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/Moodle/BehatExtension/Output/Printer/MoodleProgressPrinter.php: -------------------------------------------------------------------------------- 1 | . 17 | 18 | /** 19 | * Moodle behat context class resolver. 20 | * 21 | * @package behat 22 | * @copyright 2016 Rajesh Taneja 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | namespace Moodle\BehatExtension\Output\Printer; 27 | 28 | use Behat\Behat\Output\Node\Printer\SetupPrinter; 29 | use Behat\Testwork\Output\Formatter; 30 | use Behat\Testwork\Tester\Setup\Setup; 31 | use Behat\Testwork\Tester\Setup\Teardown; 32 | use Behat\Testwork\Call\CallResult; 33 | use Behat\Testwork\Hook\Tester\Setup\HookedTeardown; 34 | use Behat\Testwork\Output\Printer\OutputPrinter; 35 | use Behat\Testwork\Tester\Result\TestResult; 36 | 37 | /** 38 | * Prints hooks in a pretty fashion. 39 | */ 40 | final class MoodleProgressPrinter implements SetupPrinter { 41 | 42 | /** 43 | * @var string Moodle directory root. 44 | */ 45 | private $moodledirroot; 46 | 47 | /** 48 | * @var bool true if output is displayed. 49 | */ 50 | private static $outputdisplayed; 51 | 52 | /** 53 | * Constructor. 54 | * 55 | * @param string $moodledirroot Moodle dir root. 56 | */ 57 | public function __construct($moodledirroot) { 58 | $this->moodledirroot = $moodledirroot; 59 | } 60 | 61 | /** 62 | * {@inheritdoc} 63 | */ 64 | public function printSetup(Formatter $formatter, Setup $setup) { 65 | if (empty(self::$outputdisplayed)) { 66 | $this->printMoodleInfo($formatter->getOutputPrinter()); 67 | self::$outputdisplayed = true; 68 | } 69 | } 70 | 71 | /** 72 | * {@inheritdoc} 73 | */ 74 | public function printTeardown(Formatter $formatter, Teardown $teardown) { 75 | if (!$teardown instanceof HookedTeardown) { 76 | return; 77 | } 78 | 79 | foreach ($teardown->getHookCallResults() as $callResult) { 80 | $this->printTeardownHookCallResult($formatter->getOutputPrinter(), $callResult); 81 | } 82 | } 83 | 84 | /** 85 | * We print the site info + driver used and OS. 86 | * 87 | * @param Printer $printer 88 | * @return void 89 | */ 90 | public function printMoodleInfo($printer) { 91 | require_once($this->moodledirroot . '/lib/behat/classes/util.php'); 92 | 93 | $browser = \Moodle\BehatExtension\Driver\MoodleSelenium2Driver::getBrowser(); 94 | 95 | // Calling all directly from here as we avoid more behat framework extensions. 96 | $runinfo = \behat_util::get_site_info(); 97 | $runinfo .= 'Server OS "' . PHP_OS . '"' . ', Browser: "' . $browser . '"' . PHP_EOL; 98 | if (in_array(strtolower($browser), array('chrome', 'safari', 'iexplore'))) { 99 | $runinfo .= 'Browser specific fixes have been applied. See http://docs.moodle.org/dev/Acceptance_testing#Browser_specific_fixes' . PHP_EOL; 100 | } 101 | $runinfo .= 'Started at ' . date('d-m-Y, H:i', time()); 102 | 103 | $printer->writeln($runinfo); 104 | } 105 | 106 | /** 107 | * Prints teardown hook call result. 108 | * 109 | * @param OutputPrinter $printer 110 | * @param CallResult $callResult 111 | */ 112 | private function printTeardownHookCallResult(OutputPrinter $printer, CallResult $callResult) { 113 | // Notify dev that chained step is being used. 114 | if (\Moodle\BehatExtension\EventDispatcher\Tester\ChainedStepTester::is_chained_step_used()) { 115 | $printer->writeln(); 116 | $printer->write("{+failed}Chained steps are deprecated. See https://docs.moodle.org/dev/Acceptance_testing/Migrating_from_Behat_2.5_to_3.x_in_Moodle#Changes_required_in_context_file{-failed}"); 117 | } 118 | 119 | if (!$callResult->hasStdOut() && !$callResult->hasException()) { 120 | return; 121 | } 122 | 123 | $hook = $callResult->getCall()->getCallee(); 124 | $path = $hook->getPath(); 125 | 126 | $printer->writeln($hook); 127 | $printer->writeln($path); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/Moodle/BehatExtension/Output/Formatter/MoodleListFormatter.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Feature step counter for distributing features between parallel runs. 19 | * 20 | * Use it with --dry-run (and any other selectors combination) to 21 | * get the results quickly. 22 | * 23 | * @copyright 2015 onwards Rajesh Taneja 24 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 25 | */ 26 | 27 | namespace Moodle\BehatExtension\Output\Formatter; 28 | 29 | use Behat\Behat\EventDispatcher\Event\AfterFeatureTested; 30 | use Behat\Behat\EventDispatcher\Event\AfterOutlineTested; 31 | use Behat\Behat\EventDispatcher\Event\AfterScenarioTested; 32 | use Behat\Behat\EventDispatcher\Event\AfterStepTested; 33 | use Behat\Behat\EventDispatcher\Event\BeforeFeatureTested; 34 | use Behat\Behat\EventDispatcher\Event\BeforeOutlineTested; 35 | use Behat\Behat\EventDispatcher\Event\BeforeScenarioTested; 36 | use Behat\Behat\Tester\Result\ExecutedStepResult; 37 | use Behat\Testwork\Counter\Memory; 38 | use Behat\Testwork\Counter\Timer; 39 | use Behat\Testwork\EventDispatcher\Event\AfterExerciseCompleted; 40 | use Behat\Testwork\EventDispatcher\Event\AfterSuiteTested; 41 | use Behat\Testwork\EventDispatcher\Event\BeforeExerciseCompleted; 42 | use Behat\Testwork\EventDispatcher\Event\BeforeSuiteTested; 43 | use Behat\Testwork\Output\Exception\BadOutputPathException; 44 | use Behat\Testwork\Output\Formatter; 45 | use Behat\Testwork\Output\Printer\OutputPrinter; 46 | 47 | class MoodleListFormatter implements Formatter { 48 | 49 | /** 50 | * @var OutputPrinter 51 | */ 52 | private $printer; 53 | /** 54 | * @var array 55 | */ 56 | private $parameters; 57 | /** 58 | * @var string 59 | */ 60 | private $name; 61 | /** 62 | * @var string 63 | */ 64 | private $description; 65 | 66 | /** 67 | * Initializes formatter. 68 | * 69 | * @param string $name 70 | * @param string $description 71 | * @param array $parameters 72 | * @param OutputPrinter $printer 73 | */ 74 | public function __construct($name, $description, array $parameters, OutputPrinter $printer) { 75 | $this->name = $name; 76 | $this->description = $description; 77 | $this->parameters = $parameters; 78 | $this->printer = $printer; 79 | } 80 | 81 | /** 82 | * Returns an array of event names this subscriber wants to listen to. 83 | * @return array The event names to listen to 84 | */ 85 | public static function getSubscribedEvents() { 86 | return array( 87 | 88 | 'tester.scenario_tested.after' => 'afterScenario', 89 | 'tester.outline_tested.after' => 'afterOutlineExample', 90 | ); 91 | } 92 | 93 | /** 94 | * {@inheritdoc} 95 | */ 96 | public function getName() { 97 | return $this->name; 98 | } 99 | 100 | /** 101 | * {@inheritdoc} 102 | */ 103 | public function getDescription() { 104 | return $this->description; 105 | } 106 | 107 | /** 108 | * {@inheritdoc} 109 | */ 110 | public function getOutputPrinter() { 111 | return $this->printer; 112 | } 113 | 114 | /** 115 | * {@inheritdoc} 116 | */ 117 | public function setParameter($name, $value) { 118 | $this->parameters[$name] = $value; 119 | } 120 | 121 | /** 122 | * {@inheritdoc} 123 | */ 124 | public function getParameter($name) { 125 | return isset($this->parameters[$name]) ? $this->parameters[$name] : null; 126 | } 127 | 128 | /** 129 | * Listens to "scenario.after" event. 130 | * 131 | * @param ScenarioEvent $event 132 | */ 133 | public function afterScenario(AfterScenarioTested $event) { 134 | $scenario = $event->getScenario(); 135 | $this->printer->writeln($event->getFeature()->getFile() . ':' . $scenario->getLine()); 136 | } 137 | 138 | 139 | /** 140 | * Listens to "outline.example.after" event. 141 | * 142 | * @param OutlineExampleEvent $event 143 | */ 144 | public function afterOutlineExample(AfterOutlineTested $event) { 145 | $outline = $event->getOutline(); 146 | $line = $outline->getLine(); 147 | $this->printer->writeln($event->getFeature()->getFile() . ':' . $line); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/Moodle/BehatExtension/Output/Formatter/MoodleStepcountFormatter.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Feature step counter for distributing features between parallel runs. 19 | * 20 | * Use it with --dry-run (and any other selectors combination) to 21 | * get the results quickly. 22 | * 23 | * @copyright 2016 onwards Rajesh Taneja 24 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 25 | */ 26 | 27 | namespace Moodle\BehatExtension\Output\Formatter; 28 | 29 | use Behat\Behat\EventDispatcher\Event\AfterFeatureTested; 30 | use Behat\Behat\EventDispatcher\Event\AfterOutlineTested; 31 | use Behat\Behat\EventDispatcher\Event\AfterScenarioTested; 32 | use Behat\Behat\EventDispatcher\Event\AfterStepTested; 33 | use Behat\Behat\EventDispatcher\Event\BeforeFeatureTested; 34 | use Behat\Behat\EventDispatcher\Event\BeforeOutlineTested; 35 | use Behat\Behat\EventDispatcher\Event\BeforeScenarioTested; 36 | use Behat\Behat\Tester\Result\ExecutedStepResult; 37 | use Behat\Testwork\Counter\Memory; 38 | use Behat\Testwork\Counter\Timer; 39 | use Behat\Testwork\EventDispatcher\Event\AfterExerciseCompleted; 40 | use Behat\Testwork\EventDispatcher\Event\AfterSuiteTested; 41 | use Behat\Testwork\EventDispatcher\Event\BeforeExerciseCompleted; 42 | use Behat\Testwork\EventDispatcher\Event\BeforeSuiteTested; 43 | use Behat\Testwork\Output\Exception\BadOutputPathException; 44 | use Behat\Testwork\Output\Formatter; 45 | use Behat\Testwork\Output\Printer\OutputPrinter; 46 | 47 | class MoodleStepcountFormatter implements Formatter { 48 | 49 | /** @var int Number of steps executed in feature file. */ 50 | private static $stepcount = 0; 51 | 52 | /** 53 | * @var OutputPrinter 54 | */ 55 | private $printer; 56 | /** 57 | * @var array 58 | */ 59 | private $parameters; 60 | /** 61 | * @var string 62 | */ 63 | private $name; 64 | /** 65 | * @var string 66 | */ 67 | private $description; 68 | 69 | /** 70 | * Initializes formatter. 71 | * 72 | * @param string $name 73 | * @param string $description 74 | * @param array $parameters 75 | * @param OutputPrinter $printer 76 | * @param EventListener $listener 77 | */ 78 | public function __construct($name, $description, array $parameters, OutputPrinter $printer) { 79 | $this->name = $name; 80 | $this->description = $description; 81 | $this->parameters = $parameters; 82 | $this->printer = $printer; 83 | } 84 | 85 | /** 86 | * Returns an array of event names this subscriber wants to listen to. 87 | * @return array The event names to listen to 88 | */ 89 | public static function getSubscribedEvents() { 90 | return array( 91 | 92 | 'tester.feature_tested.before' => 'beforeFeature', 93 | 'tester.feature_tested.after' => 'afterFeature', 94 | 'tester.step_tested.after' => 'afterStep', 95 | ); 96 | } 97 | 98 | /** 99 | * {@inheritdoc} 100 | */ 101 | public function getName() { 102 | return $this->name; 103 | } 104 | 105 | /** 106 | * {@inheritdoc} 107 | */ 108 | public function getDescription() { 109 | return $this->description; 110 | } 111 | 112 | /** 113 | * {@inheritdoc} 114 | */ 115 | public function getOutputPrinter() { 116 | return $this->printer; 117 | } 118 | 119 | /** 120 | * {@inheritdoc} 121 | */ 122 | public function setParameter($name, $value) { 123 | $this->parameters[$name] = $value; 124 | } 125 | 126 | /** 127 | * {@inheritdoc} 128 | */ 129 | public function getParameter($name) { 130 | return isset($this->parameters[$name]) ? $this->parameters[$name] : null; 131 | } 132 | 133 | /** 134 | * Listens to "feature.before" event. 135 | * 136 | * @param FeatureEvent $event 137 | */ 138 | public function beforeFeature(BeforeFeatureTested $event) { 139 | self::$stepcount = 0; 140 | } 141 | 142 | /** 143 | * Listens to "feature.after" event. 144 | * 145 | * @param FeatureEvent $event 146 | */ 147 | public function afterFeature(AfterFeatureTested $event) { 148 | $this->printer->writeln($event->getFeature()->getFile() . '::' . self::$stepcount); 149 | } 150 | 151 | /** 152 | * Listens to "step.after" event. 153 | * 154 | * @param StepEvent $event 155 | */ 156 | public function afterStep(AfterStepTested $event) { 157 | self::$stepcount++; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/Moodle/BehatExtension/Tester/Cli/SkipPassedController.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Caches passed scenarios and skip only them if `--skip-passed` option provided. 19 | * 20 | * @copyright 2016 onwards Rajesh Taneja 21 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 22 | */ 23 | 24 | namespace Moodle\BehatExtension\Tester\Cli; 25 | 26 | use Behat\Behat\EventDispatcher\Event\AfterFeatureTested; 27 | use Behat\Behat\EventDispatcher\Event\AfterScenarioTested; 28 | use Behat\Behat\EventDispatcher\Event\ExampleTested; 29 | use Behat\Behat\EventDispatcher\Event\FeatureTested; 30 | use Behat\Behat\EventDispatcher\Event\ScenarioTested; 31 | use Behat\Testwork\Cli\Controller; 32 | use Behat\Testwork\EventDispatcher\Event\ExerciseCompleted; 33 | use Symfony\Component\Console\Command\Command; 34 | use Symfony\Component\Console\Input\InputInterface; 35 | use Symfony\Component\Console\Input\InputOption; 36 | use Symfony\Component\Console\Output\OutputInterface; 37 | use Symfony\Component\EventDispatcher\EventDispatcherInterface; 38 | use Behat\Testwork\Tester\Result\TestResult; 39 | 40 | /** 41 | * Caches passed scenarios and skip only them if `--skip-passed` option provided. 42 | * 43 | * @copyright 2016 onwards Rajesh Taneja 44 | */ 45 | final class SkipPassedController implements Controller { 46 | /** 47 | * @var EventDispatcherInterface 48 | */ 49 | private $eventDispatcher; 50 | 51 | /** 52 | * @var null|string 53 | */ 54 | private $cachePath; 55 | 56 | /** 57 | * @var string 58 | */ 59 | private $key; 60 | 61 | /** 62 | * @var string[] 63 | */ 64 | private $lines = array(); 65 | 66 | /** 67 | * @var string 68 | */ 69 | private $basepath; 70 | 71 | /** 72 | * Initializes controller. 73 | * 74 | * @param EventDispatcherInterface $eventDispatcher 75 | * @param null|string $cachePath 76 | * @param string $basepath 77 | */ 78 | public function __construct(EventDispatcherInterface $eventDispatcher, $cachePath, $basepath) { 79 | $this->eventDispatcher = $eventDispatcher; 80 | $this->cachePath = null !== $cachePath ? rtrim($cachePath, DIRECTORY_SEPARATOR) : null; 81 | $this->basepath = $basepath; 82 | } 83 | 84 | /** 85 | * Configures command to be executable by the controller. 86 | * 87 | * @param Command $command 88 | */ 89 | public function configure(Command $command) { 90 | $command->addOption('--skip-passed', null, InputOption::VALUE_NONE, 91 | 'Skip scenarios that passed during last execution.' 92 | ); 93 | } 94 | 95 | /** 96 | * Executes controller. 97 | * 98 | * @param InputInterface $input 99 | * @param OutputInterface $output 100 | * 101 | * @return null|integer 102 | */ 103 | public function execute(InputInterface $input, OutputInterface $output) { 104 | if (!$input->getOption('skip-passed')) { 105 | // If no skip option is passed then remove any old file which we are saving. 106 | if (!$this->getFileName()) { 107 | return; 108 | } 109 | if (file_exists($this->getFileName())) { 110 | unlink($this->getFileName()); 111 | } 112 | return; 113 | } 114 | 115 | $this->eventDispatcher->addListener(ScenarioTested::AFTER, array($this, 'collectPassedScenario'), -50); 116 | $this->eventDispatcher->addListener(ExampleTested::AFTER, array($this, 'collectPassedScenario'), -50); 117 | $this->eventDispatcher->addListener(ExerciseCompleted::AFTER, array($this, 'writeCache'), -50); 118 | $this->key = $this->generateKey($input); 119 | 120 | if (!$this->getFileName() || !file_exists($this->getFileName())) { 121 | return; 122 | } 123 | $input->setArgument('paths', $this->getFileName()); 124 | 125 | $existing = json_decode(file_get_contents($this->getFileName()), true); 126 | if (!empty($existing)) { 127 | $this->lines = array_merge_recursive($existing, $this->lines); 128 | } 129 | } 130 | 131 | /** 132 | * Records scenario if it is passed. 133 | * 134 | * @param AfterScenarioTested $event 135 | */ 136 | public function collectPassedScenario(AfterScenarioTested $event) { 137 | if (!$this->getFileName()) { 138 | return; 139 | } 140 | 141 | $feature = $event->getFeature(); 142 | $suitename = $event->getSuite()->getName(); 143 | 144 | if (($event->getTestResult()->getResultCode() !== TestResult::PASSED) && 145 | ($event->getTestResult()->getResultCode() !== TestResult::SKIPPED)) { 146 | unset($this->lines[$suitename][$feature->getFile()]); 147 | return; 148 | } 149 | 150 | $this->lines[$suitename][$feature->getFile()] = $feature->getFile(); 151 | } 152 | 153 | /** 154 | * Writes passed scenarios cache. 155 | */ 156 | public function writeCache() { 157 | if (!$this->getFileName()) { 158 | return; 159 | } 160 | if (0 === count($this->lines)) { 161 | return; 162 | } 163 | file_put_contents($this->getFileName(), json_encode($this->lines)); 164 | } 165 | 166 | /** 167 | * Generates cache key. 168 | * 169 | * @param InputInterface $input 170 | * 171 | * @return string 172 | */ 173 | private function generateKey(InputInterface $input) { 174 | return md5( 175 | $input->getParameterOption(array('--profile', '-p')) . 176 | $input->getOption('suite') . 177 | implode(' ', $input->getOption('name')) . 178 | implode(' ', $input->getOption('tags')) . 179 | $input->getOption('role') . 180 | $input->getArgument('paths') . 181 | $this->basepath 182 | ); 183 | } 184 | 185 | /** 186 | * Returns cache filename (if exists). 187 | * 188 | * @return null|string 189 | */ 190 | private function getFileName() { 191 | if (null === $this->cachePath || null === $this->key) { 192 | return null; 193 | } 194 | if (!is_dir($this->cachePath)) { 195 | mkdir($this->cachePath, 0777); 196 | } 197 | return $this->cachePath . DIRECTORY_SEPARATOR . $this->key . '.passed'; 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/Moodle/BehatExtension/Driver/MoodleSelenium2Factory.php: -------------------------------------------------------------------------------- 1 | . 17 | 18 | /** 19 | * Moodle behat context class resolver. 20 | * 21 | * @package behat 22 | * @copyright 2016 Rajesh Taneja 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | namespace Moodle\BehatExtension\Driver; 27 | 28 | use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; 29 | use Symfony\Component\DependencyInjection\Definition; 30 | use Behat\MinkExtension\ServiceContainer\Driver\DriverFactory; 31 | 32 | class MoodleSelenium2Factory implements DriverFactory { 33 | /** 34 | * {@inheritdoc} 35 | */ 36 | public function getDriverName() { 37 | return 'selenium2'; 38 | } 39 | 40 | /** 41 | * {@inheritdoc} 42 | */ 43 | public function supportsJavascript() { 44 | return true; 45 | } 46 | 47 | /** 48 | * {@inheritdoc} 49 | */ 50 | public function configure(ArrayNodeDefinition $builder) { 51 | $builder 52 | ->children() 53 | ->scalarNode('browser') 54 | ->defaultValue('%mink.browser_name%') 55 | ->end() 56 | ->append($this->getCapabilitiesNode()) 57 | ->scalarNode('wd_host') 58 | ->defaultValue('http://localhost:4444/wd/hub') 59 | ->end() 60 | ->arrayNode('moodle_parameter') 61 | ->useAttributeAsKey('key') 62 | ->prototype('variable') 63 | ->defaultValue('%behat.moodle.parameter%') 64 | ->end() 65 | ->end() 66 | ; 67 | } 68 | 69 | /** 70 | * {@inheritdoc} 71 | */ 72 | public function buildDriver(array $config) { 73 | if (!class_exists('Moodle\BehatExtension\Driver\MoodleSelenium2Driver')) { 74 | throw new \RuntimeException(sprintf( 75 | 'Install MinkSelenium2Driver in order to use %s driver.', 76 | $this->getDriverName() 77 | )); 78 | } 79 | 80 | $extraCapabilities = $config['capabilities']['extra_capabilities']; 81 | unset($config['capabilities']['extra_capabilities']); 82 | 83 | if (getenv('TRAVIS_JOB_NUMBER')) { 84 | $guessedCapabilities = array( 85 | 'tunnel-identifier' => getenv('TRAVIS_JOB_NUMBER'), 86 | 'build' => getenv('TRAVIS_BUILD_NUMBER'), 87 | 'tags' => array('Travis-CI', 'PHP '.phpversion()), 88 | ); 89 | } elseif (getenv('JENKINS_HOME')) { 90 | $guessedCapabilities = array( 91 | 'tunnel-identifier' => getenv('JOB_NAME'), 92 | 'build' => getenv('BUILD_NUMBER'), 93 | 'tags' => array('Jenkins', 'PHP '.phpversion(), getenv('BUILD_TAG')), 94 | ); 95 | } else { 96 | $guessedCapabilities = array( 97 | 'tags' => array(php_uname('n'), 'PHP '.phpversion()), 98 | ); 99 | } 100 | 101 | return new Definition('Moodle\BehatExtension\Driver\MoodleSelenium2Driver', array( 102 | $config['browser'], 103 | array_replace($extraCapabilities, $guessedCapabilities, $config['capabilities']), 104 | $config['wd_host'], 105 | $config['moodle_parameter'] 106 | )); 107 | } 108 | 109 | /** 110 | * {@inheritdoc} 111 | */ 112 | protected function getCapabilitiesNode() { 113 | $node = new ArrayNodeDefinition('capabilities'); 114 | 115 | $node 116 | ->addDefaultsIfNotSet() 117 | ->normalizeKeys(false) 118 | ->children() 119 | ->scalarNode('browserName')->defaultValue('firefox')->end() 120 | ->scalarNode('version')->defaultValue('21')->end() 121 | ->scalarNode('platform')->defaultValue('ANY')->end() 122 | ->scalarNode('browserVersion')->defaultValue('9')->end() 123 | ->scalarNode('browser')->defaultValue('firefox')->end() 124 | ->scalarNode('ignoreZoomSetting')->defaultValue('false')->end() 125 | ->scalarNode('name')->defaultValue('Behat feature suite')->end() 126 | ->scalarNode('deviceOrientation')->defaultValue('portrait')->end() 127 | ->scalarNode('deviceType')->defaultValue('tablet')->end() 128 | ->booleanNode('javascriptEnabled')->end() 129 | ->booleanNode('databaseEnabled')->end() 130 | ->booleanNode('locationContextEnabled')->end() 131 | ->booleanNode('applicationCacheEnabled')->end() 132 | ->booleanNode('browserConnectionEnabled')->end() 133 | ->booleanNode('webStorageEnabled')->end() 134 | ->booleanNode('rotatable')->end() 135 | ->booleanNode('acceptSslCerts')->end() 136 | ->booleanNode('nativeEvents')->end() 137 | ->arrayNode('proxy') 138 | ->children() 139 | ->scalarNode('proxyType')->end() 140 | ->scalarNode('proxyAuthconfigUrl')->end() 141 | ->scalarNode('ftpProxy')->end() 142 | ->scalarNode('httpProxy')->end() 143 | ->scalarNode('sslProxy')->end() 144 | ->end() 145 | ->validate() 146 | ->ifTrue(function ($v) { 147 | return empty($v); 148 | }) 149 | ->thenUnset() 150 | ->end() 151 | ->end() 152 | ->arrayNode('firefox') 153 | ->children() 154 | ->scalarNode('profile') 155 | ->validate() 156 | ->ifTrue(function ($v) { 157 | return !file_exists($v); 158 | }) 159 | ->thenInvalid('Cannot find profile zip file %s') 160 | ->end() 161 | ->end() 162 | ->scalarNode('binary')->end() 163 | ->end() 164 | ->end() 165 | ->arrayNode('chrome') 166 | ->children() 167 | ->arrayNode('switches')->prototype('scalar')->end()->end() 168 | ->scalarNode('binary')->end() 169 | ->arrayNode('extensions')->prototype('scalar')->end()->end() 170 | ->end() 171 | ->end() 172 | ->arrayNode('extra_capabilities') 173 | ->info('Custom capabilities merged with the known ones') 174 | ->normalizeKeys(false) 175 | ->useAttributeAsKey('name') 176 | ->prototype('variable')->end() 177 | ->end() 178 | ->end(); 179 | 180 | return $node; 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/Moodle/BehatExtension/EventDispatcher/Tester/ChainedStepTester.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Override step tester to ensure chained steps gets executed. 19 | * 20 | * @package behat 21 | * @copyright 2016 Rajesh Taneja 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 | */ 24 | 25 | namespace Moodle\BehatExtension\EventDispatcher\Tester; 26 | 27 | use Behat\Behat\Tester\Result\ExecutedStepResult; 28 | use Behat\Behat\Tester\Result\SkippedStepResult; 29 | use Behat\Behat\Tester\Result\StepResult; 30 | use Behat\Behat\Tester\StepTester; 31 | use Behat\Behat\Tester\Result\UndefinedStepResult; 32 | use Moodle\BehatExtension\Context\Step\Given; 33 | use Moodle\BehatExtension\Context\Step\ChainedStep; 34 | use Behat\Gherkin\Node\FeatureNode; 35 | use Behat\Gherkin\Node\StepNode; 36 | use Behat\Testwork\Call\CallResult; 37 | use Behat\Testwork\Environment\Environment; 38 | use Behat\Behat\EventDispatcher\Event\AfterStepSetup; 39 | use Behat\Behat\EventDispatcher\Event\AfterStepTested; 40 | use Behat\Behat\EventDispatcher\Event\BeforeStepTeardown; 41 | use Behat\Behat\EventDispatcher\Event\BeforeStepTested; 42 | use Symfony\Component\EventDispatcher\EventDispatcherInterface; 43 | use Moodle\BehatExtension\Exception\SkippedException; 44 | 45 | /** 46 | * Override step tester to ensure chained steps gets executed. 47 | * 48 | * @package behat 49 | * @copyright 2016 Rajesh Taneja 50 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 51 | */ 52 | class ChainedStepTester implements StepTester { 53 | /** 54 | * The text of the step to look for exceptions / debugging messages. 55 | */ 56 | const EXCEPTIONS_STEP_TEXT = 'I look for exceptions'; 57 | 58 | /** 59 | * @var StepTester Base step tester. 60 | */ 61 | private $singlesteptester; 62 | 63 | /** 64 | * @var EventDispatcher keep step event dispatcher. 65 | */ 66 | private $eventDispatcher; 67 | 68 | /** 69 | * Keep status of chained steps if used. 70 | * @var bool 71 | */ 72 | protected static $chainedstepused = false; 73 | 74 | /** 75 | * Constructor. 76 | * 77 | * @param StepTester $steptester single step tester. 78 | */ 79 | public function __construct(StepTester $steptester) { 80 | $this->singlesteptester = $steptester; 81 | } 82 | 83 | /** 84 | * Set event dispatcher to use for events. 85 | * 86 | * @param EventDispatcherInterface $eventDispatcher 87 | */ 88 | public function setEventDispatcher(EventDispatcherInterface $eventDispatcher) { 89 | $this->eventDispatcher = $eventDispatcher; 90 | } 91 | 92 | /** 93 | * {@inheritdoc} 94 | */ 95 | public function setUp(Environment $env, FeatureNode $feature, StepNode $step, $skip) { 96 | return $this->singlesteptester->setUp($env, $feature, $step, $skip); 97 | } 98 | 99 | /** 100 | * {@inheritdoc} 101 | */ 102 | public function test(Environment $env, FeatureNode $feature, StepNode $step, $skip) { 103 | $result = $this->singlesteptester->test($env, $feature, $step, $skip); 104 | 105 | if (!($result instanceof ExecutedStepResult) || !$this->supportsResult($result->getCallResult())) { 106 | $result = $this->checkSkipResult($result); 107 | 108 | // If undefined step then don't continue chained steps. 109 | if ($result instanceof UndefinedStepResult) { 110 | return $result; 111 | } 112 | 113 | // If exception caught, then don't continue chained steps. 114 | if (($result instanceof ExecutedStepResult) && $result->hasException()) { 115 | return $result; 116 | } 117 | 118 | // If step is skipped, then return. no need to continue chain steps. 119 | if ($result instanceof SkippedStepResult) { 120 | return $result; 121 | } 122 | 123 | // Check for exceptions. 124 | // Extra step, looking for a moodle exception, a debugging() message or a PHP debug message. 125 | $checkingStep = new StepNode('Given', self::EXCEPTIONS_STEP_TEXT, array(), $step->getLine()); 126 | $afterExceptionCheckingEvent = $this->singlesteptester->test($env, $feature, $checkingStep, $skip); 127 | return $this->checkSkipResult($afterExceptionCheckingEvent); 128 | } 129 | 130 | return $this->runChainedSteps($env, $feature, $result, $skip); 131 | } 132 | 133 | /** 134 | * {@inheritdoc} 135 | */ 136 | public function tearDown(Environment $env, FeatureNode $feature, StepNode $step, $skip, StepResult $result) { 137 | return $this->singlesteptester->tearDown($env, $feature, $step, $skip, $result); 138 | } 139 | 140 | /** 141 | * Check if results supported. 142 | * 143 | * @param CallResult $result 144 | * @return bool 145 | */ 146 | private function supportsResult(CallResult $result) { 147 | $return = $result->getReturn(); 148 | if ($return instanceof ChainedStep) { 149 | return true; 150 | } 151 | if (!is_array($return) || empty($return)) { 152 | return false; 153 | } 154 | foreach ($return as $value) { 155 | if (!$value instanceof ChainedStep) { 156 | return false; 157 | } 158 | } 159 | return true; 160 | } 161 | 162 | /** 163 | * Run chained steps. 164 | * 165 | * @param Environment $env 166 | * @param FeatureNode $feature 167 | * @param ExecutedStepResult $result 168 | * @param $skip 169 | * 170 | * @return ExecutedStepResult|StepResult 171 | */ 172 | private function runChainedSteps(Environment $env, FeatureNode $feature, ExecutedStepResult $result, $skip) { 173 | // Set chained setp is used, so it can be used by formatter to o/p. 174 | self::$chainedstepused = true; 175 | 176 | $callResult = $result->getCallResult(); 177 | $steps = $callResult->getReturn(); 178 | 179 | if (!is_array($steps)) { 180 | // Test it, no need to dispatch events for single chain. 181 | $stepResult = $this->test($env, $feature, $steps, $skip); 182 | return $this->checkSkipResult($stepResult); 183 | } 184 | 185 | // Test all steps. 186 | foreach ($steps as $step) { 187 | // Setup new step. 188 | $event = new BeforeStepTested($env, $feature, $step); 189 | $this->eventDispatcher->dispatch($event::BEFORE, $event); 190 | 191 | $setup = $this->setUp($env, $feature, $step, $skip); 192 | 193 | $event = new AfterStepSetup($env, $feature, $step, $setup); 194 | $this->eventDispatcher->dispatch($event::AFTER_SETUP, $event); 195 | 196 | // Test it. 197 | $stepResult = $this->test($env, $feature, $step, $skip); 198 | 199 | // Tear down. 200 | $event = new BeforeStepTeardown($env, $feature, $step, $result); 201 | $this->eventDispatcher->dispatch($event::BEFORE_TEARDOWN, $event); 202 | 203 | $teardown = $this->tearDown($env, $feature, $step, $skip, $result); 204 | 205 | $event = new AfterStepTested($env, $feature, $step, $result, $teardown); 206 | $this->eventDispatcher->dispatch($event::AFTER, $event); 207 | 208 | // 209 | if (!$stepResult->isPassed()) { 210 | return $this->checkSkipResult($stepResult); 211 | } 212 | } 213 | return $this->checkSkipResult($stepResult); 214 | } 215 | 216 | /** 217 | * Handle skip exception. 218 | * 219 | * @param StepResult $result 220 | * 221 | * @return ExecutedStepResult|SkippedStepResult 222 | */ 223 | private function checkSkipResult(StepResult $result) { 224 | if ((method_exists($result, 'getException')) && ($result->getException() instanceof SkippedException)) { 225 | return new SkippedStepResult($result->getSearchResult()); 226 | } else { 227 | return $result; 228 | } 229 | } 230 | 231 | /** 232 | * Returns if cahined steps are used. 233 | * @return bool. 234 | */ 235 | public static function is_chained_step_used() { 236 | return self::$chainedstepused; 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /src/Moodle/BehatExtension/Output/Formatter/MoodleProgressFormatterFactory.php: -------------------------------------------------------------------------------- 1 | . 17 | 18 | /** 19 | * Moodle behat context class resolver. 20 | * 21 | * @package behat 22 | * @copyright 2016 Rajesh Taneja 23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 | */ 25 | 26 | namespace Moodle\BehatExtension\Output\Formatter; 27 | 28 | use Behat\Testwork\Exception\ServiceContainer\ExceptionExtension; 29 | use Behat\Testwork\Output\ServiceContainer\OutputExtension; 30 | use Behat\Testwork\ServiceContainer\ServiceProcessor; 31 | use Symfony\Component\DependencyInjection\ContainerBuilder; 32 | use Symfony\Component\DependencyInjection\Definition; 33 | use Symfony\Component\DependencyInjection\Reference; 34 | use Behat\Behat\Output\ServiceContainer\Formatter\ProgressFormatterFactory; 35 | use Behat\Behat\EventDispatcher\Event\OutlineTested; 36 | use Behat\Testwork\Output\ServiceContainer\Formatter\FormatterFactory; 37 | use Behat\Testwork\Translator\ServiceContainer\TranslatorExtension; 38 | 39 | class MoodleProgressFormatterFactory implements FormatterFactory { 40 | /** 41 | * @var ServiceProcessor 42 | */ 43 | private $processor; 44 | 45 | /* 46 | * Available services 47 | */ 48 | const ROOT_LISTENER_ID_MOODLE = 'output.node.listener.moodleprogress'; 49 | const RESULT_TO_STRING_CONVERTER_ID_MOODLE = 'output.node.printer.result_to_string'; 50 | 51 | /* 52 | * Available extension points 53 | */ 54 | const ROOT_LISTENER_WRAPPER_TAG_MOODLE = 'output.node.listener.moodleprogress.wrapper'; 55 | 56 | /** 57 | * Initializes extension. 58 | * 59 | * @param null|ServiceProcessor $processor 60 | */ 61 | public function __construct(ServiceProcessor $processor = null) { 62 | $this->processor = $processor ? : new ServiceProcessor(); 63 | } 64 | 65 | /** 66 | * {@inheritdoc} 67 | */ 68 | public function buildFormatter(ContainerBuilder $container) { 69 | $this->loadRootNodeListener($container); 70 | $this->loadCorePrinters($container); 71 | $this->loadPrinterHelpers($container); 72 | $this->loadFormatter($container); 73 | } 74 | 75 | /** 76 | * {@inheritdoc} 77 | */ 78 | public function processFormatter(ContainerBuilder $container) { 79 | $this->processListenerWrappers($container); 80 | } 81 | 82 | /** 83 | * Loads progress formatter node event listener. 84 | * 85 | * @param ContainerBuilder $container 86 | */ 87 | protected function loadRootNodeListener(ContainerBuilder $container) { 88 | $definition = new Definition('Behat\Behat\Output\Node\EventListener\AST\StepListener', array( 89 | new Reference('output.node.printer.moodleprogress.step') 90 | )); 91 | $container->setDefinition(self::ROOT_LISTENER_ID_MOODLE, $definition); 92 | } 93 | 94 | /** 95 | * Loads formatter itself. 96 | * 97 | * @param ContainerBuilder $container 98 | */ 99 | protected function loadFormatter(ContainerBuilder $container) { 100 | 101 | $definition = new Definition('Behat\Behat\Output\Statistics\TotalStatistics'); 102 | $container->setDefinition('output.moodleprogress.statistics', $definition); 103 | 104 | $moodleconfig = $container->getParameter('behat.moodle.parameters'); 105 | 106 | $definition = new Definition('Moodle\BehatExtension\Output\Printer\MoodleProgressPrinter', 107 | array($moodleconfig['moodledirroot'])); 108 | $container->setDefinition('moodle.output.node.printer.moodleprogress.printer', $definition); 109 | 110 | $definition = new Definition('Behat\Testwork\Output\NodeEventListeningFormatter', array( 111 | 'moodle_progress', 112 | 'Prints information about then run followed by one character per step.', 113 | array( 114 | 'timer' => true 115 | ), 116 | $this->createOutputPrinterDefinition(), 117 | new Definition('Behat\Testwork\Output\Node\EventListener\ChainEventListener', array( 118 | array( 119 | new Reference(self::ROOT_LISTENER_ID_MOODLE), 120 | new Definition('Behat\Behat\Output\Node\EventListener\Statistics\StatisticsListener', array( 121 | new Reference('output.moodleprogress.statistics'), 122 | new Reference('output.node.printer.moodleprogress.statistics') 123 | )), 124 | new Definition('Behat\Behat\Output\Node\EventListener\Statistics\ScenarioStatsListener', array( 125 | new Reference('output.moodleprogress.statistics') 126 | )), 127 | new Definition('Behat\Behat\Output\Node\EventListener\Statistics\StepStatsListener', array( 128 | new Reference('output.moodleprogress.statistics'), 129 | new Reference(ExceptionExtension::PRESENTER_ID) 130 | )), 131 | new Definition('Behat\Behat\Output\Node\EventListener\Statistics\HookStatsListener', array( 132 | new Reference('output.moodleprogress.statistics'), 133 | new Reference(ExceptionExtension::PRESENTER_ID) 134 | )), 135 | new Definition('Behat\Behat\Output\Node\EventListener\AST\SuiteListener', array( 136 | new Reference('moodle.output.node.printer.moodleprogress.printer') 137 | )) 138 | ) 139 | ) 140 | ) 141 | )); 142 | $definition->addTag(OutputExtension::FORMATTER_TAG, array('priority' => 1)); 143 | $container->setDefinition(OutputExtension::FORMATTER_TAG . '.moodleprogress', $definition); 144 | } 145 | 146 | /** 147 | * Loads printer helpers. 148 | * 149 | * @param ContainerBuilder $container 150 | */ 151 | protected function loadPrinterHelpers(ContainerBuilder $container) { 152 | $definition = new Definition('Behat\Behat\Output\Node\Printer\Helper\ResultToStringConverter'); 153 | $container->setDefinition(self::RESULT_TO_STRING_CONVERTER_ID_MOODLE, $definition); 154 | } 155 | 156 | /** 157 | * Loads feature, scenario and step printers. 158 | * 159 | * @param ContainerBuilder $container 160 | */ 161 | protected function loadCorePrinters(ContainerBuilder $container) { 162 | $definition = new Definition('Behat\Behat\Output\Node\Printer\CounterPrinter', array( 163 | new Reference(self::RESULT_TO_STRING_CONVERTER_ID_MOODLE), 164 | new Reference(TranslatorExtension::TRANSLATOR_ID), 165 | )); 166 | $container->setDefinition('output.node.moodle.printer.counter', $definition); 167 | 168 | $definition = new Definition('Behat\Behat\Output\Node\Printer\ListPrinter', array( 169 | new Reference(self::RESULT_TO_STRING_CONVERTER_ID_MOODLE), 170 | new Reference(ExceptionExtension::PRESENTER_ID), 171 | new Reference(TranslatorExtension::TRANSLATOR_ID), 172 | '%paths.base%' 173 | )); 174 | $container->setDefinition('output.node.moodle.printer.list', $definition); 175 | 176 | $definition = new Definition('Behat\Behat\Output\Node\Printer\Progress\ProgressStepPrinter', array( 177 | new Reference(self::RESULT_TO_STRING_CONVERTER_ID_MOODLE) 178 | )); 179 | $container->setDefinition('output.node.printer.moodleprogress.step', $definition); 180 | 181 | $definition = new Definition('Behat\Behat\Output\Node\Printer\Progress\ProgressStatisticsPrinter', array( 182 | new Reference('output.node.moodle.printer.counter'), 183 | new Reference('output.node.moodle.printer.list') 184 | )); 185 | $container->setDefinition('output.node.printer.moodleprogress.statistics', $definition); 186 | } 187 | 188 | /** 189 | * Creates output printer definition. 190 | * 191 | * @return Definition 192 | */ 193 | protected function createOutputPrinterDefinition() { 194 | return new Definition('Behat\Testwork\Output\Printer\StreamOutputPrinter', array( 195 | new Definition('Behat\Behat\Output\Printer\ConsoleOutputFactory'), 196 | )); 197 | } 198 | 199 | /** 200 | * Processes all registered pretty formatter node listener wrappers. 201 | * 202 | * @param ContainerBuilder $container 203 | */ 204 | protected function processListenerWrappers(ContainerBuilder $container) { 205 | $this->processor->processWrapperServices($container, self::ROOT_LISTENER_ID_MOODLE, self::ROOT_LISTENER_WRAPPER_TAG_MOODLE); 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/Moodle/BehatExtension/Driver/MoodleSelenium2Driver.php: -------------------------------------------------------------------------------- 1 | $capability) { 32 | $desiredCapabilities[$key] = $capability; 33 | } 34 | } 35 | 36 | parent::__construct($browserName, $desiredCapabilities, $wdHost); 37 | 38 | // This class is instantiated by the dependencies injection system so 39 | // prior to all of beforeSuite subscribers which will call getBrowser*() 40 | self::$browser = $browserName; 41 | } 42 | 43 | /** 44 | * Forwards to getBrowser() so we keep compatibility with both static and non-static accesses. 45 | * 46 | * @deprecated 47 | * @param string $name 48 | * @param array $arguments 49 | * @return mixed 50 | */ 51 | public static function __callStatic($name, $arguments) { 52 | if ($name == 'getBrowserName') { 53 | return self::getBrowser(); 54 | } 55 | 56 | // Fallbacks calling the requested static method, we don't 57 | // even know if it exists or not. 58 | return call_user_func(array(self, $name), $arguments); 59 | } 60 | 61 | /** 62 | * Forwards to getBrowser() so we keep compatibility with both static and non-static accesses. 63 | * 64 | * @deprecated 65 | * @param string $name 66 | * @param array $arguments 67 | * @return mixed 68 | */ 69 | public function __call($name, $arguments) { 70 | if ($name == 'getBrowserName') { 71 | return self::getBrowser(); 72 | } 73 | 74 | // Fallbacks calling the requested instance method, we don't 75 | // even know if it exists or not. 76 | return call_user_func(array($this, $name), $arguments); 77 | } 78 | 79 | /** 80 | * Returns the browser being used. 81 | * 82 | * We need to know it: 83 | * - To show info about the run. 84 | * - In case there are differences between browsers in the steps. 85 | * 86 | * @static 87 | * @return string 88 | */ 89 | public static function getBrowser() { 90 | return self::$browser; 91 | } 92 | 93 | /** 94 | * Drag one element onto another. 95 | * 96 | * Override the original one to give YUI drag & drop 97 | * time to consider it a valid drag & drop. It will need 98 | * more changes in future to properly adapt to how YUI dd 99 | * component behaves. 100 | * 101 | * @param string $sourceXpath 102 | * @param string $destinationXpath 103 | */ 104 | public function dragTo($sourceXpath, $destinationXpath) { 105 | $source = $this->getWebDriverSession()->element('xpath', $sourceXpath); 106 | $destination = $this->getWebDriverSession()->element('xpath', $destinationXpath); 107 | 108 | // TODO: MDL-39727 This method requires improvements according to the YUI drag and drop component. 109 | 110 | $this->getWebDriverSession()->moveto(array( 111 | 'element' => $source->getID() 112 | )); 113 | 114 | $script = <<withSyn()->executeJsOnXpath($sourceXpath, $script); 125 | 126 | $this->getWebDriverSession()->buttondown(); 127 | $this->getWebDriverSession()->moveto(array( 128 | 'element' => $destination->getID() 129 | )); 130 | 131 | // We add a 2 seconds wait to make YUI dd happy. 132 | $this->wait(2 * 1000, false); 133 | 134 | $this->getWebDriverSession()->buttonup(); 135 | 136 | $script = <<withSyn()->executeJsOnXpath($destinationXpath, $script); 147 | } 148 | 149 | /** 150 | * Overwriten method to use our custom Syn library. 151 | * 152 | * Makes sure that the Syn event library has been injected into the current page, 153 | * and return $this for a fluid interface, 154 | * 155 | * $this->withSyn()->executeJsOnXpath($xpath, $script); 156 | * 157 | * @return Selenium2Driver 158 | */ 159 | protected function withSyn() 160 | { 161 | $hasSyn = $this->getWebDriverSession()->execute(array( 162 | 'script' => 'return typeof window["Syn"]!=="undefined"', 163 | 'args' => array() 164 | )); 165 | 166 | if (!$hasSyn) { 167 | $synJs = file_get_contents(__DIR__.'/Selenium2/moodle_syn-min.js'); 168 | $this->getWebDriverSession()->execute(array( 169 | 'script' => $synJs, 170 | 'args' => array() 171 | )); 172 | } 173 | 174 | return $this; 175 | } 176 | 177 | /** 178 | * Public interface to run Syn scripts. 179 | * 180 | * @see self::executeJsOnXpath() 181 | * 182 | * @param string $xpath the xpath to search with 183 | * @param string $script the script to execute 184 | * @param Boolean $sync whether to run the script synchronously (default is TRUE) 185 | * 186 | * @return mixed 187 | */ 188 | public function triggerSynScript($xpath, $script, $sync = true) { 189 | return $this->withSyn()->executeJsOnXpath($xpath, $script, $sync); 190 | } 191 | 192 | /** 193 | * Overriding this as key::TAB is causing page scroll and rubrics scenarios are failing. 194 | * https://github.com/minkphp/MinkSelenium2Driver/issues/194 195 | * {@inheritdoc} 196 | */ 197 | public function setValue($xpath, $value) { 198 | $value = strval($value); 199 | $element = $this->getWebDriverSession()->element('xpath', $xpath); 200 | $elementName = strtolower($element->name()); 201 | 202 | if ('select' === $elementName) { 203 | if (is_array($value)) { 204 | $this->deselectAllOptions($element); 205 | 206 | foreach ($value as $option) { 207 | $this->selectOptionOnElement($element, $option, true); 208 | } 209 | 210 | return; 211 | } 212 | 213 | $this->selectOption($element, $value); 214 | 215 | return; 216 | } 217 | 218 | if ('input' === $elementName) { 219 | $elementType = strtolower($element->attribute('type')); 220 | 221 | if (in_array($elementType, array('submit', 'image', 'button', 'reset'))) { 222 | throw new DriverException(sprintf('Impossible to set value an element with XPath "%s" as it is not a select, textarea or textbox', $xpath)); 223 | } 224 | 225 | if ('checkbox' === $elementType) { 226 | if ($element->selected() xor (bool) $value) { 227 | $this->clickOnElement($element); 228 | } 229 | 230 | return; 231 | } 232 | 233 | if ('radio' === $elementType) { 234 | $this->selectRadioValue($element, $value); 235 | 236 | return; 237 | } 238 | 239 | if ('file' === $elementType) { 240 | $element->postValue(array('value' => array(strval($value)))); 241 | 242 | return; 243 | } 244 | } 245 | 246 | $value = strval($value); 247 | 248 | if (in_array($elementName, array('input', 'textarea'))) { 249 | $existingValueLength = strlen($element->attribute('value')); 250 | // Add the TAB key to ensure we unfocus the field as browsers are triggering the change event only 251 | // after leaving the field. 252 | $value = str_repeat(Key::BACKSPACE . Key::DELETE, $existingValueLength) . $value; 253 | } 254 | 255 | $element->postValue(array('value' => array($value))); 256 | $script = "Syn.trigger('change', {}, {{ELEMENT}})"; 257 | $this->withSyn()->executeJsOnXpath($xpath, $script); 258 | } 259 | 260 | /** 261 | * Post key on specified xpath. 262 | * 263 | * @param string $xpath 264 | */ 265 | public function post_key($key, $xpath) { 266 | $element = $this->getWebDriverSession()->element('xpath', $xpath); 267 | $element->postValue(array('value' => array($key))); 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /src/Moodle/BehatExtension/Output/Formatter/MoodleScreenshotFormatter.php: -------------------------------------------------------------------------------- 1 | . 16 | 17 | /** 18 | * Feature step counter for distributing features between parallel runs. 19 | * 20 | * Use it with --dry-run (and any other selectors combination) to 21 | * get the results quickly. 22 | * 23 | * @copyright 2016 onwards Rajesh Taneja 24 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 25 | */ 26 | 27 | namespace Moodle\BehatExtension\Output\Formatter; 28 | 29 | use Behat\Behat\EventDispatcher\Event\AfterFeatureTested; 30 | use Behat\Behat\EventDispatcher\Event\AfterOutlineTested; 31 | use Behat\Behat\EventDispatcher\Event\AfterScenarioTested; 32 | use Behat\Behat\EventDispatcher\Event\AfterStepTested; 33 | use Behat\Behat\EventDispatcher\Event\BeforeFeatureTested; 34 | use Behat\Behat\EventDispatcher\Event\BeforeOutlineTested; 35 | use Behat\Behat\EventDispatcher\Event\BeforeScenarioTested; 36 | use Behat\Behat\EventDispatcher\Event\BeforeStepTested; 37 | use Behat\Behat\Tester\Result\ExecutedStepResult; 38 | use Behat\Testwork\Counter\Memory; 39 | use Behat\Testwork\Counter\Timer; 40 | use Behat\Testwork\EventDispatcher\Event\AfterExerciseCompleted; 41 | use Behat\Testwork\EventDispatcher\Event\AfterSuiteTested; 42 | use Behat\Testwork\EventDispatcher\Event\BeforeExerciseCompleted; 43 | use Behat\Testwork\EventDispatcher\Event\BeforeSuiteTested; 44 | use Behat\Testwork\Output\Exception\BadOutputPathException; 45 | use Behat\Testwork\Output\Formatter; 46 | use Behat\Testwork\Output\Printer\OutputPrinter; 47 | 48 | class MoodleScreenshotFormatter implements Formatter { 49 | 50 | /** 51 | * @var OutputPrinter 52 | */ 53 | private $printer; 54 | /** 55 | * @var array 56 | */ 57 | private $parameters; 58 | /** 59 | * @var string 60 | */ 61 | private $name; 62 | /** 63 | * @var string 64 | */ 65 | private $description; 66 | 67 | /** 68 | * @var int The scenario count. 69 | */ 70 | protected static $currentscenariocount = 0; 71 | 72 | /** 73 | * @var int The step count within the current scenario. 74 | */ 75 | protected static $currentscenariostepcount = 0; 76 | 77 | /** 78 | * If we are saving any kind of dump on failure we should use the same parent dir during a run. 79 | * 80 | * @var The parent dir name 81 | */ 82 | protected static $faildumpdirname = false; 83 | 84 | /** 85 | * Initializes formatter. 86 | * 87 | * @param string $name 88 | * @param string $description 89 | * @param array $parameters 90 | * @param OutputPrinter $printer 91 | * @param EventListener $listener 92 | */ 93 | public function __construct($name, $description, array $parameters, OutputPrinter $printer) { 94 | $this->name = $name; 95 | $this->description = $description; 96 | $this->parameters = $parameters; 97 | $this->printer = $printer; 98 | } 99 | 100 | /** 101 | * Returns an array of event names this subscriber wants to listen to. 102 | * @return array The event names to listen to 103 | */ 104 | public static function getSubscribedEvents() { 105 | return array( 106 | 107 | 'tester.scenario_tested.before' => 'beforeScenario', 108 | 'tester.step_tested.before' => 'beforeStep', 109 | 'tester.step_tested.after' => 'afterStep', 110 | ); 111 | } 112 | 113 | /** 114 | * {@inheritdoc} 115 | */ 116 | public function getName() { 117 | return $this->name; 118 | } 119 | 120 | /** 121 | * {@inheritdoc} 122 | */ 123 | public function getDescription() { 124 | return $this->description; 125 | } 126 | 127 | /** 128 | * {@inheritdoc} 129 | */ 130 | public function getOutputPrinter() { 131 | return $this->printer; 132 | } 133 | 134 | /** 135 | * {@inheritdoc} 136 | */ 137 | public function setParameter($name, $value) { 138 | $this->parameters[$name] = $value; 139 | } 140 | 141 | /** 142 | * {@inheritdoc} 143 | */ 144 | public function getParameter($name) { 145 | return isset($this->parameters[$name]) ? $this->parameters[$name] : null; 146 | } 147 | 148 | /** 149 | * Reset currentscenariostepcount 150 | * 151 | * @param BeforeScenarioTested $event 152 | */ 153 | public function beforeScenario(BeforeScenarioTested $event) { 154 | 155 | self::$currentscenariostepcount = 0; 156 | self::$currentscenariocount++; 157 | } 158 | 159 | /** 160 | * Increment currentscenariostepcount 161 | * 162 | * @param BeforeStepTested $event 163 | */ 164 | public function beforeStep(BeforeStepTested $event) { 165 | self::$currentscenariostepcount++; 166 | } 167 | 168 | /** 169 | * Take screenshot after step is executed. Behat\Behat\Event\html 170 | * 171 | * @param AfterStepTested $event 172 | */ 173 | public function afterStep(AfterStepTested $event) { 174 | $behathookcontext = $event->getEnvironment()->getContext('behat_hooks'); 175 | 176 | $formats = $this->getParameter('formats'); 177 | $formats = explode(',', $formats); 178 | 179 | // Take screenshot. 180 | if (in_array('image', $formats)) { 181 | $this->take_screenshot($event, $behathookcontext); 182 | } 183 | 184 | // Save html content. 185 | if (in_array('html', $formats)) { 186 | $this->take_contentdump($event, $behathookcontext); 187 | } 188 | } 189 | 190 | /** 191 | * Return screenshot directory where all screenshots will be saved. 192 | * 193 | * @return string 194 | */ 195 | protected function get_run_screenshot_dir() { 196 | global $CFG; 197 | 198 | if (self::$faildumpdirname) { 199 | return self::$faildumpdirname; 200 | } 201 | 202 | // If output_path is set then use output_path else use faildump_path. 203 | if ($this->getOutputPrinter()->getOutputPath()) { 204 | $screenshotpath = $this->getOutputPrinter()->getOutputPath(); 205 | } else if ($CFG->behat_faildump_path) { 206 | $screenshotpath = $CFG->behat_faildump_path; 207 | } else { 208 | // It should never reach here. 209 | throw new FormatterException('You should specify --out "SOME/PATH" for moodle_screenshot format'); 210 | } 211 | 212 | if ($this->getParameter('dir_permissions')) { 213 | $dirpermissions = $this->getParameter('dir_permissions'); 214 | } else { 215 | $dirpermissions = 0777; 216 | } 217 | 218 | // All the screenshot dumps should be in the same parent dir. 219 | self::$faildumpdirname = $screenshotpath . DIRECTORY_SEPARATOR . date('Ymd_His'); 220 | 221 | if (!is_dir(self::$faildumpdirname) && !mkdir(self::$faildumpdirname, $dirpermissions, true)) { 222 | // It shouldn't, we already checked that the directory is writable. 223 | throw new FormatterException(sprintf( 224 | 'No directories can be created inside %s, check the directory permissions.', $screenshotpath)); 225 | } 226 | 227 | return self::$faildumpdirname; 228 | } 229 | 230 | /** 231 | * Take screenshot when a step fails. 232 | * 233 | * @throws Exception 234 | * @param AfterStepTested $event 235 | */ 236 | protected function take_screenshot(AfterStepTested $event, $context) { 237 | // Goutte can't save screenshots. 238 | if ($context->getMink()->isSessionStarted($context->getMink()->getDefaultSessionName())) { 239 | if (get_class($context->getMink()->getSession()->getDriver()) === 'Behat\Mink\Driver\GoutteDriver') { 240 | return false; 241 | } 242 | list ($dir, $filename) = $this->get_faildump_filename($event, 'png'); 243 | $context->saveScreenshot($filename, $dir); 244 | } 245 | } 246 | 247 | /** 248 | * Take a dump of the page content when a step fails. 249 | * 250 | * @throws Exception 251 | * @param AfterStepTested $event 252 | */ 253 | protected function take_contentdump(AfterStepTested $event, $context) { 254 | list ($dir, $filename) = $this->get_faildump_filename($event, 'html'); 255 | $fh = fopen($dir . DIRECTORY_SEPARATOR . $filename, 'w'); 256 | fwrite($fh, $context->getMink()->getSession()->getPage()->getContent()); 257 | fclose($fh); 258 | } 259 | 260 | /** 261 | * Determine the full pathname to store a failure-related dump. 262 | * 263 | * This is used for content such as the DOM, and screenshots. 264 | * 265 | * @param AfterStepTested $event 266 | * @param String $filetype The file suffix to use. Limited to 4 chars. 267 | */ 268 | protected function get_faildump_filename(AfterStepTested $event, $filetype) { 269 | // Make a directory for the scenario. 270 | $featurename = $event->getFeature()->getTitle(); 271 | $featurename = preg_replace('/([^a-zA-Z0-9\_]+)/', '-', $featurename); 272 | if ($this->getParameter('dir_permissions')) { 273 | $dirpermissions = $this->getParameter('dir_permissions'); 274 | } else { 275 | $dirpermissions = 0777; 276 | } 277 | 278 | $dir = $this->get_run_screenshot_dir(); 279 | 280 | // We want a i-am-the-scenario-title format. 281 | $dir = $dir . DIRECTORY_SEPARATOR . self::$currentscenariocount . '-' . $featurename; 282 | if (!is_dir($dir) && !mkdir($dir, $dirpermissions, true)) { 283 | // We already checked that the directory is writable. This should not fail. 284 | throw new FormatterException(sprintf( 285 | 'No directories can be created inside %s, check the directory permissions.', $dir)); 286 | } 287 | 288 | // The failed step text. 289 | // We want a stepno-i-am-the-failed-step.$filetype format. 290 | $filename = $event->getStep()->getText(); 291 | $filename = preg_replace('/([^a-zA-Z0-9\_]+)/', '-', $filename); 292 | $filename = self::$currentscenariostepcount . '-' . $filename; 293 | 294 | // File name limited to 255 characters. Leaving 4 chars for the file 295 | // extension as we allow .png for images and .html for DOM contents. 296 | $filename = substr($filename, 0, 250) . '.' . $filetype; 297 | return array($dir, $filename); 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /src/Moodle/BehatExtension/ServiceContainer/BehatExtension.php: -------------------------------------------------------------------------------- 1 | processor = $processor ? : new ServiceProcessor(); 51 | } 52 | 53 | /** 54 | * Loads moodle specific configuration. 55 | * 56 | * @param array $config Extension configuration hash (from behat.yml) 57 | * @param ContainerBuilder $container ContainerBuilder instance 58 | */ 59 | public function load(ContainerBuilder $container, array $config) { 60 | $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/services')); 61 | $loader->load('core.xml'); 62 | 63 | // Getting the extension parameters. 64 | $container->setParameter('behat.moodle.parameters', $config); 65 | 66 | // Load moodle progress formatter. 67 | $moodleprogressformatter = new MoodleProgressFormatterFactory(); 68 | $moodleprogressformatter->buildFormatter($container); 69 | 70 | // Load custom step tester event dispatcher. 71 | $this->loadEventDispatchingStepTester($container); 72 | 73 | // Load chained step tester. 74 | $this->loadChainedStepTester($container); 75 | 76 | // Load step count formatter. 77 | $this->loadMoodleListFormatter($container); 78 | 79 | // Load step count formatter. 80 | $this->loadMoodleStepcountFormatter($container); 81 | 82 | // Load screenshot formatter. 83 | $this->loadMoodleScreenshotFormatter($container); 84 | 85 | // Load namespace alias. 86 | $this->alias_old_namespaces(); 87 | 88 | // Load skip passed controller and list locator. 89 | $this->loadSkipPassedController($container, $config['passed_cache']); 90 | $this->loadFilesystemSkipPassedScenariosListLocator($container); 91 | } 92 | 93 | /** 94 | * Loads moodle List formatter. 95 | * 96 | * @param ContainerBuilder $container 97 | */ 98 | protected function loadMoodleListFormatter(ContainerBuilder $container) { 99 | $definition = new Definition('Moodle\BehatExtension\Output\Formatter\MoodleListFormatter', array( 100 | 'moodle_list', 101 | 'List all scenarios. Use with --dry-run', 102 | array('stepcount' => false), 103 | $this->createOutputPrinterDefinition() 104 | )); 105 | $definition->addTag(OutputExtension::FORMATTER_TAG, array('priority' => 101)); 106 | $container->setDefinition(OutputExtension::FORMATTER_TAG . '.moodle_list', $definition); 107 | } 108 | 109 | /** 110 | * Loads moodle Step count formatter. 111 | * 112 | * @param ContainerBuilder $container 113 | */ 114 | protected function loadMoodleStepcountFormatter(ContainerBuilder $container) { 115 | $definition = new Definition('Moodle\BehatExtension\Output\Formatter\MoodleStepcountFormatter', array( 116 | 'moodle_stepcount', 117 | 'Count steps in feature files. Use with --dry-run', 118 | array('stepcount' => false), 119 | $this->createOutputPrinterDefinition() 120 | )); 121 | $definition->addTag(OutputExtension::FORMATTER_TAG, array('priority' => 101)); 122 | $container->setDefinition(OutputExtension::FORMATTER_TAG . '.moodle_stepcount', $definition); 123 | } 124 | 125 | /** 126 | * Loads moodle screenshot formatter. 127 | * 128 | * @param ContainerBuilder $container 129 | */ 130 | protected function loadMoodleScreenshotFormatter(ContainerBuilder $container) { 131 | $definition = new Definition('Moodle\BehatExtension\Output\Formatter\MoodleScreenshotFormatter', array( 132 | 'moodle_screenshot', 133 | 'Take screenshot of all steps. Use --format-settings \'{"formats": "html,image"}\' to get specific o/p type', 134 | array('formats' => 'html,image'), 135 | $this->createOutputPrinterDefinition() 136 | )); 137 | $definition->addTag(OutputExtension::FORMATTER_TAG, array('priority' => 102)); 138 | $container->setDefinition(OutputExtension::FORMATTER_TAG . '.moodle_screenshot', $definition); 139 | } 140 | 141 | /** 142 | * Creates output printer definition. 143 | * 144 | * @return Definition 145 | */ 146 | protected function createOutputPrinterDefinition() { 147 | return new Definition('Behat\Testwork\Output\Printer\StreamOutputPrinter', array( 148 | new Definition('Behat\Behat\Output\Printer\ConsoleOutputFactory'), 149 | )); 150 | } 151 | 152 | /** 153 | * Loads skip passed controller. 154 | * 155 | * @param ContainerBuilder $container 156 | * @param null|string $cachePath 157 | */ 158 | protected function loadSkipPassedController(ContainerBuilder $container, $cachePath) { 159 | $definition = new Definition('Moodle\BehatExtension\Tester\Cli\SkipPassedController', array( 160 | new Reference(EventDispatcherExtension::DISPATCHER_ID), 161 | $cachePath, 162 | $container->getParameter('paths.base') 163 | )); 164 | $definition->addTag(CliExtension::CONTROLLER_TAG, array('priority' => 200)); 165 | $container->setDefinition(CliExtension::CONTROLLER_TAG . '.passed', $definition); 166 | } 167 | 168 | /** 169 | * Loads filesystem passed scenarios list locator. 170 | * 171 | * @param ContainerBuilder $container 172 | */ 173 | private function loadFilesystemSkipPassedScenariosListLocator(ContainerBuilder $container) { 174 | $definition = new Definition('Moodle\BehatExtension\Locator\FilesystemSkipPassedListLocator', array( 175 | new Reference(self::GHERKIN_ID) 176 | )); 177 | $definition->addTag(SpecificationExtension::LOCATOR_TAG, array('priority' => 50)); 178 | $container->setDefinition(SpecificationExtension::LOCATOR_TAG . '.filesystem_skip_passed_scenarios_list', $definition); 179 | } 180 | 181 | /** 182 | * Loads definition printers. 183 | * 184 | * @param ContainerBuilder $container 185 | */ 186 | private function loadDefinitionPrinters(ContainerBuilder $container) { 187 | $definition = new Definition('Moodle\BehatExtension\Definition\Printer\ConsoleDefinitionInformationPrinter', array( 188 | new Reference(CliExtension::OUTPUT_ID), 189 | new Reference(DefinitionExtension::PATTERN_TRANSFORMER_ID), 190 | new Reference(DefinitionExtension::DEFINITION_TRANSLATOR_ID), 191 | new Reference(GherkinExtension::KEYWORDS_ID) 192 | )); 193 | $container->removeDefinition('definition.information_printer'); 194 | $container->setDefinition('definition.information_printer', $definition); 195 | 196 | } 197 | 198 | /** 199 | * Loads definition controller. 200 | * 201 | * @param ContainerBuilder $container 202 | */ 203 | private function loadController(ContainerBuilder $container) { 204 | $definition = new Definition('Moodle\BehatExtension\Definition\Cli\AvailableDefinitionsController', array( 205 | new Reference(SuiteExtension::REGISTRY_ID), 206 | new Reference(DefinitionExtension::WRITER_ID), 207 | new Reference('definition.list_printer'), 208 | new Reference('definition.information_printer')) 209 | ); 210 | $container->removeDefinition(CliExtension::CONTROLLER_TAG . '.available_definitions'); 211 | $container->setDefinition(CliExtension::CONTROLLER_TAG . '.available_definitions', $definition); 212 | } 213 | 214 | /** 215 | * Loads chained step tester. 216 | * 217 | * @param ContainerBuilder $container 218 | */ 219 | protected function loadChainedStepTester(ContainerBuilder $container) { 220 | // Chained steps. 221 | $definition = new Definition('Moodle\BehatExtension\EventDispatcher\Tester\ChainedStepTester', array( 222 | new Reference(TesterExtension::STEP_TESTER_ID), 223 | )); 224 | $definition->addTag(TesterExtension::STEP_TESTER_WRAPPER_TAG, array('priority' => 100)); 225 | $container->setDefinition(TesterExtension::STEP_TESTER_WRAPPER_TAG . '.substep', $definition); 226 | } 227 | 228 | /** 229 | * Loads event-dispatching step tester. 230 | * 231 | * @param ContainerBuilder $container 232 | */ 233 | protected function loadEventDispatchingStepTester(ContainerBuilder $container) { 234 | $definition = new Definition('Moodle\BehatExtension\EventDispatcher\Tester\MoodleEventDispatchingStepTester', array( 235 | new Reference(TesterExtension::STEP_TESTER_ID), 236 | new Reference(EventDispatcherExtension::DISPATCHER_ID) 237 | )); 238 | $definition->addTag(TesterExtension::STEP_TESTER_WRAPPER_TAG, array('priority' => -9999)); 239 | $container->setDefinition(TesterExtension::STEP_TESTER_WRAPPER_TAG . '.event_dispatching', $definition); 240 | } 241 | 242 | /** 243 | * Setups configuration for current extension. 244 | * 245 | * @param ArrayNodeDefinition $builder 246 | */ 247 | public function configure(ArrayNodeDefinition $builder) { 248 | $builder-> 249 | children()-> 250 | arrayNode('capabilities')-> 251 | useAttributeAsKey('key')-> 252 | prototype('variable')->end()-> 253 | end()-> 254 | arrayNode('steps_definitions')-> 255 | useAttributeAsKey('key')-> 256 | prototype('variable')->end()-> 257 | end()-> 258 | scalarNode('moodledirroot')-> 259 | defaultNull()-> 260 | end()-> 261 | scalarNode('passed_cache')-> 262 | info('Sets the passed cache path')-> 263 | defaultValue( 264 | is_writable(sys_get_temp_dir()) 265 | ? sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'behat_passed_cache' 266 | : null)-> 267 | end()-> 268 | end()-> 269 | end(); 270 | } 271 | 272 | /** 273 | * {@inheritDoc} 274 | */ 275 | public function getConfigKey() { 276 | return self::MOODLE_ID; 277 | } 278 | 279 | /** 280 | * {@inheritdoc} 281 | */ 282 | public function initialize(ExtensionManager $extensionManager) { 283 | if (null !== $minkExtension = $extensionManager->getExtension('mink')) { 284 | $minkExtension->registerDriverFactory(new MoodleSelenium2Factory()); 285 | } 286 | } 287 | 288 | public function process(ContainerBuilder $container) { 289 | // Load controller for definition printing. 290 | $this->loadDefinitionPrinters($container); 291 | $this->loadController($container); 292 | } 293 | 294 | /** 295 | * Alias old namespace of given. when and then for BC. 296 | */ 297 | private function alias_old_namespaces() { 298 | class_alias('Moodle\\BehatExtension\\Context\\Step\\Given', 'Behat\\Behat\\Context\\Step\\Given', true); 299 | class_alias('Moodle\\BehatExtension\\Context\\Step\\When', 'Behat\\Behat\\Context\\Step\\When', true); 300 | class_alias('Moodle\\BehatExtension\\Context\\Step\\Then', 'Behat\\Behat\\Context\\Step\\Then', true); 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /src/Moodle/BehatExtension/Driver/Selenium2/moodle_syn-min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Syn - 0.0.2 3 | * 4 | * @copyright 2014 Bitovi 5 | * Wed, 26 Feb 2014 08:30:25 GMT 6 | * @license MIT 7 | */ 8 | !function(window){var __m2=function(){var a,b,c,d=window.Syn?window.Syn:{},e=function(a,b){var c;for(c in b)a[c]=b[c];return a},f={msie:!(!window.attachEvent||window.opera),opera:!!window.opera,webkit:navigator.userAgent.indexOf("AppleWebKit/")>-1,safari:navigator.userAgent.indexOf("AppleWebKit/")>-1&&-1===navigator.userAgent.indexOf("Chrome/"),gecko:navigator.userAgent.indexOf("Gecko")>-1,mobilesafari:!!navigator.userAgent.match(/Apple.*Mobile.*Safari/),rhino:navigator.userAgent.match(/Rhino/)&&!0},g=function(a,b,c){var d=c.ownerDocument.createEventObject();return e(d,b)},h={},i=1,j="_synthetic"+(new Date).getTime(),k=/keypress|keyup|keydown/,l=/load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll/,m=function(a,b,c,d){return new m.init(a,b,c,d)};m.config=d,a=function(a,b,c){return a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent("on"+b,c)},b=function(a,b,c){return a.addEventListener?a.removeEventListener(b,c,!1):a.detachEvent("on"+b,c)},e(m,{init:function(a,b,c,d){var e=m.args(b,c,d),f=this;this.queue=[],this.element=e.element,"function"==typeof this[a]?this[a](e.options,e.element,function(){e.callback&&e.callback.apply(f,arguments),f.done.apply(f,arguments)}):(this.result=m.trigger(a,e.options,e.element),e.callback&&e.callback.call(this,e.element,this.result))},jquery:function(a){return window.FuncUnit&&window.FuncUnit.jQuery?window.FuncUnit.jQuery:a?m.helpers.getWindow(a).jQuery||window.jQuery:window.jQuery},args:function(){for(var a={},b=0;b0&&g.apply(this,[])},d.dispatchEvent(c),0>=h}try{window.event=c}catch(i){}return d.sourceIndex<=0||d.fireEvent&&d.fireEvent("on"+e,c)},create:{page:{event:function(a,b,c){var d,e=m.helpers.getWindow(c).document||document;if(e.createEvent)return d=e.createEvent("Events"),d.initEvent(a,!0,!0),d;try{d=g(a,b,c)}catch(f){}return d}},focus:{event:function(a,b,d){return m.onParents(d,function(a){if(m.isFocusable(a)){if("html"!==a.nodeName.toLowerCase())a.focus(),c=a;else if(c){var b=m.helpers.getWindow(d).document;if(b!==window.document)return!1;b.activeElement?(b.activeElement.blur(),c=null):(c.blur(),c=null)}return!1}}),!0}}},support:{clickChanges:!1,clickSubmits:!1,keypressSubmits:!1,mouseupSubmits:!1,radioClickChanges:!1,focusChanges:!1,linkHrefJS:!1,keyCharacters:!1,backspaceWorks:!1,mouseDownUpClicks:!1,tabKeyTabs:!1,keypressOnAnchorClicks:!1,optionClickBubbles:!1,ready:0},trigger:function(a,b,c){b||(b={});var d,e,f,g=m.create,h=g[a]&&g[a].setup,i=k.test(a)?"key":l.test(a)?"page":"mouse",j=g[a]||{},n=g[i],o=c;return 2===m.support.ready&&h&&h(a,b,c),f=b._autoPrevent,delete b._autoPrevent,j.event?e=j.event(a,b,c):(b=n.options?n.options(a,b,c):b,!m.support.changeBubbles&&/option/i.test(c.nodeName)&&(o=c.parentNode),d=n.event(a,b,o),e=m.dispatch(d,o,a,f)),e&&2===m.support.ready&&m.defaults[a]&&m.defaults[a].call(c,b,f),e},eventSupported:function(a){var b=document.createElement("div");a="on"+a;var c=a in b;return c||(b.setAttribute(a,"return;"),c="function"==typeof b[a]),b=null,c}}),e(m.init.prototype,{then:function(a,b,c,d){m.autoDelay&&this.delay();var e=m.args(b,c,d),f=this;return this.queue.unshift(function(b){return"function"!=typeof this[a]?(this.result=m.trigger(a,e.options,e.element),e.callback&&e.callback.call(this,e.element,this.result),this):(this.element=e.element||b,void this[a](e.options,this.element,function(){e.callback&&e.callback.apply(f,arguments),f.done.apply(f,arguments)}))}),this},delay:function(a,b){"function"==typeof a&&(b=a,a=null),a=a||600;var c=this;return this.queue.unshift(function(){setTimeout(function(){b&&b.apply(c,[]),c.done.apply(c,arguments)},a)}),this},done:function(a,b){b&&(this.element=b),this.queue.length&&this.queue.pop().call(this,this.element,a)},_click:function(a,b,c,d){m.helpers.addOffset(a,b),m.trigger("mousedown",a,b),setTimeout(function(){m.trigger("mouseup",a,b),!m.support.mouseDownUpClicks||d?(m.trigger("click",a,b),c(!0)):(m.create.click.setup("click",a,b),m.defaults.click.call(b),setTimeout(function(){c(!0)},1))},1)},_rightClick:function(a,b,c){m.helpers.addOffset(a,b);var d=e(e({},m.mouse.browser.right.mouseup),a);m.trigger("mousedown",d,b),setTimeout(function(){m.trigger("mouseup",d,b),m.mouse.browser.right.contextmenu&&m.trigger("contextmenu",e(e({},m.mouse.browser.right.contextmenu),a),b),c(!0)},1)},_dblclick:function(a,b,c){m.helpers.addOffset(a,b);var d=this;this._click(a,b,function(){setTimeout(function(){d._click(a,b,function(){m.trigger("dblclick",a,b),c(!0)},!0)},2)})}});for(var n=["click","dblclick","move","drag","key","type","rightClick"],o=function(a){m[a]=function(b,c,d){return m("_"+a,b,c,d)},m.init.prototype[a]=function(b,c,d){return this.then("_"+a,b,c,d)}},p=0;p",document.documentElement.appendChild(e),c=e.firstChild,a=c.childNodes[0],b=c.childNodes[2],d=c.getElementsByTagName("select")[0],Syn.trigger("click",{},c.childNodes[6]),a.checked=!1,a.onchange=function(){Syn.support.clickChanges=!0},Syn.trigger("click",{},a),Syn.support.clickChecks=a.checked,a.checked=!1,Syn.trigger("change",{},a),Syn.support.changeChecks=a.checked,c.onsubmit=function(a){return a.preventDefault&&a.preventDefault(),Syn.support.clickSubmits=!0,!1},Syn.trigger("click",{},b),c.childNodes[1].onchange=function(){Syn.support.radioClickChanges=!0},Syn.trigger("click",{},c.childNodes[1]),Syn.bind(e,"click",function(){Syn.support.optionClickBubbles=!0,Syn.unbind(e,"click",arguments.callee)}),Syn.trigger("click",{},d.firstChild),Syn.support.changeBubbles=Syn.eventSupported("change");e.onclick=function(){Syn.support.mouseDownUpClicks=!0},Syn.trigger("mousedown",{},e),Syn.trigger("mouseup",{},e),document.documentElement.removeChild(e),Syn.support.ready++}(),Syn}(__m2),__m4=function(a){return a.key.browsers={webkit:{prevent:{keyup:[],keydown:["char","keypress"],keypress:["char"]},character:{keydown:[0,"key"],keypress:["char","char"],keyup:[0,"key"]},specialChars:{keydown:[0,"char"],keyup:[0,"char"]},navigation:{keydown:[0,"key"],keyup:[0,"key"]},special:{keydown:[0,"key"],keyup:[0,"key"]},tab:{keydown:[0,"char"],keyup:[0,"char"]},"pause-break":{keydown:[0,"key"],keyup:[0,"key"]},caps:{keydown:[0,"key"],keyup:[0,"key"]},escape:{keydown:[0,"key"],keyup:[0,"key"]},"num-lock":{keydown:[0,"key"],keyup:[0,"key"]},"scroll-lock":{keydown:[0,"key"],keyup:[0,"key"]},print:{keyup:[0,"key"]},"function":{keydown:[0,"key"],keyup:[0,"key"]},"\r":{keydown:[0,"key"],keypress:["char","key"],keyup:[0,"key"]}},gecko:{prevent:{keyup:[],keydown:["char"],keypress:["char"]},character:{keydown:[0,"key"],keypress:["char",0],keyup:[0,"key"]},specialChars:{keydown:[0,"key"],keypress:[0,"key"],keyup:[0,"key"]},navigation:{keydown:[0,"key"],keypress:[0,"key"],keyup:[0,"key"]},special:{keydown:[0,"key"],keyup:[0,"key"]}," ":{keydown:[0,"key"],keypress:[0,"key"],keyup:[0,"key"]},"pause-break":{keydown:[0,"key"],keypress:[0,"key"],keyup:[0,"key"]},caps:{keydown:[0,"key"],keyup:[0,"key"]},escape:{keydown:[0,"key"],keypress:[0,"key"],keyup:[0,"key"]},"num-lock":{keydown:[0,"key"],keyup:[0,"key"]},"scroll-lock":{keydown:[0,"key"],keyup:[0,"key"]},print:{keyup:[0,"key"]},"function":{keydown:[0,"key"],keyup:[0,"key"]},"\r":{keydown:[0,"key"],keypress:[0,"key"],keyup:[0,"key"]}},msie:{prevent:{keyup:[],keydown:["char","keypress"],keypress:["char"]},character:{keydown:[null,"key"],keypress:[null,"char"],keyup:[null,"key"]},specialChars:{keydown:[null,"char"],keyup:[null,"char"]},navigation:{keydown:[null,"key"],keyup:[null,"key"]},special:{keydown:[null,"key"],keyup:[null,"key"]},tab:{keydown:[null,"char"],keyup:[null,"char"]},"pause-break":{keydown:[null,"key"],keyup:[null,"key"]},caps:{keydown:[null,"key"],keyup:[null,"key"]},escape:{keydown:[null,"key"],keypress:[null,"key"],keyup:[null,"key"]},"num-lock":{keydown:[null,"key"],keyup:[null,"key"]},"scroll-lock":{keydown:[null,"key"],keyup:[null,"key"]},print:{keyup:[null,"key"]},"function":{keydown:[null,"key"],keyup:[null,"key"]},"\r":{keydown:[null,"key"],keypress:[null,"key"],keyup:[null,"key"]}},opera:{prevent:{keyup:[],keydown:[],keypress:["char"]},character:{keydown:[null,"key"],keypress:[null,"char"],keyup:[null,"key"]},specialChars:{keydown:[null,"char"],keypress:[null,"char"],keyup:[null,"char"]},navigation:{keydown:[null,"key"],keypress:[null,"key"]},special:{keydown:[null,"key"],keypress:[null,"key"],keyup:[null,"key"]},tab:{keydown:[null,"char"],keypress:[null,"char"],keyup:[null,"char"]},"pause-break":{keydown:[null,"key"],keypress:[null,"key"],keyup:[null,"key"]},caps:{keydown:[null,"key"],keyup:[null,"key"]},escape:{keydown:[null,"key"],keypress:[null,"key"]},"num-lock":{keyup:[null,"key"],keydown:[null,"key"],keypress:[null,"key"]},"scroll-lock":{keydown:[null,"key"],keypress:[null,"key"],keyup:[null,"key"]},print:{},"function":{keydown:[null,"key"],keypress:[null,"key"],keyup:[null,"key"]},"\r":{keydown:[null,"key"],keypress:[null,"key"],keyup:[null,"key"]}}},a.mouse.browsers={webkit:{right:{mousedown:{button:2,which:3},mouseup:{button:2,which:3},contextmenu:{button:2,which:3}},left:{mousedown:{button:0,which:1},mouseup:{button:0,which:1},click:{button:0,which:1}}},opera:{right:{mousedown:{button:2,which:3},mouseup:{button:2,which:3}},left:{mousedown:{button:0,which:1},mouseup:{button:0,which:1},click:{button:0,which:1}}},msie:{right:{mousedown:{button:2},mouseup:{button:2},contextmenu:{button:0}},left:{mousedown:{button:1},mouseup:{button:1},click:{button:0}}},chrome:{right:{mousedown:{button:2,which:3},mouseup:{button:2,which:3},contextmenu:{button:2,which:3}},left:{mousedown:{button:0,which:1},mouseup:{button:0,which:1},click:{button:0,which:1}}},gecko:{left:{mousedown:{button:0,which:1},mouseup:{button:0,which:1},click:{button:0,which:1}},right:{mousedown:{button:2,which:3},mouseup:{button:2,which:3},contextmenu:{button:2,which:3}}}},a.key.browser=function(){if(a.key.browsers[window.navigator.userAgent])return a.key.browsers[window.navigator.userAgent];for(var b in a.browser)if(a.browser[b]&&a.key.browsers[b])return a.key.browsers[b];return a.key.browsers.gecko}(),a.mouse.browser=function(){if(a.mouse.browsers[window.navigator.userAgent])return a.mouse.browsers[window.navigator.userAgent];for(var b in a.browser)if(a.browser[b]&&a.mouse.browsers[b])return a.mouse.browsers[b];return a.mouse.browsers.gecko}(),a}(__m2,__m3),__m6=function(a){var b=[];a.typeable=function(a){-1==b.indexOf(a)&&b.push(a)},a.typeable.test=function(a){for(var c=0,d=b.length;d>c;c++)if(b[c](a))return!0;return!1};var c=a.typeable,d=/input|textarea/i;return c(function(a){return d.test(a.nodeName)}),c(function(a){return-1!=["","true"].indexOf(a.getAttribute("contenteditable"))}),a}(__m2),__m5=function(a){var b=a.helpers,c=function(a){if(void 0!==a.selectionStart)return document.activeElement&&document.activeElement!=a&&a.selectionStart==a.selectionEnd&&0==a.selectionStart?{start:a.value.length,end:a.value.length}:{start:a.selectionStart,end:a.selectionEnd};try{if("input"==a.nodeName.toLowerCase()){var c=b.getWindow(a).document.selection.createRange(),d=a.createTextRange();d.setEndPoint("EndToStart",c);var f=d.text.length;return{start:f,end:f+c.text.length}}var c=b.getWindow(a).document.selection.createRange(),d=c.duplicate(),g=c.duplicate(),h=c.duplicate();g.collapse(),h.collapse(!1),g.moveStart("character",-1),h.moveStart("character",-1),d.moveToElementText(a),d.setEndPoint("EndToEnd",c);var f=d.text.length-c.text.length,i=d.text.length;return 0!=f&&""==g.text&&(f+=2),0!=i&&""==h.text&&(i+=2),{start:f,end:i}}catch(j){var k=e.test(a.nodeName)?"value":"textContent";return{start:a[k].length,end:a[k].length}}},d=function(c){for(var d=b.getWindow(c).document,e=[],f=d.getElementsByTagName("*"),g=f.length,h=0;g>h;h++)a.isFocusable(f[h])&&f[h]!=d.documentElement&&e.push(f[h]);return e},e=/input|textarea/i,f=function(a){return e.test(a.nodeName)?a.value:a.textContent||a.innerText},g=function(a,b){e.test(a.nodeName)?a.value=b:a.textContent?a.textContent=b:a.innerText=b};b.extend(a,{keycodes:{"\b":8," ":9,"\r":13,shift:16,ctrl:17,alt:18,"pause-break":19,caps:20,escape:27,"num-lock":144,"scroll-lock":145,print:44,"page-up":33,"page-down":34,end:35,home:36,left:37,up:38,right:39,down:40,insert:45,"delete":46," ":32,0:48,1:49,2:50,3:51,4:52,5:53,6:54,7:55,8:56,9:57,a:65,b:66,c:67,d:68,e:69,f:70,g:71,h:72,i:73,j:74,k:75,l:76,m:77,n:78,o:79,p:80,q:81,r:82,s:83,t:84,u:85,v:86,w:87,x:88,y:89,z:90,num0:96,num1:97,num2:98,num3:99,num4:100,num5:101,num6:102,num7:103,num8:104,num9:105,"*":106,"+":107,subtract:109,decimal:110,divide:111,";":186,"=":187,",":188,dash:189,"-":189,period:190,".":190,"forward-slash":191,"/":191,"`":192,"[":219,"\\":220,"]":221,"'":222,"left window key":91,"right window key":92,"select key":93,f1:112,f2:113,f3:114,f4:115,f5:116,f6:117,f7:118,f8:119,f9:120,f10:121,f11:122,f12:123},selectText:function(a,b,c){if(a.setSelectionRange)c?(a.selectionStart=b,a.selectionEnd=c):(a.focus(),a.setSelectionRange(b,b));else if(a.createTextRange){var d=a.createTextRange();d.moveStart("character",b),c=c||b,d.moveEnd("character",c-a.value.length),d.select()}},getText:function(b){if(a.typeable.test(b)){var d=c(b);return b.value.substring(d.start,d.end)}var e=a.helpers.getWindow(b);return e.getSelection?e.getSelection().toString():e.document.getSelection?e.document.getSelection().toString():e.document.selection.createRange().text},getSelection:c}),b.extend(a.key,{data:function(c){if(a.key.browser[c])return a.key.browser[c];for(var d in a.key.kinds)if(b.inArray(c,a.key.kinds[d])>-1)return a.key.browser[d];return a.key.browser.character},isSpecial:function(b){for(var c=a.key.kinds.special,d=0;d-1&&a.key.defaults[d])return a.key.defaults[d];return a.key.defaults.character},defaults:{character:function(b,c,d,e,h){if(/num\d+/.test(d)&&(d=d.match(/\d+/)[0]),e||!a.support.keyCharacters&&a.typeable.test(this)){var i=f(this),j=i.substr(0,h.start),k=i.substr(h.end),l=d;g(this,j+l+k);var m="\n"==l&&a.support.textareaCarriage?2:l.length;a.selectText(this,j.length+m)}},c:function(){a.key.ctrlKey?a.key.clipboard=a.getText(this):a.key.defaults.character.apply(this,arguments)},v:function(b,c,d,e,f){a.key.ctrlKey?a.key.defaults.character.call(this,b,c,a.key.clipboard,!0,f):a.key.defaults.character.apply(this,arguments)},a:function(){a.key.ctrlKey?a.selectText(this,0,f(this).length):a.key.defaults.character.apply(this,arguments)},home:function(){a.onParents(this,function(a){return a.scrollHeight!=a.clientHeight?(a.scrollTop=0,!1):void 0})},end:function(){a.onParents(this,function(a){return a.scrollHeight!=a.clientHeight?(a.scrollTop=a.scrollHeight,!1):void 0})},"page-down":function(){a.onParents(this,function(a){if(a.scrollHeight!=a.clientHeight){var b=a.clientHeight;return a.scrollTop+=b,!1}})},"page-up":function(){a.onParents(this,function(a){if(a.scrollHeight!=a.clientHeight){var b=a.clientHeight;return a.scrollTop-=b,!1}})},"\b":function(b,c,d,e,h){if(!a.support.backspaceWorks&&a.typeable.test(this)){var i=f(this),j=i.substr(0,h.start),k=i.substr(h.end);h.start==h.end&&h.start>0?(g(this,j.substring(0,j.length-1)+k),a.selectText(this,h.start-1)):(g(this,j+k),a.selectText(this,h.start))}},"delete":function(b,c,d,e,h){if(!a.support.backspaceWorks&&a.typeable.test(this)){var i=f(this),j=i.substr(0,h.start),k=i.substr(h.end);h.start==h.end&&h.start<=f(this).length-1?g(this,j+k.substring(1)):g(this,j+k),a.selectText(this,h.start)}},"\r":function(b,c,d,e,f){var g=this.nodeName.toLowerCase();if("input"==g&&a.trigger("change",{},this),!a.support.keypressSubmits&&"input"==g){var h=a.closest(this,"form");h&&a.trigger("submit",{},h)}a.support.keyCharacters||"textarea"!=g||a.key.defaults.character.call(this,b,c,"\n",void 0,f),a.support.keypressOnAnchorClicks||"a"!=g||a.trigger("click",{},this)}," ":function(){var b,c,e=d(this),f=(a.tabIndex(this),null),g=0;for(orders=[];gf(this).length?f(this).length:g.end+1):a.selectText(this,g.end+1>f(this).length?f(this).length:g.end+1))},up:function(){/select/i.test(this.nodeName)&&(this.selectedIndex=this.selectedIndex?this.selectedIndex-1:0)},down:function(){/select/i.test(this.nodeName)&&(a.changeOnBlur(this,"selectedIndex",this.selectedIndex),this.selectedIndex=this.selectedIndex+1)},shift:function(){return null},ctrl:function(){return null}}}),b.extend(a.create,{keydown:{setup:function(c,d,e){-1!=b.inArray(d,a.key.kinds.special)&&(a.key[d+"Key"]=e)}},keypress:{setup:function(b,c,d){a.support.keyCharacters&&!a.support.keysOnNotFocused&&d.focus()}},keyup:{setup:function(c,d){-1!=b.inArray(d,a.key.kinds.special)&&(a.key[d+"Key"]=null)}},key:{options:function(c,d){return d="object"!=typeof d?{character:d}:d,d=b.extend({},d),d.character&&(b.extend(d,a.key.options(d.character,c)),delete d.character),d=b.extend({ctrlKey:!!a.key.ctrlKey,altKey:!!a.key.altKey,shiftKey:!!a.key.shiftKey,metaKey:!!a.key.metaKey},d)},event:function(a,c,d){var e=b.getWindow(d).document||document;if(e.createEvent){var f;try{f=e.createEvent("KeyEvents"),f.initKeyEvent(a,!0,!0,window,c.ctrlKey,c.altKey,c.shiftKey,c.metaKey,c.keyCode,c.charCode)}catch(g){f=b.createBasicStandardEvent(a,c,e)}return f.synthetic=!0,f}var f;try{f=b.createEventObject.apply(this,arguments),b.extend(f,c)}catch(g){}return f}}});var h={enter:"\r",backspace:"\b",tab:" ",space:" "};return b.extend(a.init.prototype,{_key:function(d,e,f){if(/-up$/.test(d)&&-1!=b.inArray(d.replace("-up",""),a.key.kinds.special))return a.trigger("keyup",d.replace("-up",""),e),void f(!0,e);var g,i=b.getWindow(e).document.activeElement,j=a.typeable.test(e)&&c(e),k=h[d]||d,l=a.trigger("keydown",k,e),m=a.key.getDefault,n=a.key.browser.prevent,o=a.key.options(k,"keypress");return l?o?(i!==b.getWindow(e).document.activeElement&&(e=b.getWindow(e).document.activeElement),l=a.trigger("keypress",o,e),l&&(g=m(k).call(e,o,b.getWindow(e),k,void 0,j))):g=m(k).call(e,o,b.getWindow(e),k,void 0,j):o&&-1==b.inArray("keypress",n.keydown)&&(i!==b.getWindow(e).document.activeElement&&(e=b.getWindow(e).document.activeElement),a.trigger("keypress",o,e)),g&&g.nodeName&&(e=g),null!==g?setTimeout(function(){a.support.oninput&&a.trigger("input",a.key.options(k,"input"),e),a.trigger("keyup",a.key.options(k,"keyup"),e),f(l,e)},1):f(l,e),e},_type:function(a,b,c){var d=(a+"").match(/(\[[^\]]+\])|([^\[])/g),e=this,f=function(a,g){var h=d.shift();return h?(g=g||b,h.length>1&&(h=h.substr(1,h.length-2)),void e._key(h,g,f)):void c(a,g)};f()}}),a.config.support?a.helpers.extend(a.support,a.config.support):!function(){if(!document.body)return void setTimeout(arguments.callee,1);var b,c,d,e,f,g,h,i=[window.scrollX,window.scrollY],j=window.document.activeElement,k=document.createElement("div");k.innerHTML="
",document.documentElement.appendChild(k),d=k.firstChild,b=d.childNodes[0],c=d.childNodes[2],e=d.getElementsByTagName("a")[0],f=d.getElementsByTagName("textarea")[0],g=d.childNodes[3],h=d.childNodes[4],d.onsubmit=function(b){return b.preventDefault&&b.preventDefault(),a.support.keypressSubmits=!0,b.returnValue=!1,!1},g.focus(),a.trigger("keypress","\r",g),a.trigger("keypress","a",g),a.support.keyCharacters="a"==g.value,g.value="a",a.trigger("keypress","\b",g),a.support.backspaceWorks=""==g.value,g.onchange=function(){a.support.focusChanges=!0},g.focus(),a.trigger("keypress","a",g),d.childNodes[5].focus(),a.trigger("keypress","b",g),a.support.keysOnNotFocused="ab"==g.value,a.bind(e,"click",function(b){return b.preventDefault&&b.preventDefault(),a.support.keypressOnAnchorClicks=!0,b.returnValue=!1,!1}),a.trigger("keypress","\r",e),a.support.textareaCarriage=4==f.value.length,a.support.oninput="oninput"in h,document.documentElement.removeChild(k),j&&j.focus(),window.scrollTo(i[0],i[1]),a.support.ready++}(),a}(__m2,__m6,__m4),__m7=function(a){!function(){if(!document.body)return void setTimeout(arguments.callee,1);var b=document.createElement("div");if(document.body.appendChild(b),a.helpers.extend(b.style,{width:"100px",height:"10000px",backgroundColor:"blue",position:"absolute",top:"10px",left:"0px",zIndex:19999}),document.body.scrollTop=11,document.elementFromPoint){var c=document.elementFromPoint(3,1);c==b?a.support.elementFromClient=!0:a.support.elementFromPage=!0,document.body.removeChild(b),document.body.scrollTop=0}}();var b=function(b,c){var d,e=b.clientX,f=b.clientY,g=a.helpers.getWindow(c);if(a.support.elementFromPage){var h=a.helpers.scrollOffset(g);e+=h.left,f+=h.top}return d=g.document.elementFromPoint?g.document.elementFromPoint(e,f):c,d===g.document.documentElement&&(b.clientY<0||b.clientX<0)?c:d},c=function(c,d,e){var f=b(d,e);return a.trigger(c,d,f||e),f},d=function(c,d,e){var f=b(c,d);if(e!=f&&f&&e){var g=a.helpers.extend({},c);g.relatedTarget=f,a.trigger("mouseout",g,e),g.relatedTarget=e,a.trigger("mouseover",g,f)}return a.trigger("mousemove",c,f||d),f},e=function(c,e,f,g,h){var i=new Date,j=e.clientX-c.clientX,k=e.clientY-c.clientY,l=a.helpers.getWindow(g),m=b(c,g),n=l.document.createElement("div"),o=0;move=function(){var b=new Date,p=a.helpers.scrollOffset(l),q=(0==o?0:b-i)/f,r={clientX:j*q+c.clientX,clientY:k*q+c.clientY};o++,1>q?(a.helpers.extend(n.style,{left:r.clientX+p.left+2+"px",top:r.clientY+p.top+2+"px"}),m=d(r,g,m),setTimeout(arguments.callee,15)):(m=d(e,g,m),l.document.body.removeChild(n),h())},a.helpers.extend(n.style,{height:"5px",width:"5px",backgroundColor:"red",position:"absolute",zIndex:19999,fontSize:"1px"}),l.document.body.appendChild(n),move()},f=function(a,b,d,f,g){c("mousedown",a,f),e(a,b,d,f,function(){c("mouseup",b,f),g()})},g=function(b){var c=a.jquery()(b),d=c.offset();return{pageX:d.left+c.outerWidth()/2,pageY:d.top+c.outerHeight()/2}},h=function(b,c,d){var e=/(\d+)[x ](\d+)/,f=/(\d+)X(\d+)/,h=/([+-]\d+)[xX ]([+-]\d+)/;if("string"==typeof b&&h.test(b)&&d){var i=g(d),j=b.match(h);b={pageX:i.pageX+parseInt(j[1]),pageY:i.pageY+parseInt(j[2])}}if("string"==typeof b&&e.test(b)){var j=b.match(e);b={pageX:parseInt(j[1]),pageY:parseInt(j[2])}}if("string"==typeof b&&f.test(b)){var j=b.match(f);b={clientX:parseInt(j[1]),clientY:parseInt(j[2])}}if("string"==typeof b&&(b=a.jquery()(b,c.document)[0]),b.nodeName&&(b=g(b)),b.pageX){var k=a.helpers.scrollOffset(c);b={clientX:b.pageX-k.left,clientY:b.pageY-k.top}}return b},i=function(b,c,d){if(b.clientY<0){var e=a.helpers.scrollOffset(d),f=(a.helpers.scrollDimensions(d),e.top+b.clientY-100),g=f-e.top;f>0||(f=0,g=-e.top),b.clientY=b.clientY-g,c.clientY=c.clientY-g,a.helpers.scrollOffset(d,{top:f,left:e.left})}};return a.helpers.extend(a.init.prototype,{_move:function(b,c,d){var f=a.helpers.getWindow(c),g=h(b.from||c,f,c),j=h(b.to||b,f,c);b.adjust!==!1&&i(g,j,f),e(g,j,b.duration||500,c,d)},_drag:function(b,c,d){var e=a.helpers.getWindow(c),g=h(b.from||c,e,c),j=h(b.to||b,e,c);b.adjust!==!1&&i(g,j,e),f(g,j,b.duration||500,c,d)}}),a}(__m2),__m1=function(a){return window.Syn=a,a}(__m2,__m3,__m4,__m5,__m7)}(window); --------------------------------------------------------------------------------