See Release Notes
Long Term Support Release
<?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. namespace Moodle\BehatExtension\Output\Formatter; use Behat\Behat\EventDispatcher\Event\AfterStepTested; use Behat\Behat\EventDispatcher\Event\BeforeScenarioTested; use Behat\Behat\EventDispatcher\Event\BeforeStepTested; use Behat\Testwork\Output\Formatter; use Behat\Testwork\Output\Printer\OutputPrinter; // phpcs:disable moodle.NamingConventions.ValidFunctionName.LowercaseMethod /** * Feature step counter for distributing features between parallel runs. * * Use it with --dry-run (and any other selectors combination) to * get the results quickly. * * @package core * @copyright 2016 onwards Rajesh Taneja * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class MoodleScreenshotFormatter implements Formatter { /** @var OutputPrinter */ private $printer; /** @var array */ private $parameters; /** @var string */ private $name; /** @var string */ private $description; /** @var int The scenario count */ protected static $currentscenariocount = 0; /** @var int The step count within the current scenario */ protected static $currentscenariostepcount = 0; /** * If we are saving any kind of dump on failure we should use the same parent dir during a run. * * @var The parent dir name */ protected static $faildumpdirname = false; /** * Initializes formatter. * * @param string $name * @param string $description * @param array $parameters * @param OutputPrinter $printer */ public function __construct($name, $description, array $parameters, OutputPrinter $printer) { $this->name = $name; $this->description = $description; $this->parameters = $parameters; $this->printer = $printer; } /** * Returns an array of event names this subscriber wants to listen to. * * @return array The event names to listen to */ public static function getSubscribedEvents() { return [ 'tester.scenario_tested.before' => 'beforeScenario', 'tester.step_tested.before' => 'beforeStep', 'tester.step_tested.after' => 'afterStep', ]; } /** * Returns formatter name. * * @return string */ public function getName() { return $this->name; } /** * Returns formatter description. * * @return string */ public function getDescription() { return $this->description; } /** * Returns formatter output printer. * * @return OutputPrinter */ public function getOutputPrinter() { return $this->printer; } /** * Sets formatter parameter. * * @param string $name * @param mixed $value */ public function setParameter($name, $value) { $this->parameters[$name] = $value; } /** * Returns parameter name. * * @param string $name * @return mixed */ public function getParameter($name) { return isset($this->parameters[$name]) ? $this->parameters[$name] : null; } /** * Reset currentscenariostepcount * * @param BeforeScenarioTested $event */ public function beforeScenario(BeforeScenarioTested $event) { self::$currentscenariostepcount = 0; self::$currentscenariocount++; } /** * Increment currentscenariostepcount * * @param BeforeStepTested $event */ public function beforeStep(BeforeStepTested $event) { self::$currentscenariostepcount++; } /** * Take screenshot after step is executed. Behat\Behat\Event\html * * @param AfterStepTested $event */ public function afterStep(AfterStepTested $event) { $behathookcontext = $event->getEnvironment()->getContext('behat_hooks'); $formats = $this->getParameter('formats'); $formats = explode(',', $formats); // Take screenshot. if (in_array('image', $formats)) { $this->take_screenshot($event, $behathookcontext); } // Save html content. if (in_array('html', $formats)) { $this->take_contentdump($event, $behathookcontext); } } /** * Return screenshot directory where all screenshots will be saved. * * @return string */ protected function get_run_screenshot_dir() { global $CFG; if (self::$faildumpdirname) { return self::$faildumpdirname; } // If output_path is set then use output_path else use faildump_path. if ($this->getOutputPrinter()->getOutputPath()) { $screenshotpath = $this->getOutputPrinter()->getOutputPath(); } else if ($CFG->behat_faildump_path) { $screenshotpath = $CFG->behat_faildump_path; } else { // It should never reach here. throw new FormatterException('You should specify --out "SOME/PATH" for moodle_screenshot format'); } if ($this->getParameter('dir_permissions')) { $dirpermissions = $this->getParameter('dir_permissions'); } else { $dirpermissions = 0777; } // All the screenshot dumps should be in the same parent dir. self::$faildumpdirname = $screenshotpath . DIRECTORY_SEPARATOR . date('Ymd_His'); if (!is_dir(self::$faildumpdirname) && !mkdir(self::$faildumpdirname, $dirpermissions, true)) { // It shouldn't, we already checked that the directory is writable. throw new FormatterException(sprintf( 'No directories can be created inside %s, check the directory permissions.', $screenshotpath )); } return self::$faildumpdirname; } /** * Take screenshot when a step fails. * * @throws Exception * @param AfterStepTested $event * @param Context $context */ protected function take_screenshot(AfterStepTested $event, $context) {< // Goutte can't save screenshots.> // BrowserKit can't save screenshots.if ($context->getMink()->isSessionStarted($context->getMink()->getDefaultSessionName())) {< if (get_class($context->getMink()->getSession()->getDriver()) === 'Behat\Mink\Driver\GoutteDriver') {> if (get_class($context->getMink()->getSession()->getDriver()) === 'Behat\Mink\Driver\BrowserKitDriver') {return false; } list ($dir, $filename) = $this->get_faildump_filename($event, 'png'); $context->saveScreenshot($filename, $dir); } } /** * Take a dump of the page content when a step fails. * * @throws Exception * @param AfterStepTested $event * @param \Behat\Context\Context\Context $context */ protected function take_contentdump(AfterStepTested $event, $context) { list ($dir, $filename) = $this->get_faildump_filename($event, 'html'); $fh = fopen($dir . DIRECTORY_SEPARATOR . $filename, 'w'); fwrite($fh, $context->getMink()->getSession()->getPage()->getContent()); fclose($fh); } /** * Determine the full pathname to store a failure-related dump. * * This is used for content such as the DOM, and screenshots. * * @param AfterStepTested $event * @param String $filetype The file suffix to use. Limited to 4 chars. */ protected function get_faildump_filename(AfterStepTested $event, $filetype) { // Make a directory for the scenario. $featurename = $event->getFeature()->getTitle(); $featurename = preg_replace('/([^a-zA-Z0-9\_]+)/', '-', $featurename); if ($this->getParameter('dir_permissions')) { $dirpermissions = $this->getParameter('dir_permissions'); } else { $dirpermissions = 0777; } $dir = $this->get_run_screenshot_dir(); // We want a i-am-the-scenario-title format. $dir = $dir . DIRECTORY_SEPARATOR . self::$currentscenariocount . '-' . $featurename; if (!is_dir($dir) && !mkdir($dir, $dirpermissions, true)) { // We already checked that the directory is writable. This should not fail. throw new FormatterException(sprintf( 'No directories can be created inside %s, check the directory permissions.', $dir )); } // The failed step text. // We want a stepno-i-am-the-failed-step.$filetype format. $filename = $event->getStep()->getText(); $filename = preg_replace('/([^a-zA-Z0-9\_]+)/', '-', $filename); $filename = self::$currentscenariostepcount . '-' . $filename; // File name limited to 255 characters. Leaving 4 chars for the file // extension as we allow .png for images and .html for DOM contents. $filename = substr($filename, 0, 250) . '.' . $filetype; return [$dir, $filename]; } }