Merge branch 'MDL-42625_master' of git://github.com/dmonllao/moodle

This commit is contained in:
Eloy Lafuente (stronk7) 2013-12-10 23:29:06 +01:00
commit ebc77165a4
51 changed files with 991 additions and 335 deletions

View File

@ -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.

View File

@ -42,5 +42,4 @@ Feature: Upload users
And I expand "Users" node
And I follow "Groups"
And I select "Section 1 (1)" from "groups"
And I wait "4" seconds
And the "members" select box should contain "Tom Jones"

View File

@ -39,7 +39,7 @@ Feature: Page contents assertions
| Course 1 | C1 | 0 |
And I log in as "admin"
And I follow "Course 1"
When I click on "Move this to the dock" "button" in the "Administration" "block"
When I dock "Administration" block
Then I should not see "Question bank" in the "region-pre" "region"
And I click on "//div[@id='dock']/descendant::h2[normalize-space(.)='Administration']" "xpath_element"
@ -49,5 +49,5 @@ Feature: Page contents assertions
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And I log in as "admin"
When I click on "Move this to the dock" "button" in the "Administration" "block"
When I dock "Administration" block
Then I should not see "Turn editing on" in the "region-pre" "region"

View File

@ -230,8 +230,6 @@ Feature: Set up contextual data for tests
Then the "groups" select box should contain "Group 1 (1)"
And the "groups" select box should contain "Group 2 (1)"
And I select "Group 1 (1)" from "groups"
And I wait "5" seconds
And the "members" select box should contain "Student 1"
And I select "Group 2 (1)" from "groups"
And I wait "5" seconds
And the "members" select box should contain "Student 2"

View File

@ -38,7 +38,7 @@ Feature: Backup Moodle courses
And I press "Continue"
And I click on "Continue" "button" in the ".bcs-current-course" "css_element"
And "//div[contains(concat(' ', normalize-space(@class), ' '), ' fitem ')][contains(., 'Include calendar events')]/descendant::img" "xpath_element" should exists
And I check "Include course logs"
And "Include course logs" "checkbox" should exists
And I press "Next"
@javascript

View File

