See Release Notes
Long Term Support Release
Differences Between: [Versions 401 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 18 namespace Moodle\BehatExtension\Output\Formatter; 19 20 use Behat\Behat\EventDispatcher\Event\AfterStepTested; 21 use Behat\Behat\EventDispatcher\Event\BeforeScenarioTested; 22 use Behat\Behat\EventDispatcher\Event\BeforeStepTested; 23 use Behat\Testwork\Output\Formatter; 24 use Behat\Testwork\Output\Printer\OutputPrinter; 25 26 // phpcs:disable moodle.NamingConventions.ValidFunctionName.LowercaseMethod 27 28 /** 29 * Feature step counter for distributing features between parallel runs. 30 * 31 * Use it with --dry-run (and any other selectors combination) to 32 * get the results quickly. 33 * 34 * @package core 35 * @copyright 2016 onwards Rajesh Taneja 36 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 37 */ 38 class MoodleScreenshotFormatter implements Formatter { 39 40 /** @var OutputPrinter */ 41 private $printer; 42 43 /** @var array */ 44 private $parameters; 45 46 /** @var string */ 47 private $name; 48 49 /** @var string */ 50 private $description; 51 52 /** @var int The scenario count */ 53 protected static $currentscenariocount = 0; 54 55 /** @var int The step count within the current scenario */ 56 protected static $currentscenariostepcount = 0; 57 58 /** 59 * If we are saving any kind of dump on failure we should use the same parent dir during a run. 60 * 61 * @var The parent dir name 62 */ 63 protected static $faildumpdirname = false; 64 65 /** 66 * Initializes formatter. 67 * 68 * @param string $name 69 * @param string $description 70 * @param array $parameters 71 * @param OutputPrinter $printer 72 */ 73 public function __construct($name, $description, array $parameters, OutputPrinter $printer) { 74 $this->name = $name; 75 $this->description = $description; 76 $this->parameters = $parameters; 77 $this->printer = $printer; 78 } 79 80 /** 81 * Returns an array of event names this subscriber wants to listen to. 82 * 83 * @return array The event names to listen to 84 */ 85 public static function getSubscribedEvents() { 86 return [ 87 'tester.scenario_tested.before' => 'beforeScenario', 88 'tester.step_tested.before' => 'beforeStep', 89 'tester.step_tested.after' => 'afterStep', 90 ]; 91 } 92 93 /** 94 * Returns formatter name. 95 * 96 * @return string 97 */ 98 public function getName() { 99 return $this->name; 100 } 101 102 /** 103 * Returns formatter description. 104 * 105 * @return string 106 */ 107 public function getDescription() { 108 return $this->description; 109 } 110 111 /** 112 * Returns formatter output printer. 113 * 114 * @return OutputPrinter 115 */ 116 public function getOutputPrinter() { 117 return $this->printer; 118 } 119 120 /** 121 * Sets formatter parameter. 122 * 123 * @param string $name 124 * @param mixed $value 125 */ 126 public function setParameter($name, $value) { 127 $this->parameters[$name] = $value; 128 } 129 130 /** 131 * Returns parameter name. 132 * 133 * @param string $name 134 * @return mixed 135 */ 136 public function getParameter($name) { 137 return isset($this->parameters[$name]) ? $this->parameters[$name] : null; 138 } 139 140 /** 141 * Reset currentscenariostepcount 142 * 143 * @param BeforeScenarioTested $event 144 */ 145 public function beforeScenario(BeforeScenarioTested $event) { 146 147 self::$currentscenariostepcount = 0; 148 self::$currentscenariocount++; 149 } 150 151 /** 152 * Increment currentscenariostepcount 153 * 154 * @param BeforeStepTested $event 155 */ 156 public function beforeStep(BeforeStepTested $event) { 157 self::$currentscenariostepcount++; 158 } 159 160 /** 161 * Take screenshot after step is executed. Behat\Behat\Event\html 162 * 163 * @param AfterStepTested $event 164 */ 165 public function afterStep(AfterStepTested $event) { 166 $behathookcontext = $event->getEnvironment()->getContext('behat_hooks'); 167 168 $formats = $this->getParameter('formats'); 169 $formats = explode(',', $formats); 170 171 // Take screenshot. 172 if (in_array('image', $formats)) { 173 $this->take_screenshot($event, $behathookcontext); 174 } 175 176 // Save html content. 177 if (in_array('html', $formats)) { 178 $this->take_contentdump($event, $behathookcontext); 179 } 180 } 181 182 /** 183 * Return screenshot directory where all screenshots will be saved. 184 * 185 * @return string 186 */ 187 protected function get_run_screenshot_dir() { 188 global $CFG; 189 190 if (self::$faildumpdirname) { 191 return self::$faildumpdirname; 192 } 193 194 // If output_path is set then use output_path else use faildump_path. 195 if ($this->getOutputPrinter()->getOutputPath()) { 196 $screenshotpath = $this->getOutputPrinter()->getOutputPath(); 197 } else if ($CFG->behat_faildump_path) { 198 $screenshotpath = $CFG->behat_faildump_path; 199 } else { 200 // It should never reach here. 201 throw new FormatterException('You should specify --out "SOME/PATH" for moodle_screenshot format'); 202 } 203 204 if ($this->getParameter('dir_permissions')) { 205 $dirpermissions = $this->getParameter('dir_permissions'); 206 } else { 207 $dirpermissions = 0777; 208 } 209 210 // All the screenshot dumps should be in the same parent dir. 211 self::$faildumpdirname = $screenshotpath . DIRECTORY_SEPARATOR . date('Ymd_His'); 212 213 if (!is_dir(self::$faildumpdirname) && !mkdir(self::$faildumpdirname, $dirpermissions, true)) { 214 // It shouldn't, we already checked that the directory is writable. 215 throw new FormatterException(sprintf( 216 'No directories can be created inside %s, check the directory permissions.', $screenshotpath 217 )); 218 } 219 220 return self::$faildumpdirname; 221 } 222 223 /** 224 * Take screenshot when a step fails. 225 * 226 * @throws Exception 227 * @param AfterStepTested $event 228 * @param Context $context 229 */ 230 protected function take_screenshot(AfterStepTested $event, $context) { 231 // Goutte can't save screenshots. 232 if ($context->getMink()->isSessionStarted($context->getMink()->getDefaultSessionName())) { 233 if (get_class($context->getMink()->getSession()->getDriver()) === 'Behat\Mink\Driver\GoutteDriver') { 234 return false; 235 } 236 list ($dir, $filename) = $this->get_faildump_filename($event, 'png'); 237 $context->saveScreenshot($filename, $dir); 238 } 239 } 240 241 /** 242 * Take a dump of the page content when a step fails. 243 * 244 * @throws Exception 245 * @param AfterStepTested $event 246 * @param \Behat\Context\Context\Context $context 247 */ 248 protected function take_contentdump(AfterStepTested $event, $context) { 249 list ($dir, $filename) = $this->get_faildump_filename($event, 'html'); 250 $fh = fopen($dir . DIRECTORY_SEPARATOR . $filename, 'w'); 251 fwrite($fh, $context->getMink()->getSession()->getPage()->getContent()); 252 fclose($fh); 253 } 254 255 /** 256 * Determine the full pathname to store a failure-related dump. 257 * 258 * This is used for content such as the DOM, and screenshots. 259 * 260 * @param AfterStepTested $event 261 * @param String $filetype The file suffix to use. Limited to 4 chars. 262 */ 263 protected function get_faildump_filename(AfterStepTested $event, $filetype) { 264 // Make a directory for the scenario. 265 $featurename = $event->getFeature()->getTitle(); 266 $featurename = preg_replace('/([^a-zA-Z0-9\_]+)/', '-', $featurename); 267 if ($this->getParameter('dir_permissions')) { 268 $dirpermissions = $this->getParameter('dir_permissions'); 269 } else { 270 $dirpermissions = 0777; 271 } 272 273 $dir = $this->get_run_screenshot_dir(); 274 275 // We want a i-am-the-scenario-title format. 276 $dir = $dir . DIRECTORY_SEPARATOR . self::$currentscenariocount . '-' . $featurename; 277 if (!is_dir($dir) && !mkdir($dir, $dirpermissions, true)) { 278 // We already checked that the directory is writable. This should not fail. 279 throw new FormatterException(sprintf( 280 'No directories can be created inside %s, check the directory permissions.', $dir 281 )); 282 } 283 284 // The failed step text. 285 // We want a stepno-i-am-the-failed-step.$filetype format. 286 $filename = $event->getStep()->getText(); 287 $filename = preg_replace('/([^a-zA-Z0-9\_]+)/', '-', $filename); 288 $filename = self::$currentscenariostepcount . '-' . $filename; 289 290 // File name limited to 255 characters. Leaving 4 chars for the file 291 // extension as we allow .png for images and .html for DOM contents. 292 $filename = substr($filename, 0, 250) . '.' . $filetype; 293 return [$dir, $filename]; 294 } 295 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body