2 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
18 * Backup and restore actions to help behat feature files writting.
20 * @package core_backup
22 * @copyright 2013 David Monllaó
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
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;
37 * Backup-related steps definitions.
39 * @package core_backup
41 * @copyright 2013 David Monllaó
42 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
44 class behat_backup extends behat_base {
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}.
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'));
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.
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
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.
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');
81 $this->fill_backup_restore_form($this->get_step_options($options, "Initial"));
82 $this->find_button(get_string('backupstage1action', 'backup'))->press();
86 $this->fill_backup_restore_form($this->get_step_options($options, "Schema"));
87 $this->find_button(get_string('backupstage2action', 'backup'))->press();
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();
102 * Imports the specified origin course into the other course using the provided options.
104 * Keeping it separatelly from backup & restore, it the number of
105 * steps and duplicate code becomes bigger a common method should
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
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.
119 $this->getSession()->visit($this->locate_path('/'));
122 // Click the course link.
123 $this->find_link($tocourse)->click();
126 // Click the import link.
127 $this->navigate_to_course_settings_link('import');
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);
142 $this->find_button(get_string('continue'))->press();
146 $this->fill_backup_restore_form($this->get_step_options($options, "Initial"));
147 $this->find_button(get_string('importbackupstage1action', 'backup'))->press();
151 $this->fill_backup_restore_form($this->get_step_options($options, "Schema"));
152 $this->find_button(get_string('importbackupstage2action', 'backup'))->press();
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();
164 * Restores the backup into the specified course and the provided options. You should be in the 'Restore' page where the backup is.
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
171 public function i_restore_backup_into_course_using_this_options($backupfilename, $existingcourse, $options = false) {
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']");
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();
193 // Common restore process using provided key/value options.
194 $this->process_restore($options);
198 * Restores the specified backup into a new course using the provided options. You should be in the 'Restore' page where the backup is.
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
204 public function i_restore_backup_into_a_new_course_using_this_options($backupfilename, $options = false) {
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']");
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();
222 // Common restore process using provided key/value options.
223 $this->process_restore($options);
227 * Merges the backup into the current course using the provided restore options. You should be in the 'Restore' page where the backup is.
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
233 public function i_merge_backup_into_the_current_course($backupfilename, $options = false) {
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']");
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();
250 // Common restore process using provided key/value options.
251 $this->process_restore($options);
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.
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
261 public function i_merge_backup_into_current_course_deleting_its_contents($backupfilename, $options = false) {
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']");
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();
278 // Common restore process using provided key/value options.
279 $this->process_restore($options);
283 * Selects the backup to restore.
285 * @throws ExpectationException
286 * @param string $backupfilename
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();
306 * Executes the common steps of all restore processes.
308 * @param TableNode $options The backup and restore options or false if no options provided
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.
317 $this->fill_backup_restore_form($this->get_step_options($options, "Settings"));
318 $this->find_button(get_string('restorestage4action', 'backup'))->press();
322 $this->fill_backup_restore_form($this->get_step_options($options, "Schema"));
323 $this->find_button(get_string('restorestage8action', 'backup'))->press();
326 // Review, no options here.
327 $this->find_button(get_string('restorestage16action', 'backup'))->press();
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);
338 * Tries to fill the current page form elements with the provided options.
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.
345 * @param TableNode $options The backup and restore options or false if no options provided
348 protected function fill_backup_restore_form($options) {
350 // Nothing to fill if no options are provided.
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);
364 * Get the options specific to this step of the backup/restore process.
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
371 protected function get_step_options($options, $step) {
372 // Nothing to fill if no options are provided.
377 $pageoptions = clone $options;
379 $rows = $options->getRows();
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) {
390 $pageoptions->setRows($newrows);
396 * Waits until the DOM and the page Javascript code is ready.
398 * @param int $timeout The number of seconds that we wait.
401 protected function wait($timeout = false) {
403 if (!$this->running_javascript()) {
408 $timeout = self::TIMEOUT;
411 $this->getSession()->wait($timeout * 1000, self::PAGE_READY_JS);