MDL-58428 theme: Shift templates ready for Bootstrapbase removal
[moodle.git] / lib / tests / behat / behat_navigation.php
CommitLineData
b1bc8cfc 1<?php
b1bc8cfc
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 * Navigation steps definitions.
19 *
20 * @package core
21 * @category test
22 * @copyright 2012 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__ . '/../../behat/behat_base.php');
29
7b05a0fe
DW
30use Behat\Mink\Exception\ExpectationException as ExpectationException;
31use Behat\Mink\Exception\DriverException as DriverException;
e3652936 32use Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
c4509f80 33
b1bc8cfc
DM
34/**
35 * Steps definitions to navigate through the navigation tree nodes.
36 *
37 * @package core
38 * @category test
39 * @copyright 2012 David MonllaĆ³
40 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
41 */
42class behat_navigation extends behat_base {
43
817d39b8
SH
44 /**
45 * Helper function to get a navigation nodes text element given its text from within the navigation block.
46 *
47 * This function finds the node with the given text from within the navigation block.
48 * It checks to make sure the node is visible, and then returns it.
49 *
50 * @param string $text
51 * @param bool $branch Set this true if you're only interested in the node if its a branch.
52 * @param null|bool $collapsed Set this to true or false if you want the node to either be collapsed or not.
53 * If its left as null then we don't worry about it.
54 * @param null|string|Exception|false $exception The exception to throw if the node is not found.
55 * @return \Behat\Mink\Element\NodeElement
56 */
57 protected function get_node_text_node($text, $branch = false, $collapsed = null, $exception = null) {
58 if ($exception === null) {
59 $exception = new ExpectationException('The "' . $text . '" node could not be found', $this->getSession());
60 } else if (is_string($exception)) {
61 $exception = new ExpectationException($exception, $this->getSession());
62 }
63
921faad9 64 $nodetextliteral = behat_context_helper::escape($text);
817d39b8
SH
65 $hasblocktree = "[contains(concat(' ', normalize-space(@class), ' '), ' block_tree ')]";
66 $hasbranch = "[contains(concat(' ', normalize-space(@class), ' '), ' branch ')]";
f65ce33f
CB
67 $hascollapsed = "p[@aria-expanded='false']";
68 $notcollapsed = "p[@aria-expanded='true']";
817d39b8
SH
69 $match = "[normalize-space(.)={$nodetextliteral}]";
70
71 // Avoid problems with quotes.
72 $isbranch = ($branch) ? $hasbranch : '';
73 if ($collapsed === true) {
74 $iscollapsed = $hascollapsed;
75 } else if ($collapsed === false) {
76 $iscollapsed = $notcollapsed;
77 } else {
f65ce33f 78 $iscollapsed = 'p';
817d39b8
SH
79 }
80
10ac8baf 81 // First check root nodes, it can be a span or link.
f65ce33f
CB
82 $xpath = "//ul{$hasblocktree}/li/{$hascollapsed}{$isbranch}/span{$match}|";
83 $xpath .= "//ul{$hasblocktree}/li/{$hascollapsed}{$isbranch}/a{$match}|";
10ac8baf 84
487e68b4 85 // Next search for the node containing the text within a link.
f65ce33f 86 $xpath .= "//ul{$hasblocktree}//ul/li/{$iscollapsed}{$isbranch}/a{$match}|";
10ac8baf 87
487e68b4 88 // Finally search for the node containing the text within a span.
f65ce33f 89 $xpath .= "//ul{$hasblocktree}//ul/li/{$iscollapsed}{$isbranch}/span{$match}";
817d39b8
SH
90
91 $node = $this->find('xpath', $xpath, $exception);
92 $this->ensure_node_is_visible($node);
93 return $node;
94 }
95
96 /**
97 * Returns true if the navigation node with the given text is expandable.
98 *
99 * @Given /^navigation node "([^"]*)" should be expandable$/
100 *
101 * @throws ExpectationException
102 * @param string $nodetext
103 * @return bool
104 */
105 public function navigation_node_should_be_expandable($nodetext) {
106 if (!$this->running_javascript()) {
107 // Nodes are only expandable when JavaScript is enabled.
108 return false;
109 }
110
111 $node = $this->get_node_text_node($nodetext, true);
112 $node = $node->getParent();
10ac8baf
RT
113 if ($node->hasClass('emptybranch')) {
114 throw new ExpectationException('The "' . $nodetext . '" node is not expandable', $this->getSession());
817d39b8 115 }
10ac8baf
RT
116
117 return true;
817d39b8
SH
118 }
119
120 /**
121 * Returns true if the navigation node with the given text is not expandable.
122 *
123 * @Given /^navigation node "([^"]*)" should not be expandable$/
124 *
125 * @throws ExpectationException
126 * @param string $nodetext
127 * @return bool
128 */
129 public function navigation_node_should_not_be_expandable($nodetext) {
130 if (!$this->running_javascript()) {
131 // Nodes are only expandable when JavaScript is enabled.
132 return false;
133 }
134
135 $node = $this->get_node_text_node($nodetext);
136 $node = $node->getParent();
10ac8baf
RT
137
138 if ($node->hasClass('emptybranch') || $node->hasClass('tree_item')) {
139 return true;
817d39b8 140 }
10ac8baf 141 throw new ExpectationException('The "' . $nodetext . '" node is expandable', $this->getSession());
817d39b8
SH
142 }
143
7b05a0fe
DW
144 /**
145 * Click on an entry in the user menu.
146 * @Given /^I follow "(?P<nodetext_string>(?:[^"]|\\")*)" in the user menu$/
147 *
7b05a0fe 148 * @param string $nodetext
7b05a0fe
DW
149 */
150 public function i_follow_in_the_user_menu($nodetext) {
151
317fd6fe
AN
152 if ($this->running_javascript()) {
153 // The user menu must be expanded when JS is enabled.
e3652936 154 $xpath = "//div[contains(concat(' ', @class, ' '), ' usermenu ')]//a[contains(concat(' ', @class, ' '), ' dropdown-toggle ')]";
eb9ca848 155 $this->execute("behat_general::i_click_on", array($this->escape($xpath), "xpath_element"));
7b05a0fe
DW
156 }
157
317fd6fe
AN
158 // Now select the link.
159 // The CSS path is always present, with or without JS.
e3652936 160 $csspath = ".usermenu .dropdown-menu";
7b05a0fe 161
eb9ca848 162 $this->execute('behat_general::i_click_on_in_the',
e3652936 163 array($nodetext, "link", $csspath, "css_element")
eb9ca848 164 );
7b05a0fe
DW
165 }
166
b1bc8cfc
DM
167 /**
168 * Expands the selected node of the navigation tree that matches the text.
b1bc8cfc 169 * @Given /^I expand "(?P<nodetext_string>(?:[^"]|\\")*)" node$/
1f9ffbdb 170 *
c4509f80 171 * @throws ExpectationException
b1bc8cfc 172 * @param string $nodetext
817d39b8 173 * @return bool|void
b1bc8cfc
DM
174 */
175 public function i_expand_node($nodetext) {
176
e6d18d9d
DM
177 // This step is useless with Javascript disabled as Moodle auto expands
178 // all of tree's nodes; adding this because of scenarios that shares the
179 // same steps with and without Javascript enabled.
180 if (!$this->running_javascript()) {
6dfd8325
MG
181 if ($nodetext === get_string('administrationsite')) {
182 // Administration menu is not loaded by default any more. Click the link to expand.
eb9ca848
RT
183 $this->execute('behat_general::i_click_on_in_the',
184 array($nodetext, "link", get_string('administration'), "block")
185 );
186 return true;
6dfd8325 187 }
fb99ef1d 188 return true;
e6d18d9d
DM
189 }
190
817d39b8
SH
191 $node = $this->get_node_text_node($nodetext, true, true, 'The "' . $nodetext . '" node can not be expanded');
192 // Check if the node is a link AND a branch.
193 if (strtolower($node->getTagName()) === 'a') {
194 // We just want to expand the node, we don't want to follow it.
195 $node = $node->getParent();
196 }
b1bc8cfc
DM
197 $node->click();
198 }
199
c4509f80
DM
200 /**
201 * Collapses the selected node of the navigation tree that matches the text.
202 *
203 * @Given /^I collapse "(?P<nodetext_string>(?:[^"]|\\")*)" node$/
204 * @throws ExpectationException
205 * @param string $nodetext
817d39b8 206 * @return bool|void
c4509f80
DM
207 */
208 public function i_collapse_node($nodetext) {
209
210 // No collapsible nodes with non-JS browsers.
211 if (!$this->running_javascript()) {
fb99ef1d 212 return true;
c4509f80
DM
213 }
214
817d39b8
SH
215 $node = $this->get_node_text_node($nodetext, true, false, 'The "' . $nodetext . '" node can not be collapsed');
216 // Check if the node is a link AND a branch.
217 if (strtolower($node->getTagName()) === 'a') {
218 // We just want to expand the node, we don't want to follow it.
219 $node = $node->getParent();
220 }
c4509f80
DM
221 $node->click();
222 }
26fb3f5b 223
ebcff7e2 224 /**
c0ad000f 225 * Finds a node in the Navigation or Administration tree
ebcff7e2
MG
226 *
227 * @param string $nodetext
228 * @param array $parentnodes
c0ad000f
MG
229 * @param string $nodetype node type (link or text)
230 * @return NodeElement|null
231 * @throws ExpectationException when one of the parent nodes is not found
ebcff7e2 232 */
c0ad000f 233 protected function find_node_in_navigation($nodetext, $parentnodes, $nodetype = 'link') {
a542553d
DM
234 // Site admin is different and needs special treatment.
235 $siteadminstr = get_string('administrationsite');
236
26fb3f5b 237 // Create array of all parentnodes.
26fb3f5b
RT
238 $countparentnode = count($parentnodes);
239
a542553d
DM
240 // If JS is disabled and Site administration is not expanded we
241 // should follow it, so all the lower-level nodes are available.
242 if (!$this->running_javascript()) {
243 if ($parentnodes[0] === $siteadminstr) {
244 // We don't know if there if Site admin is already expanded so
245 // don't wait, it is non-JS and we already waited for the DOM.
42ad096f
RT
246 $siteadminlink = $this->getSession()->getPage()->find('named_exact', array('link', "'" . $siteadminstr . "'"));
247 if ($siteadminlink) {
a542553d
DM
248 $siteadminlink->click();
249 }
250 }
251 }
252
180fdb2f 253 // Get top level node.
26fb3f5b
RT
254 $node = $this->get_top_navigation_node($parentnodes[0]);
255
180fdb2f
T
256 // Expand all nodes.
257 for ($i = 0; $i < $countparentnode; $i++) {
258 if ($i > 0) {
259 // Sub nodes within top level node.
260 $node = $this->get_navigation_node($parentnodes[$i], $node);
261 }
262
f65ce33f
CB
263 // The p node contains the aria jazz.
264 $pnodexpath = "/p[contains(concat(' ', normalize-space(@class), ' '), ' tree_item ')]";
265 $pnode = $node->find('xpath', $pnodexpath);
10ac8baf 266
f65ce33f
CB
267 // Keep expanding all sub-parents if js enabled.
268 if ($pnode && $this->running_javascript() && $pnode->hasAttribute('aria-expanded') &&
269 ($pnode->getAttribute('aria-expanded') == "false")) {
370e88d4 270
01f2a530 271 $this->js_trigger_click($pnode);
a542553d 272
180fdb2f 273 // Wait for node to load, if not loaded before.
f65ce33f
CB
274 if ($pnode->hasAttribute('data-loaded') && $pnode->getAttribute('data-loaded') == "false") {
275 $jscondition = '(document.evaluate("' . $pnode->getXpath() . '", document, null, '.
10ac8baf 276 'XPathResult.ANY_TYPE, null).iterateNext().getAttribute(\'data-loaded\') == "true")';
26fb3f5b 277
180fdb2f 278 $this->getSession()->wait(self::EXTENDED_TIMEOUT * 1000, $jscondition);
26fb3f5b
RT
279 }
280 }
281 }
282
283 // Finally, click on requested node under navigation.
921faad9 284 $nodetextliteral = behat_context_helper::escape($nodetext);
c0ad000f 285 $tagname = ($nodetype === 'link') ? 'a' : 'span';
26fb3f5b 286 $xpath = "/ul/li/p[contains(concat(' ', normalize-space(@class), ' '), ' tree_item ')]" .
c0ad000f
MG
287 "/{$tagname}[normalize-space(.)=" . $nodetextliteral . "]";
288 return $node->find('xpath', $xpath);
289 }
26fb3f5b 290
c0ad000f
MG
291 /**
292 * Finds a node in the Navigation or Administration tree and clicks on it.
293 *
294 * @param string $nodetext
295 * @param array $parentnodes
296 * @throws ExpectationException
297 */
298 protected function select_node_in_navigation($nodetext, $parentnodes) {
299 $nodetoclick = $this->find_node_in_navigation($nodetext, $parentnodes);
370e88d4 300 // Throw exception if no node found.
180fdb2f 301 if (!$nodetoclick) {
a542553d 302 throw new ExpectationException('Navigation node "' . $nodetext . '" not found under "' .
ab7fa556 303 implode($parentnodes, ' > ') . '"', $this->getSession());
26fb3f5b 304 }
180fdb2f 305 $nodetoclick->click();
26fb3f5b 306 }
a542553d
DM
307
308 /**
309 * Helper function to get top navigation node in tree.
310 *
311 * @throws ExpectationException if note not found.
312 * @param string $nodetext name of top navigation node in tree.
313 * @return NodeElement
314 */
315 protected function get_top_navigation_node($nodetext) {
316
317 // Avoid problems with quotes.
921faad9 318 $nodetextliteral = behat_context_helper::escape($nodetext);
a542553d
DM
319 $exception = new ExpectationException('Top navigation node "' . $nodetext . ' not found in "', $this->getSession());
320
321 // First find in navigation block.
e3652936
MM
322 $xpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' card-text ')]" .
323 "/ul[contains(concat(' ', normalize-space(@class), ' '), ' block_tree ')]" .
324 "/li[contains(concat(' ', normalize-space(@class), ' '), ' contains_branch ')]" .
325 "/ul/li[contains(concat(' ', normalize-space(@class), ' '), ' contains_branch ')]" .
326 "[p[contains(concat(' ', normalize-space(@class), ' '), ' branch ')]" .
327 "/*[contains(normalize-space(.), " . $nodetextliteral .")]]" .
328 "|" .
329 "//div[contains(concat(' ', normalize-space(@class), ' '), ' card-text ')]/div" .
330 "/ul[contains(concat(' ', normalize-space(@class), ' '), ' block_tree ')]" .
331 "/li[p[contains(concat(' ', normalize-space(@class), ' '), ' branch ')]" .
332 "/*[contains(normalize-space(.), " . $nodetextliteral .")]]";
10ac8baf
RT
333
334 $node = $this->find('xpath', $xpath, $exception);
a542553d
DM
335
336 return $node;
337 }
338
339 /**
340 * Helper function to get sub-navigation node.
341 *
342 * @throws ExpectationException if note not found.
343 * @param string $nodetext node to find.
344 * @param NodeElement $parentnode parent navigation node.
345 * @return NodeElement.
346 */
347 protected function get_navigation_node($nodetext, $parentnode = null) {
348
349 // Avoid problems with quotes.
921faad9 350 $nodetextliteral = behat_context_helper::escape($nodetext);
a542553d
DM
351
352 $xpath = "/ul/li[contains(concat(' ', normalize-space(@class), ' '), ' contains_branch ')]" .
353 "[child::p[contains(concat(' ', normalize-space(@class), ' '), ' branch ')]" .
354 "/child::span[normalize-space(.)=" . $nodetextliteral ."]]";
355 $node = $parentnode->find('xpath', $xpath);
356 if (!$node) {
357 $xpath = "/ul/li[contains(concat(' ', normalize-space(@class), ' '), ' contains_branch ')]" .
358 "[child::p[contains(concat(' ', normalize-space(@class), ' '), ' branch ')]" .
359 "/child::a[normalize-space(.)=" . $nodetextliteral ."]]";
360 $node = $parentnode->find('xpath', $xpath);
361 }
362
363 if (!$node) {
364 throw new ExpectationException('Sub-navigation node "' . $nodetext . '" not found under "' .
365 $parentnode->getText() . '"', $this->getSession());
366 }
367 return $node;
368 }
86055d11
RT
369
370 /**
371 * Step to open the navigation bar if it is needed.
372 *
373 * The top log in and log out links are hidden when middle or small
374 * size windows (or devices) are used. This step returns a step definition
375 * clicking to expand the navbar if it is hidden.
376 *
377 * @Given /^I expand navigation bar$/
378 */
379 public function get_expand_navbar_step() {
380
381 // Checking if we need to click the navbar button to show the navigation menu, it
382 // is hidden by default when using clean theme and a medium or small screen size.
383
384 // The DOM and the JS should be all ready and loaded. Running without spinning
385 // as this is a widely used step and we can not spend time here trying to see
386 // a DOM node that is not always there (at the moment clean is not even the
387 // default theme...).
388 $navbuttonjs = "return (
389 Y.one('.btn-navbar') &&
390 Y.one('.btn-navbar').getComputedStyle('display') !== 'none'
391 )";
392
393 // Adding an extra click we need to show the 'Log in' link.
394 if (!$this->getSession()->getDriver()->evaluateScript($navbuttonjs)) {
395 return false;
396 }
397
eb9ca848 398 $this->execute('behat_general::i_click_on', array(".btn-navbar", "css_element"));
86055d11 399 }
ebcff7e2
MG
400
401 /**
402 * Go to current page setting item
403 *
404 * This can be used on front page, course, category or modules pages.
405 *
406 * @Given /^I navigate to "(?P<nodetext_string>(?:[^"]|\\")*)" in current page administration$/
407 *
408 * @throws ExpectationException
409 * @param string $nodetext navigation node to click, may contain path, for example "Reports > Overview"
410 * @return void
411 */
412 public function i_navigate_to_in_current_page_administration($nodetext) {
e3652936
MM
413 $nodelist = array_map('trim', explode('>', $nodetext));
414 $this->select_from_administration_menu($nodelist);
ebcff7e2
MG
415 }
416
417 /**
418 * Checks that current page administration contains text
419 *
420 * @Given /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should exist in current page administration$/
421 *
422 * @throws ExpectationException
423 * @param string $element The locator of the specified selector.
424 * This may be a path, for example "Subscription mode > Forced subscription"
c0ad000f 425 * @param string $selectortype The selector type (link or text)
ebcff7e2
MG
426 * @return void
427 */
428 public function should_exist_in_current_page_administration($element, $selectortype) {
e3652936
MM
429 $nodes = array_map('trim', explode('>', $element));
430 $nodetext = end($nodes);
ebcff7e2 431
e3652936
MM
432 // Find administration menu.
433 $menuxpath = $this->find_header_administration_menu() ?: $this->find_page_administration_menu(true);
434
435 $this->toggle_page_administration_menu($menuxpath);
436 $this->execute('behat_general::should_exist_in_the', [$nodetext, $selectortype, $menuxpath, 'xpath_element']);
437 $this->toggle_page_administration_menu($menuxpath);
ebcff7e2
MG
438 }
439
440 /**
441 * Checks that current page administration contains text
442 *
443 * @Given /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should not exist in current page administration$/
444 *
445 * @throws ExpectationException
446 * @param string $element The locator of the specified selector.
447 * This may be a path, for example "Subscription mode > Forced subscription"
c0ad000f 448 * @param string $selectortype The selector type (link or text)
ebcff7e2
MG
449 * @return void
450 */
451 public function should_not_exist_in_current_page_administration($element, $selectortype) {
e3652936
MM
452 $nodes = array_map('trim', explode('>', $element));
453 $nodetext = end($nodes);
454
455 // Find administration menu.
456 $menuxpath = $this->find_header_administration_menu() ?: $this->find_page_administration_menu();
457 if (!$menuxpath) {
458 // Menu not found, exit.
459 return;
ebcff7e2 460 }
e3652936
MM
461
462 $this->toggle_page_administration_menu($menuxpath);
463 $this->execute('behat_general::should_not_exist_in_the', [$nodetext, $selectortype, $menuxpath, 'xpath_element']);
464 $this->toggle_page_administration_menu($menuxpath);
ebcff7e2
MG
465 }
466
467 /**
468 * Go to site administration item
469 *
470 * @Given /^I navigate to "(?P<nodetext_string>(?:[^"]|\\")*)" in site administration$/
471 *
472 * @throws ExpectationException
473 * @param string $nodetext navigation node to click, may contain path, for example "Reports > Overview"
474 * @return void
475 */
476 public function i_navigate_to_in_site_administration($nodetext) {
e3652936
MM
477 $nodelist = array_map('trim', explode('>', $nodetext));
478 $this->i_select_from_flat_navigation_drawer(get_string('administrationsite'));
479 $this->select_on_administration_page($nodelist);
ebcff7e2 480 }
353e7876 481
482 /**
483 * Opens the current users profile page in edit mode.
484 *
485 * @Given /^I open my profile in edit mode$/
486 * @throws coding_exception
487 * @return void
488 */
489 public function i_open_my_profile_in_edit_mode() {
490 global $USER;
491
492 $user = $this->get_session_user();
493 $globuser = $USER;
494 $USER = $user; // We need this set to the behat session user so we can call isloggedin.
495
496 $systemcontext = context_system::instance();
497
498 $bodynode = $this->find('xpath', 'body');
499 $bodyclass = $bodynode->getAttribute('class');
500 $matches = [];
770f0532 501 if (preg_match('/(?<=^course-|\scourse-)\d+/', $bodyclass, $matches) && !empty($matches)) {
353e7876 502 $courseid = intval($matches[0]);
503 } else {
504 $courseid = SITEID;
505 }
506
507 if (isloggedin() && !isguestuser($user) && !is_mnet_remote_user($user)) {
508 if (is_siteadmin($user) || has_capability('moodle/user:update', $systemcontext)) {
509 $url = new moodle_url('/user/editadvanced.php', array('id' => $user->id, 'course' => SITEID,
510 'returnto' => 'profile'));
511 } else if (has_capability('moodle/user:editownprofile', $systemcontext)) {
512 $userauthplugin = false;
513 if (!empty($user->auth)) {
514 $userauthplugin = get_auth_plugin($user->auth);
515 }
516 if ($userauthplugin && $userauthplugin->can_edit_profile()) {
517 $url = $userauthplugin->edit_profile_url();
518 if (empty($url)) {
519 if (empty($course)) {
520 $url = new moodle_url('/user/edit.php', array('id' => $user->id, 'returnto' => 'profile'));
521 } else {
522 $url = new moodle_url('/user/edit.php', array('id' => $user->id, 'course' => $courseid,
523 'returnto' => 'profile'));
524 }
525 }
526
527 }
528 }
529 $this->getSession()->visit($this->locate_path($url->out_as_local_url()));
530 }
531
532 // Restore global user variable.
533 $USER = $globuser;
534 }
535
95f11fde 536 /**
3353cfd8 537 * Opens the course homepage.
95f11fde 538 *
3353cfd8 539 * @Given /^I am on "(?P<coursefullname_string>(?:[^"]|\\")*)" course homepage$/
95f11fde 540 * @throws coding_exception
966cbed6 541 * @param string $coursefullname The full name of the course.
95f11fde
SL
542 * @return void
543 */
3353cfd8 544 public function i_am_on_course_homepage($coursefullname) {
95f11fde 545 global $DB;
95f11fde 546 $course = $DB->get_record("course", array("fullname" => $coursefullname), 'id', MUST_EXIST);
3353cfd8
SL
547 $url = new moodle_url('/course/view.php', ['id' => $course->id]);
548 $this->getSession()->visit($this->locate_path($url->out_as_local_url(false)));
549 }
95f11fde 550
3353cfd8
SL
551 /**
552 * Opens the course homepage with editing mode on.
553 *
554 * @Given /^I am on "(?P<coursefullname_string>(?:[^"]|\\")*)" course homepage with editing mode on$/
555 * @throws coding_exception
966cbed6 556 * @param string $coursefullname The course full name of the course.
3353cfd8
SL
557 * @return void
558 */
559 public function i_am_on_course_homepage_with_editing_mode_on($coursefullname) {
560 global $DB;
561 $course = $DB->get_record("course", array("fullname" => $coursefullname), 'id', MUST_EXIST);
95f11fde 562 $url = new moodle_url('/course/view.php', ['id' => $course->id]);
3353cfd8
SL
563 $this->getSession()->visit($this->locate_path($url->out_as_local_url(false)));
564 try {
565 $this->execute("behat_forms::press_button", get_string('turneditingon'));
566 } catch (Exception $e) {
567 $this->execute("behat_navigation::i_navigate_to_in_current_page_administration", [get_string('turneditingon')]);
568 }
95f11fde 569 }
e3652936
MM
570
571 /**
572 * Opens the flat navigation drawer if it is not already open
573 *
574 * @When /^I open flat navigation drawer$/
575 * @throws ElementNotFoundException Thrown by behat_base::find
576 */
577 public function i_open_flat_navigation_drawer() {
578 if (!$this->running_javascript()) {
579 // Navigation drawer is always open without JS.
580 return;
581 }
582 $xpath = "//button[contains(@data-action,'toggle-drawer')]";
583 $node = $this->find('xpath', $xpath);
584 $expanded = $node->getAttribute('aria-expanded');
585 if ($expanded === 'false') {
586 $node->click();
587 $this->ensure_node_attribute_is_set($node, 'aria-expanded', 'true');
588 $this->wait_for_pending_js();
589 }
590 }
591
592 /**
593 * Closes the flat navigation drawer if it is open (does nothing if JS disabled)
594 *
595 * @When /^I close flat navigation drawer$/
596 * @throws ElementNotFoundException Thrown by behat_base::find
597 */
598 public function i_close_flat_navigation_drawer() {
599 if (!$this->running_javascript()) {
600 // Navigation drawer can not be closed without JS.
601 return;
602 }
603 $xpath = "//button[contains(@data-action,'toggle-drawer')]";
604 $node = $this->find('xpath', $xpath);
605 $expanded = $node->getAttribute('aria-expanded');
606 if ($expanded === 'true') {
607 $node->click();
608 $this->wait_for_pending_js();
609 }
610 }
611
612 /**
613 * Clicks link with specified id|title|alt|text in the flat navigation drawer.
614 *
615 * @When /^I select "(?P<link_string>(?:[^"]|\\")*)" from flat navigation drawer$/
616 * @throws ElementNotFoundException Thrown by behat_base::find
617 * @param string $link
618 */
619 public function i_select_from_flat_navigation_drawer($link) {
620 $this->i_open_flat_navigation_drawer();
621 $this->execute('behat_general::i_click_on_in_the', [$link, 'link', '#nav-drawer', 'css_element']);
622 }
623
624 /**
625 * If we are not on the course main page, click on the course link in the navbar
626 */
627 protected function go_to_main_course_page() {
628 $url = $this->getSession()->getCurrentUrl();
629 if (!preg_match('|/course/view.php\?id=[\d]+$|', $url)) {
630 $this->find('xpath', '//header//div[@id=\'page-navbar\']//a[contains(@href,\'/course/view.php?id=\')]')->click();
631 $this->execute('behat_general::wait_until_the_page_is_ready');
632 }
633 }
634
635 /**
636 * Finds and clicks a link on the admin page (site administration or course administration)
637 *
638 * @param array $nodelist
639 */
640 protected function select_on_administration_page($nodelist) {
641 $parentnodes = $nodelist;
642 $lastnode = array_pop($parentnodes);
643 $xpath = '//section[@id=\'region-main\']';
644
645 // Check if there is a separate tab for this submenu of the page. If found go to it.
646 if ($parentnodes) {
647 $tabname = behat_context_helper::escape($parentnodes[0]);
648 $tabxpath = '//ul[@role=\'tablist\']/li/a[contains(normalize-space(.), ' . $tabname . ')]';
649 if ($node = $this->getSession()->getPage()->find('xpath', $tabxpath)) {
650 if ($this->running_javascript()) {
651 // Click on the tab and add 'active' tab to the xpath.
652 $node->click();
653 $xpath .= '//div[contains(@class,\'active\')]';
654 } else {
655 // Add the tab content selector to the xpath.
656 $tabid = behat_context_helper::escape(ltrim($node->getAttribute('href'), '#'));
657 $xpath .= '//div[@id = ' . $tabid . ']';
658 }
659 array_shift($parentnodes);
660 }
661 }
662
663 // Find a section with the parent name in it.
664 if ($parentnodes) {
665 // Find the section on the page (links may be repeating in different sections).
666 $section = behat_context_helper::escape($parentnodes[0]);
667 $xpath .= '//div[@class=\'row\' and contains(.,'.$section.')]';
668 }
669
670 // Find a link and click on it.
671 $linkname = behat_context_helper::escape($lastnode);
672 $xpath .= '//a[contains(normalize-space(.), ' . $linkname . ')]';
673 if (!$node = $this->getSession()->getPage()->find('xpath', $xpath)) {
674 throw new ElementNotFoundException($this->getSession(), 'Link "' . join(' > ', $nodelist) . '"" not found on the page');
675 }
676 $node->click();
677 $this->wait_for_pending_js();
678 }
679
680 /**
681 * Locates the administration menu in the <header> element and returns its xpath
682 *
683 * @param bool $mustexist if specified throws an exception if menu is not found
684 * @return null|string
685 */
686 protected function find_header_administration_menu($mustexist = false) {
687 $menuxpath = '//header[@id=\'page-header\']//div[contains(@class,\'moodle-actionmenu\')]';
688 if ($mustexist) {
689 $exception = new ElementNotFoundException($this->getSession(), 'Page header administration menu is not found');
690 $this->find('xpath', $menuxpath, $exception);
691 } else if (!$this->getSession()->getPage()->find('xpath', $menuxpath)) {
692 return null;
693 }
694 return $menuxpath;
695 }
696
697 /**
698 * Locates the administration menu on the page (but not in the header) and returns its xpath
699 *
700 * @param bool $mustexist if specified throws an exception if menu is not found
701 * @return null|string
702 */
703 protected function find_page_administration_menu($mustexist = false) {
704 $menuxpath = '//div[@id=\'region-main-settings-menu\']';
705 if ($mustexist) {
706 $exception = new ElementNotFoundException($this->getSession(), 'Page administration menu is not found');
707 $this->find('xpath', $menuxpath, $exception);
708 } else if (!$this->getSession()->getPage()->find('xpath', $menuxpath)) {
709 return null;
710 }
711 return $menuxpath;
712 }
713
714 /**
715 * Toggles administration menu
716 *
717 * @param string $menuxpath (optional) xpath to the page administration menu if already known
718 */
719 protected function toggle_page_administration_menu($menuxpath = null) {
720 if (!$menuxpath) {
721 $menuxpath = $this->find_header_administration_menu() ?: $this->find_page_administration_menu();
722 }
723 if ($menuxpath && $this->running_javascript()) {
724 $this->find('xpath', $menuxpath . '//a[@data-toggle=\'dropdown\']')->click();
725 $this->wait_for_pending_js();
726 }
727 }
728
729 /**
730 * Finds a page edit cog and select an item from it
731 *
732 * If the page edit cog is in the page header and the item is not found there, click "More..." link
733 * and find the item on the course/frontpage administration page
734 *
735 * @param array $nodelist
736 * @throws ElementNotFoundException
737 */
738 protected function select_from_administration_menu($nodelist) {
739 // Find administration menu.
740 if ($menuxpath = $this->find_header_administration_menu()) {
741 $isheader = true;
742 } else {
743 $menuxpath = $this->find_page_administration_menu(true);
744 $isheader = false;
745 }
746
747 $this->toggle_page_administration_menu($menuxpath);
748
749 if (!$isheader || count($nodelist) == 1) {
750 $lastnode = end($nodelist);
751 $linkname = behat_context_helper::escape($lastnode);
752 $link = $this->getSession()->getPage()->find('xpath', $menuxpath . '//a[contains(normalize-space(.), ' . $linkname . ')]');
753 if ($link) {
754 $link->click();
755 $this->wait_for_pending_js();
756 return;
757 }
758 }
759
760 if ($isheader) {
761 // Course administration and Front page administration will have subnodes under "More...".
762 $linkname = behat_context_helper::escape(get_string('morenavigationlinks'));
763 $link = $this->getSession()->getPage()->find('xpath', $menuxpath . '//a[contains(normalize-space(.), ' . $linkname . ')]');
764 if ($link) {
765 $link->click();
766 $this->execute('behat_general::wait_until_the_page_is_ready');
767 $this->select_on_administration_page($nodelist);
768 return;
769 }
770 }
771
772 throw new ElementNotFoundException($this->getSession(),
773 'Link "' . join(' > ', $nodelist) . '" not found in the current page edit menu"');
774 }
b1bc8cfc 775}