@ -56,27 +56,32 @@ class behat_backup extends behat_base {
// Go to homepage.
$this->getSession()->visit($this->locate_path('/'));
$this->wait();
// Click the course link.
$this->find_link($backupcourse)->click();
$this->wait();
// Click the backup link.
$this->find_link(get_string('backup'))->click();
$this->wait();
// Initial settings.
$this->fill_backup_restore_form($options);
$this->find_button(get_string('backupstage1action', 'backup'))->press();
$this->wait();
// Schema settings.
$this->fill_backup_restore_form($options);
$this->find_button(get_string('backupstage2action', 'backup'))->press();
$this->wait();
// Confirmation and review, backup filename can also be specified.
$this->fill_backup_restore_form($options);
$this->find_button(get_string('backupstage4action', 'backup'))->press();
// Waiting for it to finish.
$this->wait(10);
$this->wait(self::EXTENDED_TIMEOUT);
// Last backup continue button.
$this->find_button(get_string('backupstage16action', 'backup'))->press();
@ -101,12 +106,15 @@ class behat_backup extends behat_base {
// Go to homepage.
$this->getSession()->visit($this->locate_path('/'));
$this->wait();
// Click the course link.
$this->find_link($tocourse)->click();
$this->wait();
// Click the import link.
$this->find_link(get_string('import'))->click();
$this->wait();
// Select the course.
$exception = new ExpectationException('"' . $fromcourse . '" course not found in the list of courses to import from', $this->getSession());
@ -121,18 +129,21 @@ class behat_backup extends behat_base {
$radionode->click();
$this->find_button(get_string('continue'))->press();
$this->wait();
// Initial settings.
$this->fill_backup_restore_form($options);
$this->find_button(get_string('importbackupstage1action', 'backup'))->press();
$this->wait();
// Schema settings.
$this->fill_backup_restore_form($options);
$this->find_button(get_string('importbackupstage2action', 'backup'))->press();
$this->wait();
// Run it.
$this->find_button(get_string('importbackupstage4action', 'backup'))->press();
$this->wait();
$this->wait(self::EXTENDED_TIMEOUT);
// Continue and redirect to 'to' course.
$this->find_button(get_string('continue'))->press();
@ -294,17 +305,22 @@ class behat_backup extends behat_base {
// Settings.
$this->fill_backup_restore_form($options);
$this->find_button(get_string('restorestage4action', 'backup'))->press();
$this->wait();
// Schema.
$this->fill_backup_restore_form($options);
$this->find_button(get_string('restorestage8action', 'backup'))->press();
$this->wait();
// 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();
// Long wait when waiting for the restore to finish.
$this->wait(self::EXTENDED_TIMEOUT);
}
/**
@ -325,11 +341,15 @@ class behat_backup extends behat_base {
return;
}
// Wait for the page to be loaded and the JS ready.
$this->wait();
// If we find any of the provided options in the current form we should set the value.
$datahash = $options->getRowsHash();
foreach ($datahash as $locator => $value) {
try {
// Using $this->find* to enforce stability over speed.
$fieldnode = $this->find_field($locator);
$field = behat_field_manager::get_form_field($fieldnode, $this->getSession());
$field->set_value($value);
@ -341,9 +361,9 @@ class behat_backup extends behat_base {
}
/**
* Waits until the DOM is ready.
* Waits until the DOM and the page Javascript code is ready.
*
* @param int To override the default timeout
* @param int $timeout The number of seconds that we wait.
* @return void
*/
protected function wait($timeout = false) {
@ -355,7 +375,8 @@ class behat_backup extends behat_base {
if (!$timeout) {
$timeout = self::TIMEOUT;
}
$this->getSession()->wait($timeout, '(document.readyState === "complete")');
$this->getSession()->wait($timeout * 1000, self::PAGE_READY_JS);
}
}

View File

@ -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:

View File

@ -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 |

View File

@ -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);
}

View File

@ -58,14 +58,11 @@ Feature: View my courses in navigation block
And I should see "cat3" in the "Navigation" "block"
And I should not see "cat2" in the "Navigation" "block"
And I expand "cat3" node
And I wait "2" seconds
And I should see "cat31" in the "Navigation" "block"
And I should see "cat33" in the "Navigation" "block"
And I should not see "cat32" in the "Navigation" "block"
And I expand "cat31" node
And I wait "2" seconds
And I should see "c31" in the "Navigation" "block"
And I expand "cat33" node
And I wait "2" seconds
And I should see "c331" in the "Navigation" "block"
And I should not see "c332" in the "Navigation" "block"

View File

@ -58,6 +58,20 @@ class behat_blocks extends behat_base {
return $steps;
}
/**
* Docks a block. Editing mode should be previously enabled.
*
* @Given /^I dock "(?P<block_name_string>(?:[^"]|\\")*)" block$/
* @param string $blockname
* @return Given
*/
public function i_dock_block($blockname) {
// Looking for both title and alt.
$xpath = "//input[@type='image'][@title='" . get_string('dockblock', 'block', $blockname) . "' or @alt='" . get_string('addtodock', 'block') . "']";
return new Given('I click on " ' . $xpath . '" "xpath_element" in the "' . $this->escape($blockname) . '" "block"');
}
/**
* Opens a block's actions menu if it is not already opened.
*

View File

@ -33,11 +33,9 @@ Feature: Comment on a blog entry
And I follow "Comments (0)"
When I fill in "content" with "$My own >nasty< \"string\"!"
And I follow "Save comment"
And I wait "4" seconds
Then I should see "$My own >nasty< \"string\"!"
And I fill in "content" with "Another $Nasty <string?>"
And I follow "Save comment"
And I wait "4" seconds
And I should see "Comments (2)" in the ".comment-link" "css_element"
@javascript
@ -53,8 +51,8 @@ Feature: Comment on a blog entry
And I follow "Comments (0)"
And I fill in "content" with "$My own >nasty< \"string\"!"
And I follow "Save comment"
And I wait "4" seconds
When I click on ".comment-delete a" "css_element"
# Waiting for the animation to finish.
And I wait "4" seconds
Then I should not see "$My own >nasty< \"string\"!"
And I follow "Blog post from user 1"
@ -73,5 +71,4 @@ Feature: Comment on a blog entry
When I follow "Comments (0)"
And I fill in "content" with "$My own >nasty< \"string\"!"
And I follow "Save comment"
And I wait "4" seconds
Then I should see "$My own >nasty< \"string\"!"

View File

@ -37,7 +37,6 @@ Feature: Allow students to manually mark an activity as complete
And I log in as "student1"
And I follow "Course 1"
And I press "Mark as complete: Test forum name"
And I wait "3" seconds
And I log out
And I log in as "teacher1"
And I follow "Course 1"

View File

@ -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
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 log in as "teacher1"
And I follow "Course 1"
And I turn editing mode on
And I add a "Assignment" to section "1"
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"

View File

@ -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"

View File

@ -29,14 +29,12 @@ Feature: Toggle activities groups mode from the course page
Then "No groups (Click to change)" "link" should exists
And "//a/child::img[contains(@src, 'groupn')]" "xpath_element" should exists
And I click on "No groups (Click to change)" "link" in the "Test forum name" activity
And I wait "3" seconds
And "Separate groups (Click to change)" "link" should exists
And "//a/child::img[contains(@src, 'groups')]" "xpath_element" should exists
And I reload the page
And "Separate groups (Click to change)" "link" should exists
And "//a/child::img[contains(@src, 'groups')]" "xpath_element" should exists
And I click on "Separate groups (Click to change)" "link" in the "Test forum name" activity
And I wait "3" seconds
And "Visible groups (Click to change)" "link" should exists
And "//a/child::img[contains(@src, 'groupv')]" "xpath_element" should exists
And I reload the page

View File

@ -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'"

View File

@ -67,15 +67,50 @@ 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);
// Adding a forced wait until editors are loaded as otherwise selenium sometimes tries clicks on the
// format field when the editor is being rendered and the click misses the field coordinates.
$steps[] = new Given('I wait until the editors are loaded');
$steps[] = new Given('I select "' . $formatvalue . '" from "' . $formatfield . '"');
$steps[] = new Given('I fill the moodle form with:', $table);
} else {
$steps[] = new Given('I fill the moodle form with:', $table);
}
$steps[] = new Given('I press "' . get_string('savechanges') . '"');
return $steps;
}
/**
@ -181,10 +216,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 +231,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 +244,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 +260,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 +343,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 +372,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 +386,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 +591,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 +619,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 +659,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 +678,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 +703,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 +729,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 +749,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 +785,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 +976,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.
*

View File

@ -1,4 +1,4 @@
@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

View File

@ -1,4 +1,4 @@
@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

View File

@ -91,7 +91,7 @@ function UpdatableMembersCombo(wwwRoot, courseId) {
this.courseId = courseId;
this.connectCallback = {
success: function(o) {
success: function(t, o) {
if (o.responseText !== undefined) {
var selectEl = document.getElementById("members");
@ -124,7 +124,7 @@ function UpdatableMembersCombo(wwwRoot, courseId) {
removeLoaderImgs("membersloader", "memberslabel");
},
failure: function(o) {
failure: function() {
removeLoaderImgs("membersloader", "memberslabel");
}
@ -185,9 +185,13 @@ UpdatableMembersCombo.prototype.refreshMembers = function () {
if(singleSelection) {
var sUrl = this.wwwRoot+"/group/index.php?id="+this.courseId+"&group="+groupId+"&act_ajax_getmembersingroup";
var callback = this.connectCallback;
YUI().use('yui2-connection', function (Y) {
Y.YUI2.util.Connect.asyncRequest("GET", sUrl, callback, null);
var self = this;
YUI().use('io', function (Y) {
Y.io(sUrl, {
method: 'GET',
context: this,
on: self.connectCallback
});
});
}
};
@ -271,4 +275,4 @@ function init_add_remove_members_page(Y) {
addselect = document.getElementById('addselect');
addselect.onchange = updateUserSummary;
}
}

View File

@ -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();

View File

@ -40,12 +40,10 @@ Feature: Organize students into groups
And I add "student2" user to "Group 2" group
And I add "student3" user to "Group 2" group
Then I select "Group 1 (2)" from "groups"
And I wait "5" seconds
And the "members" select box should contain "Student 0"
And the "members" select box should contain "Student 1"
And the "members" select box should not contain "Student 2"
And I select "Group 2 (2)" from "groups"
And I wait "5" seconds
And the "members" select box should contain "Student 2"
And the "members" select box should contain "Student 3"
And the "members" select box should not contain "Student 0"

View File

@ -1,4 +1,4 @@
@core @core_group
@core @core_group @_only_local
Feature: Importing of groups and groupings
In order to import groups and grouping
As a teacher

View File

@ -55,7 +55,17 @@ 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;
/**
* The JS code to check that the page is ready.
*/
const PAGE_READY_JS = '(M && M.util && M.util.pending_js && !Boolean(M.util.pending_js.length)) && (document.readyState === "complete")';
/**
* Locates url, based on provided path.
@ -420,4 +430,194 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
return get_class($this->getSession()->getDriver()) !== 'Behat\Mink\Driver\GoutteDriver';
}
/**
* Spins around an element until it exists
*
* @throws ExpectationException
* @param string $element
* @param string $selectortype
* @return void
*/
protected function ensure_element_exists($element, $selectortype) {
// Getting the behat selector & locator.
list($selector, $locator) = $this->transform_selector($selectortype, $element);
// Exception if it timesout and the element is still there.
$msg = 'The "' . $element . '" element does not exist and should exist';
$exception = new ExpectationException($msg, $this->getSession());
// It will stop spinning once the find() method returns true.
$this->spin(
function($context, $args) {
// We don't use behat_base::find as it is already spinning.
if ($context->getSession()->getPage()->find($args['selector'], $args['locator'])) {
return true;
}
return false;
},
array('selector' => $selector, 'locator' => $locator),
self::EXTENDED_TIMEOUT,
$exception,
true
);
}
/**
* Spins until the element does not exist
*
* @throws ExpectationException
* @param string $element
* @param string $selectortype
* @return void
*/
protected function ensure_element_does_not_exist($element, $selectortype) {
// Getting the behat selector & locator.
list($selector, $locator) = $this->transform_selector($selectortype, $element);
// Exception if it timesout and the element is still there.
$msg = 'The "' . $element . '" element exists and should not exist';
$exception = new ExpectationException($msg, $this->getSession());
// It will stop spinning once the find() method returns false.
$this->spin(
function($context, $args) {
// We don't use behat_base::find() as we are already spinning.
if (!$context->getSession()->getPage()->find($args['selector'], $args['locator'])) {
return true;
}
return false;
},
array('selector' => $selector, 'locator' => $locator),
self::EXTENDED_TIMEOUT,
$exception,
true
);
}
/**
* Ensures that the provided node is visible and we can interact with it.
*
* @throws ExpectationException
* @param NodeElement $node
* @return void Throws an exception if it times out without the element being visible
*/
protected function ensure_node_is_visible($node) {
if (!$this->running_javascript()) {
return;
}
// Exception if it timesout and the element is still there.
$msg = 'The "' . $node->getXPath() . '" xpath node is not visible and it should be visible';
$exception = new ExpectationException($msg, $this->getSession());
// It will stop spinning once the isVisible() method returns true.
$this->spin(
function($context, $args) {
if ($args->isVisible()) {
return true;
}
return false;
},
$node,
self::EXTENDED_TIMEOUT,
$exception,
true
);
}
/**
* Ensures that the provided element is visible and we can interact with it.
*
* Returns the node in case other actions are interested in using it.
*
* @throws ExpectationException
* @param string $element
* @param string $selectortype
* @return NodeElement Throws an exception if it times out without being visible
*/
protected function ensure_element_is_visible($element, $selectortype) {
if (!$this->running_javascript()) {
return;
}
$node = $this->get_selected_node($selectortype, $element);
$this->ensure_node_is_visible($node);
return $node;
}
/**
* Ensures that all the page's editors are loaded.
*
* This method is expensive as it waits for .mceEditor CSS
* so use with caution and only where there will be editors.
*
* @throws ElementNotFoundException
* @throws ExpectationException
* @return void
*/
protected function ensure_editors_are_loaded() {
if (!$this->running_javascript()) {
return;
}
// If there are no editors we don't need to wait.
try {
$this->find('css', '.mceEditor');
} catch (ElementNotFoundException $e) {
return;
}
// Exception if it timesout and the element is not appearing.
$msg = 'The editors are not completely loaded';
$exception = new ExpectationException($msg, $this->getSession());
// Here we know that there are .mceEditor editors in the page and we will
// probably need to interact with them, if we use tinyMCE JS var before
// it exists it will throw an exception and we want to catch it until all
// the page's editors are ready to interact with them.
$this->spin(
function($context) {
// It may return 0 if tinyMCE is loaded but not the instances, so we just loop again.
$neditors = $context->getSession()->evaluateScript('return tinyMCE.editors.length;');
if ($neditors == 0) {
return false;
}
// It may be there but not ready.
$iframeready = $context->getSession()->evaluateScript('
var readyeditors = new Array;
for (editorid in tinyMCE.editors) {
if (tinyMCE.editors[editorid].getDoc().readyState === "complete") {
readyeditors[editorid] = editorid;
}
}
if (tinyMCE.editors.length === readyeditors.length) {
return "complete";
}
return "";
');
// Now we know that the editors are there.
if ($iframeready) {
return true;
}
// Loop again if it is not ready.
return false;
},
false,
self::EXTENDED_TIMEOUT,
$exception,
true
);
}
}

View File

@ -93,6 +93,7 @@ class behat_files extends behat_base {
$classname = 'fp-file-' . $action;
$button = $this->find('css', '.moodle-dialogue-focused button.' . $classname, $exception);
$this->ensure_node_is_visible($button);
$button->click();
}
@ -148,13 +149,14 @@ class behat_files extends behat_base {
$locatorprefix .
"//descendant::*[self::div | self::a][contains(concat(' ', normalize-space(@class), ' '), ' fp-file ')]" .
"[normalize-space(.)=$name]" .
"//descendant::div[contains(concat(' ', normalize-space(@class), ' '), ' fp-thumbnail ')]",
"//descendant::div[contains(concat(' ', normalize-space(@class), ' '), ' fp-filename-field ')]",
false,
$containernode
);
}
// Click opens the contextual menu when clicking on files.
$this->ensure_node_is_visible($node);
$node->click();
}
@ -179,6 +181,7 @@ class behat_files extends behat_base {
// 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();
// Getting the repository link and opening it.
@ -197,12 +200,16 @@ class behat_files extends behat_base {
);
// Selecting the repo.
$this->ensure_node_is_visible($repositorylink);
$repositorylink->click();
}
/**
* Waits until the file manager modal windows are closed.
*
* This method is not used by any of our step definitions,
* keeping it here for users already using it.
*
* @throws ExpectationException
* @return void
*/
@ -220,6 +227,9 @@ class behat_files extends behat_base {
/**
* Checks that the file manager contents are not being updated.
*
* This method is not used by any of our step definitions,
* keeping it here for users already using it.
*
* @throws ExpectationException
* @param NodeElement $filepickernode The file manager DOM node
* @return void
@ -243,9 +253,6 @@ class behat_files extends behat_base {
$exception,
$filepickernode
);
// After removing the class FileManagerHelper.view_files() performs other actions.
$this->getSession()->wait(4 * 1000, false);
}
}

View File

@ -48,19 +48,38 @@ class behat_form_editor extends behat_form_field {
*/
public function set_value($value) {
// Get tinyMCE editor id if it exists.
if ($editorid = $this->get_editor_id()) {
$lastexception = null;
// Set the value to the iframe and save it to the textarea.
$this->session->executeScript('
tinyMCE.get("'.$editorid.'").setContent("' . $value . '");
tinyMCE.get("'.$editorid.'").save();
');
// We want the editor to be ready, otherwise the value can not
// be set and an exception is thrown.
for ($i = 0; $i < behat_base::EXTENDED_TIMEOUT; $i++) {
try {
// Get tinyMCE editor id if it exists.
if ($editorid = $this->get_editor_id()) {
} else {
// Set the value to a textarea otherwise.
parent::set_value($value);
// Set the value to the iframe and save it to the textarea.
$this->session->executeScript('
tinyMCE.get("'.$editorid.'").setContent("' . $value . '");
tinyMCE.get("'.$editorid.'").save();
');
} else {
// Set the value to a textarea otherwise.
parent::set_value($value);
}
return;
} catch (Exception $e) {
// Catching any kind of exception and ignoring it until times out.
$lastexception = $e;
// Waiting 0.1 seconds.
usleep(100000);
}
}
// If it is not available we throw the last exception.
throw $lastexception;
}
/**
@ -70,14 +89,45 @@ class behat_form_editor extends behat_form_field {
*/
public function get_value() {
// Get tinyMCE editor id if it exists.
if ($editorid = $this->get_editor_id()) {
// Can be be a string value or an exception depending whether the editor loads or not.
$lastoutcome = '';
// Save the current iframe value in case default value has been edited.
$this->session->executeScript('tinyMCE.get("'.$editorid.'").save();');
// We want the editor to be ready to return the correct value, sometimes the
// page loads too fast and the returned value may be '' if the editor didn't
// have enough time to load completely despite having a different value.
for ($i = 0; $i < behat_base::EXTENDED_TIMEOUT; $i++) {
try {
// Get tinyMCE editor id if it exists.
if ($editorid = $this->get_editor_id()) {
// Save the current iframe value in case default value has been edited.
$this->session->executeScript('tinyMCE.get("'.$editorid.'").save();');
}
$lastoutcome = $this->field->getValue();
// We only want to wait until it times out if the value is empty.
if ($lastoutcome != '') {
return $lastoutcome;
}
} catch (Exception $e) {
// Catching any kind of exception and ignoring it until times out.
$lastoutcome = $e;
// Waiting 0.1 seconds.
usleep(100000);
}
}
return $this->field->getValue();
// If it is not available we throw the last exception.
if (is_a($lastoutcome, 'Exception')) {
throw $lastoutcome;
}
// Return the value if there are no exceptions it will be '' at this point
return $lastoutcome;
}
/**
@ -87,7 +137,7 @@ class behat_form_editor extends behat_form_field {
* can not execute Javascript, also some Moodle settings disables the HTML
* editor.
*
* @return mixed The id of the editor of false if is not available
* @return mixed The id of the editor of false if it is not available
*/
protected function get_editor_id() {
@ -95,7 +145,7 @@ class behat_form_editor extends behat_form_field {
try {
$available = $this->session->evaluateScript('return (typeof tinyMCE != "undefined")');
// Also checking that it exist a tinyMCE editor for the requested field.
// Also checking that it exists a tinyMCE editor for the requested field.
$editorid = $this->field->getAttribute('id');
$available = $this->session->evaluateScript('return (typeof tinyMCE.get("'.$editorid.'") != "undefined")');

View File

@ -137,6 +137,7 @@ class behat_form_field {
$classname = 'behat_form_select';
} else {
// We can not provide a closer field type.
return false;
}
@ -154,4 +155,24 @@ class behat_form_field {
return get_class($this->session->getDriver()) !== 'Behat\Mink\Driver\GoutteDriver';
}
/**
* Gets the field internal id used by selenium wire protocol.
*
* Only available when running_javascript().
*
* @throws coding_exception
* @return int
*/
protected function get_internal_field_id() {
if (!$this->running_javascript()) {
throw new coding_exception('You can only get an internal ID using the selenium driver.');
}
return $this->session->
getDriver()->
getWebDriverSession()->
element('xpath', $this->field->getXPath())->
getID();
}
}

View File

@ -40,40 +40,82 @@ class behat_form_select extends behat_form_field {
/**
* Sets the value of a single select.
*
* Seems an easy select, but there are lots of combinations
* of browsers and operative systems and each one manages the
* autosubmits and the multiple option selects in a diferent way.
*
* @param string $value
* @return void
*/
public function set_value($value) {
// In some browsers we select an option and it triggers all the
// autosubmits and works as expected but not in all of them, so we
// try to catch all the possibilities to make this function work as
// expected.
// Get the internal id of the element we are going to click.
// This kind of internal IDs are only available in the selenium wire
// protocol, so only available using selenium drivers, phantomjs and family.
if ($this->running_javascript()) {
$currentelementid = $this->get_internal_field_id();
}
// Here we select an option.
$this->field->selectOption($value);
// Adding a click as Selenium requires it to fire some JS events.
if ($this->running_javascript()) {
// With JS disabled this is enough and we finish here.
if (!$this->running_javascript()) {
return;
}
// In some browsers the selectOption actions can perform a page reload
// so we need to ensure the element is still available to continue interacting
// with it. We don't wait here.
if (!$this->session->getDriver()->find($this->field->getXpath())) {
// With JS enabled we add more clicks as some selenium
// drivers requires it to fire JS events.
// In some browsers the selectOption actions can perform a form submit or reload page
// so we need to ensure the element is still available to continue interacting
// with it. We don't wait here.
$selectxpath = $this->field->getXpath();
if (!$this->session->getDriver()->find($selectxpath)) {
return;
}
// We also check the selenium internal element id, if it have changed
// we are dealing with an autosubmit that was already executed, and we don't to
// execute anything else as the action we wanted was already performed.
if ($currentelementid != $this->get_internal_field_id()) {
return;
}
// We also check that the option is still there. We neither wait.
$valueliteral = $this->session->getSelectorsHandler()->xpathLiteral($value);
$optionxpath = $selectxpath . "/descendant::option[(./@value=$valueliteral or normalize-space(.)=$valueliteral)]";
if (!$this->session->getDriver()->find($optionxpath)) {
return;
}
// Single select sometimes needs an extra click in the option.
if (!$this->field->hasAttribute('multiple')) {
// Using the driver direcly because Element methods are messy when dealing
// with elements inside containers.
$optionnodes = $this->session->getDriver()->find($optionxpath);
if ($optionnodes) {
current($optionnodes)->click();
}
} else {
// Multiple ones needs the click in the select.
$this->field->click();
// We ensure that the option is still there.
if (!$this->session->getDriver()->find($optionxpath)) {
return;
}
// Single select needs an extra click in the option.
if (!$this->field->hasAttribute('multiple')) {
$value = $this->session->getSelectorsHandler()->xpathLiteral($value);
// Using the driver direcly because Element methods are messy when dealing
// with elements inside containers.
$optionxpath = $this->field->getXpath() .
"/descendant::option[(./@value=$value or normalize-space(.)=$value)]";
$optionnodes = $this->session->getDriver()->find($optionxpath);
if ($optionnodes) {
current($optionnodes)->click();
}
} else {
// Multiple ones needs the click in the select.
$this->field->click();
}
// Repeating the select as some drivers (chrome that I know) are moving
// to another option after the general select field click above.
$this->field->selectOption($value);
}
}

View File

@ -43,6 +43,8 @@ M.editor_tinymce.init_editor = function(Y, editorid, options) {
};
M.editor_tinymce.initialised = true;
M.util.js_pending('editors');
options.oninit = "M.editor_tinymce.init_callback";
}
M.editor_tinymce.editor_options[editorid] = options;
@ -86,6 +88,10 @@ M.editor_tinymce.init_editor = function(Y, editorid, options) {
}
};
M.editor_tinymce.init_callback = function() {
M.util.js_complete('editors');
}
M.editor_tinymce.init_filepicker = function(Y, editorid, options) {
M.editor_tinymce.filepicker_options[editorid] = options;
};

View File

@ -45,6 +45,7 @@ Feature: Add or remove items from the TinyMCE editor toolbar
Given I follow "Course 1"
And I turn editing mode on
When I add a "Database" to section "1"
And I wait until "#id_introeditor_tbl" "css_element" exists
Then "#id_introeditor_tbl .mce_bold" "css_element" should exists
And "#id_introeditor_tbl .mce_anchor" "css_element" should not exists
And I press "Cancel"

View File

@ -755,6 +755,76 @@ M.util.init_block_hider = function(Y, config) {
});
};
/**
* @var pending_js - The keys are the list of all pending js actions.
* @type Object
*/
M.util.pending_js = [];
M.util.complete_js = [];
/**
* Register any long running javascript code with a unique identifier.
* Should be followed with a call to js_complete with a matching
* idenfitier when the code is complete. May also be called with no arguments
* to test if there is any js calls pending. This is relied on by behat so that
* it can wait for all pending updates before interacting with a page.
* @param String uniqid - optional, if provided,
* registers this identifier until js_complete is called.
* @return boolean - True if there is any pending js.
*/
M.util.js_pending = function(uniqid) {
if (uniqid !== false) {
M.util.pending_js.push(uniqid);
}
return M.util.pending_js.length;
};
/**
* Register listeners for Y.io start/end so we can wait for them in behat.
*/
M.util.js_watch_io = function() {
YUI.add('moodle-core-io', function(Y) {
Y.on('io:start', function(id) {
M.util.js_pending('io:' + id);
});
Y.on('io:end', function(id) {
M.util.js_complete('io:' + id);
});
});
YUI.applyConfig({
modules: {
'moodle-core-io': {
after: ['io-base']
},
'io-base': {
requires: ['moodle-core-io'],
}
}
});
};
// Start this asap.
M.util.js_pending('init');
M.util.js_watch_io();
/**
* Unregister any long running javascript code by unique identifier.
* This function should form a matching pair with js_pending
*
* @param String uniqid - required, unregisters this identifier
* @return boolean - True if there is any pending js.
*/
M.util.js_complete = function(uniqid) {
var index = M.util.pending_js.indexOf(uniqid);
if (index >= 0) {
M.util.complete_js.push(M.util.pending_js.splice(index, 1));
}
return M.util.pending_js.length;
};
/**
* Returns a string registered in advance for usage in JavaScript
*

View File

@ -1043,14 +1043,18 @@ class page_requirements_manager {
public function js_init_code($jscode, $ondomready = false, array $module = null) {
$jscode = trim($jscode, " ;\n"). ';';
$uniqid = html_writer::random_id();
$startjs = " M.util.js_pending('" . $uniqid . "');";
$endjs = " M.util.js_complete('" . $uniqid . "');";
if ($module) {
$this->js_module($module);
$modulename = $module['name'];
$jscode = "Y.use('$modulename', function(Y) { $jscode });";
$jscode = "$startjs Y.use('$modulename', function(Y) { $jscode $endjs });";
}
if ($ondomready) {
$jscode = "Y.on('domready', function() { $jscode });";
$jscode = "$startjs Y.on('domready', function() { $jscode $endjs });";
}
$this->jsinitcode[] = $jscode;
@ -1216,7 +1220,7 @@ class page_requirements_manager {
$output .= js_writer::function_call($data[0], $data[1], $data[2]);
}
if (!empty($ondomready)) {
$output = " Y.on('domready', function() {\n$output\n });";
$output = " Y.on('domready', function() {\n$output\n});";
}
}
return $output;
@ -1453,6 +1457,8 @@ class page_requirements_manager {
// Add other requested modules.
$output = $this->get_extra_modules_code();
$this->js_init_code('M.util.js_complete("init");', true);
// All the other linked scripts - there should be as few as possible.
if ($this->jsincludes['footer']) {
foreach ($this->jsincludes['footer'] as $url) {

View File

@ -68,6 +68,7 @@ class behat_deprecated extends behat_base {
// Looking for the element DOM node inside the specified row.
list($selector, $locator) = $this->transform_selector($selectortype, $element);
$elementnode = $this->find($selector, $locator, false, $rownode);
$this->ensure_element_is_visible($elementnode);
$elementnode->click();
}

View File

@ -69,6 +69,9 @@ class behat_forms extends behat_base {
*/
public function i_fill_the_moodle_form_with(TableNode $data) {
// We ensure that all the editors are loaded and we can interact with them.
$this->ensure_editors_are_loaded();
// Expand all fields in case we have.
$this->expand_all_fields();
@ -171,31 +174,11 @@ class behat_forms extends behat_base {
public function select_option($option, $select) {
$selectnode = $this->find_field($select);
$selectnode->selectOption($option);
// Adding a click as Selenium requires it to fire some JS events.
if ($this->running_javascript()) {
// In some browsers the selectOption actions can perform a page reload
// so we need to ensure the element is still available to continue interacting
// with it. We don't wait here.
if (!$this->getSession()->getDriver()->find($selectnode->getXpath())) {
return;
}
// Single select needs an extra click in the option.
if (!$selectnode->hasAttribute('multiple')) {
// Avoid quotes problems.
$option = $this->getSession()->getSelectorsHandler()->xpathLiteral($option);
$xpath = "//option[(./@value=$option or normalize-space(.)=$option)]";
$optionnode = $this->find('xpath', $xpath, false, $selectnode);
$optionnode->click();
} else {
// Multiple ones needs the click in the select.
$selectnode->click();
}
}
// We delegate to behat_form_field class, it will
// guess the type properly as it is a select tag.
$selectformfield = behat_field_manager::get_form_field($selectnode, $this->getSession());
$selectformfield->set_value($option);
}
/**
@ -225,6 +208,8 @@ class behat_forms extends behat_base {
*/
public function check_option($option) {
// We don't delegate to behat_form_checkbox as the
// step is explicitly saying I check.
$checkboxnode = $this->find_field($option);
$checkboxnode->check();
}
@ -238,6 +223,8 @@ class behat_forms extends behat_base {
*/
public function uncheck_option($option) {
// We don't delegate to behat_form_checkbox as the
// step is explicitly saying I uncheck.
$checkboxnode = $this->find_field($option);
$checkboxnode->uncheck();
}

View File

@ -124,7 +124,20 @@ class behat_general extends behat_base {
* @param string $iframename
*/
public function switch_to_iframe($iframename) {
$this->getSession()->switchToIFrame($iframename);
// We spin to give time to the iframe to be loaded.
// Using extended timeout as we don't know about which
// kind of iframe will be loaded.
$this->spin(
function($context, $iframename) {
$context->getSession()->switchToIFrame($iframename);
// If no exception we are done.
return true;
},
$iframename,
self::EXTENDED_TIMEOUT
);
}
/**
@ -173,6 +186,7 @@ class behat_general extends behat_base {
public function click_link($link) {
$linknode = $this->find_link($link);
$this->ensure_node_is_visible($linknode);
$linknode->click();
}
@ -202,7 +216,56 @@ class behat_general extends behat_base {
throw new DriverException('Waits are disabled in scenarios without Javascript support');
}
$this->getSession()->wait(self::TIMEOUT, '(document.readyState === "complete")');
$this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
}
/**
* Waits until the editors are all completely loaded.
*
* @Given /^I wait until the editors are loaded$/
* @throws DriverException
*/
public function wait_until_editors_are_loaded() {
if (!$this->running_javascript()) {
throw new DriverException('Editors are not loaded when running without Javascript support');
}
$this->ensure_editors_are_loaded();
}
/**
* Waits until the provided element selector exists in the DOM
*
* 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.
* @Given /^I wait until "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" exists$/
* @param string $element
* @param string $selector
* @return void
*/
public function wait_until_exists($element, $selectortype) {
$this->ensure_element_exists($element, $selectortype);
}
/**
* Waits until the provided element does not exist in the DOM
*
* 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.
* @Given /^I wait until "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" does not exist$/
* @param string $element
* @param string $selector
* @return void
*/
public function wait_until_does_not_exists($element, $selectortype) {
$this->ensure_element_does_not_exist($element, $selectortype);
}
/**
@ -230,6 +293,7 @@ class behat_general extends behat_base {
// Gets the node based on the requested selector type and locator.
$node = $this->get_selected_node($selectortype, $element);
$this->ensure_node_is_visible($node);
$node->click();
}
@ -245,6 +309,7 @@ class behat_general extends behat_base {
public function i_click_on_in_the($element, $selectortype, $nodeelement, $nodeselectortype) {
$node = $this->get_node_in_container($selectortype, $element, $nodeselectortype, $nodeelement);
$this->ensure_node_is_visible($node);
$node->click();
}
@ -298,6 +363,9 @@ class behat_general extends behat_base {
/**
* Checks, that the specified element is not visible. Only available in tests using Javascript.
*
* As a "not" method, it's performance is not specially good as we should ensure that the element
* have time to appear.
*
* @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>(?:[^"]|\\")*)" should not be visible$/
* @throws ElementNotFoundException
* @throws ExpectationException
@ -381,24 +449,35 @@ class behat_general extends behat_base {
$xpath = "/descendant-or-self::*[contains(., $xpathliteral)]" .
"[count(descendant::*[contains(., $xpathliteral)]) = 0]";
// Wait until it finds the text, otherwise custom exception.
try {
$nodes = $this->find_all('xpath', $xpath);
// We also check for the element visibility when running JS tests.
if ($this->running_javascript()) {
foreach ($nodes as $node) {
if ($node->isVisible()) {
return;
}
}
throw new ExpectationException("'{$text}' text was found but was not visible", $this->getSession());
}
} catch (ElementNotFoundException $e) {
throw new ExpectationException('"' . $text . '" text was not found in the page', $this->getSession());
}
// If we are not running javascript we have enough with the
// element existing as we can't check if it is visible.
if (!$this->running_javascript()) {
return;
}
// We spin as we don't have enough checking that the element is there, we
// should also ensure that the element is visible.
$this->spin(
function($context, $args) {
foreach ($args['nodes'] as $node) {
if ($node->isVisible()) {
return true;
}
}
// If non of the nodes is visible we loop again.
throw new ExpectationException('"' . $args['text'] . '" text was found but was not visible', $context->getSession());
},
array('nodes' => $nodes, 'text' => $text)
);
}
/**
@ -410,16 +489,43 @@ class behat_general extends behat_base {
*/
public function assert_page_not_contains_text($text) {
// Delegating the process to assert_page_contains_text.
// Looking for all the matching nodes without any other descendant matching the
// same xpath (we are using contains(., ....).
$xpathliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($text);
$xpath = "/descendant-or-self::*[contains(., $xpathliteral)]" .
"[count(descendant::*[contains(., $xpathliteral)]) = 0]";
// We should wait a while to ensure that the page is not still loading elements.
// Giving preference to the reliability of the results rather than to the performance.
try {
$this->assert_page_contains_text($text);
} catch (ExpectationException $e) {
// It should not appear, so this is good.
$nodes = $this->find_all('xpath', $xpath);
} catch (ElementNotFoundException $e) {
// All ok.
return;
}
// If the page contains the text this is failing.
throw new ExpectationException('"' . $text . '" text was found in the page', $this->getSession());
// If we are not running javascript we have enough with the
// element existing as we can't check if it is hidden.
if (!$this->running_javascript()) {
throw new ExpectationException('"' . $text . '" text was found in the page', $this->getSession());
}
// If the element is there we should be sure that it is not visible.
$this->spin(
function($context, $args) {
foreach ($args['nodes'] as $node) {
if ($node->isVisible()) {
throw new ExpectationException('"' . $args['text'] . '" text was found in the page', $context->getSession());
}
}
// If non of the found nodes is visible we consider that the text is not visible.
return true;
},
array('nodes' => $nodes, 'text' => $text)
);
}
/**
@ -446,22 +552,30 @@ class behat_general extends behat_base {
// Wait until it finds the text inside the container, otherwise custom exception.
try {
$nodes = $this->find_all('xpath', $xpath, false, $container);
} catch (ElementNotFoundException $e) {
throw new ExpectationException('"' . $text . '" text was not found in the "' . $element . '" element', $this->getSession());
}
// We also check for the element visibility when running JS tests.
if ($this->running_javascript()) {
foreach ($nodes as $node) {
// If we are not running javascript we have enough with the
// element existing as we can't check if it is visible.
if (!$this->running_javascript()) {
return;
}
// We also check the element visibility when running JS tests.
$this->spin(
function($context, $args) {
foreach ($args['nodes'] as $node) {
if ($node->isVisible()) {
return;
return true;
}
}
throw new ExpectationException("'{$text}' text was found in the {$element} element but was not visible", $this->getSession());
}
} catch (ElementNotFoundException $e) {
throw new ExpectationException('"' . $text . '" text was not found in the ' . $element . ' element', $this->getSession());
}
throw new ExpectationException('"' . $args['text'] . '" text was found in the "' . $args['element'] . '" element but was not visible', $context->getSession());
},
array('nodes' => $nodes, 'text' => $text, 'element' => $element)
);
}
/**
@ -476,18 +590,45 @@ class behat_general extends behat_base {
*/
public function assert_element_not_contains_text($text, $element, $selectortype) {
// Delegating the process to assert_element_contains_text.
// Getting the container where the text should be found.
$container = $this->get_selected_node($selectortype, $element);
// Looking for all the matching nodes without any other descendant matching the
// same xpath (we are using contains(., ....).
$xpathliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($text);
$xpath = "/descendant-or-self::*[contains(., $xpathliteral)]" .
"[count(descendant::*[contains(., $xpathliteral)]) = 0]";
// We should wait a while to ensure that the page is not still loading elements.
// Giving preference to the reliability of the results rather than to the performance.
try {
$this->assert_element_contains_text($text, $element, $selectortype);
} catch (ExpectationException $e) {
// It should not appear, so this is good.
// We only catch ExpectationException as ElementNotFoundException
// will be thrown if the container does not exist.
$nodes = $this->find_all('xpath', $xpath, false, $container);
} catch (ElementNotFoundException $e) {
// All ok.
return;
}
// If the element contains the text this is failing.
throw new ExpectationException('"' . $text . '" text was found in the ' . $element . ' element', $this->getSession());
// If we are not running javascript we have enough with the
// element not being found as we can't check if it is visible.
if (!$this->running_javascript()) {
throw new ExpectationException('"' . $text . '" text was found in the "' . $element . '" element', $this->getSession());
}
// We need to ensure all the found nodes are hidden.
$this->spin(
function($context, $args) {
foreach ($args['nodes'] as $node) {
if ($node->isVisible()) {
throw new ExpectationException('"' . $args['text'] . '" text was found in the "' . $args['element'] . '" element', $context->getSession());
}
}
// If all the found nodes are hidden we are happy.
return true;
},
array('nodes' => $nodes, 'text' => $text, 'element' => $element)
);
}
/**

View File

@ -222,42 +222,68 @@ class behat_hooks extends behat_base {
}
/**
* Checks that all DOM is ready.
* Wait for JS to complete before beginning interacting with the DOM.
*
* Executed only when running against a real browser.
*
* @BeforeStep @javascript
*/
public function before_step_javascript($event) {
$this->wait_for_pending_js();
}
/**
* Wait for JS to complete after finishing the step.
*
* With this we ensure that there are not AJAX calls
* still in progress.
*
* Executed only when running against a real browser.
*
* @AfterStep @javascript
*/
public function after_step_javascript($event) {
$this->wait_for_pending_js();
}
// If it doesn't have definition or it fails there is no need to check it.
if ($event->getResult() != StepEvent::PASSED ||
!$event->hasDefinition()) {
return;
}
/**
* Waits for all the JS to be loaded.
*
* @throws NoSuchWindow
* @throws UnknownError
* @return bool True or false depending whether all the JS is loaded or not.
*/
protected function wait_for_pending_js() {
// Wait until the page is ready.
// We are already checking that we use a JS browser, this could
// change in case we use another JS driver.
try {
// Safari and Internet Explorer requires time between steps,
// otherwise Selenium tries to click in the previous page's DOM.
if ($this->getSession()->getDriver()->getBrowserName() == 'safari' ||
$this->getSession()->getDriver()->getBrowserName() == 'internet explorer') {
$this->getSession()->wait(self::TIMEOUT * 1000, false);
} else {
// With other browsers we just wait for the DOM ready.
$this->getSession()->wait(self::TIMEOUT * 1000, '(document.readyState === "complete")');
// We don't use behat_base::spin() here as we don't want to end up with an exception
// if the page & JSs don't finish loading properly.
for ($i = 0; $i < self::EXTENDED_TIMEOUT * 10; $i++) {
$pending = '';
try {
$jscode = 'return ' . self::PAGE_READY_JS . ' ? "" : M.util.pending_js.join(":");';
$pending = $this->getSession()->evaluateScript($jscode);
} catch (NoSuchWindow $nsw) {
// We catch an exception here, in case we just closed the window we were interacting with.
// No javascript is running if there is no window right?
$pending = '';
} catch (UnknownError $e) {
// Same exception as before, but some combinations of browser + OS reports it as an unknown error
// exception.
$pending = '';
}
} catch (NoSuchWindow $e) {
// If we were interacting with a popup window it will not exists after closing it.
} catch (UnknownError $e) {
// Custom exception to provide more feedback about possible solutions.
$this->throw_unknown_exception($e);
// If there are no pending JS we stop waiting.
if ($pending === '') {
return true;
}
// 0.1 seconds.
usleep(100000);
}
// Timeout waiting for JS to complete.
// TODO MDL-43173 We should fail the scenarios if JS loading times out.
return false;
}
/**

View File

@ -76,6 +76,7 @@ class behat_navigation extends behat_base {
$exception = new ExpectationException('The "' . $nodetext . '" node can not be expanded', $this->getSession());
$node = $this->find('xpath', $xpath, $exception);
$this->ensure_node_is_visible($node);
$node->click();
}

View File

@ -95,7 +95,16 @@ class behat_permissions extends behat_base {
try {
$advancedtoggle = $this->find_button(get_string('showadvanced', 'form'));
if ($advancedtoggle) {
$this->getSession()->getPage()->pressButton(get_string('showadvanced', 'form'));
// As we are interacting with a moodle form we wait for the editor to be ready
// otherwise we may have problems when setting values on it or clicking on elements
// as the position of the elements will change once the editor is loaded.
$this->ensure_editors_are_loaded();
$advancedtoggle->click();
// Wait for the page to load.
$this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
}
} catch (Exception $e) {
// We already are in advanced mode.

View File

@ -38,10 +38,10 @@ Feature: In an assignment, students can upload files for assessment
And I should see "Not graded"
And I press "Edit submission"
And I upload "lib/tests/fixtures/upload_users.csv" file to "File submissions" filemanager
And ".ffilemanager .fm-maxfiles .fp-btn-add" "css_element" should exists
And ".ffilemanager .fm-maxfiles .fp-btn-add" "css_element" should not be visible
And I press "Save changes"
And I should see "Submitted for grading"
And I should see "empty.txt"
And I should see "upload_users.csv"
And I press "Edit submission"
And ".ffilemanager .fm-maxfiles .fp-btn-add" "css_element" should exists
And ".ffilemanager .fm-maxfiles .fp-btn-add" "css_element" should not be visible

View File

@ -25,5 +25,4 @@ Feature: Add forum activities and discussions
When I add a new discussion to "Test forum name" forum with:
| Subject | Forum post 1 |
| Message | This is the body |
And I wait "6" seconds
Then I should see "Test forum name"

View File

@ -41,7 +41,6 @@ Feature: Teachers can edit or delete any forum post
And I follow "Teacher post subject"
And I click on "Delete" "link" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' forumpost ')][contains(., 'Student post subject')]" "xpath_element"
And I press "Continue"
And I wait "4" seconds
Then I should not see "Student post subject"
And I should not see "Student post message"
@ -56,7 +55,7 @@ Feature: Teachers can edit or delete any forum post
And I fill the moodle form with:
| Subject | Edited student subject |
And I press "Save changes"
And I wait "4" seconds
And I wait to be redirected
Then I should see "Edited student subject"
And I should see "Edited by Teacher 1 - original submission"

View File

@ -30,7 +30,6 @@ Feature: A teacher can set one of 3 possible options for tracking read forum pos
And I add a new discussion to "Test forum name" forum with:
| Subject | Test post subject |
| Message | Test post message |
And I wait "6" seconds
And I log out
When I log in as "student1"
And I follow "Course 1"
@ -48,19 +47,18 @@ Feature: A teacher can set one of 3 possible options for tracking read forum pos
And I add a new discussion to "Test forum name" forum with:
| Subject | Test post subject |
| Message | Test post message |
And I wait "6" seconds
And I log out
When I log in as "student1"
And I follow "Course 1"
Then I should see "1 unread post"
And I follow "Test forum name"
And I follow "Don't track unread posts"
And I wait "4" seconds
And I wait to be redirected
And I follow "Course 1"
And I should not see "1 unread post"
And I follow "Test forum name"
And I follow "Track unread posts"
And I wait "4" seconds
And I wait to be redirected
And I follow "1"
And I follow "Course 1"
And I should not see "1 unread post"
@ -75,7 +73,6 @@ Feature: A teacher can set one of 3 possible options for tracking read forum pos
And I add a new discussion to "Test forum name" forum with:
| Subject | Test post subject |
| Message | Test post message |
And I wait "6" seconds
And I log out
When I log in as "student2"
And I follow "Course 1"
@ -85,7 +82,7 @@ Feature: A teacher can set one of 3 possible options for tracking read forum pos
@javascript
Scenario: Tracking forum posts forced with user tracking on
And I set the following administration settings values:
Given I set the following administration settings values:
| Allow forced read tracking | 1 |
And I follow "Home"
And I follow "Course 1"
@ -97,7 +94,6 @@ Feature: A teacher can set one of 3 possible options for tracking read forum pos
And I add a new discussion to "Test forum name" forum with:
| Subject | Test post subject |
| Message | Test post message |
And I wait "6" seconds
And I log out
When I log in as "student1"
And I follow "Course 1"
@ -110,7 +106,7 @@ Feature: A teacher can set one of 3 possible options for tracking read forum pos
@javascript
Scenario: Tracking forum posts forced with user tracking off
And I set the following administration settings values:
Given I set the following administration settings values:
| Allow forced read tracking | 1 |
And I follow "Home"
And I follow "Course 1"
@ -122,7 +118,6 @@ Feature: A teacher can set one of 3 possible options for tracking read forum pos
And I add a new discussion to "Test forum name" forum with:
| Subject | Test post subject |
| Message | Test post message |
And I wait "6" seconds
And I log out
When I log in as "student2"
And I follow "Course 1"
@ -135,7 +130,7 @@ Feature: A teacher can set one of 3 possible options for tracking read forum pos
@javascript
Scenario: Tracking forum posts forced (with force disabled) with user tracking on
And I set the following administration settings values:
Given I set the following administration settings values:
| Allow forced read tracking | 1 |
And I follow "Home"
And I follow "Course 1"
@ -147,7 +142,6 @@ Feature: A teacher can set one of 3 possible options for tracking read forum pos
And I add a new discussion to "Test forum name" forum with:
| Subject | Test post subject |
| Message | Test post message |
And I wait "6" seconds
And I set the following administration settings values:
| Allow forced read tracking | 0 |
And I log out
@ -156,19 +150,19 @@ Feature: A teacher can set one of 3 possible options for tracking read forum pos
Then I should see "1 unread post"
And I follow "Test forum name"
And I follow "Don't track unread posts"
And I wait "4" seconds
And I wait to be redirected
And I follow "Course 1"
And I should not see "1 unread post"
And I follow "Test forum name"
And I follow "Track unread posts"
And I wait "4" seconds
And I wait to be redirected
And I follow "1"
And I follow "Course 1"
And I should not see "1 unread post"
@javascript
Scenario: Tracking forum posts forced (with force disabled) with user tracking off
And I set the following administration settings values:
Given I set the following administration settings values:
| Allow forced read tracking | 1 |
And I follow "Home"
And I follow "Course 1"
@ -180,7 +174,6 @@ Feature: A teacher can set one of 3 possible options for tracking read forum pos
And I add a new discussion to "Test forum name" forum with:
| Subject | Test post subject |
| Message | Test post message |
And I wait "6" seconds
And I set the following administration settings values:
| Allow forced read tracking | 0 |
And I log out

View File

@ -1,4 +1,4 @@
@mod @mod_scorm
@mod @mod_scorm @_only_local @_switch_frame
Feature: Add scorm activity
In order to let students access a scorm package
As a teacher
@ -21,10 +21,10 @@ Feature: Add scorm activity
And I follow "Course 1"
And I turn editing mode on
And I add a "SCORM package" to section "1"
And I upload "mod/scorm/tests/packages/singlescobasic.zip" file to "Package file" filemanager
And I fill the moodle form with:
| Name | Awesome SCORM package |
| Description | Description |
And I upload "mod/scorm/tests/packages/singlescobasic.zip" file to "Package file" filemanager
And I click on "Save and display" "button"
Then I should see "Awesome SCORM package"
And I should see "Normal"
@ -35,7 +35,6 @@ Feature: Add scorm activity
And I follow "Awesome SCORM package"
And I should see "Normal"
And I press "Enter"
And I wait "5" seconds
And I switch to "scorm_object" iframe
And I switch to "contentFrame" iframe
And I should see "Play of the game"

View File

@ -38,21 +38,23 @@ M.mod_wiki.init = function(Y, args) {
});
new WikiHelper(args);
};
M.mod_wiki.renew_lock = function(Y, args) {
function renewLock() {
var args = {};
args['sesskey'] = M.cfg.sesskey;
args['pageid'] = wiki.pageid;
if (wiki.section) {
args['section'] = wiki.section;
}
var callback = {};
Y.use('yui2-connection', function(Y) {
Y.YUI2.util.Connect.asyncRequest('GET', 'lock.php?' + build_querystring(args), callback);
});
M.mod_wiki.renew_lock = function() {
var args = {
sesskey: M.cfg.sesskey,
pageid: wiki.pageid
};
if (wiki.section) {
args.section = wiki.section;
}
setInterval(renewLock, wiki.renew_lock_timeout * 1000);
}
YUI().use('io', function(Y) {
function renewLock() {
Y.io('lock.php?' + build_querystring(args), {
method: 'POST'
});
}
setInterval(renewLock, wiki.renew_lock_timeout * 1000);
});
};
M.mod_wiki.history = function(Y, args) {
var compare = false;
var comparewith = false;

View File

@ -32,7 +32,8 @@ Feature: Edited wiki pages may be previewed before saving
And I fill the moodle form with:
| HTML format | Student page contents to be previewed |
And I press "Preview"
Then I should see "This is a preview. Changes have not been saved yet"
Then I expand all fieldsets
And I should see "This is a preview. Changes have not been saved yet"
And I should see "Student page contents to be previewed"
And I press "Save"
And I should see "Student page contents to be previewed"

View File

@ -60,7 +60,7 @@ class behat_question extends behat_base {
new Given('I follow "' . get_string('questionbank', 'question') . '"'),
new Given('I press "' . get_string('createnewquestion', 'question') . '"'),
new Given('I click on "' . $this->escape($questiontypexpath) . '" "xpath_element"'),
new Given('I click on "Next" "button" in the "#qtypechoicecontainer" "css_element"'),
new Given('I click on "#chooseqtype_submit" "css_element"'),
new Given('I fill the moodle form with:', $questiondata),
new Given('I press "' . get_string('savechanges') . '"')
);

View File

@ -4,7 +4,7 @@ Feature: A teacher can edit questions in the question bank
As a teacher
I need to edit questions
@javascript
@javascript @_switch_window
Scenario: Edit a previously created question
Given the following "users" exists:
| username | firstname | lastname | email |

View File

@ -1,4 +1,4 @@
@core @core_question
@core @core_question @_switch_window
Feature: A teacher can preview questions in the question bank
In order to ensure the questions are properly created
As a teacher

View File

@ -68,12 +68,6 @@ class behat_filepicker extends behat_files {
$dialognode = $this->find('css', '.moodle-dialogue-focused');
$buttonnode = $this->find('css', '.fp-dlg-butcreate', $exception, $dialognode);
$buttonnode->click();
// Wait until the process finished and modal windows are hidden.
$this->wait_until_return_to_form();
// Wait until the current folder contents are updated
$this->wait_until_contents_are_updated($fieldnode);
}
/**
@ -93,9 +87,6 @@ class behat_filepicker extends behat_files {
$this->getSession()
);
// Just in case there is any contents refresh in progress.
$this->wait_until_contents_are_updated($fieldnode);
$folderliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($foldername);
// We look both in the pathbar and in the contents.
@ -124,9 +115,6 @@ class behat_filepicker extends behat_files {
// It should be a NodeElement, otherwise an exception would have been thrown.
$folder->click();
// Wait until the current folder contents are updated
$this->wait_until_contents_are_updated($fieldnode);
}
/**
@ -145,13 +133,6 @@ class behat_filepicker extends behat_files {
// Execute the action.
$exception = new ExpectationException($filename.' element can not be unzipped', $this->getSession());
$this->perform_on_element('unzip', $exception);
// Wait until the process finished and modal windows are hidden.
$this->wait_until_return_to_form();
// Wait until the current folder contents are updated
$containernode = $this->get_filepicker_node($filemanagerelement);
$this->wait_until_contents_are_updated($containernode);
}
/**
@ -170,13 +151,6 @@ class behat_filepicker extends behat_files {
// Execute the action.
$exception = new ExpectationException($foldername.' element can not be zipped', $this->getSession());
$this->perform_on_element('zip', $exception);
// Wait until the process finished and modal windows are hidden.
$this->wait_until_return_to_form();
// Wait until the current folder contents are updated
$containernode = $this->get_filepicker_node($filemanagerelement);
$this->wait_until_contents_are_updated($containernode);
}
/**
@ -200,13 +174,6 @@ class behat_filepicker extends behat_files {
// Using xpath + click instead of pressButton as 'Ok' it is a common string.
$okbutton = $this->find('css', 'div.fp-dlg button.fp-dlg-butconfirm');
$okbutton->click();
// Wait until the process finished and modal windows are hidden.
$this->wait_until_return_to_form();
// Wait until file manager contents are updated.
$containernode = $this->get_filepicker_node($filemanagerelement);
$this->wait_until_contents_are_updated($containernode);
}
@ -220,7 +187,6 @@ class behat_filepicker extends behat_files {
*/
public function i_should_see_elements_in_filemanager($elementscount, $filemanagerelement) {
$filemanagernode = $this->get_filepicker_node($filemanagerelement);
$this->wait_until_contents_are_updated($filemanagernode);
$elements = $this->find_all('css', '.fp-content .fp-file', false, $filemanagernode);
if (count($elements) != $elementscount) {
throw new ExpectationException('Found '.count($elements).' elements in filemanager instead of expected '.$elementscount);
@ -298,9 +264,6 @@ class behat_filepicker extends behat_files {
$overwriteaction = false) {
$filemanagernode = $this->get_filepicker_node($filemanagerelement);
// Wait until file manager is completely loaded.
$this->wait_until_contents_are_updated($filemanagernode);
// Opening the select repository window and selecting the upload repository.
$this->open_add_file_window($filemanagernode, $repository);
@ -323,16 +286,18 @@ class behat_filepicker extends behat_files {
$this->find_button(get_string('getfile', 'repository'))->click();
// We wait for all the JS to finish as it is performing an action.
$this->getSession()->wait(self::TIMEOUT, self::PAGE_READY_JS);
if ($overwriteaction !== false) {
$this->getSession()->wait(1 * 1000, false);
$this->find_button($overwriteaction)->click();
$overwritebutton = $this->find_button($overwriteaction);
$this->ensure_node_is_visible($overwritebutton);
$overwritebutton->click();
// We wait for all the JS to finish.
$this->getSession()->wait(self::TIMEOUT, self::PAGE_READY_JS);
}
// Ensure the file has been uploaded and all ajax processes finished.
$this->wait_until_return_to_form();
// Wait until file manager contents are updated.
$this->wait_until_contents_are_updated($filemanagernode);
}
}

View File

@ -109,14 +109,10 @@ class behat_repository_upload extends behat_files {
$filemanagernode = $this->get_filepicker_node($filemanagerelement);
// Wait until file manager is completely loaded.
$this->wait_until_contents_are_updated($filemanagernode);
// Opening the select repository window and selecting the upload repository.
$this->open_add_file_window($filemanagernode, get_string('pluginname', 'repository_upload'));
// Ensure all the form is ready.
$this->getSession()->wait(2 * 1000, false);
$noformexception = new ExpectationException('The upload file form is not ready', $this->getSession());
$this->find(
'xpath',
@ -161,16 +157,18 @@ class behat_repository_upload extends behat_files {
$submit = $this->find_button(get_string('upload', 'repository'));
$submit->press();
// We wait for all the JS to finish as it is performing an action.
$this->getSession()->wait(self::TIMEOUT, self::PAGE_READY_JS);
if ($overwriteaction !== false) {
$this->getSession()->wait(1 * 1000, false);
$this->find_button($overwriteaction)->click();
$overwritebutton = $this->find_button($overwriteaction);
$this->ensure_node_is_visible($overwritebutton);
$overwritebutton->click();
// We wait for all the JS to finish.
$this->getSession()->wait(self::TIMEOUT, self::PAGE_READY_JS);
}
// Ensure the file has been uploaded and all ajax processes finished.
$this->wait_until_return_to_form();
// Wait until file manager contents are updated.
$this->wait_until_contents_are_updated($filemanagernode);
}
}