MDL-57319 outputlib: raise timeout for scss/less compilation
[moodle.git] / backup / util / ui / tests / behat / behat_backup.php
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/>.
17 /**
18  * Backup and restore actions to help behat feature files writting.
19  *
20  * @package    core_backup
21  * @category   test
22  * @copyright  2013 David MonllaĆ³
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
26 // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
28 require_once(__DIR__ . '/../../../../../lib/behat/behat_base.php');
29 require_once(__DIR__ . '/../../../../../lib/behat/behat_field_manager.php');
30 require_once(__DIR__ . '/../../../../../lib/tests/behat/behat_navigation.php');
32 use Behat\Gherkin\Node\TableNode as TableNode,
33     Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException,
34     Behat\Mink\Exception\ExpectationException as ExpectationException;
36 /**
37  * Backup-related steps definitions.
38  *
39  * @package    core_backup
40  * @category   test
41  * @copyright  2013 David MonllaĆ³
42  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
43  */
44 class behat_backup extends behat_base {
46     /**
47      * Follow a link like 'Backup' or 'Import', where the link name comes from
48      * a language string, in the settings nav block of a course.
49      * @param string $langstring the lang string to look for. E.g. 'backup' or 'import'.
50      * @param string $component (optional) second argument to {@link get_string}.
51      */
52     protected function navigate_to_course_settings_link($langstring, $component = '') {
53         $behatnavigation = new behat_navigation();
54         $behatnavigation->setMink($this->getMink());
55         $behatnavigation->i_navigate_to_node_in(get_string($langstring, $component),
56                 get_string('courseadministration'));
57     }
59     /**
60      * Backups the specified course using the provided options. If you are interested in restoring this backup would be
61      * useful to provide a 'Filename' option.
62      *
63      * @Given /^I backup "(?P<course_fullname_string>(?:[^"]|\\")*)" course using this options:$/
64      * @param string $backupcourse
65      * @param TableNode $options Backup options or false if no options provided
66      */
67     public function i_backup_course_using_this_options($backupcourse, $options = false) {
68         // We can not use other steps here as we don't know where the provided data
69         // table elements are used, and we need to catch exceptions contantly.
71         // Go to homepage.
72         $this->getSession()->visit($this->locate_path('/?redirect=0'));
74         // Click the course link.
75         $this->find_link($backupcourse)->click();
77         // Click the backup link.
78         $this->navigate_to_course_settings_link('backup');
79         $this->wait();
81         // Initial settings.
82         $this->fill_backup_restore_form($this->get_step_options($options, "Initial"));
83         $this->find_button(get_string('backupstage1action', 'backup'))->press();
84         $this->wait();
86         // Schema settings.
87         $this->fill_backup_restore_form($this->get_step_options($options, "Schema"));
88         $this->find_button(get_string('backupstage2action', 'backup'))->press();
89         $this->wait();
91         // Confirmation and review, backup filename can also be specified.
92         $this->fill_backup_restore_form($this->get_step_options($options, "Confirmation"));
93         $this->find_button(get_string('backupstage4action', 'backup'))->press();
95         // Waiting for it to finish.
96         $this->wait(self::EXTENDED_TIMEOUT);
98         // Last backup continue button.
99         $this->find_button(get_string('backupstage16action', 'backup'))->press();
100     }
102     /**
103      * Performs a quick (one click) backup of a course.
104      *
105      * Please note that because you can't set settings with this there is no way to know what the filename
106      * that was produced was. It contains a timestamp making it hard to find.
107      *
108      * @Given /^I perform a quick backup of course "(?P<course_fullname_string>(?:[^"]|\\")*)"$/
109      * @param string $backupcourse
110      */
111     public function i_perform_a_quick_backup_of_course($backupcourse) {
112         // We can not use other steps here as we don't know where the provided data
113         // table elements are used, and we need to catch exceptions contantly.
115         // Go to homepage.
116         $this->getSession()->visit($this->locate_path('/?redirect=0'));
118         // Click the course link.
119         $this->find_link($backupcourse)->click();
121         // Click the backup link.
122         $this->find_link(get_string('backup'))->click();
123         $this->wait();
125         // Initial settings.
126         $this->find_button(get_string('jumptofinalstep', 'backup'))->press();
127         $this->wait();
129         // Waiting for it to finish.
130         $this->wait(self::EXTENDED_TIMEOUT);
132         // Last backup continue button.
133         $this->find_button(get_string('backupstage16action', 'backup'))->press();
134     }
136     /**
137      * Imports the specified origin course into the other course using the provided options.
138      *
139      * Keeping it separatelly from backup & restore, it the number of
140      * steps and duplicate code becomes bigger a common method should
141      * be generalized.
142      *
143      * @Given /^I import "(?P<from_course_fullname_string>(?:[^"]|\\")*)" course into "(?P<to_course_fullname_string>(?:[^"]|\\")*)" course using this options:$/
144      * @param string $fromcourse
145      * @param string $tocourse
146      * @param TableNode $options
147      */
148     public function i_import_course_into_course($fromcourse, $tocourse, $options = false) {
150         // We can not use other steps here as we don't know where the provided data
151         // table elements are used, and we need to catch exceptions contantly.
153         // Go to homepage.
154         $this->getSession()->visit($this->locate_path('/?redirect=0'));
155         $this->wait();
157         // Click the course link.
158         $this->find_link($tocourse)->click();
159         $this->wait();
161         // Click the import link.
162         $this->navigate_to_course_settings_link('import');
163         $this->wait();
165         // Select the course.
166         $exception = new ExpectationException('"' . $fromcourse . '" course not found in the list of courses to import from',
167             $this->getSession());
169         // The argument should be converted to an xpath literal.
170         $fromcourse = behat_context_helper::escape($fromcourse);
171         $xpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' ics-results ')]" .
172             "/descendant::tr[contains(., $fromcourse)]" .
173             "/descendant::input[@type='radio']";
174         $radionode = $this->find('xpath', $xpath, $exception);
175         $radiofield = new behat_form_field($this->getSession(), $radionode);
176         $radiofield->set_value(1);
178         $this->find_button(get_string('continue'))->press();
179         $this->wait();
181         // Initial settings.
182         $this->fill_backup_restore_form($this->get_step_options($options, "Initial"));
183         $this->find_button(get_string('importbackupstage1action', 'backup'))->press();
184         $this->wait();
186         // Schema settings.
187         $this->fill_backup_restore_form($this->get_step_options($options, "Schema"));
188         $this->find_button(get_string('importbackupstage2action', 'backup'))->press();
189         $this->wait();
191         // Run it.
192         $this->find_button(get_string('importbackupstage4action', 'backup'))->press();
193         $this->wait(self::EXTENDED_TIMEOUT);
195         // Continue and redirect to 'to' course.
196         $this->find_button(get_string('continue'))->press();
197     }
199     /**
200      * Restores the backup into the specified course and the provided options.
201      *
202      * You should be in the 'Restore' page where the backup is.
203      *
204      * @Given /^I restore "(?P<backup_filename_string>(?:[^"]|\\")*)" backup into "(?P<existing_course_fullname_string>(?:[^"]|\\")*)" course using this options:$/
205      * @param string $backupfilename
206      * @param string $existingcourse
207      * @param TableNode $options Restore forms options or false if no options provided
208      */
209     public function i_restore_backup_into_course_using_this_options($backupfilename, $existingcourse, $options = false) {
211         // Confirm restore.
212         $this->select_backup($backupfilename);
214         // The argument should be converted to an xpath literal.
215         $existingcourse = behat_context_helper::escape($existingcourse);
217         // Selecting the specified course (we can not call behat_forms::select_radio here as is in another behat subcontext).
218         $radionode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), ' bcs-existing-course ')]" .
219             "/descendant::div[contains(concat(' ', normalize-space(@class), ' '), ' restore-course-search ')]" .
220             "/descendant::tr[contains(., $existingcourse)]" .
221             "/descendant::input[@type='radio']");
222         $radionode->click();
224         // Pressing the continue button of the restore into an existing course section.
225         $continuenode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), ' bcs-existing-course ')]" .
226             "/descendant::input[@type='submit'][@value='" . get_string('continue') . "']");
227         $continuenode->click();
228         $this->wait();
230         // Common restore process using provided key/value options.
231         $this->process_restore($options);
232     }
234     /**
235      * Restores the specified backup into a new course using the provided options.
236      *
237      * You should be in the 'Restore' page where the backup is.
238      *
239      * @Given /^I restore "(?P<backup_filename_string>(?:[^"]|\\")*)" backup into a new course using this options:$/
240      * @param string $backupfilename
241      * @param TableNode $options Restore forms options or false if no options provided
242      */
243     public function i_restore_backup_into_a_new_course_using_this_options($backupfilename, $options = false) {
245         // Confirm restore.
246         $this->select_backup($backupfilename);
248         // The first category in the list.
249         $radionode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), ' bcs-new-course ')]" .
250             "/descendant::div[contains(concat(' ', normalize-space(@class), ' '), ' restore-course-search ')]" .
251             "/descendant::input[@type='radio']");
252         $radionode->click();
254         // Pressing the continue button of the restore into an existing course section.
255         $continuenode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), ' bcs-new-course ')]" .
256             "/descendant::input[@type='submit'][@value='" . get_string('continue') . "']");
257         $continuenode->click();
258         $this->wait();
260         // Common restore process using provided key/value options.
261         $this->process_restore($options);
262     }
264     /**
265      * Merges the backup into the current course using the provided restore options.
266      *
267      * You should be in the 'Restore' page where the backup is.
268      *
269      * @Given /^I merge "(?P<backup_filename_string>(?:[^"]|\\")*)" backup into the current course using this options:$/
270      * @param string $backupfilename
271      * @param TableNode $options Restore forms options or false if no options provided
272      */
273     public function i_merge_backup_into_the_current_course($backupfilename, $options = false) {
275         // Confirm restore.
276         $this->select_backup($backupfilename);
278         // Merge without deleting radio option.
279         $radionode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), 'bcs-current-course')]" .
280             "/descendant::input[@type='radio'][@name='target'][@value='1']");
281         $radionode->click();
283         // Pressing the continue button of the restore merging section.
284         $continuenode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), 'bcs-current-course')]" .
285             "/descendant::input[@type='submit'][@value='" . get_string('continue') . "']");
286         $continuenode->click();
287         $this->wait();
289         // Common restore process using provided key/value options.
290         $this->process_restore($options);
291     }
293     /**
294      * Merges the backup into the current course after deleting this contents, using the provided restore options.
295      *
296      * You should be in the 'Restore' page where the backup is.
297      *
298      * @Given /^I merge "(?P<backup_filename_string>(?:[^"]|\\")*)" backup into the current course after deleting it's contents using this options:$/
299      * @param string $backupfilename
300      * @param TableNode $options Restore forms options or false if no options provided
301      */
302     public function i_merge_backup_into_current_course_deleting_its_contents($backupfilename, $options = false) {
304         // Confirm restore.
305         $this->select_backup($backupfilename);
307         // Delete contents radio option.
308         $radionode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), 'bcs-current-course')]" .
309             "/descendant::input[@type='radio'][@name='target'][@value='0']");
310         $radionode->click();
312         // Pressing the continue button of the restore merging section.
313         $continuenode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), 'bcs-current-course')]" .
314             "/descendant::input[@type='submit'][@value='" . get_string('continue') . "']");
315         $continuenode->click();
316         $this->wait();
318         // Common restore process using provided key/value options.
319         $this->process_restore($options);
320     }
322     /**
323      * Selects the backup to restore.
324      *
325      * @throws ExpectationException
326      * @param string $backupfilename
327      * @return void
328      */
329     protected function select_backup($backupfilename) {
331         // Using xpath as there are other restore links before this one.
332         $exception = new ExpectationException('The "' . $backupfilename . '" backup file can not be found in this page',
333             $this->getSession());
335         // The argument should be converted to an xpath literal.
336         $backupfilename = behat_context_helper::escape($backupfilename);
338         $xpath = "//tr[contains(., $backupfilename)]/descendant::a[contains(., '" . get_string('restore') . "')]";
339         $restorelink = $this->find('xpath', $xpath, $exception);
340         $restorelink->click();
342         // Confirm the backup contents.
343         $restore = $this->find_button(get_string('continue'))->press();
344     }
346     /**
347      * Executes the common steps of all restore processes.
348      *
349      * @param TableNode $options The backup and restore options or false if no options provided
350      * @return void
351      */
352     protected function process_restore($options) {
354         // We can not use other steps here as we don't know where the provided data
355         // table elements are used, and we need to catch exceptions contantly.
357         // Settings.
358         $this->fill_backup_restore_form($this->get_step_options($options, "Settings"));
359         $this->find_button(get_string('restorestage4action', 'backup'))->press();
360         $this->wait();
362         // Schema.
363         $this->fill_backup_restore_form($this->get_step_options($options, "Schema"));
364         $this->find_button(get_string('restorestage8action', 'backup'))->press();
365         $this->wait();
367         // Review, no options here.
368         $this->find_button(get_string('restorestage16action', 'backup'))->press();
369         $this->wait();
371         // Last restore continue button, redirected to restore course after this.
372         $this->find_button(get_string('restorestage32action', 'backup'))->press();
374         // Long wait when waiting for the restore to finish.
375         $this->wait(self::EXTENDED_TIMEOUT);
376     }
378     /**
379      * Tries to fill the current page form elements with the provided options.
380      *
381      * This step is slow as it spins over each provided option, we are
382      * not expected to have lots of provided options, anyways, is better
383      * to be conservative and wait for the elements to appear rather than
384      * to have false failures.
385      *
386      * @param TableNode $options The backup and restore options or false if no options provided
387      * @return void
388      */
389     protected function fill_backup_restore_form($options) {
391         // Nothing to fill if no options are provided.
392         if (!$options) {
393             return;
394         }
396         // If we find any of the provided options in the current form we should set the value.
397         $datahash = $options->getRowsHash();
398         foreach ($datahash as $locator => $value) {
399             $field = behat_field_manager::get_form_field_from_label($locator, $this);
400             $field->set_value($value);
401         }
402     }
404     /**
405      * Get the options specific to this step of the backup/restore process.
406      *
407      * @param TableNode $options The options table to filter
408      * @param string $step The name of the step
409      * @return TableNode The filtered options table
410      * @throws ExpectationException
411      */
412     protected function get_step_options($options, $step) {
413         // Nothing to fill if no options are provided.
414         if (!$options) {
415             return;
416         }
418         $rows = $options->getRows();
419         $newrows = array();
420         foreach ($rows as $k => $data) {
421             if (count($data) !== 3) {
422                 // Not enough information to guess the page.
423                 throw new ExpectationException("The backup/restore step must be specified for all backup options",
424                     $this->getSession());
425             } else if ($data[0] == $step) {
426                 unset($data[0]);
427                 $newrows[] = $data;
428             }
429         }
430         $pageoptions = new TableNode($newrows);
432         return $pageoptions;
433     }
436     /**
437      * Waits until the DOM and the page Javascript code is ready.
438      *
439      * @param int $timeout The number of seconds that we wait.
440      * @return void
441      */
442     protected function wait($timeout = false) {
444         if (!$this->running_javascript()) {
445             return;
446         }
448         if (!$timeout) {
449             $timeout = self::TIMEOUT;
450         }
452         $this->getSession()->wait($timeout * 1000, self::PAGE_READY_JS);
453     }