See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401]
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 * Navigation step definition overrides for the Classic theme. 19 * 20 * @package theme_classic 21 * @category test 22 * @copyright 2019 Michael Hawkins 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 28 require_once (__DIR__ . '/../../../../lib/tests/behat/behat_navigation.php'); 29 30 use Behat\Mink\Exception\ExpectationException as ExpectationException; 31 use Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException; 32 33 /** 34 * Step definitions and overrides to navigate through the navigation tree nodes in the Classic theme. 35 * 36 * @package theme_classic 37 * @category test 38 * @copyright 2019 Michael Hawkins 39 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 40 */ 41 class behat_theme_classic_behat_navigation extends behat_navigation { 42 /** 43 * Navigate to an item in a current page administration menu. 44 * 45 * @throws ExpectationException 46 * @param string $nodetext The navigation node/path to follow, eg "Course administration > Edit settings" 47 * @return void 48 */ 49 public function i_navigate_to_in_current_page_administration($nodetext) { 50 $parentnodes = array_map('trim', explode('>', $nodetext)); 51 52 // Find the name of the first category of the administration block tree. 53 $xpath = "//section[contains(@class,'block_settings')]//div[@id='settingsnav']/ul[1]/li[1]/p[1]/span"; 54 $node = $this->find('xpath', $xpath); 55 56 array_unshift($parentnodes, $node->getText()); 57 $lastnode = array_pop($parentnodes); 58 try { 59 $this->select_node_in_navigation($lastnode, $parentnodes); 60 } catch (Exception $e) { 61 try { 62 $this->execute("behat_general::click_link", $lastnode); 63 } catch (Exception $e) { 64 // We must be in a weird state i.e. Add competencies to course. 65 $this->execute("behat_general::click_link", array_pop($parentnodes)); 66 $this->execute('behat_forms::press_button', $lastnode); 67 } 68 } 69 } 70 71 /** 72 * Navigate to an item within the site administration menu. 73 * 74 * @throws ExpectationException 75 * @param string $nodetext The navigation node/path to follow, excluding "Site administration" itself, eg "Grades > Scales" 76 * @return void 77 */ 78 public function i_navigate_to_in_site_administration($nodetext) { 79 $parentnodes = array_map('trim', explode('>', $nodetext)); 80 array_unshift($parentnodes, get_string('administrationsite')); 81 $lastnode = array_pop($parentnodes); 82 $this->select_node_in_navigation($lastnode, $parentnodes); 83 } 84 85 /** 86 * Helper function to get top navigation node in the tree. 87 * 88 * @throws ExpectationException if node not found. 89 * @param string $nodetext name of top navigation node in tree. 90 * @return NodeElement 91 */ 92 protected function get_top_navigation_node($nodetext) { 93 // Avoid problems with quotes. 94 $nodetextliteral = behat_context_helper::escape($nodetext); 95 $exception = new ExpectationException('Top navigation node "' . $nodetext . '" not found', $this->getSession()); 96 97 $xpath = // Navigation block. 98 "//div[contains(concat(' ', normalize-space(@class), ' '), ' content ')]" . 99 "/ul[contains(concat(' ', normalize-space(@class), ' '), ' block_tree ')]" . 100 "/li[contains(concat(' ', normalize-space(@class), ' '), ' contains_branch ')]" . 101 "/ul/li[contains(concat(' ', normalize-space(@class), ' '), ' contains_branch ')]" . 102 "[p[contains(concat(' ', normalize-space(@class), ' '), ' branch ')]" . 103 "[span[normalize-space(.)={$nodetextliteral}] or a[normalize-space(.)={$nodetextliteral}]]]" . 104 "|" . 105 // Administration block. 106 "//div[contains(concat(' ', normalize-space(@class), ' '), ' content ')]/div" . 107 "/ul[contains(concat(' ', normalize-space(@class), ' '), ' block_tree ')]" . 108 "/li[contains(concat(' ', normalize-space(@class), ' '), ' contains_branch ')]" . 109 "/ul/li[contains(concat(' ', normalize-space(@class), ' '), ' contains_branch ')]" . 110 "[p[contains(concat(' ', normalize-space(@class), ' '), ' branch ')]" . 111 "/span[normalize-space(.)={$nodetextliteral}]]" . 112 "|" . 113 "//div[contains(concat(' ', normalize-space(@class), ' '), ' content ')]/div" . 114 "/ul[contains(concat(' ', normalize-space(@class), ' '), ' block_tree ')]" . 115 "/li[p[contains(concat(' ', normalize-space(@class), ' '), ' branch ')]" . 116 "/span[normalize-space(.)={$nodetextliteral}]]" . 117 "|" . 118 "//div[contains(concat(' ', normalize-space(@class), ' '), ' content ')]/div" . 119 "/ul[contains(concat(' ', normalize-space(@class), ' '), ' block_tree ')]" . 120 "/li[p[contains(concat(' ', normalize-space(@class), ' '), ' branch ')]" . 121 "/a[normalize-space(.)={$nodetextliteral}]]"; 122 123 $node = $this->find('xpath', $xpath, $exception); 124 125 return $node; 126 } 127 128 /** 129 * Check that current page administration contains an element. 130 * 131 * @throws ElementNotFoundException 132 * @param string $element The locator of the specified selector. 133 * This may be a path, for example "Subscription mode > Forced subscription" 134 * @param string $selectortype The selector type (link or text) 135 * @return void 136 */ 137 public function should_exist_in_current_page_administration($element, $selectortype) { 138 $nodes = array_map('trim', explode('>', $element)); 139 $nodetext = end($nodes); 140 141 // Find administration menu. 142 $rootxpath = $this->find_header_administration_menu() ?: $this->find_page_administration_menu(true); 143 $menuxpath = $rootxpath . '/p/../ul[1]'; 144 145 for ($i = 0; $i < (count($nodes) - 1); $i++) { 146 $menuxpath .= "/li/p/span[contains(text(), '{$nodes[$i]}')]/../../ul[1]"; 147 } 148 149 if ($selectortype == 'link') { 150 $menuxpath .= "/li/p[a[contains(text(), '{$nodetext}')]"; 151 $menuxpath .= "|a/span[contains(text(), '{$nodetext}')]]"; 152 } else { 153 $menuxpath .= "/li/p/span[contains(text(), '{$nodes[$i]}')]"; 154 } 155 156 $exception = new ElementNotFoundException($this->getSession(), "\"{$element}\" \"{$selectortype}\""); 157 try { 158 $this->find('xpath', $menuxpath, $exception); 159 } catch (Exception $e) { 160 // For question bank a different approach. 161 $menuxpath = $rootxpath . "//div[contains(@class, 'dropdown-menu')]"; 162 if ($selectortype === 'link') { 163 $menuxpath .= "//a[contains(text(), 'Categories')]"; 164 } 165 $this->find('xpath', $menuxpath, $e); 166 } 167 } 168 169 /** 170 * Check that current page administration does not contains an element. 171 * 172 * @throws ExpectationException 173 * @param string $element The locator of the specified selector. 174 * This may be a path, for example "Subscription mode > Forced subscription" 175 * @param string $selectortype The selector type (link or text) 176 * @return void 177 */ 178 public function should_not_exist_in_current_page_administration($element, $selectortype) { 179 try { 180 $menuxpath = $this->find_header_administration_menu() ?: $this->find_page_administration_menu(true); 181 } catch (Exception $e) { 182 // If an exception was thrown, it means the root note does not exist, so we can conclude the test is a success. 183 return; 184 } 185 186 // Test if the element exists. 187 try { 188 $this->should_exist_in_current_page_administration($element, $selectortype); 189 } catch (ElementNotFoundException $e) { 190 191 // If an exception was thrown, it means the element does not exist, so the test is successful. 192 return; 193 } 194 195 // If the try block passed, the element exists, so throw an exception. 196 $exception = 'The "' . $element . '" "' . $selectortype . '" was found, but should not exist'; 197 throw new ExpectationException($exception, $this->getSession()); 198 } 199 200 /** 201 * Check that the page administration menu exists on the page. 202 * 203 * This confirms the existence of the menu, which authorised users should have access to. 204 * @Given /^I should see the page administration menu$/ 205 * 206 * @throws ExpectationException 207 * @return void 208 */ 209 public function page_administration_exists() { 210 $menuxpath = "//section[contains(@class,'block_settings')]//div[@id='settingsnav']"; 211 $this->ensure_element_exists($menuxpath, 'xpath_element'); 212 } 213 214 /** 215 * Check that the page administration menu does not exist on the page. 216 * 217 * This confirms the absence of the menu, which unauthorised users should not have access to. 218 * @Given /^I should not see the page administration menu$/ 219 * 220 * @throws ExpectationException 221 * @return void 222 */ 223 public function page_administration_does_not_exist() { 224 $menuxpath = "//section[contains(@class,'block_settings')]//div[@id='settingsnav']"; 225 $this->ensure_element_does_not_exist($menuxpath, 'xpath_element'); 226 } 227 228 /** 229 * Locate the administration menu on the page (but not in the header) and return its xpath. 230 * 231 * @throws ElementNotFoundException 232 * @param bool $mustexist If true, throws an exception if menu is not found 233 * @return null|string 234 */ 235 protected function find_page_administration_menu($mustexist = false) { 236 $menuxpath = "//section[contains(@class,'block_settings')]//div[@id='settingsnav']/ul[1]/li[1]"; 237 238 if ($mustexist) { 239 $exception = new ElementNotFoundException($this->getSession(), 'Page administration menu'); 240 $this->find('xpath', $menuxpath, $exception); 241 242 } else if (!$this->getSession()->getPage()->find('xpath', $menuxpath)) { 243 return null; 244 } 245 246 return $menuxpath; 247 } 248 249 /** 250 * Turns editing mode off. 251 */ 252 public function i_turn_editing_mode_off(): void { 253 $buttonnames = [get_string('turneditingoff'), get_string('updatemymoodleoff'), get_string('blockseditoff')]; 254 foreach ($buttonnames as $buttonname) { 255 if ($editbutton = $this->getSession()->getPage()->findButton($buttonname)) { 256 $this->execute('behat_general::i_click_on', [$editbutton, 'NodeElement']); 257 return; 258 } 259 } 260 // Click the turneditingoff link in the Site Administration block. 261 if ($this->is_editing_on()) { 262 $this->execute('behat_general::i_click_on', [get_string('turneditingoff'), "link"]); 263 } 264 } 265 266 /** 267 * Turns editing mode on. 268 */ 269 public function i_turn_editing_mode_on(): void { 270 $buttonnames = [get_string('turneditingon'), get_string('updatemymoodleon'), get_string('blocksediton')]; 271 foreach ($buttonnames as $buttonname) { 272 if ($editbutton = $this->getSession()->getPage()->findButton($buttonname)) { 273 $this->execute('behat_general::i_click_on', [$editbutton, 'NodeElement']); 274 return; 275 } 276 } 277 278 if (!$this->is_editing_on()) { 279 $this->execute('behat_general::i_click_on', [get_string('turneditingon'), "link"]); 280 } 281 } 282 283 /** 284 * Finds and clicks a link on the admin page (site administration or course administration) 285 * 286 * @param array $nodelist 287 */ 288 protected function select_on_administration_page($nodelist) { 289 $parentnodes = $nodelist; 290 $lastnode = array_pop($parentnodes); 291 $xpath = '//section[@id=\'region-main\']'; 292 293 // Check if there is a separate tab for this submenu of the page. If found go to it. 294 if ($parentnodes) { 295 $tabname = behat_context_helper::escape($parentnodes[0]); 296 $tabxpath = '//ul[@role=\'tablist\']/li/a[contains(normalize-space(.), ' . $tabname . ')]'; 297 $menubarxpath = '//ul[@role=\'menubar\']/li/a[contains(normalize-space(.), ' . $tabname . ')]'; 298 $linkname = behat_context_helper::escape(get_string('moremenu')); 299 $menubarmorexpath = '//ul[@role=\'menubar\']/li/a[contains(normalize-space(.), ' . $linkname . ')]'; 300 $tabnode = $this->getSession()->getPage()->find('xpath', $tabxpath); 301 $menunode = $this->getSession()->getPage()->find('xpath', $menubarxpath); 302 $menubuttons = $this->getSession()->getPage()->findAll('xpath', $menubarmorexpath); 303 if ($tabnode || $menunode) { 304 $node = is_object($tabnode) ? $tabnode : $menunode; 305 if ($this->running_javascript()) { 306 $this->execute('behat_general::i_click_on', [$node, 'NodeElement']); 307 // Click on the tab and add 'active' tab to the xpath. 308 $xpath .= '//div[contains(@class,\'active\')]'; 309 } else { 310 // Add the tab content selector to the xpath. 311 $tabid = behat_context_helper::escape(ltrim($node->getAttribute('href'), '#')); 312 $xpath .= '//div[@id = ' . $tabid . ']'; 313 } 314 array_shift($parentnodes); 315 } else if (count($menubuttons) > 0) { 316 try { 317 $menubuttons[0]->isVisible(); 318 try { 319 $this->execute('behat_general::i_click_on', [$menubuttons[1], 'NodeElement']); 320 } catch (Exception $e) { 321 $this->execute('behat_general::i_click_on', [$menubuttons[0], 'NodeElement']); 322 } 323 $moreitemxpath = '//ul[@data-region=\'moredropdown\']/li/a[contains(normalize-space(.), ' . $tabname . ')]'; 324 if ($morenode = $this->getSession()->getPage()->find('xpath', $moreitemxpath)) { 325 $this->execute('behat_general::i_click_on', [$morenode, 'NodeElement']); 326 $xpath .= '//div[contains(@class,\'active\')]'; 327 array_shift($parentnodes); 328 } 329 } catch (Exception $e) { 330 return; 331 } 332 } 333 } 334 335 // Find a section with the parent name in it. 336 if ($parentnodes) { 337 // Find the section on the page (links may be repeating in different sections). 338 $section = behat_context_helper::escape($parentnodes[0]); 339 $xpath .= '//div[@class=\'row\' and contains(.,'.$section.')]'; 340 } 341 342 // Find a link and click on it. 343 $linkname = behat_context_helper::escape($lastnode); 344 $xpath .= '//a[contains(normalize-space(.), ' . $linkname . ')]'; 345 if (!$node = $this->getSession()->getPage()->find('xpath', $xpath)) { 346 throw new ElementNotFoundException($this->getSession(), 'Link "' . join(' > ', $nodelist) . '"'); 347 } 348 $this->execute('behat_general::i_click_on', [$node, 'NodeElement']); 349 } 350 351 /** 352 * Locates the administration menu in the <header> element and returns its xpath 353 * 354 * @param bool $mustexist if specified throws an exception if menu is not found 355 * @return null|string 356 */ 357 protected function find_header_administration_menu($mustexist = false) { 358 $menuxpath = '//header[@id=\'page-header\']//div[contains(@class,\'moodle-actionmenu\')]'; 359 if ($mustexist) { 360 $exception = new ElementNotFoundException($this->getSession(), 'Page header administration menu'); 361 $this->find('xpath', $menuxpath, $exception); 362 } else if (!$this->getSession()->getPage()->find('xpath', $menuxpath)) { 363 return null; 364 } 365 return $menuxpath; 366 } 367 368 /** 369 * Toggles administration menu 370 * 371 * @param string $menuxpath (optional) xpath to the page administration menu if already known 372 */ 373 protected function toggle_page_administration_menu($menuxpath = null) { 374 if (!$menuxpath) { 375 $menuxpath = $this->find_header_administration_menu() ?: $this->find_page_administration_menu(); 376 } 377 if ($menuxpath && $this->running_javascript()) { 378 $node = $this->find('xpath', $menuxpath . '//a[@data-toggle=\'dropdown\']'); 379 $this->execute('behat_general::i_click_on', [$node, 'NodeElement']); 380 } 381 } 382 383 /** 384 * Finds a page edit cog and select an item from it 385 * 386 * If the page edit cog is in the page header and the item is not found there, click "More..." link 387 * and find the item on the course/frontpage administration page 388 * 389 * @param array $nodelist 390 * @throws ElementNotFoundException 391 */ 392 protected function select_from_administration_menu($nodelist) { 393 // Find administration menu. 394 if ($menuxpath = $this->find_header_administration_menu()) { 395 $isheader = true; 396 } else { 397 $menuxpath = $this->find_page_administration_menu(true); 398 $isheader = false; 399 } 400 401 $this->execute('behat_navigation::toggle_page_administration_menu', [$menuxpath]); 402 403 if (!$isheader || count($nodelist) == 1) { 404 $lastnode = end($nodelist); 405 $linkname = behat_context_helper::escape($lastnode); 406 $link = $this->getSession()->getPage()->find('xpath', $menuxpath . '//a[contains(normalize-space(.), ' . 407 $linkname . ')]' 408 ); 409 if ($link) { 410 $this->execute('behat_general::i_click_on', [$link, 'NodeElement']); 411 return; 412 } 413 } 414 415 if ($isheader) { 416 // Course administration and Front page administration will have subnodes under "More...". 417 $linkname = behat_context_helper::escape(get_string('morenavigationlinks')); 418 $link = $this->getSession()->getPage()->find('xpath', $menuxpath . '//a[contains(normalize-space(.), ' . 419 $linkname . ')]' 420 ); 421 if ($link) { 422 $this->execute('behat_general::i_click_on', [$link, 'NodeElement']); 423 $this->select_on_administration_page($nodelist); 424 return; 425 } 426 } 427 428 throw new ElementNotFoundException($this->getSession(), 429 'Link "' . join(' > ', $nodelist) . '" in the current page edit menu"'); 430 } 431 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body