MDL-46481 backup: implemented quick backup process (one click)
[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 useful to provide a 'Filename' option.
61      *
62      * @Given /^I backup "(?P<course_fullname_string>(?:[^"]|\\")*)" course using this options:$/
63      * @param string $backupcourse
64      * @param TableNode $options Backup options or false if no options provided
65      */
66     public function i_backup_course_using_this_options($backupcourse, $options = false) {
67         // We can not use other steps here as we don't know where the provided data
68         // table elements are used, and we need to catch exceptions contantly.
70         // Go to homepage.
71         $this->getSession()->visit($this->locate_path('/'));
73         // Click the course link.
74         $this->find_link($backupcourse)->click();
76         // Click the backup link.
77         $this->navigate_to_course_settings_link('backup');
78         $this->wait();
80         // Initial settings.
81         $this->fill_backup_restore_form($this->get_step_options($options, "Initial"));
82         $this->find_button(get_string('backupstage1action', 'backup'))->press();
83         $this->wait();
85         // Schema settings.
86         $this->fill_backup_restore_form($this->get_step_options($options, "Schema"));
87         $this->find_button(get_string('backupstage2action', 'backup'))->press();
88         $this->wait();
90         // Confirmation and review, backup filename can also be specified.
91         $this->fill_backup_restore_form($this->get_step_options($options, "Confirmation"));
92         $this->find_button(get_string('backupstage4action', 'backup'))->press();
94         // Waiting for it to finish.
95         $this->wait(self::EXTENDED_TIMEOUT);
97         // Last backup continue button.
98         $this->find_button(get_string('backupstage16action', 'backup'))->press();
99     }
101     /**
102      * Imports the specified origin course into the other course using the provided options.
103      *
104      * Keeping it separatelly from backup & restore, it the number of
105      * steps and duplicate code becomes bigger a common method should
106      * be generalized.
107      *
108      * @Given /^I import "(?P<from_course_fullname_string>(?:[^"]|\\")*)" course into "(?P<to_course_fullname_string>(?:[^"]|\\")*)" course using this options:$/
109      * @param string $fromcourse
110      * @param string $tocourse
111      * @param TableNode $options
112      */
113     public function i_import_course_into_course($fromcourse, $tocourse, $options = false) {
115         // We can not use other steps here as we don't know where the provided data
116         // table elements are used, and we need to catch exceptions contantly.
118         // Go to homepage.
119         $this->getSession()->visit($this->locate_path('/'));
120         $this->wait();
122         // Click the course link.
123         $this->find_link($tocourse)->click();
124         $this->wait();
126         // Click the import link.
127         $this->navigate_to_course_settings_link('import');
128         $this->wait();
130         // Select the course.
131         $exception = new ExpectationException('"' . $fromcourse . '" course not found in the list of courses to import from', $this->getSession());
133         // The argument should be converted to an xpath literal.
134         $fromcourse = $this->getSession()->getSelectorsHandler()->xpathLiteral($fromcourse);
135         $xpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' ics-results ')]" .
136             "/descendant::tr[contains(., $fromcourse)]" .
137             "/descendant::input[@type='radio']";
138         $radionode = $this->find('xpath', $xpath, $exception);
139         $radionode->check();
140         $radionode->click();
142         $this->find_button(get_string('continue'))->press();
143         $this->wait();
145         // Initial settings.
146         $this->fill_backup_restore_form($this->get_step_options($options, "Initial"));
147         $this->find_button(get_string('importbackupstage1action', 'backup'))->press();
148         $this->wait();
150         // Schema settings.
151         $this->fill_backup_restore_form($this->get_step_options($options, "Schema"));
152         $this->find_button(get_string('importbackupstage2action', 'backup'))->press();
153         $this->wait();
155         // Run it.
156         $this->find_button(get_string('importbackupstage4action', 'backup'))->press();
157         $this->wait(self::EXTENDED_TIMEOUT);
159         // Continue and redirect to 'to' course.
160         $this->find_button(get_string('continue'))->press();
161     }
163     /**
164      * Restores the backup into the specified course and the provided options. You should be in the 'Restore' page where the backup is.
165      *
166      * @Given /^I restore "(?P<backup_filename_string>(?:[^"]|\\")*)" backup into "(?P<existing_course_fullname_string>(?:[^"]|\\")*)" course using this options:$/
167      * @param string $backupfilename
168      * @param string $existingcourse
169      * @param TableNode $options Restore forms options or false if no options provided
170      */
171     public function i_restore_backup_into_course_using_this_options($backupfilename, $existingcourse, $options = false) {
173         // Confirm restore.
174         $this->select_backup($backupfilename);
176         // The argument should be converted to an xpath literal.
177         $existingcourse = $this->getSession()->getSelectorsHandler()->xpathLiteral($existingcourse);
179         // Selecting the specified course (we can not call behat_forms::select_radio here as is in another behat subcontext).
180         $radionode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), ' bcs-existing-course ')]" .
181             "/descendant::div[@class='restore-course-search']" .
182             "/descendant::tr[contains(., $existingcourse)]" .
183             "/descendant::input[@type='radio']");
184         $radionode->check();
185         $radionode->click();
187         // Pressing the continue button of the restore into an existing course section.
188         $continuenode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), ' bcs-existing-course ')]" .
189             "/descendant::input[@type='submit'][@value='" . get_string('continue') . "']");
190         $continuenode->click();
191         $this->wait();
193         // Common restore process using provided key/value options.
194         $this->process_restore($options);
195     }
197     /**
198      * Restores the specified backup into a new course using the provided options. You should be in the 'Restore' page where the backup is.
199      *
200      * @Given /^I restore "(?P<backup_filename_string>(?:[^"]|\\")*)" backup into a new course using this options:$/
201      * @param string $backupfilename
202      * @param TableNode $options Restore forms options or false if no options provided
203      */
204     public function i_restore_backup_into_a_new_course_using_this_options($backupfilename, $options = false) {
206         // Confirm restore.
207         $this->select_backup($backupfilename);
209         // The first category in the list.
210         $radionode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), ' bcs-new-course ')]" .
211             "/descendant::div[@class='restore-course-search']" .
212             "/descendant::input[@type='radio']");
213         $radionode->check();
214         $radionode->click();
216         // Pressing the continue button of the restore into an existing course section.
217         $continuenode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), ' bcs-new-course ')]" .
218             "/descendant::input[@type='submit'][@value='" . get_string('continue') . "']");
219         $continuenode->click();
220         $this->wait();
222         // Common restore process using provided key/value options.
223         $this->process_restore($options);
224     }
226     /**
227      * Merges the backup into the current course using the provided restore options. You should be in the 'Restore' page where the backup is.
228      *
229      * @Given /^I merge "(?P<backup_filename_string>(?:[^"]|\\")*)" backup into the current course using this options:$/
230      * @param string $backupfilename
231      * @param TableNode $options Restore forms options or false if no options provided
232      */
233     public function i_merge_backup_into_the_current_course($backupfilename, $options = false) {
235         // Confirm restore.
236         $this->select_backup($backupfilename);
238         // Merge without deleting radio option.
239         $radionode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), 'bcs-current-course')]" .
240             "/descendant::input[@type='radio'][@name='target'][@value='1']");
241         $radionode->check();
242         $radionode->click();
244         // Pressing the continue button of the restore merging section.
245         $continuenode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), 'bcs-current-course')]" .
246             "/descendant::input[@type='submit'][@value='" . get_string('continue') . "']");
247         $continuenode->click();
248         $this->wait();
250         // Common restore process using provided key/value options.
251         $this->process_restore($options);
252     }
254     /**
255      * Merges the backup into the current course after deleting this contents, using the provided restore options. You should be in the 'Restore' page where the backup is.
256      *
257      * @Given /^I merge "(?P<backup_filename_string>(?:[^"]|\\")*)" backup into the current course after deleting it's contents using this options:$/
258      * @param string $backupfilename
259      * @param TableNode $options Restore forms options or false if no options provided
260      */
261     public function i_merge_backup_into_current_course_deleting_its_contents($backupfilename, $options = false) {
263         // Confirm restore.
264         $this->select_backup($backupfilename);
266         // Delete contents radio option.
267         $radionode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), 'bcs-current-course')]" .
268             "/descendant::input[@type='radio'][@name='target'][@value='0']");
269         $radionode->check();
270         $radionode->click();
272         // Pressing the continue button of the restore merging section.
273         $continuenode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), 'bcs-current-course')]" .
274             "/descendant::input[@type='submit'][@value='" . get_string('continue') . "']");
275         $continuenode->click();
276         $this->wait();
278         // Common restore process using provided key/value options.
279         $this->process_restore($options);
280     }
282     /**
283      * Selects the backup to restore.
284      *
285      * @throws ExpectationException
286      * @param string $backupfilename
287      * @return void
288      */
289     protected function select_backup($backupfilename) {
291         // Using xpath as there are other restore links before this one.
292         $exception = new ExpectationException('The "' . $backupfilename . '" backup file can not be found in this page', $this->getSession());
294         // The argument should be converted to an xpath literal.
295         $backupfilename = $this->getSession()->getSelectorsHandler()->xpathLiteral($backupfilename);
297         $xpath = "//tr[contains(., $backupfilename)]/descendant::a[contains(., '" . get_string('restore') . "')]";
298         $restorelink = $this->find('xpath', $xpath, $exception);
299         $restorelink->click();
301         // Confirm the backup contents.
302         $restore = $this->find_button(get_string('continue'))->press();
303     }
305     /**
306      * Executes the common steps of all restore processes.
307      *
308      * @param TableNode $options The backup and restore options or false if no options provided
309      * @return void
310      */
311     protected function process_restore($options) {
313         // We can not use other steps here as we don't know where the provided data
314         // table elements are used, and we need to catch exceptions contantly.
316         // Settings.
317         $this->fill_backup_restore_form($this->get_step_options($options, "Settings"));
318         $this->find_button(get_string('restorestage4action', 'backup'))->press();
319         $this->wait();
321         // Schema.
322         $this->fill_backup_restore_form($this->get_step_options($options, "Schema"));
323         $this->find_button(get_string('restorestage8action', 'backup'))->press();
324         $this->wait();
326         // Review, no options here.
327         $this->find_button(get_string('restorestage16action', 'backup'))->press();
328         $this->wait();
330         // Last restore continue button, redirected to restore course after this.
331         $this->find_button(get_string('restorestage32action', 'backup'))->press();
333         // Long wait when waiting for the restore to finish.
334         $this->wait(self::EXTENDED_TIMEOUT);
335     }
337     /**
338      * Tries to fill the current page form elements with the provided options.
339      *
340      * This step is slow as it spins over each provided option, we are
341      * not expected to have lots of provided options, anyways, is better
342      * to be conservative and wait for the elements to appear rather than
343      * to have false failures.
344      *
345      * @param TableNode $options The backup and restore options or false if no options provided
346      * @return void
347      */
348     protected function fill_backup_restore_form($options) {
350         // Nothing to fill if no options are provided.
351         if (!$options) {
352             return;
353         }
355         // If we find any of the provided options in the current form we should set the value.
356         $datahash = $options->getRowsHash();
357         foreach ($datahash as $locator => $value) {
358             $field = behat_field_manager::get_form_field_from_label($locator, $this);
359             $field->set_value($value);
360         }
361     }
363     /**
364      * Get the options specific to this step of the backup/restore process.
365      *
366      * @param TableNode $options The options table to filter
367      * @param string $step The name of the step
368      * @return TableNode The filtered options table
369      * @throws ExpectationException
370      */
371     protected function get_step_options($options, $step) {
372         // Nothing to fill if no options are provided.
373         if (!$options) {
374             return;
375         }
377         $pageoptions = clone $options;
379         $rows = $options->getRows();
380         $newrows = array();
381         foreach ($rows as $k => $data) {
382             if (count($data) !== 3) {
383                 // Not enough information to guess the page.
384                 throw new ExpectationException("The backup/restore step must be specified for all backup options");
385             } else if ($data[0] == $step) {
386                 unset($data[0]);
387                 $newrows[] = $data;
388             }
389         }
390         $pageoptions->setRows($newrows);
391         return $pageoptions;
392     }
395     /**
396      * Waits until the DOM and the page Javascript code is ready.
397      *
398      * @param int $timeout The number of seconds that we wait.
399      * @return void
400      */
401     protected function wait($timeout = false) {
403         if (!$this->running_javascript()) {
404             return;
405         }
407         if (!$timeout) {
408             $timeout = self::TIMEOUT;
409         }
411         $this->getSession()->wait($timeout * 1000, self::PAGE_READY_JS);
412     }