Commit d1e55a47 authored by David Monllaó's avatar David Monllaó
Browse files

MDL-42625 behat: Step definitions + related changes in features

In general aiming for compatibility with multiple browsers,
firefox, chrome and phantomjs to be more specific.

* Removing hardcoded waits
* Adding @_alert, @_switch_window and @_switch_frame tags,
  to label actions that different drivers have problems with.
* Adding missing @_files_upload and @_only_local tags to features that
  uploads files.
* Fixing a few wait for page ready what specified miliseconds.
* New methods to ensure elements (usual selectors), sections and editors
  are ready to interact with
* Changing the select an option implementation to deal with the different
  drivers implementations when listening to JS events.
parent 333db2e9
......@@ -59,7 +59,7 @@ class behat_admin extends behat_base {
// 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, '(document.readyState === "complete")');
$this->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
}
// Search by label.
......@@ -68,7 +68,7 @@ class behat_admin extends behat_base {
$submitsearch = $this->find('css', 'form.adminsearchform input[type=submit]');
$submitsearch->press();
$this->wait(self::TIMEOUT, '(document.readyState === "complete")');
$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.
......
......@@ -76,7 +76,7 @@ class behat_backup extends behat_base {
$this->find_button(get_string('backupstage4action', 'backup'))->press();
// Waiting for it to finish.
$this->wait(10);
$this->wait();
// Last backup continue button.
$this->find_button(get_string('backupstage16action', 'backup'))->press();
......@@ -301,7 +301,7 @@ class behat_backup extends behat_base {
// Review, no options here.
$this->find_button(get_string('restorestage16action', 'backup'))->press();
$this->wait(10);
$this->wait();
// Last restore continue button, redirected to restore course after this.
$this->find_button(get_string('restorestage32action', 'backup'))->press();
......@@ -343,19 +343,15 @@ class behat_backup extends behat_base {
/**
* Waits until the DOM is ready.
*
* @param int To override the default timeout
* @return void
*/
protected function wait($timeout = false) {
protected function wait() {
if (!$this->running_javascript()) {
return;
}
if (!$timeout) {
$timeout = self::TIMEOUT;
}
$this->getSession()->wait($timeout, '(document.readyState === "complete")');
$this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
}
}
......@@ -21,8 +21,8 @@ Feature: Duplicate activities
And I add a "Database" to section "1" and I fill the form with:
| Name | Test database name |
| Description | Test database description |
And I open "Test database name" actions menu
When I click on "Duplicate" "link" in the "Test database name" activity
And I duplicate "Test database name" activity
And I wait until section "1" is available
And I open "Test database name" actions menu
And I click on "Edit settings" "link" in the "Test database name" activity
And I fill the moodle form with:
......
......@@ -4,13 +4,10 @@ Feature: Award badges
As an admin
I need to add criteria to badges in the system
Background:
Given I am on homepage
And I log in as "admin"
@javascript
Scenario: Award profile badge
Given I expand "Site administration" node
Given I log in as "admin"
And I expand "Site administration" node
And I expand "Badges" node
And I follow "Add a new badge"
And I fill the moodle form with:
......@@ -46,6 +43,7 @@ Feature: Award badges
| username | firstname | lastname | email |
| teacher | teacher | 1 | teacher1@asd.com |
| student | student | 1 | student1@asd.com |
And I log in as "admin"
And I expand "Site administration" node
And I expand "Badges" node
And I follow "Add a new badge"
......@@ -89,7 +87,6 @@ Feature: Award badges
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
| student2 | C1 | student |
And I log out
And I log in as "teacher1"
And I follow "Course 1"
And I click on "//span[text()='Badges']" "xpath_element" in the "Administration" "block"
......@@ -133,7 +130,6 @@ Feature: Award badges
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And I log out
And I log in as "admin"
And I set the following administration settings values:
| Enable completion tracking | 1 |
......@@ -172,7 +168,6 @@ Feature: Award badges
And I follow "Home"
And I follow "Course 1"
And I press "Mark as complete: Test assignment name"
And I wait "2" seconds
And I expand "My profile" node
And I follow "My badges"
Then I should see "Course Badge"
......@@ -190,7 +185,6 @@ Feature: Award badges
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And I log out
And I log in as "admin"
And I set the following administration settings values:
| Enable completion tracking | 1 |
......
......@@ -65,9 +65,6 @@ class behat_block_comments extends behat_base {
$this->find_link(get_string('savecomment'))->click();
// Wait for the AJAX request.
$this->getSession()->wait(4 * 1000, false);
} else {
$commentstextarea = $this->find('css', '.block_comments form textarea', $exception);
......@@ -103,7 +100,7 @@ class behat_block_comments extends behat_base {
$deleteicon = $this->find('css', '.comment-delete a img', $deleteexception, $commentnode);
$deleteicon->click();
// Wait for the AJAX request.
// Wait for the animation to finish, in theory is just 1 sec, adding 4 just in case.
$this->getSession()->wait(4 * 1000, false);
}
......
......@@ -53,7 +53,7 @@ Feature: Comment on a blog entry
And I follow "Save comment"
When I click on ".comment-delete a" "css_element"
# Waiting for the animation to finish.
And I wait "2" seconds
And I wait "4" seconds
Then I should not see "$My own >nasty< \"string\"!"
And I follow "Blog post from user 1"
And I click on ".comment-link" "css_element"
......
......@@ -20,13 +20,16 @@ Feature: Restrict activity availability through date conditions
And I set the following administration settings values:
| Enable conditional access | 1 |
And I log out
@javascript
Scenario: Show activity greyed-out to students when available from date is in future
Given I log in as "teacher1"
And I log in as "teacher1"
And I follow "Course 1"
And I turn editing mode on
# Adding the page like this because id_available*_enabled needs to be clicked to trigger the action.
And I add a "Assignment" to section "1"
And I expand all fieldsets
@javascript
Scenario: Show activity greyed-out to students when available from date is in future
Given I click on "id_availablefrom_enabled" "checkbox"
And I fill the moodle form with:
| Assignment name | Test assignment 1 |
| Description | This assignment is restricted by date |
......@@ -36,7 +39,6 @@ Feature: Restrict activity availability through date conditions
| id_availablefrom_month | 12 |
| id_availablefrom_year | 2050 |
| id_showavailability | 1 |
And I click on "id_availablefrom_enabled" "checkbox"
And I press "Save and return to course"
And I log out
When I log in as "student1"
......@@ -47,10 +49,7 @@ Feature: Restrict activity availability through date conditions
@javascript
Scenario: Show activity hidden to students when available until date is in past
Given I log in as "teacher1"
And I follow "Course 1"
And I turn editing mode on
And I add a "Assignment" to section "2"
Given I click on "id_availableuntil_enabled" "checkbox"
And I fill the moodle form with:
| Assignment name | Test assignment 2 |
| Description | This assignment is restricted by date |
......@@ -60,7 +59,6 @@ Feature: Restrict activity availability through date conditions
| id_availableuntil_month | 2 |
| id_availableuntil_year | 2013 |
| id_showavailability | 0 |
And I click on "id_availableuntil_enabled" "checkbox"
And I press "Save and return to course"
And I log out
When I log in as "student1"
......
......@@ -29,13 +29,18 @@ Feature: Restrict activity availability through grade conditions
| Description | Grade this assignment to revoke restriction on restricted assignment |
| assignsubmission_onlinetext_enabled | 1 |
| assignsubmission_file_enabled | 0 |
And I add a "Page" to section "2" and I fill the form with:
# Adding the page like this because id_availableform_enabled needs to be clicked to trigger the action.
And I add a "Page" to section "2"
And I expand all fieldsets
And I click on "id_availablefrom_enabled" "checkbox"
And I fill the moodle form with:
| Name | Test page name |
| Description | Restricted page, till grades in Grade assignment is at least 20% |
| Page content | Test page contents |
| id_conditiongradegroup_0_conditiongradeitemid | 2 |
| id_conditiongradegroup_0_conditiongrademin | 20 |
| id_showavailability | 1 |
And I press "Save and return to course"
And I log out
When I log in as "student1"
And I follow "Course 1"
......
......@@ -40,8 +40,10 @@ Feature: Add activities to courses
Scenario: Add an activity without the required fields
When I add a "Database" to section "3" and I fill the form with:
| Name | Test name |
And I press "Save and return to course"
Then I should see "Adding a new"
And I should see "Required"
And I press "Cancel"
Scenario: Add an activity to a course with Javascript disabled
Then I should see "Add a resource to section 'Topic 1'"
......
......@@ -67,15 +67,48 @@ class behat_course extends behat_base {
* @return Given[]
*/
public function i_create_a_course_with(TableNode $table) {
return array(
$steps = array(
new Given('I go to the courses management page'),
new Given('I should see the "'.get_string('categories').'" management page'),
new Given('I click on category "'.get_string('miscellaneous').'" in the management interface'),
new Given('I should see the "'.get_string('categoriesandcoures').'" management page'),
new Given('I click on "'.get_string('createnewcourse').'" "link" in the "#course-listing" "css_element"'),
new Given('I fill the moodle form with:', $table),
new Given('I press "' . get_string('savechanges') . '"')
new Given('I click on "'.get_string('createnewcourse').'" "link" in the "#course-listing" "css_element"')
);
// If the course format is one of the fields we change how we
// fill the form as we need to wait for the form to be set.
$rowshash = $table->getRowsHash();
$formatfieldrefs = array(get_string('format'), 'format', 'id_format');
foreach ($formatfieldrefs as $fieldref) {
if (!empty($rowshash[$fieldref])) {
$formatfield = $fieldref;
}
}
// Setting the format separately.
if (!empty($formatfield)) {
// Removing the format field from the TableNode.
$rows = $table->getRows();
$formatvalue = $rowshash[$formatfield];
foreach ($rows as $key => $row) {
if ($row[0] == $formatfield) {
unset($rows[$key]);
}
}
$table->setRows($rows);
$steps[] = new Given('I fill the moodle form with:', $table);
$steps[] = new Given('I select "' . $formatvalue . '" from "' . $formatfield . '"');
$steps[] = new Given('I wait until the page is ready');
} else {
$steps[] = new Given('I fill the moodle form with:', $table);
}
$steps[] = new Given('I press "' . get_string('savechanges') . '"');
return $steps;
}
/**
......@@ -181,10 +214,7 @@ class behat_course extends behat_base {
// Ensures the section exists.
$xpath = $this->section_exists($sectionnumber);
return array(
new Given('I click on "' . get_string('markthistopic') . '" "link" in the "' . $this->escape($xpath) . '" "xpath_element"'),
new Given('I wait "2" seconds')
);
return new Given('I click on "' . get_string('markthistopic') . '" "link" in the "' . $this->escape($xpath) . '" "xpath_element"');
}
/**
......@@ -199,10 +229,7 @@ class behat_course extends behat_base {
// Ensures the section exists.
$xpath = $this->section_exists($sectionnumber);
return array(
new Given('I click on "' . get_string('markedthistopic') . '" "link" in the "' . $this->escape($xpath) . '" "xpath_element"'),
new Given('I wait "2" seconds')
);
return new Given('I click on "' . get_string('markedthistopic') . '" "link" in the "' . $this->escape($xpath) . '" "xpath_element"');
}
/**
......@@ -215,9 +242,9 @@ class behat_course extends behat_base {
$showlink = $this->show_section_icon_exists($sectionnumber);
$showlink->click();
// It requires time.
if ($this->running_javascript()) {
$this->getSession()->wait(5000, false);
$this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
$this->i_wait_until_section_is_available($sectionnumber);
}
}
......@@ -231,9 +258,9 @@ class behat_course extends behat_base {
$hidelink = $this->hide_section_icon_exists($sectionnumber);
$hidelink->click();
// It requires time.
if ($this->running_javascript()) {
$this->getSession()->wait(5000, false);
$this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
$this->i_wait_until_section_is_available($sectionnumber);
}
}
......@@ -314,6 +341,11 @@ class behat_course extends behat_base {
$sectionxpath = $this->section_exists($sectionnumber);
// Preventive in case there is any action in progress.
// Adding it here because we are interacting (click) with
// the elements, not necessary when we just find().
$this->i_wait_until_section_is_available($sectionnumber);
// Section should be hidden.
$exception = new ExpectationException('The section is not hidden', $this->getSession());
$this->find('xpath', $sectionxpath . "[contains(concat(' ', normalize-space(@class), ' '), ' hidden ')]", $exception);
......@@ -338,9 +370,12 @@ class behat_course extends behat_base {
// Non-JS browsers can not click on img elements.
if ($this->running_javascript()) {
// Expanding the actions menu.
$actionsmenu = $this->find('css', "a[role='menuitem']", false, $activity);
$actionsmenu->click();
// Expanding the actions menu if it is not shown.
$classes = array_flip(explode(' ', $activity->getAttribute('class')));
if (empty($classes['action-menu-shown'])) {
$actionsmenu = $this->find('css', "a[role='menuitem']", false, $activity);
$actionsmenu->click();
}
// To check that the visibility is not clickable we check the funcionality rather than the applied style.
$visibilityiconnode = $this->find('css', 'a.editing_show img', false, $activity);
......@@ -349,6 +384,17 @@ class behat_course extends behat_base {
// We ensure that we still see the show icon.
$visibilityiconnode = $this->find('css', 'a.editing_show img', $visibilityexception, $activity);
// It is there only when running JS scenarios.
if ($this->running_javascript()) {
// Collapse the actions menu if it is displayed.
$classes = array_flip(explode(' ', $activity->getAttribute('class')));
if (!empty($classes['action-menu-shown'])) {
$actionsmenu = $this->find('css', "a[role='menuitem']", false, $activity);
$actionsmenu->click();
}
}
}
}
......@@ -543,8 +589,7 @@ class behat_course extends behat_base {
$activity = $this->escape($activityname);
return array(
new Given('I click on "' . get_string('edittitle') . '" "link" in the "' . $activity .'" activity'),
new Given('I fill in "title" with "' . $this->escape($newactivityname) . chr(10) . '"'),
new Given('I wait "2" seconds')
new Given('I fill in "title" with "' . $this->escape($newactivityname) . chr(10) . '"')
);
}
......@@ -572,6 +617,30 @@ class behat_course extends behat_base {
return new Given('I click on "a[role=\'menuitem\']" "css_element" in the "' . $this->escape($activityname) . '" activity');
}
/**
* Closes an activity actions menu if it is not already closed.
*
* @Given /^I close "(?P<activity_name_string>(?:[^"]|\\")*)" actions menu$/
* @throws DriverException The step is not available when Javascript is disabled
* @param string $activityname
* @return Given
*/
public function i_close_actions_menu($activityname) {
if (!$this->running_javascript()) {
throw new DriverException('Activities actions menu not available when Javascript is disabled');
}
// If it is already closed we do nothing.
$activitynode = $this->get_activity_node($activityname);
$classes = array_flip(explode(' ', $activitynode->getAttribute('class')));
if (empty($classes['action-menu-shown'])) {
return;
}
return new Given('I click on "a[role=\'menuitem\']" "css_element" in the "' . $this->escape($activityname) . '" activity');
}
/**
* Indents to the right the activity or resource specified by it's name. Editing mode should be on.
*
......@@ -588,10 +657,6 @@ class behat_course extends behat_base {
}
$steps[] = new Given('I click on "' . get_string('moveright') . '" "link" in the "' . $activity . '" activity');
if ($this->running_javascript()) {
$steps[] = new Given('I wait "2" seconds');
}
return $steps;
}
......@@ -611,10 +676,6 @@ class behat_course extends behat_base {
}
$steps[] = new Given('I click on "' . get_string('moveleft') . '" "link" in the "' . $activity . '" activity');
if ($this->running_javascript()) {
$steps[] = new Given('I wait "2" seconds');
}
return $steps;
}
......@@ -640,8 +701,6 @@ class behat_course extends behat_base {
$this->getSession()->getDriver()->getWebDriverSession()->accept_alert();
$this->getSession()->wait(2 * 1000, false);
} else {
// With JS disabled.
......@@ -668,10 +727,7 @@ class behat_course extends behat_base {
$steps[] = new Given('I open "' . $activity . '" actions menu');
}
$steps[] = new Given('I click on "' . get_string('duplicate') . '" "link" in the "' . $activity . '" activity');
if ($this->running_javascript()) {
// Temporary wait until MDL-41030 lands.
$steps[] = new Given('I wait "4" seconds');
} else {
if (!$this->running_javascript()) {
$steps[] = new Given('I press "' . get_string('continue') .'"');
$steps[] = new Given('I press "' . get_string('duplicatecontcourse') .'"');
}
......@@ -691,12 +747,22 @@ class behat_course extends behat_base {
$steps = array();
$activity = $this->escape($activityname);
$activityliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($activityname);
if ($this->running_javascript()) {
$steps[] = new Given('I duplicate "' . $activity . '" activity');
// We wait until the AJAX request finishes and the section is visible again.
$hiddenlightboxxpath = "//li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')][contains(., $activityliteral)]" .
"/ancestor::li[contains(concat(' ', normalize-space(@class), ' '), ' section ')]" .
"/descendant::div[contains(concat(' ', @class, ' '), ' lightbox ')][contains(@style, 'display: none')]";
$steps[] = new Given('I wait until the page is ready');
$steps[] = new Given('I wait until "' . $this->escape($hiddenlightboxxpath) .'" "xpath_element" exists');
// Close the original activity actions menu.
$steps[] = new Given('I close "' . $activity . '" actions menu');
// Determine the future new activity xpath from the former one.
$activityliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($activityname);
$duplicatedxpath = "//li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')][contains(., $activityliteral)]" .
"/following-sibling::li";
$duplicatedactionsmenuxpath = $duplicatedxpath . "/descendant::a[@role='menuitem']";
......@@ -717,6 +783,32 @@ class behat_course extends behat_base {
return $steps;
}
/**
* Waits until the section is available to interact with it. Useful when the section is performing an action and the section is overlayed with a loading layout.
*
* Using the protected method as this method will be usually
* called by other methods which are not returning a set of
* steps and performs the actions directly, so it would not
* be executed if it returns another step.
*
* Hopefully we would not require test writers to use this step
* and we will manage it from other step definitions.
*
* @Given /^I wait until section "(?P<section_number>\d+)" is available$/
* @param int $sectionnumber
* @return void
*/
public function i_wait_until_section_is_available($sectionnumber) {
// Looks for a hidden lightbox or a non-existent lightbox in that section.
$sectionxpath = $this->section_exists($sectionnumber);
$hiddenlightboxxpath = $sectionxpath . "/descendant::div[contains(concat(' ', @class, ' '), ' lightbox ')][contains(@style, 'display: none')]" .
" | " .
$sectionxpath . "[count(child::div[contains(@class, 'lightbox')]) = 0]";
$this->ensure_element_exists($hiddenlightboxxpath, 'xpath_element');
}
/**
* Clicks on the specified element of the activity. You should be in the course page with editing mode turned on.
*
......@@ -882,6 +974,18 @@ class behat_course extends behat_base {
return $this->find('xpath', $xpath);
}
/**
* Gets the activity instance name from the activity node.
*
* @throws ElementNotFoundException
* @param NodeElement $activitynode
* @return string
*/
protected function get_activity_name($activitynode) {
$instancenamenode = $this->find('xpath', "//span[contains(concat(' ', normalize-space(@class), ' '), ' instancename ')]", false, $activitynode);
return $instancenamenode->getText();
}
/**
* Returns whether the user can edit the course contents or not.
*
......
@core @core_course @test
@core @core_course
Feature: Course category management interface performs as expected
In order to test JS enhanced display of categories and subcategories.
As a moodle admin
......
@core @core_course
@core @core_course @_alerts
Feature: Course activity controls works as expected
In order to manage my course's activities
As a teacher
......@@ -59,11 +59,16 @@ Feature: Course activity controls works as expected
And I click on "Edit settings" "link" in the "Test forum name 1" activity
And I should see "Updating Forum"
And I should see "Display description on course page"
And I press "Save and return to course"
And I fill the moodle form with:
| Forum name | Just to check that I can edit the name |
| Description | Just to check that I can edit the description |
| Display description on course page | 1 |
And I click on "Cancel" "button"
And "#section-2" "css_element" <should_see_other_sections> exists
And I open "Test forum name 1" actions menu
And I click on "Hide" "link" in the "Test forum name 1" activity
And "#section-2" "css_element" <should_see_other_sections> exists
And I close "Test forum name 1" actions menu
And I duplicate "Test forum name 2" activity editing the new copy with:
| Forum name | Edited test forum name 2 |
And "#section-2" "css_element" <should_see_other_sections> exists
......
......@@ -67,7 +67,7 @@ class behat_groups extends behat_base {
$this->find_button(get_string('adduserstogroup', 'group'))->click();
// Wait for add/remove members page to be loaded.
$this->getSession()->wait(self::TIMEOUT, '(document.readyState === "complete")');
$this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
// Getting the option and selecting it.
$select = $this->find_field('addselect');
......@@ -80,7 +80,7 @@ class behat_groups extends behat_base {
$this->find_button(get_string('add'))->click();
// Wait for the page to load.
$this->getSession()->wait(self::TIMEOUT, '(document.readyState === "complete")');
$this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
// Returning to the main groups page.
$this->find_button(get_string('backtogroups', 'group'))->click();
......
@core @core_group
@core @core_group @_only_local
Feature: Importing of groups and groupings
In order to import groups and grouping
As a teacher
......
......@@ -55,7 +55,22 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
/**
* The timeout for each Behat step (load page, wait for an element to load...).
*/
const TIMEOUT = 6;
const TIMEOUT = 3;
/**
* And extended timeout for specific cases.
*/
const EXTENDED_TIMEOUT = 10;
/**
* Number of retries to wait for the editor to be ready.
*/
const WAIT_FOR_EDITOR_RETRIES = 10;
/**
* The JS code to check that the page is ready.
*/