/** * Manually adds a reviewer for workshop participant. * * This step should start on manual allocation page. * * @When /^I add a reviewer "(?P<reviewer_name_string>(?:[^"]|\\")*)" for workshop participant "(?P<participant_name_string>(?:[^"]|\\")*)"$/ * @param string $reviewername * @param string $participantname */ public function i_add_a_reviewer_for_workshop_participant($reviewername, $participantname) { $participantnameliteral = behat_context_helper::escape($participantname); $xpathtd = "//table[contains(concat(' ', normalize-space(@class), ' '), ' allocations ')]/" . "tbody/tr[./td[contains(concat(' ', normalize-space(@class), ' '), ' peer ')]" . "[contains(.,{$participantnameliteral})]]/" . "td[contains(concat(' ', normalize-space(@class), ' '), ' reviewedby ')]"; $xpathselect = $xpathtd . "/descendant::select"; try { $selectnode = $this->find('xpath', $xpathselect); } catch (Exception $ex) { $this->find_button(get_string('showallparticipants', 'workshopallocation_manual'))->press(); $selectnode = $this->find('xpath', $xpathselect); } $selectformfield = behat_field_manager::get_form_field($selectnode, $this->getSession()); $selectformfield->set_value($reviewername); if (!$this->running_javascript()) { // Without Javascript we need to press the "Go" button. $go = behat_context_helper::escape(get_string('go')); $this->find('xpath', $xpathtd . "/descendant::input[@value={$go}]")->click(); } else { // With Javascript we just wait for the page to reload. $this->getSession()->wait(self::EXTENDED_TIMEOUT, self::PAGE_READY_JS); } // Check the success string to appear. $allocatedtext = behat_context_helper::escape(get_string('allocationadded', 'workshopallocation_manual')); $this->find('xpath', "//*[contains(.,{$allocatedtext})]"); }
/** * Add the specified user to the group. You should be in the groups page when running this step. The user should be specified like "Firstname Lastname (user@example.com)". * * @Given /^I add "(?P<user_fullname_string>(?:[^"]|\\")*)" user to "(?P<group_name_string>(?:[^"]|\\")*)" group members$/ * @throws ElementNotFoundException Thrown by behat_base::find * @param string $username * @param string $groupname */ public function i_add_user_to_group_members($userfullname, $groupname) { $userfullname = behat_context_helper::escape($userfullname); // Using a xpath liternal to avoid problems with quotes and double quotes. $groupname = behat_context_helper::escape($groupname); // We don't know the option text as it contains the number of users in the group. $select = $this->find_field('groups'); $xpath = "//select[@id='groups']/descendant::option[contains(., {$groupname})]"; $groupoption = $this->find('xpath', $xpath); $fulloption = $groupoption->getText(); $select->selectOption($fulloption); // This is needed by some drivers to ensure relevant event is triggred and button is enabled. $script = "Syn.trigger('change', {}, {{ELEMENT}})"; $this->getSession()->getDriver()->triggerSynScript($select->getXpath(), $script); $this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS); // Here we don't need to wait for the AJAX response. $this->find_button(get_string('adduserstogroup', 'group'))->click(); // Wait for add/remove members page to be loaded. $this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS); // Getting the option and selecting it. $select = $this->find_field('addselect'); $xpath = "//select[@id='addselect']/descendant::option[contains(., {$userfullname})]"; $memberoption = $this->find('xpath', $xpath); $fulloption = $memberoption->getText(); $select->selectOption($fulloption); // Click add button. $this->find_button(get_string('add'))->click(); // Wait for the page to load. $this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS); // Returning to the main groups page. $this->find_button(get_string('backtogroups', 'group'))->click(); }
/** * Translates string to XPath literal. * * @param string $label label to escape * @return string escaped string. */ public static function escape($label) { if (empty(self::$escaper)) { self::$escaper = new \Behat\Mink\Selector\Xpath\Escaper(); } return self::$escaper->escapeLiteral($label); }
/** * Checks that the specified user has not completed the specified activity of the current course. * * @Then /^"(?P<user_fullname_string>(?:[^"]|\\")*)" user has not completed "(?P<activity_name_string>(?:[^"]|\\")*)" activity$/ * @param string $userfullname * @param string $activityname */ public function user_has_not_completed_activity($userfullname, $activityname) { // Will throw an exception if the element can not be hovered. $titleliteral = behat_context_helper::escape($userfullname . ", " . $activityname . ": Not completed"); $xpath = "//table[@id='completion-progress']" . "/descendant::img[contains(@title, {$titleliteral})]"; $this->execute("behat_completion::go_to_the_current_course_activity_completion_report"); $this->execute("behat_general::should_exist", array($this->escape($xpath), "xpath_element")); }
/** * Creates the specified element. More info about available elements in http://docs.moodle.org/dev/Acceptance_testing#Fixtures. * * @Given /^the following "(?P<element_string>(?:[^"]|\\")*)" instances exist:$/ * * This step overrides behat generator step and accepts following parameters to call generator steps for multiple instances. * - instances : Number of instances to create * * It replaces #!count!# with the incremental value. * - refencecont: This is used in case you want a counter to be modulo of some value. * * It replaces #!count#! with incremental value. * * #!refcount!# will * - repeat: This will repeat the table row x times. * * Replaces #!repeatcount!3 with the repeating value. * * @throws Exception * @throws PendingException * @param string $elementname The name of the entity to add * @param TableNode $data */ public function the_following_instances_exist($elementname, TableNode $data) { $datageneratorcontext = behat_context_helper::get('behat_data_generators'); $datanodes = $this->fix_data_counter($data); foreach ($datanodes as $datanode) { generator::dot(); $datageneratorcontext->the_following_exist($elementname, $datanode); } }
/** * Upload the responses previously saved above, to a given filepicker. * * @param string $fieldlabel the lable of the file manager to upload to. * * @Given /^I upload the saved responses file to "([^"]*)" filemanager$/ */ public function i_upload_the_saved_responses_file_to_filemanager($fieldlabel) { $session = $this->getSession(); if ($this->downloadedfile === null) { throw new ExpectationException('No responses downloaded yet, so we can\'t upload them.', $session); } $uploadcontext = behat_context_helper::get('behat_repository_upload'); $uploadcontext->i_upload_file_to_filemanager($this->downloadedfile, $fieldlabel); }
public function i_should_see_question_in_section_in_the_quiz_navigation($questionnumber, $sectionheading) { // Using xpath literal to avoid quotes problems. $questionnumberliteral = behat_context_helper::escape('Question ' . $questionnumber); $headingliteral = behat_context_helper::escape($sectionheading); // Split in two checkings to give more feedback in case of exception. $exception = new ExpectationException('Question "' . $questionnumber . '" is not in section "' . $sectionheading . '" in the quiz navigation.', $this->getSession()); $xpath = "//*[@id = 'mod_quiz_navblock']//*[contains(concat(' ', normalize-space(@class), ' '), ' qnbutton ') and " . "contains(., {$questionnumberliteral}) and contains(preceding-sibling::h3[1], {$headingliteral})]"; $this->find('xpath', $xpath); }
protected function get_top_navigation_node($nodetext) { // Avoid problems with quotes. $nodetextliteral = behat_context_helper::escape($nodetext); $exception = new ExpectationException('Top navigation node "' . $nodetext . ' not found in "', $this->getSession()); // First find in navigation block. $xpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' card-text ')]" . "/ul[contains(concat(' ', normalize-space(@class), ' '), ' block_tree ')]" . "/li[contains(concat(' ', normalize-space(@class), ' '), ' contains_branch ')]" . "/ul/li[contains(concat(' ', normalize-space(@class), ' '), ' contains_branch ')]" . "[p[contains(concat(' ', normalize-space(@class), ' '), ' branch ')]" . "/span[normalize-space(.)=" . $nodetextliteral . "]]" . "|" . "//div[contains(concat(' ', normalize-space(@class), ' '), ' card-text ')]/div" . "/ul[contains(concat(' ', normalize-space(@class), ' '), ' block_tree ')]" . "/li[contains(concat(' ', normalize-space(@class), ' '), ' contains_branch ')]" . "/ul/li[contains(concat(' ', normalize-space(@class), ' '), ' contains_branch ')]" . "[p[contains(concat(' ', normalize-space(@class), ' '), ' branch ')]" . "/span[normalize-space(.)=" . $nodetextliteral . "]]" . "|" . "//div[contains(concat(' ', normalize-space(@class), ' '), ' card-text ')]/div" . "/ul[contains(concat(' ', normalize-space(@class), ' '), ' block_tree ')]" . "/li[p[contains(concat(' ', normalize-space(@class), ' '), ' branch ')]" . "/span[normalize-space(.)=" . $nodetextliteral . "]]" . "|" . "//div[contains(concat(' ', normalize-space(@class), ' '), ' card-text ')]/div" . "/ul[contains(concat(' ', normalize-space(@class), ' '), ' block_tree ')]" . "/li[p[contains(concat(' ', normalize-space(@class), ' '), ' branch ')]" . "/a[normalize-space(.)=" . $nodetextliteral . "]]"; $node = $this->find('xpath', $xpath, $exception); return $node; }
/** * Logs in the user. There should exist a user with the same value as username and password. * * @Given /^I log in as "(?P<username_string>(?:[^"]|\\")*)"$/ */ public function i_log_in_as($username) { // Visit login page. $this->getSession()->visit($this->locate_path('login/index.php')); // Enter username and password. $behatforms = behat_context_helper::get('behat_forms'); $behatforms->i_set_the_field_to('Username', $this->escape($username)); $behatforms->i_set_the_field_to('Password', $this->escape($username)); // Press log in button. $behatforms->press_button(get_string('login')); }
/** * Sets the value(s) of an availability element. * * At present this only supports the following value 'Grouping: xxx' where * xxx is the name of a grouping. Additional value types can be added as * necessary. * * @param string $value Value code * @return void */ public function set_value($value) { global $DB; $driver = $this->session->getDriver(); // Check the availability condition is currently unset - we don't yet // support changing an existing one. $existing = $this->get_value(); if ($existing && $existing !== '{"op":"&","c":[],"showc":[]}') { throw new Exception('Cannot automatically set availability when ' . 'there is existing setting - must clear manually'); } // Check the value matches a supported format. $matches = array(); if (!preg_match('~^\\s*([^:]*):\\s*(.*?)\\s*$~', $value, $matches)) { throw new Exception('Value for availability field does not match correct ' . 'format. Example: "Grouping: G1"'); } $type = $matches[1]; $param = $matches[2]; if ($this->running_javascript()) { switch (strtolower($type)) { case 'grouping': // Set a grouping condition. $driver->click('//div[@class="availability-button"]/button'); $driver->click('//button[@id="availability_addrestriction_grouping"]'); $escparam = behat_context_helper::escape($param); $nodes = $driver->find('//span[contains(concat(" " , @class, " "), " availability_grouping ")]//' . 'option[normalize-space(.) = ' . $escparam . ']'); if (count($nodes) != 1) { throw new Exception('Cannot find grouping in dropdown' . count($nodes)); } $node = reset($nodes); $value = $node->getValue(); $driver->selectOption('//span[contains(concat(" " , @class, " "), " availability_grouping ")]//' . 'select', $value); break; default: // We don't support other types yet. The test author must write // manual 'click on that button, etc' commands. throw new Exception('The availability type "' . $type . '" is currently not supported - must set manually'); } } else { $courseid = $driver->getValue('//input[@name="course"]'); switch (strtolower($type)) { case 'grouping': // Define result with one grouping condition. $groupingid = $DB->get_field('groupings', 'id', array('courseid' => $courseid, 'name' => $param)); $json = \core_availability\tree::get_root_json(array(\availability_grouping\condition::get_json($groupingid))); break; default: // We don't support other types yet. throw new Exception('The availability type "' . $type . '" is currently not supported - must set with JavaScript'); } $driver->setValue('//textarea[@name="availabilityconditionsjson"]', json_encode($json)); } }
/** * Checks the state of the specified question. * * @Then /^the state of "(?P<question_description_string>(?:[^"]|\\")*)" question is shown as "(?P<state_string>(?:[^"]|\\")*)"$/ * @throws ExpectationException * @throws ElementNotFoundException * @param string $questiondescription * @param string $state */ public function the_state_of_question_is_shown_as($questiondescription, $state) { // Using xpath literal to avoid quotes problems. $questiondescriptionliteral = behat_context_helper::escape($questiondescription); $stateliteral = behat_context_helper::escape($state); // Split in two checkings to give more feedback in case of exception. $exception = new ElementNotFoundException($this->getSession(), 'Question "' . $questiondescription . '" '); $questionxpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' que ')]" . "[contains(div[@class='content']/div[contains(concat(' ', normalize-space(@class), ' '), ' formulation ')]," . "{$questiondescriptionliteral})]"; $this->find('xpath', $questionxpath, $exception); $exception = new ExpectationException('Question "' . $questiondescription . '" state is not "' . $state . '"', $this->getSession()); $xpath = $questionxpath . "/div[@class='info']/div[@class='state' and contains(., {$stateliteral})]"; $this->find('xpath', $xpath, $exception); }
/** * Drag the drag item with the given text to the given space. * * @param string $marker the marker to drag. The label, optionally followed by ,<instance number> (int) if relevant. * @param string $coordinates the position to drag the marker to, 'x,y'. * * @Given /^I drag "(?P<marker>[^"]*)" to "(?P<coordinates>\d+,\d+)" in the drag and drop markers question$/ */ public function i_drag_to_in_the_drag_and_drop_markers_question($marker, $coordinates) { list($marker, $item) = $this->parse_marker_name($marker); list($x, $y) = explode(',', $coordinates); // This is a bit nasty, but Behat (indeed Selenium) will only drag on // DOM node so that its centre is over the centre of anothe DOM node. // Therefore to make it drag to the specified place, we have to add // a target div. $session = $this->getSession(); $session->evaluateScript("\n (function() {\n if (document.getElementById('target-{$x}-{$y}')) {\n return;\n }\n var image = document.querySelector('.dropbackground');\n var target = document.createElement('div');\n target.setAttribute('id', 'target-{$x}-{$y}');\n var container = document.querySelector('.droparea');\n container.style.setProperty('position', 'relative');\n container.insertBefore(target, image);\n var xadjusted = {$x} + (container.offsetWidth - image.offsetWidth) / 2;\n var yadjusted = {$y} + (container.offsetHeight - image.offsetHeight) / 2;\n target.style.setProperty('position', 'absolute');\n target.style.setProperty('left', xadjusted + 'px');\n target.style.setProperty('top', yadjusted + 'px');\n target.style.setProperty('width', '1px');\n target.style.setProperty('height', '1px');\n }())"); $generalcontext = behat_context_helper::get('behat_general'); $generalcontext->i_drag_and_i_drop_it_in($this->marker_xpath($marker, $item), 'xpath_element', "#target-{$x}-{$y}", 'css_element'); }
/** * Returns the behat selector and locator for a given moodle selector and locator * * @param string $selectortype The moodle selector type, which includes moodle selectors * @param string $element The locator we look for in that kind of selector * @param Session $session The Mink opened session * @return array Contains the selector and the locator expected by Mink. */ public static function get_behat_selector($selectortype, $element, Behat\Mink\Session $session) { // CSS and XPath selectors locator is one single argument. if ($selectortype == 'css_element' || $selectortype == 'xpath_element') { $selector = str_replace('_element', '', $selectortype); $locator = $element; } else { // Named selectors uses arrays as locators including the type of named selector. $locator = array($selectortype, behat_context_helper::escape($element)); $selector = 'named_partial'; } return array($selector, $locator); }
/** * Deletes the specified comment from the current page's comments block. * * @Given /^I delete "(?P<comment_text_string>(?:[^"]|\\")*)" comment from comments block$/ * @throws ElementNotFoundException * @throws ExpectationException * @param string $comment */ public function i_delete_comment_from_comments_block($comment) { $exception = new ElementNotFoundException($this->getSession(), '"' . $comment . '" comment '); // Using xpath liternal to avoid possible problems with comments containing quotes. $commentliteral = behat_context_helper::escape($comment); $commentxpath = "//*[contains(concat(' ', normalize-space(@class), ' '), ' block_comments ')]" . "/descendant::div[@class='comment-message'][contains(., {$commentliteral})]"; $commentnode = $this->find('xpath', $commentxpath, $exception); // Click on delete icon. $deleteexception = new ExpectationException('"' . $comment . '" comment can not be deleted', $this->getSession()); $deleteicon = $this->find('css', '.comment-delete a img', $deleteexception, $commentnode); $deleteicon->click(); // Wait for the animation to finish, in theory is just 1 sec, adding 4 just in case. $this->getSession()->wait(4 * 1000, false); }
protected function get_filepicker_node($filepickerelement) { // More info about the problem (in case there is a problem). $exception = new ExpectationException('"' . $filepickerelement . '" filepicker can not be found', $this->getSession()); // If no file picker label is mentioned take the first file picker from the page. if (empty($filepickerelement)) { $filepickercontainer = $this->find('xpath', "//*[@data-fieldtype=\"filemanager\"]", $exception); } else { // Gets the ffilemanager node specified by the locator which contains the filepicker container. $filepickerelement = behat_context_helper::escape($filepickerelement); $filepickercontainer = $this->find('xpath', "//input[./@id = //label[normalize-space(.)={$filepickerelement}]/@for]" . "//ancestor::*[@data-fieldtype = 'filemanager' or @data-fieldtype = 'filepicker']", $exception); } return $filepickercontainer; }
public function i_set_the_following_administration_settings_values(TableNode $table) { if (!($data = $table->getRowsHash())) { return; } foreach ($data as $label => $value) { // We expect admin block to be visible, otherwise go to homepage. if (!$this->getSession()->getPage()->find('css', '.block_settings')) { $this->getSession()->visit($this->locate_path('/')); $this->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS); } // Search by label. $searchbox = $this->find_field(get_string('searchinsettings', 'admin')); $searchbox->setValue($label); $submitsearch = $this->find('css', 'form.adminsearchform input[type=submit]'); $submitsearch->press(); $this->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS); // Admin settings does not use the same DOM structure than other moodle forms // but we also need to use lib/behat/form_field/* to deal with the different moodle form elements. $exception = new ElementNotFoundException($this->getSession(), '"' . $label . '" administration setting '); // The argument should be converted to an xpath literal. $label = behat_context_helper::escape($label); // Single element settings. try { $fieldxpath = "//*[self::input | self::textarea | self::select]" . "[not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')]" . "[@id=//label[contains(normalize-space(.), {$label})]/@for or " . "@id=//span[contains(normalize-space(.), {$label})]/preceding-sibling::label[1]/@for]"; $fieldnode = $this->find('xpath', $fieldxpath, $exception); $formfieldtypenode = $this->find('xpath', $fieldxpath . "/ancestor::div[contains(concat(' ', @class, ' '), ' form-setting ')]" . "/child::div[contains(concat(' ', @class, ' '), ' form-')]/child::*/parent::div"); } catch (ElementNotFoundException $e) { // Multi element settings, interacting only the first one. $fieldxpath = "//*[label[contains(., {$label})]|span[contains(., {$label})]]" . "/ancestor::div[contains(concat(' ', normalize-space(@class), ' '), ' form-item ')]" . "/descendant::div[contains(concat(' ', @class, ' '), ' form-group ')]" . "/descendant::*[self::input | self::textarea | self::select]" . "[not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')]"; $fieldnode = $this->find('xpath', $fieldxpath); // It is the same one that contains the type. $formfieldtypenode = $fieldnode; } // Getting the class which contains the field type. $classes = explode(' ', $formfieldtypenode->getAttribute('class')); $type = false; foreach ($classes as $class) { if (substr($class, 0, 5) == 'form-') { $type = substr($class, 5); } } // Instantiating the appropiate field type. $field = behat_field_manager::get_field_instance($type, $fieldnode, $this->getSession()); $field->set_value($value); $this->find_button(get_string('savechanges'))->press(); } }
/** * Opens the contents of a filemanager folder. It looks for the folder in the current folder and in the path bar. * * @Given /^I open "(?P<foldername_string>(?:[^"]|\\")*)" folder from "(?P<filemanager_field_string>(?:[^"]|\\")*)" filemanager$/ * @throws ExpectationException Thrown by behat_base::find * @param string $foldername * @param string $filemanagerelement */ public function i_open_folder_from_filemanager($foldername, $filemanagerelement) { $fieldnode = $this->get_filepicker_node($filemanagerelement); $exception = new ExpectationException('The "' . $foldername . '" folder can not be found in the "' . $filemanagerelement . '" filemanager', $this->getSession()); $folderliteral = behat_context_helper::escape($foldername); // We look both in the pathbar and in the contents. try { // In the current folder workspace. $folder = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), ' fp-folder ')]" . "/descendant::div[contains(concat(' ', normalize-space(@class), ' '), ' fp-filename ')]" . "[normalize-space(.)={$folderliteral}]", $exception, $fieldnode); } catch (ExpectationException $e) { // And in the pathbar. $folder = $this->find('xpath', "//a[contains(concat(' ', normalize-space(@class), ' '), ' fp-path-folder-name ')]" . "[normalize-space(.)={$folderliteral}]", $exception, $fieldnode); } // It should be a NodeElement, otherwise an exception would have been thrown. $folder->click(); }
/** * Chooses the specified option from the choice activity named as specified and save the choice. * You should be located in the activity's course page. * * @Given /^I choose options (?P<option_string>"(?:[^"]|\\")*"(?:,"(?:[^"]|\\")*")*) from "(?P<choice_activity_string>(?:[^"]|\\")*)" choice activity$/ * @param string $option * @param string $choiceactivity * @return array */ public function I_choose_options_from_activity($option, $choiceactivity) { // Get Behat general and forms contexts. $behatgeneral = behat_context_helper::get('behat_general'); $behatforms = behat_context_helper::get('behat_forms'); // Go to choice activity. $behatgeneral->click_link($this->escape($choiceactivity)); // Wait for page to be loaded. $this->wait_for_pending_js(); // Set all options. $options = explode('","', trim($option, '"')); foreach ($options as $option) { $behatforms->i_set_the_field_to($this->escape($option), '1'); } // Save choice. $behatforms->press_button(get_string('savemychoice', 'choice')); }
/** * Enrols the specified user in the current course without options. * * This is a simple step, to set enrolment options would be better to * create a separate step as a TableNode will be required. * * @Given /^I enrol "(?P<user_fullname_string>(?:[^"]|\\")*)" user as "(?P<rolename_string>(?:[^"]|\\")*)"$/ * @param string $userfullname * @param string $rolename */ public function i_enrol_user_as($userfullname, $rolename) { // Navigate to enrolment page. $parentnodes = get_string('courseadministration') . ' > ' . get_string('users', 'admin'); $this->execute("behat_navigation::i_navigate_to_node_in", array(get_string('enrolledusers', 'enrol'), $parentnodes)); $this->execute("behat_forms::press_button", get_string('enrolusers', 'enrol')); $this->execute('behat_forms::i_set_the_field_to', array(get_string('assignroles', 'role'), $rolename)); if ($this->running_javascript()) { // We have a div here, not a tr. $userliteral = behat_context_helper::escape($userfullname); $userrowxpath = "//div[contains(concat(' ',normalize-space(@class),' '),' user ')][contains(., {$userliteral})]"; $this->execute('behat_general::i_click_on_in_the', array(get_string('enrol', 'enrol'), "button", $userrowxpath, "xpath_element")); $this->execute("behat_forms::press_button", get_string('finishenrollingusers', 'enrol')); } else { $this->execute('behat_forms::i_set_the_field_to', array("addselect", $userfullname)); $this->execute("behat_forms::press_button", "add"); } }
/** * Resets the test environment. * * @throws coding_exception If here we are not using the test database it should be because of a coding error * @BeforeScenario */ public function before_scenario($event) { global $DB, $SESSION, $CFG; // As many checks as we can. if (!defined('BEHAT_TEST') || !defined('BEHAT_SITE_RUNNING') || php_sapi_name() != 'cli' || !behat_util::is_test_mode_enabled() || !behat_util::is_test_site()) { throw new coding_exception('Behat only can modify the test database and the test dataroot!'); } $moreinfo = 'More info in ' . behat_command::DOCS_URL . '#Running_tests'; $driverexceptionmsg = 'Selenium server is not running, you need to start it to run tests that involve Javascript. ' . $moreinfo; try { $session = $this->getSession(); } catch (CurlExec $e) { // Exception thrown by WebDriver, so only @javascript tests will be caugth; in // behat_util::is_server_running() we already checked that the server is running. throw new Exception($driverexceptionmsg); } catch (DriverException $e) { throw new Exception($driverexceptionmsg); } catch (UnknownError $e) { // Generic 'I have no idea' Selenium error. Custom exception to provide more feedback about possible solutions. $this->throw_unknown_exception($e); } // We need the Mink session to do it and we do it only before the first scenario. if (self::is_first_scenario()) { behat_selectors::register_moodle_selectors($session); behat_context_helper::set_session($session); } // Reset $SESSION. \core\session\manager::init_empty_session(); behat_util::reset_database(); behat_util::reset_dataroot(); accesslib_clear_all_caches(true); // Reset the nasty strings list used during the last test. nasty_strings::reset_used_strings(); // Assign valid data to admin user (some generator-related code needs a valid user). $user = $DB->get_record('user', array('username' => 'admin')); \core\session\manager::set_user($user); // Reset the browser if specified in config.php. if (!empty($CFG->behat_restart_browser_after) && $this->running_javascript()) { $now = time(); if (self::$lastbrowsersessionstart + $CFG->behat_restart_browser_after < $now) { $session->restart(); self::$lastbrowsersessionstart = $now; } } // Start always in the the homepage. try { // Let's be conservative as we never know when new upstream issues will affect us. $session->visit($this->locate_path('/')); } catch (UnknownError $e) { $this->throw_unknown_exception($e); } // Checking that the root path is a Moodle test site. if (self::is_first_scenario()) { $notestsiteexception = new Exception('The base URL (' . $CFG->wwwroot . ') is not a behat test site, ' . 'ensure you started the built-in web server in the correct directory or your web server is correctly started and set up'); $this->find("xpath", "//head/child::title[normalize-space(.)='" . behat_util::BEHATSITENAME . "']", $notestsiteexception); self::$initprocessesfinished = true; } // Run all test with medium (1024x768) screen size, to avoid responsive problems. $this->resize_window('medium'); }
/** * Helper function to get sub-navigation node. * * @throws ExpectationException if note not found. * @param string $nodetext node to find. * @param NodeElement $parentnode parent navigation node. * @return NodeElement. */ protected function get_navigation_node($nodetext, $parentnode = null) { // Avoid problems with quotes. $nodetextliteral = behat_context_helper::escape($nodetext); $xpath = "/ul/li[contains(concat(' ', normalize-space(@class), ' '), ' contains_branch ')]" . "[child::p[contains(concat(' ', normalize-space(@class), ' '), ' branch ')]" . "/child::span[normalize-space(.)=" . $nodetextliteral . "]]"; $node = $parentnode->find('xpath', $xpath); if (!$node) { $xpath = "/ul/li[contains(concat(' ', normalize-space(@class), ' '), ' contains_branch ')]" . "[child::p[contains(concat(' ', normalize-space(@class), ' '), ' branch ')]" . "/child::a[normalize-space(.)=" . $nodetextliteral . "]]"; $node = $parentnode->find('xpath', $xpath); } if (!$node) { throw new ExpectationException('Sub-navigation node "' . $nodetext . '" not found under "' . $parentnode->getText() . '"', $this->getSession()); } return $node; }
/** * Sets the browser session. * * @param Environment $environment * @return void */ public static function set_session(Environment $environment) { self::$environment = $environment; }
/** * Sets a previously created grading form as the activity grading form. * * @Given /^I set "(?P<activity_name_string>(?:[^"]|\\")*)" activity to use "(?P<grading_form_template_string>(?:[^"]|\\")*)" grading form$/ * @param string $activityname * @param string $templatename */ public function i_set_activity_to_use_grading_form($activityname, $templatename) { $templateliteral = behat_context_helper::escape($templatename); $templatexpath = "//h2[@class='template-name'][contains(., {$templateliteral})]/" . "following-sibling::div[contains(concat(' ', normalize-space(@class), ' '), ' template-actions ')]"; // Should work with both templates and own forms. $literaltemplate = behat_context_helper::escape(get_string('templatepick', 'grading')); $literalownform = behat_context_helper::escape(get_string('templatepickownform', 'grading')); $usetemplatexpath = "/a[./descendant::div[text()={$literaltemplate}]]|" . "/a[./descendant::div[text()={$literalownform}]]"; $this->execute('behat_grading::i_go_to_advanced_grading_page', $this->escape($activityname)); $this->execute('behat_general::click_link', $this->escape(get_string('manageactionclone', 'grading'))); $this->execute('behat_forms::i_set_the_field_to', array(get_string('searchownforms', 'grading'), 1)); $this->execute('behat_general::i_click_on_in_the', array(get_string('search'), "button", "region-main", "region")); $this->execute('behat_general::i_click_on_in_the', array($this->escape($usetemplatexpath), "xpath_element", $this->escape($templatexpath), "xpath_element")); $this->execute('behat_forms::press_button', get_string('continue')); }
/** * Set the response for a given input. * * @param string $identifier the text of the item to drag. E.g. '2:answer'. * @param string $value the response to give. * * @Given /^I set the input "(?P<name>[^"]*)" to "(?P<value>[^"]*)" in the STACK question$/ */ public function i_set_the_part_to_in_the_combined_question($name, $value) { $formscontext = behat_context_helper::get('behat_forms'); $formscontext->i_set_the_field_with_xpath_to($this->input_xpath($name), $value); }
public function i_reset_weights_for_grade_category($gradeitem) { $steps = array(); if ($this->running_javascript()) { $gradeitemliteral = behat_context_helper::escape($gradeitem); $xpath = "//tr[contains(.,$gradeitemliteral)]//*[contains(@class,'moodle-actionmenu')]"; if ($this->getSession()->getPage()->findAll('xpath', $xpath)) { $xpath = "//tr[contains(.,$gradeitemliteral)]"; $this->execute("behat_action_menu::i_open_the_action_menu_in", array($xpath, "xpath_element")); } } $linktext = get_string('resetweights', 'grades', (object)array('itemname' => $gradeitem)); $this->execute("behat_general::i_click_on", array($this->escape($linktext), "link")); }
/** * Opens the filepicker modal window and selects the repository. * * @throws ExpectationException Thrown by behat_base::find * @param NodeElement $filemanagernode The filemanager or filepicker form element DOM node. * @param mixed $repositoryname The repo name. * @return void */ protected function open_add_file_window($filemanagernode, $repositoryname) { $exception = new ExpectationException('No files can be added to the specified filemanager', $this->getSession()); // We should deal with single-file and multiple-file filemanagers, // catching the exception thrown by behat_base::find() in case is not multiple try { // Looking for the add button inside the specified filemanager. $add = $this->find('css', 'div.fp-btn-add a', $exception, $filemanagernode); } catch (Exception $e) { // Otherwise should be a single-file filepicker form element. $add = $this->find('css', 'input.fp-btn-choose', $exception, $filemanagernode); } $this->ensure_node_is_visible($add); $add->click(); // Wait for the default repository (if any) to load. This checks that // the relevant div exists and that it does not include the loading image. $this->ensure_element_exists("//div[contains(concat(' ', normalize-space(@class), ' '), ' file-picker ')]" . "//div[contains(concat(' ', normalize-space(@class), ' '), ' fp-content ')]" . "[not(descendant::div[contains(concat(' ', normalize-space(@class), ' '), ' fp-content-loading ')])]", 'xpath_element'); // Getting the repository link and opening it. $repoexception = new ExpectationException('The "' . $repositoryname . '" repository has not been found', $this->getSession()); // Avoid problems with both double and single quotes in the same string. $repositoryname = behat_context_helper::escape($repositoryname); // Here we don't need to look inside the selected element because there can only be one modal window. $repositorylink = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), ' fp-repo-area ')]" . "//descendant::span[contains(concat(' ', normalize-space(@class), ' '), ' fp-repo-name ')]" . "[normalize-space(.)={$repositoryname}]", $repoexception); // Selecting the repo. $this->ensure_node_is_visible($repositorylink); if (!$repositorylink->getParent()->getParent()->hasClass('active')) { // If the repository link is active, then the repository is already loaded. // Clicking it while it's active causes issues, so only click it when it isn't (see MDL-51014). $repositorylink->click(); } }
/** * Resets the test environment. * * @param BeforeScenarioScope $scope scope passed by event fired before scenario. * @throws behat_stop_exception If here we are not using the test database it should be because of a coding error */ public function before_scenario(BeforeScenarioScope $scope) { global $DB, $CFG; // As many checks as we can. if (!defined('BEHAT_TEST') || !defined('BEHAT_SITE_RUNNING') || php_sapi_name() != 'cli' || !behat_util::is_test_mode_enabled() || !behat_util::is_test_site()) { throw new behat_stop_exception('Behat only can modify the test database and the test dataroot!'); } $moreinfo = 'More info in ' . behat_command::DOCS_URL . '#Running_tests'; $driverexceptionmsg = 'Selenium server is not running, you need to start it to run tests that involve Javascript. ' . $moreinfo; try { $session = $this->getSession(); } catch (CurlExec $e) { // Exception thrown by WebDriver, so only @javascript tests will be caugth; in // behat_util::check_server_status() we already checked that the server is running. throw new behat_stop_exception($driverexceptionmsg); } catch (DriverException $e) { throw new behat_stop_exception($driverexceptionmsg); } catch (UnknownError $e) { // Generic 'I have no idea' Selenium error. Custom exception to provide more feedback about possible solutions. throw new behat_stop_exception($e->getMessage()); } $suitename = $scope->getSuite()->getName(); // Register behat selectors for theme, if suite is changed. We do it for every suite change. if ($suitename !== self::$runningsuite) { behat_context_helper::set_environment($scope->getEnvironment()); // We need the Mink session to do it and we do it only before the first scenario. $namedpartialclass = 'behat_partial_named_selector'; $namedexactclass = 'behat_exact_named_selector'; if ($suitename !== 'default') { // If override selector exist, then set it as default behat selectors class. $overrideclass = behat_config_util::get_behat_theme_selector_override_classname($suitename, 'named_partial', true); if (class_exists($overrideclass)) { $namedpartialclass = $overrideclass; } // If override selector exist, then set it as default behat selectors class. $overrideclass = behat_config_util::get_behat_theme_selector_override_classname($suitename, 'named_exact', true); if (class_exists($overrideclass)) { $namedexactclass = $overrideclass; } } $this->getSession()->getSelectorsHandler()->registerSelector('named_partial', new $namedpartialclass()); $this->getSession()->getSelectorsHandler()->registerSelector('named_exact', new $namedexactclass()); } // Reset mink session between the scenarios. $session->reset(); // Reset $SESSION. \core\session\manager::init_empty_session(); behat_util::reset_all_data(); // Assign valid data to admin user (some generator-related code needs a valid user). $user = $DB->get_record('user', array('username' => 'admin')); \core\session\manager::set_user($user); // Reset the browser if specified in config.php. if (!empty($CFG->behat_restart_browser_after) && $this->running_javascript()) { $now = time(); if (self::$lastbrowsersessionstart + $CFG->behat_restart_browser_after < $now) { $session->restart(); self::$lastbrowsersessionstart = $now; } } // Set the theme if not default. if ($suitename !== "default") { set_config('theme', $suitename); self::$runningsuite = $suitename; } // Start always in the the homepage. try { // Let's be conservative as we never know when new upstream issues will affect us. $session->visit($this->locate_path('/')); } catch (UnknownError $e) { throw new behat_stop_exception($e->getMessage()); } // Checking that the root path is a Moodle test site. if (self::is_first_scenario()) { $notestsiteexception = new behat_stop_exception('The base URL (' . $CFG->wwwroot . ') is not a behat test site, ' . 'ensure you started the built-in web server in the correct directory or your web server is correctly started and set up'); $this->find("xpath", "//head/child::title[normalize-space(.)='" . behat_util::BEHATSITENAME . "']", $notestsiteexception); self::$initprocessesfinished = true; } // Run all test with medium (1024x768) screen size, to avoid responsive problems. $this->resize_window('medium'); }
/** * Helper function to execute api in a given context. * * @param string $contextapi context in which api is defined. * @param array $params list of params to pass. * @throws Exception */ protected function execute($contextapi, $params = array()) { if (!is_array($params)) { $params = array($params); } // Get required context and execute the api. $contextapi = explode("::", $contextapi); $context = behat_context_helper::get($contextapi[0]); call_user_func_array(array($context, $contextapi[1]), $params); // NOTE: Wait for pending js and look for exception are not optional, as this might lead to unexpected results. // Don't make them optional for performance reasons. // Wait for pending js. $this->wait_for_pending_js(); // Look for exceptions. $this->look_for_exceptions(); }
/** * Returns the xpath representing the selected criterion. * * It is the xpath when grading a rubric or viewing a rubric, * it is not the same xpath when editing a rubric. * * @param string $criterionname Literal including the criterion name. * @return string */ protected function get_criterion_xpath($criterionname) { $literal = behat_context_helper::escape($criterionname); return "//tr[contains(concat(' ', normalize-space(@class), ' '), ' criterion ')]" . "[./descendant::td[@class='description'][text()={$literal}]]"; }
/** * Resets the test environment. * * @param OutlineExampleEvent|ScenarioEvent $event event fired before scenario. * @throws coding_exception If here we are not using the test database it should be because of a coding error * @BeforeScenario */ public function before_scenario($event) { global $DB, $CFG; // TODO: check config value to ensure site is set for performance data. $moreinfo = 'More info in ' . behat_command::DOCS_URL . '#Running_tests'; $driverexceptionmsg = 'Selenium server is not running, you need to start it to run tests that involve Javascript. ' . $moreinfo; try { $session = $this->getSession(); } catch (CurlExec $e) { // Exception thrown by WebDriver, so only @javascript tests will be caugth; in // behat_util::is_server_running() we already checked that the server is running. throw new Exception($driverexceptionmsg); } catch (DriverException $e) { throw new Exception($driverexceptionmsg); } catch (UnknownError $e) { // Generic 'I have no idea' Selenium error. Custom exception to provide more feedback about possible solutions. $this->throw_unknown_exception($e); } // We need the Mink session to do it and we do it only before the first scenario. if (self::is_first_scenario()) { behat_selectors::register_moodle_selectors($session); behat_context_helper::set_session($session); } // Reset mink session between the scenarios. $session->reset(); // Assign valid data to admin user (some generator-related code needs a valid user). $user = $DB->get_record('user', array('username' => 'admin')); \core\session\manager::set_user($user); // Start always in the the homepage. try { // Let's be conservative as we never know when new upstream issues will affect us. $session->visit($this->locate_path('/')); } catch (UnknownError $e) { $this->throw_unknown_exception($e); } // Checking that the root path is a Moodle test site. /*if (self::is_first_scenario()) { $notestsiteexception = new Exception('The base URL (' . $CFG->wwwroot . ') is not a behat test site, ' . 'ensure you started the built-in web server in the correct directory or your web server is correctly started and set up'); $this->find("xpath", "//head/child::title[normalize-space(.)='" . behat_util::BEHATSITENAME . "']", $notestsiteexception); self::$initprocessesfinished = true; }*/ }