MDL-55072 behat: refactor behat_config_manager
[moodle.git] / lib / tests / behat / behat_hooks.php
CommitLineData
f5ceb6c2 1<?php
f5ceb6c2
DM
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 * Behat hooks steps definitions.
19 *
20 * This methods are used by Behat CLI command.
21 *
22 * @package core
23 * @category test
24 * @copyright 2012 David Monllaó
25 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26 */
27
28// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
29
30require_once(__DIR__ . '/../../behat/behat_base.php');
31
42ad096f
RT
32use Behat\Testwork\Hook\Scope\BeforeSuiteScope,
33 Behat\Testwork\Hook\Scope\AfterSuiteScope,
34 Behat\Behat\Hook\Scope\BeforeFeatureScope,
35 Behat\Behat\Hook\Scope\AfterFeatureScope,
36 Behat\Behat\Hook\Scope\BeforeScenarioScope,
37 Behat\Behat\Hook\Scope\AfterScenarioScope,
38 Behat\Behat\Hook\Scope\BeforeStepScope,
39 Behat\Behat\Hook\Scope\AfterStepScope,
89cf999a 40 Behat\Mink\Exception\DriverException as DriverException,
870349ee
DM
41 WebDriver\Exception\NoSuchWindow as NoSuchWindow,
42 WebDriver\Exception\UnexpectedAlertOpen as UnexpectedAlertOpen,
3b7d3fb8
DM
43 WebDriver\Exception\UnknownError as UnknownError,
44 WebDriver\Exception\CurlExec as CurlExec,
870349ee 45 WebDriver\Exception\NoAlertOpenError as NoAlertOpenError;
f5ceb6c2
DM
46
47/**
48 * Hooks to the behat process.
49 *
50 * Behat accepts hooks after and before each
51 * suite, feature, scenario and step.
52 *
53 * They can not call other steps as part of their process
54 * like regular steps definitions does.
55 *
56 * Throws generic Exception because they are captured by Behat.
57 *
58 * @package core
59 * @category test
60 * @copyright 2012 David Monllaó
61 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
62 */
63class behat_hooks extends behat_base {
64
41eb672b
DM
65 /**
66 * @var Last browser session start time.
67 */
68 protected static $lastbrowsersessionstart = 0;
69
95b43d6b
DM
70 /**
71 * @var For actions that should only run once.
72 */
73 protected static $initprocessesfinished = false;
74
f49cede7
DM
75 /**
76 * Some exceptions can only be caught in a before or after step hook,
77 * they can not be thrown there as they will provoke a framework level
78 * failure, but we can store them here to fail the step in i_look_for_exceptions()
79 * which result will be parsed by the framework as the last step result.
80 *
81 * @var Null or the exception last step throw in the before or after hook.
82 */
83 protected static $currentstepexception = null;
84
5c0dfe32 85 /**
a964ead0 86 * If we are saving any kind of dump on failure we should use the same parent dir during a run.
5c0dfe32
DM
87 *
88 * @var The parent dir name
89 */
a964ead0 90 protected static $faildumpdirname = false;
5c0dfe32 91
3c71c15c
RT
92 /**
93 * Keeps track of time taken by feature to execute.
94 *
95 * @var array list of feature timings
96 */
97 protected static $timings = array();
98
25fbce21
RT
99 /**
100 * Hook to capture BeforeSuite event so as to give access to moodle codebase.
101 * This will try and catch any exception and exists if anything fails.
102 *
103 * @param BeforeSuiteScope $scope scope passed by event fired before suite.
104 * @BeforeSuite
105 */
106 public static function before_suite_hook(BeforeSuiteScope $scope) {
107 try {
108 self::before_suite($scope);
109 } catch (behat_stop_exception $e) {
110 echo $e->getMessage() . PHP_EOL;
111 exit(1);
112 }
113 }
114
f5ceb6c2
DM
115 /**
116 * Gives access to moodle codebase, ensures all is ready and sets up the test lock.
117 *
118 * Includes config.php to use moodle codebase with $CFG->behat_*
119 * instead of $CFG->prefix and $CFG->dataroot, called once per suite.
120 *
25fbce21 121 * @param BeforeSuiteScope $scope scope passed by event fired before suite.
f5ceb6c2 122 * @static
25fbce21 123 * @throws behat_stop_exception
f5ceb6c2 124 */
42ad096f 125 public static function before_suite(BeforeSuiteScope $scope) {
f5ceb6c2
DM
126 global $CFG;
127
cfcbc34a
DM
128 // Defined only when the behat CLI command is running, the moodle init setup process will
129 // read this value and switch to $CFG->behat_dataroot and $CFG->behat_prefix instead of
130 // the normal site.
131 define('BEHAT_TEST', 1);
132
f5ceb6c2 133 define('CLI_SCRIPT', 1);
cfcbc34a 134 // With BEHAT_TEST we will be using $CFG->behat_* instead of $CFG->dataroot, $CFG->prefix and $CFG->wwwroot.
f5ceb6c2
DM
135 require_once(__DIR__ . '/../../../config.php');
136
137 // Now that we are MOODLE_INTERNAL.
138 require_once(__DIR__ . '/../../behat/classes/behat_command.php');
17344d4c 139 require_once(__DIR__ . '/../../behat/classes/behat_selectors.php');
af4830a2 140 require_once(__DIR__ . '/../../behat/classes/behat_context_helper.php');
f5ceb6c2
DM
141 require_once(__DIR__ . '/../../behat/classes/util.php');
142 require_once(__DIR__ . '/../../testing/classes/test_lock.php');
b08b0a28 143 require_once(__DIR__ . '/../../testing/classes/nasty_strings.php');
f5ceb6c2
DM
144
145 // Avoids vendor/bin/behat to be executed directly without test environment enabled
146 // to prevent undesired db & dataroot modifications, this is also checked
147 // before each scenario (accidental user deletes) in the BeforeScenario hook.
148
149 if (!behat_util::is_test_mode_enabled()) {
25fbce21
RT
150 throw new behat_stop_exception('Behat only can run if test mode is enabled. More info in ' .
151 behat_command::DOCS_URL . '#Running_tests');
f5ceb6c2
DM
152 }
153
d9c55725 154 // Reset all data, before checking for check_server_status.
ab25d8a7
RT
155 // If not done, then it can return apache error, while running tests.
156 behat_util::reset_all_data();
157
e891c838
RT
158 // Check if server is running and using same version for cli and apache.
159 behat_util::check_server_status();
f5ceb6c2 160
b831d479
DM
161 // Prevents using outdated data, upgrade script would start and tests would fail.
162 if (!behat_util::is_test_data_updated()) {
b32ca4ca 163 $commandpath = 'php admin/tool/behat/cli/init.php';
25fbce21 164 throw new behat_stop_exception("Your behat test site is outdated, please run\n\n " .
d69a6ad9 165 $commandpath . "\n\nfrom your moodle dirroot to drop and install the behat test site again.");
b831d479 166 }
f5ceb6c2
DM
167 // Avoid parallel tests execution, it continues when the previous lock is released.
168 test_lock::acquire('behat');
41eb672b
DM
169
170 // Store the browser reset time if reset after N seconds is specified in config.php.
171 if (!empty($CFG->behat_restart_browser_after)) {
172 // Store the initial browser session opening.
173 self::$lastbrowsersessionstart = time();
174 }
5c0dfe32 175
a964ead0 176 if (!empty($CFG->behat_faildump_path) && !is_writable($CFG->behat_faildump_path)) {
25fbce21 177 throw new behat_stop_exception('You set $CFG->behat_faildump_path to a non-writable directory');
5c0dfe32 178 }
e5f2478b
RT
179
180 // Handle interrupts on PHP7.
181 if (extension_loaded('pcntl')) {
182 $disabled = explode(',', ini_get('disable_functions'));
183 if (!in_array('pcntl_signal', $disabled)) {
184 declare(ticks = 1);
185 }
186 }
f5ceb6c2
DM
187 }
188
3c71c15c
RT
189 /**
190 * Gives access to moodle codebase, to keep track of feature start time.
191 *
25fbce21 192 * @param BeforeFeatureScope $scope scope passed by event fired before feature.
3c71c15c
RT
193 * @BeforeFeature
194 */
25fbce21 195 public static function before_feature(BeforeFeatureScope $scope) {
027212b0 196 if (!defined('BEHAT_FEATURE_TIMING_FILE')) {
3c71c15c
RT
197 return;
198 }
25fbce21 199 $file = $scope->getFeature()->getFile();
08e7f97e
TL
200 self::$timings[$file] = microtime(true);
201 }
202
3c71c15c
RT
203 /**
204 * Gives access to moodle codebase, to keep track of feature end time.
205 *
25fbce21 206 * @param AfterFeatureScope $scope scope passed by event fired after feature.
3c71c15c
RT
207 * @AfterFeature
208 */
25fbce21 209 public static function after_feature(AfterFeatureScope $scope) {
027212b0 210 if (!defined('BEHAT_FEATURE_TIMING_FILE')) {
3c71c15c
RT
211 return;
212 }
25fbce21 213 $file = $scope->getFeature()->getFile();
08e7f97e
TL
214 self::$timings[$file] = microtime(true) - self::$timings[$file];
215 // Probably didn't actually run this, don't output it.
216 if (self::$timings[$file] < 1) {
217 unset(self::$timings[$file]);
218 }
219 }
220
3c71c15c
RT
221 /**
222 * Gives access to moodle codebase, to keep track of suite timings.
223 *
25fbce21 224 * @param AfterSuiteScope $scope scope passed by event fired after suite.
3c71c15c
RT
225 * @AfterSuite
226 */
25fbce21 227 public static function after_suite(AfterSuiteScope $scope) {
027212b0 228 if (!defined('BEHAT_FEATURE_TIMING_FILE')) {
08e7f97e
TL
229 return;
230 }
231 $realroot = realpath(__DIR__.'/../../../').'/';
232 foreach (self::$timings as $k => $v) {
233 $new = str_replace($realroot, '', $k);
234 self::$timings[$new] = round($v, 1);
235 unset(self::$timings[$k]);
236 }
027212b0 237 if ($existing = @json_decode(file_get_contents(BEHAT_FEATURE_TIMING_FILE), true)) {
08e7f97e
TL
238 self::$timings = array_merge($existing, self::$timings);
239 }
240 arsort(self::$timings);
027212b0 241 @file_put_contents(BEHAT_FEATURE_TIMING_FILE, json_encode(self::$timings, JSON_PRETTY_PRINT));
08e7f97e
TL
242 }
243
f5ceb6c2 244 /**
25fbce21 245 * Hook to capture before scenario event to get scope.
f5ceb6c2 246 *
25fbce21 247 * @param BeforeScenarioScope $scope scope passed by event fired before scenario.
f5ceb6c2
DM
248 * @BeforeScenario
249 */
25fbce21
RT
250 public function before_scenario_hook(BeforeScenarioScope $scope) {
251 try {
252 $this->before_scenario($scope);
253 } catch (behat_stop_exception $e) {
254 echo $e->getMessage() . PHP_EOL;
255 exit(1);
256 }
257 }
258
259 /**
260 * Resets the test environment.
261 *
262 * @param BeforeScenarioScope $scope scope passed by event fired before scenario.
263 * @throws behat_stop_exception If here we are not using the test database it should be because of a coding error
264 */
42ad096f 265 public function before_scenario(BeforeScenarioScope $scope) {
f5ceb6c2
DM
266 global $DB, $SESSION, $CFG;
267
268 // As many checks as we can.
cfcbc34a
DM
269 if (!defined('BEHAT_TEST') ||
270 !defined('BEHAT_SITE_RUNNING') ||
f5ceb6c2
DM
271 php_sapi_name() != 'cli' ||
272 !behat_util::is_test_mode_enabled() ||
cfcbc34a 273 !behat_util::is_test_site()) {
25fbce21 274 throw new behat_stop_exception('Behat only can modify the test database and the test dataroot!');
f5ceb6c2
DM
275 }
276
89cf999a
DM
277 $moreinfo = 'More info in ' . behat_command::DOCS_URL . '#Running_tests';
278 $driverexceptionmsg = 'Selenium server is not running, you need to start it to run tests that involve Javascript. ' . $moreinfo;
f59f6b6b
DM
279 try {
280 $session = $this->getSession();
281 } catch (CurlExec $e) {
282 // Exception thrown by WebDriver, so only @javascript tests will be caugth; in
d9c55725 283 // behat_util::check_server_status() we already checked that the server is running.
25fbce21 284 throw new behat_stop_exception($driverexceptionmsg);
89cf999a 285 } catch (DriverException $e) {
25fbce21 286 throw new behat_stop_exception($driverexceptionmsg);
f59f6b6b
DM
287 } catch (UnknownError $e) {
288 // Generic 'I have no idea' Selenium error. Custom exception to provide more feedback about possible solutions.
25fbce21 289 throw new behat_stop_exception($e->getMessage());
f59f6b6b
DM
290 }
291
17344d4c
DM
292 // We need the Mink session to do it and we do it only before the first scenario.
293 if (self::is_first_scenario()) {
f59f6b6b 294 behat_selectors::register_moodle_selectors($session);
86055d11 295 behat_context_helper::set_session($scope->getEnvironment());
17344d4c
DM
296 }
297
55f47341
RT
298 // Reset mink session between the scenarios.
299 $session->reset();
300
de230fd3 301 // Reset $SESSION.
2e00d01d 302 \core\session\manager::init_empty_session();
c8619f33 303
ef1d45b3 304 behat_util::reset_all_data();
b08b0a28 305
de230fd3 306 // Assign valid data to admin user (some generator-related code needs a valid user).
f5ceb6c2 307 $user = $DB->get_record('user', array('username' => 'admin'));
d79d5ac2 308 \core\session\manager::set_user($user);
5f4b4e91 309
41eb672b
DM
310 // Reset the browser if specified in config.php.
311 if (!empty($CFG->behat_restart_browser_after) && $this->running_javascript()) {
312 $now = time();
313 if (self::$lastbrowsersessionstart + $CFG->behat_restart_browser_after < $now) {
f59f6b6b 314 $session->restart();
41eb672b
DM
315 self::$lastbrowsersessionstart = $now;
316 }
317 }
318
5f4b4e91 319 // Start always in the the homepage.
3b7d3fb8 320 try {
f59f6b6b
DM
321 // Let's be conservative as we never know when new upstream issues will affect us.
322 $session->visit($this->locate_path('/'));
3b7d3fb8 323 } catch (UnknownError $e) {
25fbce21 324 throw new behat_stop_exception($e->getMessage());
3b7d3fb8 325 }
870349ee 326
f59f6b6b 327
95b43d6b
DM
328 // Checking that the root path is a Moodle test site.
329 if (self::is_first_scenario()) {
25fbce21 330 $notestsiteexception = new behat_stop_exception('The base URL (' . $CFG->wwwroot . ') is not a behat test site, ' .
3b7d3fb8 331 'ensure you started the built-in web server in the correct directory or your web server is correctly started and set up');
19f6703d 332 $this->find("xpath", "//head/child::title[normalize-space(.)='" . behat_util::BEHATSITENAME . "']", $notestsiteexception);
95b43d6b
DM
333
334 self::$initprocessesfinished = true;
335 }
3b0b5e57
RT
336 // Run all test with medium (1024x768) screen size, to avoid responsive problems.
337 $this->resize_window('medium');
f5ceb6c2
DM
338 }
339
f5ceb6c2 340 /**
d1e55a47 341 * Wait for JS to complete before beginning interacting with the DOM.
f5ceb6c2 342 *
f49cede7
DM
343 * Executed only when running against a real browser. We wrap it
344 * all in a try & catch to forward the exception to i_look_for_exceptions
345 * so the exception will be at scenario level, which causes a failure, by
346 * default would be at framework level, which will stop the execution of
347 * the run.
f5ceb6c2 348 *
25fbce21 349 * @param BeforeStepScope $scope scope passed by event fired before step.
42ad096f 350 * @BeforeStep
f5ceb6c2 351 */
42ad096f
RT
352 public function before_step_javascript(BeforeStepScope $scope) {
353 self::$currentstepexception = null;
f49cede7 354
42ad096f
RT
355 // Only run if JS.
356 if ($this->running_javascript()) {
357 try {
358 $this->wait_for_pending_js();
359 } catch (Exception $e) {
360 self::$currentstepexception = $e;
361 }
f49cede7 362 }
d1e55a47
DM
363 }
364
365 /**
366 * Wait for JS to complete after finishing the step.
367 *
368 * With this we ensure that there are not AJAX calls
369 * still in progress.
370 *
f49cede7
DM
371 * Executed only when running against a real browser. We wrap it
372 * all in a try & catch to forward the exception to i_look_for_exceptions
373 * so the exception will be at scenario level, which causes a failure, by
374 * default would be at framework level, which will stop the execution of
375 * the run.
d1e55a47 376 *
25fbce21 377 * @param AfterStepScope $scope scope passed by event fired after step..
42ad096f 378 * @AfterStep
d1e55a47 379 */
42ad096f
RT
380 public function after_step_javascript(AfterStepScope $scope) {
381 global $CFG, $DB;
382
383 // Save the page content if the step failed.
384 if (!empty($CFG->behat_faildump_path) &&
385 $scope->getTestResult()->getResultCode() === Behat\Testwork\Tester\Result\TestResult::FAILED) {
386 $this->take_contentdump($scope);
387 }
388
389 // Abort any open transactions to prevent subsequent tests hanging.
390 // This does the same as abort_all_db_transactions(), but doesn't call error_log() as we don't
391 // want to see a message in the behat output.
392 if (($scope->getTestResult() instanceof \Behat\Behat\Tester\Result\ExecutedStepResult) &&
393 $scope->getTestResult()->hasException()) {
394 if ($DB && $DB->is_transaction_started()) {
395 $DB->force_transaction_rollback();
396 }
397 }
398
399 // Only run if JS.
400 if (!$this->running_javascript()) {
401 return;
402 }
5c0dfe32
DM
403
404 // Save a screenshot if the step failed.
a964ead0 405 if (!empty($CFG->behat_faildump_path) &&
42ad096f
RT
406 $scope->getTestResult()->getResultCode() === Behat\Testwork\Tester\Result\TestResult::FAILED) {
407 $this->take_screenshot($scope);
5c0dfe32 408 }
f49cede7
DM
409
410 try {
411 $this->wait_for_pending_js();
412 self::$currentstepexception = null;
413 } catch (UnexpectedAlertOpen $e) {
414 self::$currentstepexception = $e;
415
416 // Accepting the alert so the framework can continue properly running
417 // the following scenarios. Some browsers already closes the alert, so
418 // wrapping in a try & catch.
419 try {
420 $this->getSession()->getDriver()->getWebDriverSession()->accept_alert();
421 } catch (Exception $e) {
422 // Catching the generic one as we never know how drivers reacts here.
423 }
424 } catch (Exception $e) {
425 self::$currentstepexception = $e;
426 }
d1e55a47
DM
427 }
428
131d4ac2
T
429 /**
430 * Executed after scenario having switch window to restart session.
431 * This is needed to close all extra browser windows and starting
432 * one browser window.
433 *
25fbce21 434 * @param AfterScenarioScope $scope scope passed by event fired after scenario.
131d4ac2
T
435 * @AfterScenario @_switch_window
436 */
25fbce21 437 public function after_scenario_switchwindow(AfterScenarioScope $scope) {
af7cadbb 438 for ($count = 0; $count < self::EXTENDED_TIMEOUT; $count++) {
5e483833
RT
439 try {
440 $this->getSession()->restart();
441 break;
442 } catch (DriverException $e) {
443 // Wait for timeout and try again.
444 sleep(self::TIMEOUT);
445 }
446 }
447 // If session is not restarted above then it will try to start session before next scenario
448 // and if that fails then exception will be thrown.
131d4ac2
T
449 }
450
a964ead0
AN
451 /**
452 * Getter for self::$faildumpdirname
5c0dfe32
DM
453 *
454 * @return string
455 */
a964ead0
AN
456 protected function get_run_faildump_dir() {
457 return self::$faildumpdirname;
5c0dfe32
DM
458 }
459
460 /**
461 * Take screenshot when a step fails.
3ec07614 462 *
5c0dfe32 463 * @throws Exception
25fbce21 464 * @param AfterStepScope $scope scope passed by event after step.
3ec07614 465 */
42ad096f 466 protected function take_screenshot(AfterStepScope $scope) {
5c0dfe32
DM
467 // Goutte can't save screenshots.
468 if (!$this->running_javascript()) {
469 return false;
3ec07614 470 }
5c0dfe32 471
be8b8950
MJ
472 // Some drivers (e.g. chromedriver) may throw an exception while trying to take a screenshot. If this isn't handled,
473 // the behat run dies. We don't want to lose the information about the failure that triggered the screenshot,
474 // so let's log the exception message to a file (to explain why there's no screenshot) and allow the run to continue,
475 // handling the failure as normal.
476 try {
477 list ($dir, $filename) = $this->get_faildump_filename($scope, 'png');
478 $this->saveScreenshot($filename, $dir);
479 } catch (Exception $e) {
480 // Catching all exceptions as we don't know what the driver might throw.
481 list ($dir, $filename) = $this->get_faildump_filename($scope, 'txt');
482 $message = "Could not save screenshot due to an error\n" . $e->getMessage();
483 file_put_contents($dir . DIRECTORY_SEPARATOR . $filename, $message);
484 }
a964ead0
AN
485 }
486
487 /**
488 * Take a dump of the page content when a step fails.
489 *
490 * @throws Exception
25fbce21 491 * @param AfterStepScope $scope scope passed by event after step.
a964ead0 492 */
42ad096f
RT
493 protected function take_contentdump(AfterStepScope $scope) {
494 list ($dir, $filename) = $this->get_faildump_filename($scope, 'html');
a964ead0 495
be8b8950
MJ
496 try {
497 // Driver may throw an exception during getContent(), so do it first to avoid getting an empty file.
498 $content = $this->getSession()->getPage()->getContent();
499 } catch (Exception $e) {
500 // Catching all exceptions as we don't know what the driver might throw.
501 $content = "Could not save contentdump due to an error\n" . $e->getMessage();
502 }
503 file_put_contents($dir . DIRECTORY_SEPARATOR . $filename, $content);
a964ead0
AN
504 }
505
506 /**
507 * Determine the full pathname to store a failure-related dump.
508 *
509 * This is used for content such as the DOM, and screenshots.
510 *
25fbce21 511 * @param AfterStepScope $scope scope passed by event after step.
167486b3 512 * @param String $filetype The file suffix to use. Limited to 4 chars.
a964ead0 513 */
42ad096f 514 protected function get_faildump_filename(AfterStepScope $scope, $filetype) {
a964ead0
AN
515 global $CFG;
516
517 // All the contentdumps should be in the same parent dir.
518 if (!$faildumpdir = self::get_run_faildump_dir()) {
519 $faildumpdir = self::$faildumpdirname = date('Ymd_His');
5c0dfe32 520
a964ead0 521 $dir = $CFG->behat_faildump_path . DIRECTORY_SEPARATOR . $faildumpdir;
5c0dfe32 522
a964ead0 523 if (!is_dir($dir) && !mkdir($dir, $CFG->directorypermissions, true)) {
5c0dfe32 524 // It shouldn't, we already checked that the directory is writable.
a964ead0 525 throw new Exception('No directories can be created inside $CFG->behat_faildump_path, check the directory permissions.');
5c0dfe32
DM
526 }
527 } else {
528 // We will always need to know the full path.
a964ead0 529 $dir = $CFG->behat_faildump_path . DIRECTORY_SEPARATOR . $faildumpdir;
5c0dfe32
DM
530 }
531
532 // The scenario title + the failed step text.
a964ead0 533 // We want a i-am-the-scenario-title_i-am-the-failed-step.$filetype format.
42ad096f 534 $filename = $scope->getFeature()->getTitle() . '_' . $scope->getStep()->getText();
167486b3
DM
535 $filename = preg_replace('/([^a-zA-Z0-9\_]+)/', '-', $filename);
536
42ad096f 537 // File name limited to 255 characters. Leaving 5 chars for line number and 4 chars for the file.
167486b3 538 // extension as we allow .png for images and .html for DOM contents.
42ad096f 539 $filename = substr($filename, 0, 245) . '_' . $scope->getStep()->getLine() . '.' . $filetype;
5c0dfe32 540
a964ead0 541 return array($dir, $filename);
3ec07614
DW
542 }
543
5f4b4e91
DM
544 /**
545 * Internal step definition to find exceptions, debugging() messages and PHP debug messages.
546 *
547 * Part of behat_hooks class as is part of the testing framework, is auto-executed
548 * after each step so no features will splicitly use it.
549 *
550 * @Given /^I look for exceptions$/
f49cede7 551 * @throw Exception Unknown type, depending on what we caught in the hook or basic \Exception.
5f4b4e91
DM
552 * @see Moodle\BehatExtension\Tester\MoodleStepTester
553 */
554 public function i_look_for_exceptions() {
f49cede7
DM
555 // If the step already failed in a hook throw the exception.
556 if (!is_null(self::$currentstepexception)) {
557 throw self::$currentstepexception;
558 }
559
eb9ca848 560 $this->look_for_exceptions();
5f4b4e91
DM
561 }
562
95b43d6b
DM
563 /**
564 * Returns whether the first scenario of the suite is running
565 *
566 * @return bool
567 */
568 protected static function is_first_scenario() {
569 return !(self::$initprocessesfinished);
570 }
f5ceb6c2 571}
a964ead0 572
25fbce21
RT
573/**
574 * Behat stop exception
575 *
576 * This exception is thrown from before suite or scenario if any setup problem found.
577 *
578 * @package core_test
579 * @copyright 2016 Rajesh Taneja <rajesh@moodle.com>
580 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
581 */
582class behat_stop_exception extends \Exception{}