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