Merge branch 'MDL-53252_master' of git://github.com/dmonllao/moodle
authorAndrew Nicols <andrew@nicols.co.uk>
Mon, 14 Mar 2016 05:35:32 +0000 (13:35 +0800)
committerAndrew Nicols <andrew@nicols.co.uk>
Mon, 14 Mar 2016 05:35:32 +0000 (13:35 +0800)
96 files changed:
admin/tests/behat/behat_admin.php
admin/tool/behat/cli/run.php
admin/tool/behat/cli/util_single_run.php
admin/tool/behat/tests/behat/manipulate_forms.feature
admin/tool/behat/tests/manager_test.php
admin/tool/langimport/tests/behat/manage_langpacks.feature
auth/manual/tests/behat/auth_manual.feature [new file with mode: 0644]
auth/tests/behat/behat_auth.php
backup/util/ui/tests/behat/behat_backup.php
behat.yml.dist
blocks/tests/behat/behat_blocks.php
calendar/tests/behat/behat_calendar.php
cohort/tests/behat/behat_cohort.php
completion/tests/behat/behat_completion.php
composer.json
composer.lock
config-dist.php
course/tests/behat/behat_course.php
course/tests/behat/category_resort.feature
course/tests/behat/course_resort.feature
enrol/tests/behat/behat_enrol.php
grade/edit/tree/index.php
grade/grading/form/guide/tests/behat/behat_gradingform_guide.php
grade/grading/form/rubric/tests/behat/behat_gradingform_rubric.php
grade/grading/tests/behat/behat_grading.php
grade/report/grader/tests/behat/behat_gradereport_grader.php
grade/tests/behat/behat_grade.php
grade/tests/behat/grade_aggregation.feature
group/tests/behat/behat_groups.php
lib/behat/behat_base.php
lib/behat/classes/behat_config_manager.php
lib/behat/classes/behat_context_helper.php
lib/behat/classes/behat_selectors.php
lib/behat/form_field/behat_form_checkbox.php
lib/behat/form_field/behat_form_radio.php
lib/behat/form_field/behat_form_select.php
lib/editor/atto/plugins/accessibilitychecker/tests/behat/accessibilitychecker.feature
lib/editor/atto/plugins/image/tests/behat/image.feature
lib/form/tests/behat/modgrade_validation.feature
lib/tests/behat/behat_deprecated.php
lib/tests/behat/behat_forms.php
lib/tests/behat/behat_general.php
lib/tests/behat/behat_hooks.php
lib/tests/behat/behat_navigation.php
lib/tests/behat/behat_permissions.php
lib/tests/behat/behat_transformations.php
message/tests/behat/behat_message.php
message/tests/behat/delete_messages.feature
mod/assign/feedback/editpdf/tests/behat/annotate_pdf.feature
mod/assign/feedback/editpdf/tests/behat/behat_assignfeedback_editpdf.php
mod/assign/tests/behat/comment_inline.feature
mod/choice/tests/behat/behat_mod_choice.php
mod/data/tests/behat/behat_mod_data.php
mod/data/tests/behat/required_entries.feature
mod/feedback/tests/behat/behat_mod_feedback.php
mod/feedback/tests/behat/question_types.feature
mod/forum/tests/behat/behat_mod_forum.php
mod/glossary/tests/behat/behat_mod_glossary.php
mod/lti/locallib.php
mod/lti/tests/externallib_test.php
mod/quiz/tests/behat/behat_mod_quiz.php
mod/quiz/tests/behat/settings_form_fields_disableif.feature
mod/wiki/classes/external.php
mod/wiki/db/services.php
mod/wiki/locallib.php
mod/wiki/tests/externallib_test.php
mod/wiki/upgrade.txt [new file with mode: 0644]
mod/workshop/allocation/manual/tests/behat/behat_workshopallocation_manual.php
mod/workshop/tests/behat/behat_mod_workshop.php
question/behaviour/adaptive/tests/walkthrough_test.php
question/behaviour/adaptivenopenalty/tests/walkthrough_test.php
question/behaviour/interactive/renderer.php
question/behaviour/interactive/tests/walkthrough_test.php
question/behaviour/interactivecountback/tests/walkthrough_test.php
question/behaviour/rendererbase.php
question/behaviour/upgrade.txt
question/engine/tests/helpers.php
question/tests/behat/behat_question.php
question/tests/behat/behat_question_base.php
question/type/calculated/tests/walkthrough_test.php
question/type/calculatedmulti/tests/walkthrough_test.php
question/type/calculatedsimple/tests/walkthrough_test.php
question/type/ddimageortext/tests/behat/preview.feature
question/type/ddimageortext/tests/walkthrough_test.php
question/type/ddmarker/tests/behat/behat_qtype_ddmarker.php
question/type/ddmarker/tests/behat/preview.feature
question/type/ddmarker/tests/walkthrough_test.php
question/type/ddwtos/tests/behat/preview.feature
question/type/ddwtos/tests/walkthrough_test.php
question/type/gapselect/tests/walkthrough_test.php
question/type/match/tests/walkthrough_test.php
question/type/multianswer/tests/walkthrough_test.php
question/type/numerical/tests/walkthrough_test.php
question/type/randomsamatch/tests/walkthrough_test.php
repository/tests/behat/behat_filepicker.php
repository/upload/tests/behat/behat_repository_upload.php

index 8c56f7a..8f69e31 100644 (file)
@@ -28,8 +28,7 @@
 require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
 require_once(__DIR__ . '/../../../lib/behat/behat_field_manager.php');
 
-use Behat\Behat\Context\Step\Given as Given,
-    Behat\Gherkin\Node\TableNode as TableNode,
+use Behat\Gherkin\Node\TableNode as TableNode,
     Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
 
 /**
index 6e19659..3df999a 100644 (file)
@@ -120,8 +120,8 @@ $tags = '';
 
 if ($options['profile']) {
     $profile = $options['profile'];
-    if (!isset($CFG->behat_config[$profile])) {
-        echo "Invalid profile passed: " . $profile;
+    if (!isset($CFG->behat_config[$profile]) && !isset($CFG->behat_profiles[$profile])) {
+        echo "Invalid profile passed: " . $profile . PHP_EOL;
         exit(1);
     }
     $extraopts[] = '--profile="' . $profile . '"';
@@ -226,22 +226,27 @@ $exitcodes = print_combined_run_output($processes, $stoponfail);
 $time = round(microtime(true) - $time, 1);
 echo "Finished in " . gmdate("G\h i\m s\s", $time) . PHP_EOL . PHP_EOL;
 
+ksort($exitcodes);
 
 // Print exit info from each run.
-$status = false;
+// Status bits contains pass/fail status of parallel runs.
+$status = 0;
+$processcounter = 0;
 foreach ($exitcodes as $exitcode) {
-    $status = (bool)$status || (bool)$exitcode;
+    if ($exitcode) {
+        $status |= (1 << $processcounter);
+    }
+    $processcounter++;
 }
 
 // Run finished. Show exit code and output from individual process.
 $verbose = empty($options['verbose']) ? false : true;
-$verbose = $verbose || $status;
+$verbose = $verbose || !empty($status);
 
 // Show exit code from each process, if any process failed.
 if ($verbose) {
     // Echo exit codes.
     echo "Exit codes for each behat run: " . PHP_EOL;
-    ksort($exitcodes);
     foreach ($exitcodes as $run => $exitcode) {
         echo $run . ": " . $exitcode . PHP_EOL;
     }
@@ -263,7 +268,7 @@ print_each_process_info($processes, $verbose);
 // Remove site symlink if necessary.
 behat_config_manager::drop_parallel_site_links();
 
-exit((int) $status);
+exit($status);
 
 /**
  * Signal handler for terminal exit.
index d523569..1991707 100644 (file)
@@ -173,6 +173,11 @@ if ($options['install']) {
 
     // This is only displayed once for parallel install.
     if (empty($options['run'])) {
+        // Notify user that 2.5 profile has been converted to 3.5.
+        if (behat_config_manager::$autoprofileconversion) {
+            mtrace("2.5 behat profile detected, automatically converted to current 3.x format");
+        }
+
         $runtestscommand = behat_command::get_behat_command(true, !empty($options['run']));
 
         $runtestscommand .= ' --config ' . behat_config_manager::get_behat_cli_config_filepath();
index 20c3c29..126a800 100644 (file)
@@ -12,7 +12,6 @@ Feature: Forms manipulation
     When I set the field "First name" to "Field value"
     And I set the field "Select a country" to "Japan"
     And I set the field "Unmask" to "1"
-    And I expand all fieldsets
     Then the field "First name" matches value "Field value"
     And the "Select a country" select box should contain "Japan"
     And the field "Unmask" matches value "1"
index 1dc44ef..0f24f38 100644 (file)
@@ -142,11 +142,10 @@ class tool_behat_manager_testcase extends advanced_testcase {
         // YAML decides when is is necessary to wrap strings between single quotes, so not controlled
         // values like paths should not be asserted including the key name as they would depend on the
         // directories values.
-        $this->assertContains($CFG->dirroot . DIRECTORY_SEPARATOR . 'lib' . DIRECTORY_SEPARATOR . 'behat' . DIRECTORY_SEPARATOR . 'features', $contents);
+        $this->assertContains($CFG->dirroot, $contents);
 
         // Not quoted strings.
         $this->assertContains('micarro: /me/lo/robaron', $contents);
-        $this->assertContains('class: behat_init_context', $contents);
 
         // YAML uses single quotes to wrap URL strings.
         $this->assertContains("base_url: '" . $CFG->behat_wwwroot . "'", $contents);
index 4d518de..6d6f0f0 100644 (file)
@@ -13,10 +13,10 @@ Feature: Manage language packs
   Scenario: Install language pack
     Given I log in as "admin"
     And I navigate to "Language packs" node in "Site administration > Language"
-    When I set the field "Available language packs" to "English - Pirate (en_ar)"
+    When I set the field "Available language packs" to "en_ar"
     And I press "Install selected language pack(s)"
     Then I should see "Language pack 'en_ar' was successfully installed"
-    And the "Installed language packs" select box should contain "English - Pirate (en_ar)"
+    And the "Installed language packs" select box should contain "en_ar"
     And I navigate to "Live logs" node in "Site administration > Reports"
     And I should see "The language pack 'en_ar' was installed."
     And I log out
@@ -35,14 +35,14 @@ Feature: Manage language packs
   Scenario: Try to uninstall language pack
     Given I log in as "admin"
     And I navigate to "Language packs" node in "Site administration > Language"
-    And I set the field "Available language packs" to "English - Pirate (en_ar)"
+    And I set the field "Available language packs" to "en_ar"
     And I press "Install selected language pack(s)"
-    When I set the field "Installed language packs" to "English - Pirate (en_ar)"
+    When I set the field "Installed language packs" to "en_ar"
     And I press "Uninstall selected language pack(s)"
     And I press "Continue"
     Then I should see "Language pack 'en_ar' was uninstalled"
-    And the "Installed language packs" select box should not contain "English - Pirate (en_ar)"
-    And the "Available language packs" select box should contain "English - Pirate (en_ar)"
+    And the "Installed language packs" select box should not contain "en_ar"
+    And the "Available language packs" select box should contain "en_ar"
     And I navigate to "Live logs" node in "Site administration > Reports"
     And I should see "The language pack 'en_ar' was removed."
     And I should see "Language pack uninstalled"
@@ -51,7 +51,7 @@ Feature: Manage language packs
   Scenario: Try to uninstall English language pack
     Given I log in as "admin"
     And I navigate to "Language packs" node in "Site administration > Language"
-    When I set the field "Installed language packs" to "English (en)"
+    When I set the field "Installed language packs" to "en"
     And I press "Uninstall selected language pack(s)"
     Then I should see "The English language pack cannot be uninstalled."
     And I navigate to "Live logs" node in "Site administration > Reports"
diff --git a/auth/manual/tests/behat/auth_manual.feature b/auth/manual/tests/behat/auth_manual.feature
new file mode 100644 (file)
index 0000000..e7970dd
--- /dev/null
@@ -0,0 +1,28 @@
+@auth @auth_manual
+Feature: Test manual authentication works.
+  In order to check manual authentication
+  As a teacher
+  I need to go on login page and enter username and password.
+
+  Background:
+    Given the following "users" exist:
+      | username |
+      | teacher1 |
+
+  @javascript
+  Scenario: Check login works with javascript.
+    Given I am on homepage
+    And I expand navigation bar
+    And I click on "Log in" "link" in the ".logininfo" "css_element"
+    When I set the field "Username" to "teacher1"
+    And I set the field "Password" to "teacher1"
+    When I press "Log in"
+    Then I should see "You are logged in as"
+
+  Scenario: Check login works without javascript.
+    Given I am on homepage
+    And I click on "Log in" "link" in the ".logininfo" "css_element"
+    When I set the field "Username" to "teacher1"
+    And I set the field "Password" to "teacher1"
+    When I press "Log in"
+    Then I should see "You are logged in as"
index 959c2fb..53349b4 100644 (file)
@@ -28,8 +28,8 @@
 
 require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
 
-use Behat\Behat\Context\Step\Given as Given;
-use Behat\Behat\Context\Step\When as When;
+use Moodle\BehatExtension\Context\Step\Given as Given;
+use Moodle\BehatExtension\Context\Step\When as When;
 
 /**
  * Log in log out steps definitions.
@@ -47,34 +47,16 @@ class behat_auth extends behat_base {
      * @Given /^I log in as "(?P<username_string>(?:[^"]|\\")*)"$/
      */
     public function i_log_in_as($username) {
+        // Visit login page.
+        $this->getSession()->visit($this->locate_path('login/index.php'));
 
-        // Running this step using the API rather than a chained step because
-        // we need to see if the 'Log in' link is available or we need to click
-        // the dropdown to expand the navigation bar before.
-        $this->getSession()->visit($this->locate_path('/'));
+        // Enter username and password.
+        $behatforms = behat_context_helper::get('behat_forms');
+        $behatforms->i_set_the_field_to('Username', $this->escape($username));
+        $behatforms->i_set_the_field_to('Password', $this->escape($username));
 
-        // Generic steps (we will prefix them later expanding the navigation dropdown if necessary).
-        $steps = array(
-            new Given('I click on "' . get_string('login') . '" "link" in the ".logininfo" "css_element"'),
-            new Given('I set the field "' . get_string('username') . '" to "' . $this->escape($username) . '"'),
-            new Given('I set the field "' . get_string('password') . '" to "'. $this->escape($username) . '"'),
-            new Given('I press "' . get_string('login') . '"')
-        );
-
-        // If Javascript is disabled we have enough with these steps.
-        if (!$this->running_javascript()) {
-            return $steps;
-        }
-
-        // Wait for the homepage to be ready.
-        $this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
-
-        // If it is needed, it expands the navigation bar with the 'Log in' link.
-        if ($clicknavbar = $this->get_expand_navbar_step()) {
-            array_unshift($steps, $clicknavbar);
-        }
-
-        return $steps;
+        // Press log in button.
+        $behatforms->press_button(get_string('login'));
     }
 
     /**
@@ -101,35 +83,4 @@ class behat_auth extends behat_base {
 
         return $steps;
     }
-
-    /**
-     * Returns a step to open the navigation bar if it is needed.
-     *
-     * The top log in and log out links are hidden when middle or small
-     * size windows (or devices) are used. This step returns a step definition
-     * clicking to expand the navbar if it is hidden.
-     *
-     * @return Given|bool A step definition or false if there is no need to show the navbar.
-     */
-    protected function get_expand_navbar_step() {
-
-        // Checking if we need to click the navbar button to show the navigation menu, it
-        // is hidden by default when using clean theme and a medium or small screen size.
-
-        // The DOM and the JS should be all ready and loaded. Running without spinning
-        // as this is a widely used step and we can not spend time here trying to see
-        // a DOM node that is not always there (at the moment clean is not even the
-        // default theme...).
-        $navbuttonjs = "return (
-            Y.one('.btn-navbar') &&
-            Y.one('.btn-navbar').getComputedStyle('display') !== 'none'
-        )";
-
-        // Adding an extra click we need to show the 'Log in' link.
-        if (!$this->getSession()->getDriver()->evaluateScript($navbuttonjs)) {
-            return false;
-        }
-
-        return new Given('I click on ".btn-navbar" "css_element"');
-    }
 }
index bd0f777..410cdef 100644 (file)
@@ -219,7 +219,6 @@ class behat_backup extends behat_base {
             "/descendant::div[@class='restore-course-search']" .
             "/descendant::tr[contains(., $existingcourse)]" .
             "/descendant::input[@type='radio']");
-        $radionode->check();
         $radionode->click();
 
         // Pressing the continue button of the restore into an existing course section.
@@ -250,7 +249,6 @@ class behat_backup extends behat_base {
         $radionode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), ' bcs-new-course ')]" .
             "/descendant::div[@class='restore-course-search']" .
             "/descendant::input[@type='radio']");
-        $radionode->check();
         $radionode->click();
 
         // Pressing the continue button of the restore into an existing course section.
@@ -280,7 +278,6 @@ class behat_backup extends behat_base {
         // Merge without deleting radio option.
         $radionode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), 'bcs-current-course')]" .
             "/descendant::input[@type='radio'][@name='target'][@value='1']");
-        $radionode->check();
         $radionode->click();
 
         // Pressing the continue button of the restore merging section.
@@ -310,7 +307,6 @@ class behat_backup extends behat_base {
         // Delete contents radio option.
         $radionode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), 'bcs-current-course')]" .
             "/descendant::input[@type='radio'][@name='target'][@value='0']");
-        $radionode->check();
         $radionode->click();
 
         // Pressing the continue button of the restore merging section.
@@ -419,8 +415,6 @@ class behat_backup extends behat_base {
             return;
         }
 
-        $pageoptions = clone $options;
-
         $rows = $options->getRows();
         $newrows = array();
         foreach ($rows as $k => $data) {
@@ -433,7 +427,8 @@ class behat_backup extends behat_base {
                 $newrows[] = $data;
             }
         }
-        $pageoptions->setRows($newrows);
+        $pageoptions = new TableNode($newrows);
+
         return $pageoptions;
     }
 
index 60cf807..8c7e910 100644 (file)
@@ -1,14 +1,13 @@
 default:
-  paths:
-    features: lib/behat/features
-    bootstrap: lib/behat/features/bootstrap
-  context:
-    class: behat_init_context
+  suites:
+    default:
+      paths: {  }
+      contexts: {  }
   extensions:
-    Behat\MinkExtension\Extension:
+    Behat\MinkExtension:
       base_url: 'http://localhost:8000'
       goutte: null
       selenium2: null
-    Moodle\BehatExtension\Extension:
-      features: {  }
+    Moodle\BehatExtension:
+      moodledirroot: /Should/Change/To/Moodle/www/dir
       steps_definitions: {  }
index a10250d..2d8143c 100644 (file)
@@ -27,7 +27,7 @@
 
 require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
 
-use Behat\Behat\Context\Step\Given as Given;
+use Moodle\BehatExtension\Context\Step\Given as Given;
 
 /**
  * Blocks management steps definitions.
index 70e57b2..9dc24a2 100644 (file)
@@ -26,7 +26,7 @@
 // NOTE: no MOODLE_INTERNAL used, this file may be required by behat before including /config.php.
 require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
 
-use Behat\Behat\Context\Step\Given as Given;
+use Moodle\BehatExtension\Context\Step\Given as Given;
 use Behat\Gherkin\Node\TableNode as TableNode;
 
 /**
index 8c860b5..e1fd621 100644 (file)
@@ -27,7 +27,7 @@
 
 require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
 
-use Behat\Behat\Context\Step\Given as Given;
+use Moodle\BehatExtension\Context\Step\Given as Given;
 
 /**
  * Steps definitions for cohort actions.
index f1b4215..0bd4e80 100644 (file)
@@ -27,8 +27,8 @@
 
 require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
 
-use Behat\Behat\Context\Step\Given,
-    Behat\Behat\Context\Step\Then,
+use Moodle\BehatExtension\Context\Step\Given,
+    Moodle\BehatExtension\Context\Step\Then,
     Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
 
 /**
index fdff4bd..f578d7a 100644 (file)
@@ -2,6 +2,6 @@
     "require-dev": {
         "phpunit/phpunit": "4.8.*",
         "phpunit/dbUnit": "1.4.*",
-        "moodlehq/behat-extension": "1.31.0"
+        "moodlehq/behat-extension": "3.31.0"
     }
 }
index c97fa52..7ce9df0 100644 (file)
@@ -4,37 +4,41 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "hash": "a957f7332dd1d221be96fb356cb9f03d",
-    "content-hash": "463a75022982e6a64bd9fc0513d9b44c",
+    "hash": "769fa23c4b31f60c9fb82d5b23171e0f",
+    "content-hash": "5fca4c69d043cb1f985fc08cd82a64f8",
     "packages": [],
     "packages-dev": [
         {
             "name": "behat/behat",
-            "version": "v2.5.5",
+            "version": "v3.0.15",
             "source": {
                 "type": "git",
                 "url": "https://github.com/Behat/Behat.git",
-                "reference": "c1e48826b84669c97a1efa78459aedfdcdcf2120"
+                "reference": "b35ae3d45332d80c532af69cc36f780a9397a996"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Behat/Behat/zipball/c1e48826b84669c97a1efa78459aedfdcdcf2120",
-                "reference": "c1e48826b84669c97a1efa78459aedfdcdcf2120",
+                "url": "https://api.github.com/repos/Behat/Behat/zipball/b35ae3d45332d80c532af69cc36f780a9397a996",
+                "reference": "b35ae3d45332d80c532af69cc36f780a9397a996",
                 "shasum": ""
             },
             "require": {
-                "behat/gherkin": "~2.3.0",
-                "php": ">=5.3.1",
+                "behat/gherkin": "~4.3",
+                "behat/transliterator": "~1.0",
+                "ext-mbstring": "*",
+                "php": ">=5.3.3",
+                "symfony/class-loader": "~2.1",
                 "symfony/config": "~2.3",
-                "symfony/console": "~2.0",
-                "symfony/dependency-injection": "~2.0",
-                "symfony/event-dispatcher": "~2.0",
-                "symfony/finder": "~2.0",
+                "symfony/console": "~2.1",
+                "symfony/dependency-injection": "~2.1",
+                "symfony/event-dispatcher": "~2.1",
                 "symfony/translation": "~2.3",
-                "symfony/yaml": "~2.0"
+                "symfony/yaml": "~2.1"
             },
             "require-dev": {
-                "phpunit/phpunit": "~3.7.19"
+                "phpspec/prophecy-phpunit": "~1.0",
+                "phpunit/phpunit": "~4.0",
+                "symfony/process": "~2.1"
             },
             "suggest": {
                 "behat/mink-extension": "for integration with Mink testing framework",
                 "bin/behat"
             ],
             "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0.x-dev"
+                }
+            },
             "autoload": {
                 "psr-0": {
-                    "Behat\\Behat": "src/"
+                    "Behat\\Behat": "src/",
+                    "Behat\\Testwork": "src/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
             "description": "Scenario-oriented BDD framework for PHP 5.3",
             "homepage": "http://behat.org/",
             "keywords": [
+                "Agile",
                 "BDD",
-                "Behat",
-                "Symfony2"
+                "ScenarioBDD",
+                "Scrum",
+                "StoryBDD",
+                "User story",
+                "business",
+                "development",
+                "documentation",
+                "examples",
+                "symfony",
+                "testing"
             ],
-            "time": "2015-06-01 09:37:55"
+            "time": "2015-02-22 14:10:33"
         },
         {
             "name": "behat/gherkin",
-            "version": "v2.3.5",
+            "version": "v4.4.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/Behat/Gherkin.git",
-                "reference": "2b33963da5525400573560c173ab5c9c057e1852"
+                "reference": "1576b485c0f92ef6d27da9c4bbfc57ee30cf6911"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Behat/Gherkin/zipball/2b33963da5525400573560c173ab5c9c057e1852",
-                "reference": "2b33963da5525400573560c173ab5c9c057e1852",
+                "url": "https://api.github.com/repos/Behat/Gherkin/zipball/1576b485c0f92ef6d27da9c4bbfc57ee30cf6911",
+                "reference": "1576b485c0f92ef6d27da9c4bbfc57ee30cf6911",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.3.1",
-                "symfony/finder": "~2.0"
+                "php": ">=5.3.1"
             },
             "require-dev": {
-                "symfony/config": "~2.0",
-                "symfony/translation": "~2.0",
-                "symfony/yaml": "~2.0"
+                "phpunit/phpunit": "~4.0",
+                "symfony/yaml": "~2.1"
             },
             "suggest": {
-                "symfony/config": "If you want to use Config component to manage resources",
-                "symfony/translation": "If you want to use Symfony2 translations adapter",
                 "symfony/yaml": "If you want to parse features, represented in YAML files"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-develop": "2.2-dev"
+                    "dev-master": "4.4-dev"
                 }
             },
             "autoload": {
             "keywords": [
                 "BDD",
                 "Behat",
+                "Cucumber",
                 "DSL",
-                "Symfony2",
+                "gherkin",
                 "parser"
             ],
-            "time": "2013-10-15 11:22:17"
+            "time": "2015-12-30 14:47:00"
         },
         {
             "name": "behat/mink",
-            "version": "v1.5.0",
+            "version": "v1.7.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/minkphp/Mink.git",
-                "reference": "0769e6d9726c140a54dbf827a438c0f9912749fe"
+                "reference": "6c129030ec2cc029905cf969a56ca8f087b2dfdf"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/minkphp/Mink/zipball/0769e6d9726c140a54dbf827a438c0f9912749fe",
-                "reference": "0769e6d9726c140a54dbf827a438c0f9912749fe",
+                "url": "https://api.github.com/repos/minkphp/Mink/zipball/6c129030ec2cc029905cf969a56ca8f087b2dfdf",
+                "reference": "6c129030ec2cc029905cf969a56ca8f087b2dfdf",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.3.1",
-                "symfony/css-selector": "~2.0"
+                "symfony/css-selector": "~2.1"
+            },
+            "require-dev": {
+                "symfony/phpunit-bridge": "~2.7"
             },
             "suggest": {
                 "behat/mink-browserkit-driver": "extremely fast headless driver for Symfony\\Kernel-based apps (Sf2, Silex)",
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-develop": "1.5.x-dev"
+                    "dev-master": "1.7.x-dev"
                 }
             },
             "autoload": {
-                "psr-0": {
-                    "Behat\\Mink": "src/"
+                "psr-4": {
+                    "Behat\\Mink\\": "src/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
                     "homepage": "http://everzet.com"
                 }
             ],
-            "description": "Web acceptance testing framework for PHP 5.3",
+            "description": "Browser controller/emulator abstraction for PHP",
             "homepage": "http://mink.behat.org/",
             "keywords": [
                 "browser",
                 "testing",
                 "web"
             ],
-            "time": "2013-04-13 23:39:27"
+            "time": "2015-09-20 20:24:03"
         },
         {
             "name": "behat/mink-browserkit-driver",
-            "version": "v1.1.0",
+            "version": "v1.3.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/minkphp/MinkBrowserKitDriver.git",
-                "reference": "63960c8fcad4529faad1ff33e950217980baa64c"
+                "reference": "2650f5420e713e3807c7f09a07370a4f48367bf9"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/minkphp/MinkBrowserKitDriver/zipball/63960c8fcad4529faad1ff33e950217980baa64c",
-                "reference": "63960c8fcad4529faad1ff33e950217980baa64c",
+                "url": "https://api.github.com/repos/minkphp/MinkBrowserKitDriver/zipball/2650f5420e713e3807c7f09a07370a4f48367bf9",
+                "reference": "2650f5420e713e3807c7f09a07370a4f48367bf9",
                 "shasum": ""
             },
             "require": {
-                "behat/mink": "~1.5.0",
-                "php": ">=5.3.1",
-                "symfony/browser-kit": "~2.0",
-                "symfony/dom-crawler": "~2.0"
+                "behat/mink": "~1.7@dev",
+                "php": ">=5.3.6",
+                "symfony/browser-kit": "~2.3|~3.0",
+                "symfony/dom-crawler": "~2.3|~3.0"
             },
             "require-dev": {
-                "silex/silex": "@dev"
+                "silex/silex": "~1.2",
+                "symfony/phpunit-bridge": "~2.7|~3.0"
             },
             "type": "mink-driver",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.1.x-dev"
+                    "dev-master": "1.3.x-dev"
                 }
             },
             "autoload": {
-                "psr-0": {
-                    "Behat\\Mink\\Driver": "src/"
+                "psr-4": {
+                    "Behat\\Mink\\Driver\\": "src/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
                 "browser",
                 "testing"
             ],
-            "time": "2013-04-13 23:46:30"
+            "time": "2016-01-19 16:59:07"
         },
         {
             "name": "behat/mink-extension",
-            "version": "v1.3.3",
+            "version": "v2.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/Behat/MinkExtension.git",
-                "reference": "b885b9407cba50a954f72c69ed1b2f8d3bc694f8"
+                "reference": "5b4bda64ff456104564317e212c823e45cad9d59"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Behat/MinkExtension/zipball/b885b9407cba50a954f72c69ed1b2f8d3bc694f8",
-                "reference": "b885b9407cba50a954f72c69ed1b2f8d3bc694f8",
+                "url": "https://api.github.com/repos/Behat/MinkExtension/zipball/5b4bda64ff456104564317e212c823e45cad9d59",
+                "reference": "5b4bda64ff456104564317e212c823e45cad9d59",
                 "shasum": ""
             },
             "require": {
-                "behat/behat": "~2.5.0",
+                "behat/behat": "~3.0,>=3.0.5",
                 "behat/mink": "~1.5",
                 "php": ">=5.3.2",
-                "symfony/config": "~2.2"
+                "symfony/config": "~2.2|~3.0"
             },
             "require-dev": {
-                "behat/mink-goutte-driver": "~1.0",
-                "fabpot/goutte": "~1.0"
+                "behat/mink-goutte-driver": "~1.1",
+                "phpspec/phpspec": "~2.0"
             },
             "type": "behat-extension",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.1.x-dev"
+                }
+            },
             "autoload": {
                 "psr-0": {
                     "Behat\\MinkExtension": "src/"
                 "MIT"
             ],
             "authors": [
+                {
+                    "name": "Christophe Coevoet",
+                    "email": "stof@notk.org"
+                },
                 {
                     "name": "Konstantin Kudryashov",
-                    "email": "ever.zet@gmail.com",
-                    "homepage": "http://everzet.com"
+                    "email": "ever.zet@gmail.com"
                 }
             ],
             "description": "Mink extension for Behat",
-            "homepage": "http://mink.behat.org",
+            "homepage": "http://extensions.behat.org/mink",
             "keywords": [
                 "browser",
                 "gui",
                 "test",
                 "web"
             ],
-            "time": "2014-05-15 19:27:39"
+            "time": "2016-02-15 07:55:18"
         },
         {
             "name": "behat/mink-goutte-driver",
-            "version": "v1.0.9",
+            "version": "v1.2.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/minkphp/MinkGoutteDriver.git",
-                "reference": "fa1b073b48761464feb0b05e6825da44b20118d8"
+                "reference": "c8e254f127d6f2242b994afd4339fb62d471df3f"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/minkphp/MinkGoutteDriver/zipball/fa1b073b48761464feb0b05e6825da44b20118d8",
-                "reference": "fa1b073b48761464feb0b05e6825da44b20118d8",
+                "url": "https://api.github.com/repos/minkphp/MinkGoutteDriver/zipball/c8e254f127d6f2242b994afd4339fb62d471df3f",
+                "reference": "c8e254f127d6f2242b994afd4339fb62d471df3f",
                 "shasum": ""
             },
             "require": {
-                "behat/mink-browserkit-driver": ">=1.0.5,<1.2.0",
-                "fabpot/goutte": "~1.0.1",
+                "behat/mink": "~1.6@dev",
+                "behat/mink-browserkit-driver": "~1.2@dev",
+                "fabpot/goutte": "~1.0.4|~2.0|~3.1",
                 "php": ">=5.3.1"
             },
+            "require-dev": {
+                "symfony/phpunit-bridge": "~2.7"
+            },
             "type": "mink-driver",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.0.x-dev"
+                    "dev-master": "1.2.x-dev"
                 }
             },
             "autoload": {
-                "psr-0": {
-                    "Behat\\Mink\\Driver": "src/"
+                "psr-4": {
+                    "Behat\\Mink\\Driver\\": "src/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
                 "headless",
                 "testing"
             ],
-            "time": "2013-07-03 18:43:54"
+            "time": "2015-09-21 21:31:11"
         },
         {
             "name": "behat/mink-selenium2-driver",
-            "version": "v1.1.1",
+            "version": "v1.3.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/minkphp/MinkSelenium2Driver.git",
-                "reference": "bcf1b537de37db6db0822d9e7bd97e600fd7a476"
+                "reference": "bedbf1999c7ba1bc6921b30ee2eadf383e7ff5c9"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/minkphp/MinkSelenium2Driver/zipball/bcf1b537de37db6db0822d9e7bd97e600fd7a476",
-                "reference": "bcf1b537de37db6db0822d9e7bd97e600fd7a476",
+                "url": "https://api.github.com/repos/minkphp/MinkSelenium2Driver/zipball/bedbf1999c7ba1bc6921b30ee2eadf383e7ff5c9",
+                "reference": "bedbf1999c7ba1bc6921b30ee2eadf383e7ff5c9",
                 "shasum": ""
             },
             "require": {
-                "behat/mink": "~1.5.0",
-                "instaclick/php-webdriver": "~1.0.12",
+                "behat/mink": "~1.7@dev",
+                "instaclick/php-webdriver": "~1.1",
                 "php": ">=5.3.1"
             },
+            "require-dev": {
+                "symfony/phpunit-bridge": "~2.7"
+            },
             "type": "mink-driver",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.1.x-dev"
+                    "dev-master": "1.3.x-dev"
                 }
             },
             "autoload": {
-                "psr-0": {
-                    "Behat\\Mink\\Driver": "src/"
+                "psr-4": {
+                    "Behat\\Mink\\Driver\\": "src/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
                 "testing",
                 "webdriver"
             ],
-            "time": "2013-06-02 19:09:45"
+            "time": "2015-09-21 21:02:54"
+        },
+        {
+            "name": "behat/transliterator",
+            "version": "v1.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Behat/Transliterator.git",
+                "reference": "868e05be3a9f25ba6424c2dd4849567f50715003"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Behat/Transliterator/zipball/868e05be3a9f25ba6424c2dd4849567f50715003",
+                "reference": "868e05be3a9f25ba6424c2dd4849567f50715003",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "Behat\\Transliterator": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Artistic-1.0"
+            ],
+            "description": "String transliterator",
+            "keywords": [
+                "i18n",
+                "slug",
+                "transliterator"
+            ],
+            "time": "2015-09-28 16:26:35"
         },
         {
             "name": "doctrine/instantiator",
         },
         {
             "name": "fabpot/goutte",
-            "version": "v1.0.7",
+            "version": "v2.0.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/FriendsOfPHP/Goutte.git",
-                "reference": "794b196e76bdd37b5155cdecbad311f0a3b07625"
+                "reference": "0ad3ee6dc2d0aaa832a80041a1e09bf394e99802"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/FriendsOfPHP/Goutte/zipball/794b196e76bdd37b5155cdecbad311f0a3b07625",
-                "reference": "794b196e76bdd37b5155cdecbad311f0a3b07625",
+                "url": "https://api.github.com/repos/FriendsOfPHP/Goutte/zipball/0ad3ee6dc2d0aaa832a80041a1e09bf394e99802",
+                "reference": "0ad3ee6dc2d0aaa832a80041a1e09bf394e99802",
                 "shasum": ""
             },
             "require": {
-                "ext-curl": "*",
-                "guzzle/http": "~3.1",
-                "php": ">=5.3.0",
+                "guzzlehttp/guzzle": ">=4,<6",
+                "php": ">=5.4.0",
                 "symfony/browser-kit": "~2.1",
                 "symfony/css-selector": "~2.1",
-                "symfony/dom-crawler": "~2.1",
-                "symfony/finder": "~2.1",
-                "symfony/process": "~2.1"
-            },
-            "require-dev": {
-                "guzzle/plugin-history": "~3.1",
-                "guzzle/plugin-mock": "~3.1"
+                "symfony/dom-crawler": "~2.1"
             },
             "type": "application",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.0-dev"
+                    "dev-master": "2.0-dev"
                 }
             },
             "autoload": {
-                "psr-0": {
-                    "Goutte": "."
+                "psr-4": {
+                    "Goutte\\": "Goutte"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
                 }
             ],
             "description": "A simple PHP Web Scraper",
-            "homepage": "https://github.com/fabpot/Goutte",
+            "homepage": "https://github.com/FriendsOfPHP/Goutte",
             "keywords": [
                 "scraper"
             ],
-            "time": "2014-10-09 15:52:51"
+            "time": "2015-05-05 21:14:57"
         },
         {
             "name": "guzzlehttp/guzzle",
-            "version": "v3.8.1",
+            "version": "5.3.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/guzzle/guzzle.git",
-                "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba"
+                "reference": "f3c8c22471cb55475105c14769644a49c3262b93"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/4de0618a01b34aa1c8c33a3f13f396dcd3882eba",
-                "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba",
+                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/f3c8c22471cb55475105c14769644a49c3262b93",
+                "reference": "f3c8c22471cb55475105c14769644a49c3262b93",
                 "shasum": ""
             },
             "require": {
-                "ext-curl": "*",
-                "php": ">=5.3.3",
-                "symfony/event-dispatcher": ">=2.1"
-            },
-            "replace": {
-                "guzzle/batch": "self.version",
-                "guzzle/cache": "self.version",
-                "guzzle/common": "self.version",
-                "guzzle/http": "self.version",
-                "guzzle/inflection": "self.version",
-                "guzzle/iterator": "self.version",
-                "guzzle/log": "self.version",
-                "guzzle/parser": "self.version",
-                "guzzle/plugin": "self.version",
-                "guzzle/plugin-async": "self.version",
-                "guzzle/plugin-backoff": "self.version",
-                "guzzle/plugin-cache": "self.version",
-                "guzzle/plugin-cookie": "self.version",
-                "guzzle/plugin-curlauth": "self.version",
-                "guzzle/plugin-error-response": "self.version",
-                "guzzle/plugin-history": "self.version",
-                "guzzle/plugin-log": "self.version",
-                "guzzle/plugin-md5": "self.version",
-                "guzzle/plugin-mock": "self.version",
-                "guzzle/plugin-oauth": "self.version",
-                "guzzle/service": "self.version",
-                "guzzle/stream": "self.version"
+                "guzzlehttp/ringphp": "^1.1",
+                "php": ">=5.4.0"
             },
             "require-dev": {
-                "doctrine/cache": "*",
-                "monolog/monolog": "1.*",
-                "phpunit/phpunit": "3.7.*",
-                "psr/log": "1.0.*",
-                "symfony/class-loader": "*",
-                "zendframework/zend-cache": "<2.3",
-                "zendframework/zend-log": "<2.3"
+                "ext-curl": "*",
+                "phpunit/phpunit": "^4.0",
+                "psr/log": "^1.0"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.8-dev"
+                    "dev-master": "5.0-dev"
                 }
             },
             "autoload": {
-                "psr-0": {
-                    "Guzzle": "src/",
-                    "Guzzle\\Tests": "tests/"
+                "psr-4": {
+                    "GuzzleHttp\\": "src/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
                     "name": "Michael Dowling",
                     "email": "mtdowling@gmail.com",
                     "homepage": "https://github.com/mtdowling"
-                },
-                {
-                    "name": "Guzzle Community",
-                    "homepage": "https://github.com/guzzle/guzzle/contributors"
                 }
             ],
             "description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients",
                 "rest",
                 "web service"
             ],
-            "time": "2014-01-28 22:29:15"
+            "time": "2015-05-20 03:47:55"
+        },
+        {
+            "name": "guzzlehttp/ringphp",
+            "version": "1.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/RingPHP.git",
+                "reference": "dbbb91d7f6c191e5e405e900e3102ac7f261bc0b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/RingPHP/zipball/dbbb91d7f6c191e5e405e900e3102ac7f261bc0b",
+                "reference": "dbbb91d7f6c191e5e405e900e3102ac7f261bc0b",
+                "shasum": ""
+            },
+            "require": {
+                "guzzlehttp/streams": "~3.0",
+                "php": ">=5.4.0",
+                "react/promise": "~2.0"
+            },
+            "require-dev": {
+                "ext-curl": "*",
+                "phpunit/phpunit": "~4.0"
+            },
+            "suggest": {
+                "ext-curl": "Guzzle will use specific adapters if cURL is present"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\Ring\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                }
+            ],
+            "description": "Provides a simple API and specification that abstracts away the details of HTTP into a single PHP function.",
+            "time": "2015-05-20 03:37:09"
+        },
+        {
+            "name": "guzzlehttp/streams",
+            "version": "3.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/streams.git",
+                "reference": "47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/streams/zipball/47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5",
+                "reference": "47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.4.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\Stream\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                }
+            ],
+            "description": "Provides a simple abstraction over streams of data",
+            "homepage": "http://guzzlephp.org/",
+            "keywords": [
+                "Guzzle",
+                "stream"
+            ],
+            "time": "2014-10-12 19:18:40"
         },
         {
             "name": "instaclick/php-webdriver",
-            "version": "1.0.17",
+            "version": "1.4.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/instaclick/php-webdriver.git",
-                "reference": "47a6019553a7a5b42d35493276ffc2c9252c53d5"
+                "reference": "0c20707dcf30a32728fd6bdeeab996c887fdb2fb"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/instaclick/php-webdriver/zipball/47a6019553a7a5b42d35493276ffc2c9252c53d5",
-                "reference": "47a6019553a7a5b42d35493276ffc2c9252c53d5",
+                "url": "https://api.github.com/repos/instaclick/php-webdriver/zipball/0c20707dcf30a32728fd6bdeeab996c887fdb2fb",
+                "reference": "0c20707dcf30a32728fd6bdeeab996c887fdb2fb",
                 "shasum": ""
             },
             "require": {
                 "ext-curl": "*",
                 "php": ">=5.3.2"
             },
-            "bin": [
-                "bin/webunit"
-            ],
+            "require-dev": {
+                "satooshi/php-coveralls": "dev-master"
+            },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.0.x-dev"
+                    "dev-master": "1.4.x-dev"
                 }
             },
             "autoload": {
                 {
                     "name": "Anthon Pang",
                     "email": "apang@softwaredevelopment.ca",
-                    "role": "developer"
+                    "role": "Fork Maintainer"
                 }
             ],
             "description": "PHP WebDriver for Selenium 2",
                 "webdriver",
                 "webtest"
             ],
-            "time": "2013-10-04 15:03:51"
+            "time": "2015-06-15 20:19:33"
         },
         {
             "name": "moodlehq/behat-extension",
-            "version": "v1.31.0",
+            "version": "v3.31.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/moodlehq/moodle-behat-extension.git",
-                "reference": "b4dd6f7d1ca1c89a44b8b010af8546d7a2808b7b"
+                "reference": "d985e9da29914b0da90d61c47aadc455586eeee5"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/moodlehq/moodle-behat-extension/zipball/b4dd6f7d1ca1c89a44b8b010af8546d7a2808b7b",
-                "reference": "b4dd6f7d1ca1c89a44b8b010af8546d7a2808b7b",
+                "url": "https://api.github.com/repos/moodlehq/moodle-behat-extension/zipball/d985e9da29914b0da90d61c47aadc455586eeee5",
+                "reference": "d985e9da29914b0da90d61c47aadc455586eeee5",
                 "shasum": ""
             },
             "require": {
-                "behat/behat": "2.5.5",
-                "behat/mink": "1.5.0",
-                "behat/mink-extension": "1.3.3",
-                "behat/mink-goutte-driver": "1.0.9",
-                "behat/mink-selenium2-driver": "1.1.1",
-                "guzzlehttp/guzzle": "~3.1",
+                "behat/behat": "3.0.*",
+                "behat/mink": "~1.7",
+                "behat/mink-extension": "~2.1",
+                "behat/mink-goutte-driver": "~1.2",
+                "behat/mink-selenium2-driver": "~1.3",
                 "php": ">=5.4.4",
-                "symfony/browser-kit": "2.7.5",
-                "symfony/css-selector": "2.7.5",
-                "symfony/dom-crawler": "2.7.5",
-                "symfony/filesystem": "2.7.5",
-                "symfony/finder": "2.7.5"
+                "symfony/process": "2.8.*"
             },
             "type": "library",
             "autoload": {
                 "Behat",
                 "moodle"
             ],
-            "time": "2016-01-05 02:55:24"
+            "time": "2016-03-04 07:15:40"
         },
         {
             "name": "phpdocumentor/reflection-docblock",
         },
         {
             "name": "phpspec/prophecy",
-            "version": "v1.5.0",
+            "version": "v1.6.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phpspec/prophecy.git",
-                "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7"
+                "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4745ded9307786b730d7a60df5cb5a6c43cf95f7",
-                "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7",
+                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/3c91bdf81797d725b14cb62906f9a4ce44235972",
+                "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972",
                 "shasum": ""
             },
             "require": {
                 "doctrine/instantiator": "^1.0.2",
+                "php": "^5.3|^7.0",
                 "phpdocumentor/reflection-docblock": "~2.0",
-                "sebastian/comparator": "~1.1"
+                "sebastian/comparator": "~1.1",
+                "sebastian/recursion-context": "~1.0"
             },
             "require-dev": {
                 "phpspec/phpspec": "~2.0"
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.4.x-dev"
+                    "dev-master": "1.5.x-dev"
                 }
             },
             "autoload": {
                 "spy",
                 "stub"
             ],
-            "time": "2015-08-13 10:07:40"
+            "time": "2016-02-15 07:46:21"
         },
         {
             "name": "phpunit/dbunit",
         },
         {
             "name": "phpunit/phpunit",
-            "version": "4.8.21",
+            "version": "4.8.23",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/phpunit.git",
-                "reference": "ea76b17bced0500a28098626b84eda12dbcf119c"
+                "reference": "6e351261f9cd33daf205a131a1ba61c6d33bd483"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ea76b17bced0500a28098626b84eda12dbcf119c",
-                "reference": "ea76b17bced0500a28098626b84eda12dbcf119c",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6e351261f9cd33daf205a131a1ba61c6d33bd483",
+                "reference": "6e351261f9cd33daf205a131a1ba61c6d33bd483",
                 "shasum": ""
             },
             "require": {
                 "testing",
                 "xunit"
             ],
-            "time": "2015-12-12 07:45:58"
+            "time": "2016-02-11 14:56:33"
         },
         {
             "name": "phpunit/phpunit-mock-objects",
             ],
             "time": "2015-10-02 06:51:40"
         },
+        {
+            "name": "react/promise",
+            "version": "v2.2.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/reactphp/promise.git",
+                "reference": "3aacad8bf10c7d83e6fa2089d413529888c2bedf"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/reactphp/promise/zipball/3aacad8bf10c7d83e6fa2089d413529888c2bedf",
+                "reference": "3aacad8bf10c7d83e6fa2089d413529888c2bedf",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.4.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "React\\Promise\\": "src/"
+                },
+                "files": [
+                    "src/functions_include.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jan Sorgalla",
+                    "email": "jsorgalla@gmail.com"
+                }
+            ],
+            "description": "A lightweight implementation of CommonJS Promises/A for PHP",
+            "time": "2016-02-26 19:09:02"
+        },
         {
             "name": "sebastian/comparator",
             "version": "1.2.0",
         },
         {
             "name": "sebastian/environment",
-            "version": "1.3.3",
+            "version": "1.3.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/environment.git",
-                "reference": "6e7133793a8e5a5714a551a8324337374be209df"
+                "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6e7133793a8e5a5714a551a8324337374be209df",
-                "reference": "6e7133793a8e5a5714a551a8324337374be209df",
+                "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf",
+                "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf",
                 "shasum": ""
             },
             "require": {
                 "environment",
                 "hhvm"
             ],
-            "time": "2015-12-02 08:37:27"
+            "time": "2016-02-26 18:40:46"
         },
         {
             "name": "sebastian/exporter",
         },
         {
             "name": "symfony/browser-kit",
-            "version": "v2.7.5",
+            "version": "v2.8.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/browser-kit.git",
-                "reference": "277a2457776d4cc25706fbdd9d1e4ab2dac884e4"
+                "reference": "6b2085020b4e86fcb7ae44c3ab8ddb91774b33d2"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/browser-kit/zipball/277a2457776d4cc25706fbdd9d1e4ab2dac884e4",
-                "reference": "277a2457776d4cc25706fbdd9d1e4ab2dac884e4",
+                "url": "https://api.github.com/repos/symfony/browser-kit/zipball/6b2085020b4e86fcb7ae44c3ab8ddb91774b33d2",
+                "reference": "6b2085020b4e86fcb7ae44c3ab8ddb91774b33d2",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.3.9",
-                "symfony/dom-crawler": "~2.0,>=2.0.5"
+                "symfony/dom-crawler": "~2.0,>=2.0.5|~3.0.0"
             },
             "require-dev": {
-                "symfony/css-selector": "~2.0,>=2.0.5",
-                "symfony/phpunit-bridge": "~2.7",
-                "symfony/process": "~2.0,>=2.0.5"
+                "symfony/css-selector": "~2.0,>=2.0.5|~3.0.0",
+                "symfony/process": "~2.3.34|~2.7,>=2.7.6|~3.0.0"
             },
             "suggest": {
                 "symfony/process": ""
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.7-dev"
+                    "dev-master": "2.8-dev"
                 }
             },
             "autoload": {
                 "psr-4": {
                     "Symfony\\Component\\BrowserKit\\": ""
-                }
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
             ],
             "description": "Symfony BrowserKit Component",
             "homepage": "https://symfony.com",
-            "time": "2015-09-06 08:36:38"
+            "time": "2016-01-27 11:34:40"
+        },
+        {
+            "name": "symfony/class-loader",
+            "version": "v2.8.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/class-loader.git",
+                "reference": "517ab0554b6a5744d04480cb06873ffbd9442d73"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/class-loader/zipball/517ab0554b6a5744d04480cb06873ffbd9442d73",
+                "reference": "517ab0554b6a5744d04480cb06873ffbd9442d73",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.9",
+                "symfony/polyfill-apcu": "~1.1"
+            },
+            "require-dev": {
+                "symfony/finder": "~2.0,>=2.0.5|~3.0.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.8-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\ClassLoader\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony ClassLoader Component",
+            "homepage": "https://symfony.com",
+            "time": "2016-01-30 15:58:35"
         },
         {
             "name": "symfony/config",
-            "version": "v2.8.1",
+            "version": "v2.8.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/config.git",
-                "reference": "17d4b2e64ce1c6ba7caa040f14469b3c44d7f7d2"
+                "reference": "0f8f94e6a32b5c480024eed5fa5cbd2790d0ad19"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/config/zipball/17d4b2e64ce1c6ba7caa040f14469b3c44d7f7d2",
-                "reference": "17d4b2e64ce1c6ba7caa040f14469b3c44d7f7d2",
+                "url": "https://api.github.com/repos/symfony/config/zipball/0f8f94e6a32b5c480024eed5fa5cbd2790d0ad19",
+                "reference": "0f8f94e6a32b5c480024eed5fa5cbd2790d0ad19",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.3.9",
                 "symfony/filesystem": "~2.3|~3.0.0"
             },
+            "suggest": {
+                "symfony/yaml": "To use the yaml reference dumper"
+            },
             "type": "library",
             "extra": {
                 "branch-alias": {
             ],
             "description": "Symfony Config Component",
             "homepage": "https://symfony.com",
-            "time": "2015-12-26 13:37:56"
+            "time": "2016-02-22 16:12:45"
         },
         {
             "name": "symfony/console",
-            "version": "v2.8.1",
+            "version": "v2.8.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/console.git",
-                "reference": "2e06a5ccb19dcf9b89f1c6a677a39a8df773635a"
+                "reference": "56cc5caf051189720b8de974e4746090aaa10d44"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/console/zipball/2e06a5ccb19dcf9b89f1c6a677a39a8df773635a",
-                "reference": "2e06a5ccb19dcf9b89f1c6a677a39a8df773635a",
+                "url": "https://api.github.com/repos/symfony/console/zipball/56cc5caf051189720b8de974e4746090aaa10d44",
+                "reference": "56cc5caf051189720b8de974e4746090aaa10d44",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony Console Component",
             "homepage": "https://symfony.com",
-            "time": "2015-12-22 10:25:57"
+            "time": "2016-02-28 16:20:50"
         },
         {
             "name": "symfony/css-selector",
-            "version": "v2.7.5",
+            "version": "v2.8.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/css-selector.git",
-                "reference": "abe19cc0429a06be0c133056d1f9859854860970"
+                "reference": "8d83ff9777cdbd83e7f90d9c48f4729823791a5e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/css-selector/zipball/abe19cc0429a06be0c133056d1f9859854860970",
-                "reference": "abe19cc0429a06be0c133056d1f9859854860970",
+                "url": "https://api.github.com/repos/symfony/css-selector/zipball/8d83ff9777cdbd83e7f90d9c48f4729823791a5e",
+                "reference": "8d83ff9777cdbd83e7f90d9c48f4729823791a5e",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.3.9"
             },
-            "require-dev": {
-                "symfony/phpunit-bridge": "~2.7"
-            },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.7-dev"
+                    "dev-master": "2.8-dev"
                 }
             },
             "autoload": {
                 "psr-4": {
                     "Symfony\\Component\\CssSelector\\": ""
-                }
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
             ],
             "description": "Symfony CssSelector Component",
             "homepage": "https://symfony.com",
-            "time": "2015-09-22 13:49:29"
+            "time": "2016-01-27 05:14:19"
         },
         {
             "name": "symfony/dependency-injection",
-            "version": "v2.8.1",
+            "version": "v2.8.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/dependency-injection.git",
-                "reference": "c5086d186f538c2711b9af6f727be7b0446979cd"
+                "reference": "62251761a7615435b22ccf562384c588b431be44"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/c5086d186f538c2711b9af6f727be7b0446979cd",
-                "reference": "c5086d186f538c2711b9af6f727be7b0446979cd",
+                "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/62251761a7615435b22ccf562384c588b431be44",
+                "reference": "62251761a7615435b22ccf562384c588b431be44",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony DependencyInjection Component",
             "homepage": "https://symfony.com",
-            "time": "2015-12-26 13:37:56"
+            "time": "2016-02-28 16:34:46"
         },
         {
             "name": "symfony/dom-crawler",
-            "version": "v2.7.5",
+            "version": "v2.8.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/dom-crawler.git",
-                "reference": "2e185ca136399f902b948694987e62c80099c052"
+                "reference": "e1a4b4c83f5ee6f5902f1d53035e3718909a0c11"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/2e185ca136399f902b948694987e62c80099c052",
-                "reference": "2e185ca136399f902b948694987e62c80099c052",
+                "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/e1a4b4c83f5ee6f5902f1d53035e3718909a0c11",
+                "reference": "e1a4b4c83f5ee6f5902f1d53035e3718909a0c11",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.3.9"
+                "php": ">=5.3.9",
+                "symfony/polyfill-mbstring": "~1.0"
             },
             "require-dev": {
-                "symfony/css-selector": "~2.3",
-                "symfony/phpunit-bridge": "~2.7"
+                "symfony/css-selector": "~2.8|~3.0.0"
             },
             "suggest": {
                 "symfony/css-selector": ""
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.7-dev"
+                    "dev-master": "2.8-dev"
                 }
             },
             "autoload": {
                 "psr-4": {
                     "Symfony\\Component\\DomCrawler\\": ""
-                }
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
             ],
             "description": "Symfony DomCrawler Component",
             "homepage": "https://symfony.com",
-            "time": "2015-09-20 21:13:58"
+            "time": "2016-02-28 16:20:50"
         },
         {
             "name": "symfony/event-dispatcher",
-            "version": "v2.8.1",
+            "version": "v2.8.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/event-dispatcher.git",
-                "reference": "a5eb815363c0388e83247e7e9853e5dbc14999cc"
+                "reference": "78c468665c9568c3faaa9c416a7134308f2d85c3"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a5eb815363c0388e83247e7e9853e5dbc14999cc",
-                "reference": "a5eb815363c0388e83247e7e9853e5dbc14999cc",
+                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/78c468665c9568c3faaa9c416a7134308f2d85c3",
+                "reference": "78c468665c9568c3faaa9c416a7134308f2d85c3",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony EventDispatcher Component",
             "homepage": "https://symfony.com",
-            "time": "2015-10-30 20:15:42"
+            "time": "2016-01-27 05:14:19"
         },
         {
             "name": "symfony/filesystem",
-            "version": "v2.7.5",
+            "version": "v2.8.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/filesystem.git",
-                "reference": "a17f8a17c20e8614c15b8e116e2f4bcde102cfab"
+                "reference": "65cb36b6539b1d446527d60457248f30d045464d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/filesystem/zipball/a17f8a17c20e8614c15b8e116e2f4bcde102cfab",
-                "reference": "a17f8a17c20e8614c15b8e116e2f4bcde102cfab",
+                "url": "https://api.github.com/repos/symfony/filesystem/zipball/65cb36b6539b1d446527d60457248f30d045464d",
+                "reference": "65cb36b6539b1d446527d60457248f30d045464d",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.3.9"
             },
-            "require-dev": {
-                "symfony/phpunit-bridge": "~2.7"
-            },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.7-dev"
+                    "dev-master": "2.8-dev"
                 }
             },
             "autoload": {
                 "psr-4": {
                     "Symfony\\Component\\Filesystem\\": ""
-                }
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
             ],
             "description": "Symfony Filesystem Component",
             "homepage": "https://symfony.com",
-            "time": "2015-09-09 17:42:36"
+            "time": "2016-02-22 15:02:30"
         },
         {
-            "name": "symfony/finder",
-            "version": "v2.7.5",
+            "name": "symfony/polyfill-apcu",
+            "version": "v1.1.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/symfony/finder.git",
-                "reference": "8262ab605973afbb3ef74b945daabf086f58366f"
+                "url": "https://github.com/symfony/polyfill-apcu.git",
+                "reference": "d1911e6caeb4b6a4c8e2d5c46b978a66b3745e4c"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/finder/zipball/8262ab605973afbb3ef74b945daabf086f58366f",
-                "reference": "8262ab605973afbb3ef74b945daabf086f58366f",
+                "url": "https://api.github.com/repos/symfony/polyfill-apcu/zipball/d1911e6caeb4b6a4c8e2d5c46b978a66b3745e4c",
+                "reference": "d1911e6caeb4b6a4c8e2d5c46b978a66b3745e4c",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.3.9"
-            },
-            "require-dev": {
-                "symfony/phpunit-bridge": "~2.7"
+                "php": ">=5.3.3"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.7-dev"
+                    "dev-master": "1.1-dev"
                 }
             },
             "autoload": {
-                "psr-4": {
-                    "Symfony\\Component\\Finder\\": ""
-                }
+                "files": [
+                    "bootstrap.php"
+                ],
+                "classmap": [
+                    "Resources/stubs"
+                ]
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
             ],
             "authors": [
                 {
-                    "name": "Fabien Potencier",
-                    "email": "fabien@symfony.com"
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
                 },
                 {
                     "name": "Symfony Community",
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Symfony Finder Component",
+            "description": "Symfony polyfill backporting apcu_* functions to lower PHP versions",
             "homepage": "https://symfony.com",
-            "time": "2015-09-19 19:59:23"
+            "keywords": [
+                "apcu",
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "time": "2016-01-20 09:13:37"
         },
         {
             "name": "symfony/polyfill-mbstring",
-            "version": "v1.0.1",
+            "version": "v1.1.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-mbstring.git",
-                "reference": "49ff736bd5d41f45240cec77b44967d76e0c3d25"
+                "reference": "1289d16209491b584839022f29257ad859b8532d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/49ff736bd5d41f45240cec77b44967d76e0c3d25",
-                "reference": "49ff736bd5d41f45240cec77b44967d76e0c3d25",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/1289d16209491b584839022f29257ad859b8532d",
+                "reference": "1289d16209491b584839022f29257ad859b8532d",
                 "shasum": ""
             },
             "require": {
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.0-dev"
+                    "dev-master": "1.1-dev"
                 }
             },
             "autoload": {
                 "portable",
                 "shim"
             ],
-            "time": "2015-11-20 09:19:13"
+            "time": "2016-01-20 09:13:37"
         },
         {
             "name": "symfony/process",
-            "version": "v2.8.1",
+            "version": "v2.8.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/process.git",
-                "reference": "62c254438b5040bc2217156e1570cf2206e8540c"
+                "reference": "7dedd5b60550f33dca16dd7e94ef8aca8b67bbfe"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/process/zipball/62c254438b5040bc2217156e1570cf2206e8540c",
-                "reference": "62c254438b5040bc2217156e1570cf2206e8540c",
+                "url": "https://api.github.com/repos/symfony/process/zipball/7dedd5b60550f33dca16dd7e94ef8aca8b67bbfe",
+                "reference": "7dedd5b60550f33dca16dd7e94ef8aca8b67bbfe",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony Process Component",
             "homepage": "https://symfony.com",
-            "time": "2015-12-23 11:03:46"
+            "time": "2016-02-02 13:33:15"
         },
         {
             "name": "symfony/translation",
-            "version": "v2.8.1",
+            "version": "v2.8.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/translation.git",
-                "reference": "c1db87c51251167dd91198b9d1edf897773adb4f"
+                "reference": "b7b4ebadd2b5e614ff7d2d6fc63e0ed0578909c7"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/translation/zipball/c1db87c51251167dd91198b9d1edf897773adb4f",
-                "reference": "c1db87c51251167dd91198b9d1edf897773adb4f",
+                "url": "https://api.github.com/repos/symfony/translation/zipball/b7b4ebadd2b5e614ff7d2d6fc63e0ed0578909c7",
+                "reference": "b7b4ebadd2b5e614ff7d2d6fc63e0ed0578909c7",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony Translation Component",
             "homepage": "https://symfony.com",
-            "time": "2015-12-05 17:37:59"
+            "time": "2016-02-02 09:49:18"
         },
         {
             "name": "symfony/yaml",
-            "version": "v2.8.1",
+            "version": "v2.8.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/yaml.git",
-                "reference": "ac84cbb98b68a6abbc9f5149eb96ccc7b07b8966"
+                "reference": "2a4ee40acb880c56f29fb1b8886e7ffe94f3b995"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/yaml/zipball/ac84cbb98b68a6abbc9f5149eb96ccc7b07b8966",
-                "reference": "ac84cbb98b68a6abbc9f5149eb96ccc7b07b8966",
+                "url": "https://api.github.com/repos/symfony/yaml/zipball/2a4ee40acb880c56f29fb1b8886e7ffe94f3b995",
+                "reference": "2a4ee40acb880c56f29fb1b8886e7ffe94f3b995",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony Yaml Component",
             "homepage": "https://symfony.com",
-            "time": "2015-12-26 13:37:56"
+            "time": "2016-02-23 07:41:20"
         }
     ],
     "aliases": [],
index 8d8d481..cdf3de9 100644 (file)
@@ -673,18 +673,16 @@ $CFG->admin = 'admin';
 // params hierarchy. More info: http://docs.behat.org/guides/7.config.html
 // Example:
 //   $CFG->behat_config = array(
-//       'default' => array(
-//           'formatter' => array(
-//               'name' => 'pretty',
-//               'parameters' => array(
-//                   'decorated' => true,
-//                   'verbose' => false
-//               )
-//           )
-//       ),
 //       'Mac-Firefox' => array(
+//           'suites' => array (
+//               'default' => array(
+//                   'filters' => array(
+//                      'tags' => '~@_file_upload'
+//                   ),
+//               ),
+//           ),
 //           'extensions' => array(
-//               'Behat\MinkExtension\Extension' => array(
+//               'Behat\MinkExtension' => array(
 //                   'selenium2' => array(
 //                       'browser' => 'firefox',
 //                       'capabilities' => array(
@@ -697,7 +695,7 @@ $CFG->admin = 'admin';
 //       ),
 //       'Mac-Safari' => array(
 //           'extensions' => array(
-//               'Behat\MinkExtension\Extension' => array(
+//               'Behat\MinkExtension' => array(
 //                   'selenium2' => array(
 //                       'browser' => 'safari',
 //                       'capabilities' => array(
@@ -709,6 +707,20 @@ $CFG->admin = 'admin';
 //           )
 //       )
 //   );
+// You can also use the following config to override default Moodle configuration for Behat.
+// This config is limited to default suite and will be supported in later versions.
+// It will have precedence over $CFG->behat_config.
+// $CFG->behat_profiles = array(
+//     'phantomjs' => array(
+//         'browser' => 'phantomjs',
+//         'tags' => '~@_file_upload&&~@_alert&&~@_bug_phantomjs',
+//         'wd_host' => 'http://127.0.0.1:4443/wd/hub',
+//         'capabilities' => array(
+//             'platform' => 'Linux',
+//             'version' => 2.1
+//         )
+//     ),
+// );
 //
 // You can force the browser session (not user's sessions) to restart after N seconds. This could
 // be useful if you are using a cloud-based service with time restrictions in the browser side.
index 344a208..09ee8d4 100644 (file)
@@ -27,7 +27,7 @@
 
 require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
 
-use Behat\Behat\Context\Step\Given as Given,
+use Moodle\BehatExtension\Context\Step\Given as Given,
     Behat\Gherkin\Node\TableNode as TableNode,
     Behat\Mink\Exception\ExpectationException as ExpectationException,
     Behat\Mink\Exception\DriverException as DriverException,
@@ -97,7 +97,7 @@ class behat_course extends behat_base {
                     unset($rows[$key]);
                 }
             }
-            $table->setRows($rows);
+            $table = new TableNode($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.
@@ -585,7 +585,7 @@ class behat_course extends behat_base {
 
             // The 'Hide' button should be available.
             $nohideexception = new ExpectationException('"' . $activityname . '" don\'t have a "' . get_string('hide') . '" icon', $this->getSession());
-            $this->find('named', array('link', get_string('hide')), $nohideexception, $activitynode);
+            $this->find('named_partial', array('link', get_string('hide')), $nohideexception, $activitynode);
         }
     }
 
@@ -611,7 +611,7 @@ class behat_course extends behat_base {
 
             // Also 'Show' icon.
             $noshowexception = new ExpectationException('"' . $activityname . '" don\'t have a "' . get_string('show') . '" icon', $this->getSession());
-            $this->find('named', array('link', get_string('show')), $noshowexception, $activitynode);
+            $this->find('named_partial', array('link', get_string('show')), $noshowexception, $activitynode);
 
         } else {
 
index dc35898..42dacac 100644 (file)
@@ -105,7 +105,6 @@ Feature: Test we can resort categories in the management interface.
     And I should see the "Course categories and courses" management page
     And I click on <sortby> action for "Master cat" in management category listing
     And a new page should have loaded since I started watching
-    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I should see category listing <cat1> before <cat2>
     And I should see category listing <cat2> before <cat3>
index cee3ccd..f117146 100644 (file)
@@ -86,7 +86,6 @@ Feature: Test we can resort course in the management interface.
     And I should see "Sort by Course time created descending" in the ".course-listing-actions" "css_element"
     And I click on <sortby> "link" in the ".course-listing-actions" "css_element"
     And a new page should have loaded since I started watching
-    And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
     And I should see course listing <course1> before <course2>
     And I should see course listing <course2> before <course3>
index 116e0a2..034898c 100644 (file)
@@ -27,7 +27,7 @@
 
 require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
 
-use Behat\Behat\Context\Step\Given as Given,
+use Moodle\BehatExtension\Context\Step\Given as Given,
     Behat\Gherkin\Node\TableNode as TableNode;
 
 /**
index 49ab967..8384259 100644 (file)
@@ -263,7 +263,7 @@ if (!$moving) {
 if (!$moving && count($grade_edit_tree->categories) > 1) {
     echo '<br /><br />';
     echo '<input type="hidden" name="bulkmove" value="0" id="bulkmoveinput" />';
-    $attributes = array('id'=>'menumoveafter', 'class' => 'ignoredirty');
+    $attributes = array('id'=>'menumoveafter', 'class' => 'ignoredirty singleselect');
     echo html_writer::label(get_string('moveselectedto', 'grades'), 'menumoveafter');
     echo html_writer::select($grade_edit_tree->categories, 'moveafter', '', array(''=>'choosedots'), $attributes);
     $OUTPUT->add_action_handler(new component_action('change', 'submit_bulk_move'), 'menumoveafter');
index 7ce443c..e63fc5d 100644 (file)
@@ -26,9 +26,9 @@
 require_once(__DIR__ . '/../../../../../../lib/behat/behat_base.php');
 
 use Behat\Gherkin\Node\TableNode as TableNode,
-    Behat\Behat\Context\Step\Given as Given,
-    Behat\Behat\Context\Step\When as When,
-    Behat\Behat\Context\Step\Then as Then,
+    Moodle\BehatExtension\Context\Step\Given as Given,
+    Moodle\BehatExtension\Context\Step\When as When,
+    Moodle\BehatExtension\Context\Step\Then as Then,
     Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException,
     Behat\Mink\Exception\ExpectationException as ExpectationException;
 
index e01387b..778c7ff 100644 (file)
@@ -28,9 +28,7 @@
 require_once(__DIR__ . '/../../../../../../lib/behat/behat_base.php');
 
 use Behat\Gherkin\Node\TableNode as TableNode,
-    Behat\Behat\Context\Step\Given as Given,
-    Behat\Behat\Context\Step\When as When,
-    Behat\Behat\Context\Step\Then as Then,
+    Moodle\BehatExtension\Context\Step\Given as Given,
     Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException,
     Behat\Mink\Exception\ExpectationException as ExpectationException;
 
@@ -107,6 +105,15 @@ class behat_gradingform_rubric extends behat_base {
                     }
                 }
 
+                // Remove empty criterion, as TableNode might contain them to make table rows equal size.
+                $newcriterion = array();
+                foreach ($criterion as $k => $c) {
+                    if (!empty($c)) {
+                        $newcriterion[$k] = $c;
+                    }
+                }
+                $criterion = $newcriterion;
+
                 // Checking the number of cells.
                 if (count($criterion) % 2 === 0) {
                     throw new ExpectationException(
index 0a0bf07..f76117c 100644 (file)
@@ -28,8 +28,8 @@
 require_once(__DIR__ . '/../../../../lib/behat/behat_base.php');
 
 use Behat\Gherkin\Node\TableNode as TableNode,
-    Behat\Behat\Context\Step\Given as Given,
-    Behat\Behat\Context\Step\When as When;
+    Moodle\BehatExtension\Context\Step\Given as Given,
+    Moodle\BehatExtension\Context\Step\When as When;
 
 /**
  * Generic grading methods step definitions.
@@ -93,7 +93,7 @@ class behat_grading extends behat_base {
 
         // Shortcut in case we already are in the grading page.
         $usergradetextliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($usergradetext);
-        if ($this->getSession()->getPage()->find('named', array('link', $usergradetextliteral))) {
+        if ($this->getSession()->getPage()->find('named_partial', array('link', $usergradetextliteral))) {
             return $gradeuserstep;
         }
 
index ee12feb..3e2bcd1 100644 (file)
@@ -27,8 +27,8 @@
 
 require_once(__DIR__ . '/../../../../../lib/behat/behat_base.php');
 
-use Behat\Behat\Context\Step\Given,
-    Behat\Behat\Context\Step\Then,
+use Moodle\BehatExtension\Context\Step\Given,
+    Moodle\BehatExtension\Context\Step\Then,
     Behat\Mink\Exception\ExpectationException as ExpectationException,
     Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
 
index 6ee26bf..76ef7f6 100644 (file)
@@ -27,7 +27,7 @@
 
 require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
 
-use Behat\Behat\Context\Step\Given as Given,
+use Moodle\BehatExtension\Context\Step\Given as Given,
     Behat\Gherkin\Node\TableNode as TableNode;
 
 class behat_grade extends behat_base {
index c51e281..3e2ab94 100644 (file)
@@ -44,6 +44,7 @@ Feature: We can use calculated grade totals
     And I follow "Course 1"
     And I navigate to "Grades" node in "Course administration"
     And I turn editing mode on
+    And I change window size to "large"
     And I give the grade "60.00" to the user "Student 1" for the grade item "Test assignment one"
     And I give the grade "20.00" to the user "Student 1" for the grade item "Test assignment two"
     And I give the grade "40.00" to the user "Student 1" for the grade item "Test assignment three"
@@ -58,6 +59,7 @@ Feature: We can use calculated grade totals
       | Hidden | 1 |
     And I set the following settings for grade item "Test assignment eight":
       | Hidden | 1 |
+    And I change window size to "medium"
     And I navigate to "Course grade settings" node in "Grade administration > Setup"
     And I set the field "Grade display type" to "Real (percentage)"
     And I press "Save changes"
index 54aea96..daede1d 100644 (file)
@@ -27,7 +27,7 @@
 
 require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
 
-use Behat\Behat\Context\Step\Then;
+use Moodle\BehatExtension\Context\Step\Then;
 use Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
 
 /**
@@ -62,6 +62,11 @@ class behat_groups extends behat_base {
         $fulloption = $groupoption->getText();
         $select->selectOption($fulloption);
 
+        // This is needed by some drivers to ensure relevant event is triggred and button is enabled.
+        $script = "Syn.trigger('change', {}, {{ELEMENT}})";
+        $this->getSession()->getDriver()->triggerSynScript($select->getXpath(), $script);
+        $this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
+
         // Here we don't need to wait for the AJAX response.
         $this->find_button(get_string('adduserstogroup', 'group'))->click();
 
index efe954b..819dba9 100644 (file)
@@ -102,6 +102,13 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
      */
     protected function find($selector, $locator, $exception = false, $node = false, $timeout = false) {
 
+        // Throw exception, so dev knows it is not supported.
+        if ($selector === 'named') {
+            $exception = 'Using the "named" selector is deprecated as of 3.1. '
+                .' Use the "named_partial" or use the "named_exact" selector instead.';
+            throw new ExpectationException($exception, $this->getSession());
+        }
+
         // Returns the first match.
         $items = $this->find_all($selector, $locator, $exception, $node, $timeout);
         return count($items) ? reset($items) : null;
@@ -122,11 +129,18 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
      */
     protected function find_all($selector, $locator, $exception = false, $node = false, $timeout = false) {
 
+        // Throw exception, so dev knows it is not supported.
+        if ($selector === 'named') {
+            $exception = 'Using the "named" selector is deprecated as of 3.1. '
+                .' Use the "named_partial" or use the "named_exact" selector instead.';
+            throw new ExpectationException($exception, $this->getSession());
+        }
+
         // Generic info.
         if (!$exception) {
 
             // With named selectors we can be more specific.
-            if ($selector == 'named') {
+            if (($selector == 'named_exact') || ($selector == 'named_partial')) {
                 $exceptiontype = $locator[0];
                 $exceptionlocator = $locator[1];
 
@@ -236,7 +250,7 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
 
         // Redirecting execution to the find method with the specified selector.
         // It will detect if it's pointing to an unexisting named selector.
-        return $this->find('named',
+        return $this->find('named_partial',
             array(
                 $cleanname,
                 $this->getSession()->getSelectorsHandler()->xpathLiteral($arguments[0])
@@ -637,4 +651,66 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
         }
         $this->getSession()->getDriver()->resizeWindow($width, $height);
     }
+
+    /**
+     * Waits for all the JS to be loaded.
+     *
+     * @throws \Exception
+     * @throws NoSuchWindow
+     * @throws UnknownError
+     * @return bool True or false depending whether all the JS is loaded or not.
+     */
+    public function wait_for_pending_js() {
+        // Waiting for JS is only valid for JS scenarios.
+        if (!$this->running_javascript()) {
+            return;
+        }
+
+        // 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 function() {
+                        if (typeof M === "undefined") {
+                            if (document.readyState === "complete") {
+                                return "";
+                            } else {
+                                return "incomplete";
+                            }
+                        } else if (' . self::PAGE_READY_JS . ') {
+                            return "";
+                        } else {
+                            return 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) {
+                // M is not defined when the window or the frame don't exist anymore.
+                if (strstr($e->getMessage(), 'M is not defined') != false) {
+                    $pending = '';
+                }
+            }
+
+            // If there are no pending JS we stop waiting.
+            if ($pending === '') {
+                return true;
+            }
+
+            // 0.1 seconds.
+            usleep(100000);
+        }
+
+        // Timeout waiting for JS to complete. It will be catched and forwarded to behat_hooks::i_look_for_exceptions().
+        // It is unlikely that Javascript code of a page or an AJAX request needs more than self::EXTENDED_TIMEOUT seconds
+        // to be loaded, although when pages contains Javascript errors M.util.js_complete() can not be executed, so the
+        // number of JS pending code and JS completed code will not match and we will reach this point.
+        throw new \Exception('Javascript code and/or AJAX requests are not ready after ' . self::EXTENDED_TIMEOUT .
+            ' seconds. There is a Javascript error or the code is extremely slow.');
+    }
 }
index 0108064..54049f5 100644 (file)
@@ -42,6 +42,11 @@ require_once(__DIR__ . '/../../testing/classes/tests_finder.php');
  */
 class behat_config_manager {
 
+    /**
+     * @var bool Keep track of the automatic profile conversion. So we can notify user.
+     */
+    public static $autoprofileconversion = false;
+
     /**
      * Updates a config file
      *
@@ -397,47 +402,160 @@ class behat_config_manager {
             $CFG->behat_wwwroot = 'http://itwillnotbeused.com';
         }
 
-        $basedir = $CFG->dirroot . DIRECTORY_SEPARATOR . 'lib' . DIRECTORY_SEPARATOR . 'behat';
-
+        // Comments use black color, so failure path is not visible. Using color other then black/white is safer.
+        // https://github.com/Behat/Behat/pull/628.
         $config = array(
             'default' => array(
-                'paths' => array(
-                    'features' => $basedir . DIRECTORY_SEPARATOR . 'features',
-                    'bootstrap' => $basedir . DIRECTORY_SEPARATOR . 'features' . DIRECTORY_SEPARATOR . 'bootstrap',
+                'formatters' => array(
+                    'moodle_progress' => array(
+                        'output_styles' => array(
+                            'comment' => array('magenta'))
+                        )
                 ),
-                'context' => array(
-                    'class' => 'behat_init_context'
+                'suites' => array(
+                    'default' => array(
+                        'paths' => $features,
+                        'contexts' => array_keys($stepsdefinitions)
+                    )
                 ),
                 'extensions' => array(
-                    'Behat\MinkExtension\Extension' => array(
+                    'Behat\MinkExtension' => array(
                         'base_url' => $CFG->behat_wwwroot,
                         'goutte' => null,
                         'selenium2' => $selenium2wdhost
                     ),
-                    'Moodle\BehatExtension\Extension' => array(
-                        'formatters' => array(
-                            'moodle_progress' => 'Moodle\BehatExtension\Formatter\MoodleProgressFormatter',
-                            'moodle_list' => 'Moodle\BehatExtension\Formatter\MoodleListFormatter',
-                            'moodle_step_count' => 'Moodle\BehatExtension\Formatter\MoodleStepCountFormatter'
-                        ),
-                        'features' => $features,
+                    'Moodle\BehatExtension' => array(
+                        'moodledirroot' => $CFG->dirroot,
                         'steps_definitions' => $stepsdefinitions
                     )
-                ),
-                'formatter' => array(
-                    'name' => 'moodle_progress'
                 )
             )
         );
 
         // In case user defined overrides respect them over our default ones.
         if (!empty($CFG->behat_config)) {
-            $config = self::merge_config($config, $CFG->behat_config);
+            foreach ($CFG->behat_config as $profile => $values) {
+                $config = self::merge_config($config, self::merge_behat_config($profile, $values));
+            }
+        }
+        // Check for Moodle custom ones.
+        if (!empty($CFG->behat_profiles) && is_array($CFG->behat_profiles)) {
+            foreach ($CFG->behat_profiles as $profile => $values) {
+                $config = self::merge_config($config, self::get_behat_profile($profile, $values));
+            }
         }
 
         return Symfony\Component\Yaml\Yaml::dump($config, 10, 2);
     }
 
+    /**
+     * Parse $CFG->behat_config and return the array with required config structure for behat.yml
+     *
+     * @param string $profile profile name
+     * @param array $values values for profile
+     * @return array
+     */
+    protected static function merge_behat_config($profile, $values) {
+        // Only add profile which are compatible with Behat 3.x
+        // Just check if any of Bheat 2.5 config is set. Not checking for 3.x as it might have some other configs
+        // Like : rerun_cache etc.
+        if (!isset($values['filters']['tags']) && !isset($values['extensions']['Behat\MinkExtension\Extension'])) {
+            return array($profile => $values);
+        }
+
+        // Parse 2.5 format and get related values.
+        $oldconfigvalues = array();
+        if (isset($values['extensions']['Behat\MinkExtension\Extension'])) {
+            $extensionvalues = $values['extensions']['Behat\MinkExtension\Extension'];
+            if (isset($extensionvalues['selenium2']['browser'])) {
+                $oldconfigvalues['browser'] = $extensionvalues['selenium2']['browser'];
+            }
+            if (isset($extensionvalues['selenium2']['wd_host'])) {
+                $oldconfigvalues['wd_host'] = $extensionvalues['selenium2']['wd_host'];
+            }
+            if (isset($extensionvalues['capabilities'])) {
+                $oldconfigvalues['capabilities'] = $extensionvalues['capabilities'];
+            }
+        }
+
+        if (isset($values['filters']['tags'])) {
+            $oldconfigvalues['tags'] = $values['filters']['tags'];
+        }
+
+        if (!empty($oldconfigvalues)) {
+            self::$autoprofileconversion = true;
+            return self::get_behat_profile($profile, $oldconfigvalues);
+        }
+
+        // If nothing set above then return empty array.
+        return array();
+    }
+
+    /**
+     * Parse $CFG->behat_profile and return the array with required config structure for behat.yml.
+     *
+     * $CFG->behat_profiles = array(
+     *     'profile' = array(
+     *         'browser' => 'firefox',
+     *         'tags' => '@javascript',
+     *         'wd_host' => 'http://127.0.0.1:4444/wd/hub',
+     *         'capabilities' => array(
+     *             'platform' => 'Linux',
+     *             'version' => 44
+     *         )
+     *     )
+     * );
+     *
+     * @param string $profile profile name
+     * @param array $values values for profile.
+     * @return array
+     */
+    protected static function get_behat_profile($profile, $values) {
+        // Values should be an array.
+        if (!is_array($values)) {
+            return array();
+        }
+
+        // Check suite values.
+        $behatprofilesuites = array();
+        // Fill tags information.
+        if (isset($values['tags'])) {
+            $behatprofilesuites = array(
+                'suites' => array(
+                    'default' => array(
+                        'filters' => array(
+                            'tags' => $values['tags'],
+                        )
+                    )
+                )
+            );
+        }
+
+        // Selenium2 config values.
+        $behatprofileextension = array();
+        $seleniumconfig = array();
+        if (isset($values['browser'])) {
+            $seleniumconfig['browser'] = $values['browser'];
+        }
+        if (isset($values['wd_host'])) {
+            $seleniumconfig['wd_host'] = $values['wd_host'];
+        }
+        if (isset($values['capabilities'])) {
+            $seleniumconfig['capabilities'] = $values['capabilities'];
+        }
+        if (!empty($seleniumconfig)) {
+            $behatprofileextension = array(
+                'extensions' => array(
+                    'Behat\MinkExtension' => array(
+                        'selenium2' => $seleniumconfig,
+                    )
+                )
+            );
+        }
+
+        return array($profile => array_merge($behatprofilesuites, $behatprofileextension));
+    }
+
     /**
      * Attempt to split feature list into fairish buckets using timing information, if available.
      * Simply add each one to lightest buckets until all files allocated.
index 0223495..2d68166 100644 (file)
@@ -25,7 +25,7 @@
 
 // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
 
-use \Behat\Behat\Context\BehatContext;
+use Behat\Testwork\Environment\Environment;
 
 /**
  * Helper to get behat contexts.
@@ -38,18 +38,20 @@ use \Behat\Behat\Context\BehatContext;
 class behat_context_helper {
 
     /**
-     * @var BehatContext main behat context.
+     * Behat environment.
+     *
+     * @var Environment
      */
-    protected static $maincontext = false;
+    protected static $environment = null;
 
     /**
-     * Save main behat context reference to be used for finding sub-contexts.
+     * Sets the browser session.
      *
-     * @param BehatContext $maincontext
+     * @param Environment $environment
      * @return void
      */
-    public static function set_main_context(BehatContext $maincontext) {
-        self::$maincontext = $maincontext;
+    public static function set_session(Environment $environment) {
+        self::$environment = $environment;
     }
 
     /**
@@ -65,7 +67,7 @@ class behat_context_helper {
      */
     public static function get($classname) {
 
-        if (!$subcontext = self::$maincontext->getSubcontextByClassName($classname)) {
+        if (!$subcontext = self::$environment->getContext($classname)) {
             throw coding_exception('The required "' . $classname . '" class does not exist');
         }
 
index 76921aa..c328f5d 100644 (file)
@@ -158,7 +158,7 @@ XPATH
         } else {
             // Named selectors uses arrays as locators including the type of named selector.
             $locator = array($selectortype, $session->getSelectorsHandler()->xpathLiteral($element));
-            $selector = 'named';
+            $selector = 'named_partial';
         }
 
         return array($selector, $locator);
@@ -173,7 +173,7 @@ XPATH
     public static function register_moodle_selectors(Behat\Mink\Session $session) {
 
         foreach (self::get_moodle_selectors() as $name => $xpath) {
-            $session->getSelectorsHandler()->getSelector('named')->registerNamedXpath($name, $xpath);
+            $session->getSelectorsHandler()->getSelector('named_partial')->registerNamedXpath($name, $xpath);
         }
     }
 
index 69a9cbc..dc568b0 100644 (file)
@@ -58,10 +58,7 @@ class behat_form_checkbox extends behat_form_field {
             $this->field->click();
 
             // Trigger the onchange event as triggered when 'checking' the checkbox.
-            $this->session->getDriver()->triggerSynScript(
-                $this->field->getXPath(),
-                "Syn.trigger('change', {}, {{ELEMENT}})"
-            );
+            $this->trigger_on_change();
 
         } else if (empty($value) && $this->field->isChecked()) {
 
@@ -74,10 +71,7 @@ class behat_form_checkbox extends behat_form_field {
             $this->field->click();
 
             // Trigger the onchange event as triggered when 'checking' the checkbox.
-            $this->session->getDriver()->triggerSynScript(
-                $this->field->getXPath(),
-                "Syn.trigger('change', {}, {{ELEMENT}})"
-            );
+            $this->trigger_on_change();
         }
     }
 
@@ -110,4 +104,13 @@ class behat_form_checkbox extends behat_form_field {
         return false;
     }
 
+    /**
+     * Trigger on change event.
+     */
+    protected function trigger_on_change() {
+        $this->session->getDriver()->triggerSynScript(
+            $this->field->getXPath(),
+            "Syn.trigger('change', {}, {{ELEMENT}})"
+        );
+    }
 }
index 8304133..22e064c 100644 (file)
@@ -59,7 +59,7 @@ class behat_form_radio extends behat_form_checkbox {
      * @return string The value attribute
      */
     public function get_value() {
-        return (bool)$this->field->getAttribute('checked');
+        return $this->field->isSelected();
     }
 
     /**
@@ -75,20 +75,17 @@ class behat_form_radio extends behat_form_checkbox {
     public function set_value($value) {
 
         if ($this->running_javascript()) {
-            parent::set_value($value);
+            // Check on radio button.
+            $this->field->click();
+
+            // Trigger the onchange event as triggered when 'selecting' the radio.
+            if (!empty($value) && !$this->field->isSelected()) {
+                $this->trigger_on_change();
+            }
         } else {
             // Goutte does not accept a check nor a click in an input[type=radio].
             $this->field->setValue($this->field->getAttribute('value'));
         }
     }
 
-    /**
-     * Returns whether the provided value matches the current value or not.
-     *
-     * @param string $expectedvalue
-     * @return bool
-     */
-    public function matches($expectedvalue = false) {
-        return $this->text_matches($expectedvalue);
-    }
 }
index 6bc66a8..880d061 100644 (file)
@@ -49,28 +49,14 @@ class behat_form_select extends behat_form_field {
      */
     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();
-        }
-
         // Is the select multiple?
         $multiple = $this->field->hasAttribute('multiple');
-
-        // By default, assume the passed value is a non-multiple option.
-        $options = array(trim($value));
+        $singleselect = ($this->field->hasClass('singleselect') || $this->field->hasClass('urlselect'));
 
         // Here we select the option(s).
         if ($multiple) {
             // Split and decode values. Comma separated list of values allowed. With valuable commas escaped with backslash.
-            $options = preg_replace('/\\\,/', ',',  preg_split('/(?<!\\\),/', $value));
+            $options = preg_replace('/\\\,/', ',',  preg_split('/(?<!\\\),/', trim($value)));
             // This is a multiple select, let's pass the multiple flag after first option.
             $afterfirstoption = false;
             foreach ($options as $option) {
@@ -78,126 +64,21 @@ class behat_form_select extends behat_form_field {
                 $afterfirstoption = true;
             }
         } else {
-            // If value is already set then don't set it again.
-            if ($this->field->getValue() == $value) {
-                return;
-            } else {
-                $opt = $this->field->find('named', array(
-                    'option', $this->field->getSession()->getSelectorsHandler()->xpathLiteral($value)
-                ));
-                if ($opt && ($this->field->getValue() == $opt->getValue())) {
-                    return;
-                }
-            }
-
-            // If not running JS or not a singleselect then use selectOption.
-            // For singleselect only click event is enough.
-            if (!$this->running_javascript() ||
-                !($this->field->hasClass('singleselect') || $this->field->hasClass('urlselect'))) {
-
-                // This is a single select, let's pass the last one specified.
-                $this->field->selectOption(end($options));
-            }
-        }
-
-        // With JS disabled this is enough and we finish here.
-        if (!$this->running_javascript()) {
-            return;
-        }
-
-        // 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.
-        // getXpath() does not send a query to selenium, so we don't need to wrap it in a try & catch.
-        $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;
-        }
+           // By default, assume the passed value is a non-multiple option.
+            $this->field->selectOption(trim($value));
+       }
 
         // Wait for all the possible AJAX requests that have been
         // already triggered by selectOption() to be finished.
-        $this->session->wait(behat_base::TIMEOUT * 1000, behat_base::PAGE_READY_JS);
-
-        // Wrapped in try & catch as the element may disappear if an AJAX request was submitted.
-        try {
-            $multiple = $this->field->hasAttribute('multiple');
-        } catch (Exception $e) {
-            // We do not specify any specific Exception type as there are
-            // different exceptions that can be thrown by the driver and
-            // we can not control them all, also depending on the selenium
-            // version the exception type can change.
-            return;
-        }
-
-        // Single select sometimes needs an extra click in the option.
-        if (!$multiple) {
-
-            // Var $options only contains 1 option.
-            $optionxpath = $this->get_option_xpath(end($options), $selectxpath);
-
-            // Using the driver direcly because Element methods are messy when dealing
-            // with elements inside containers.
-            if ($optionnodes = $this->session->getDriver()->find($optionxpath)) {
-
-                // Wrapped in a try & catch as we can fall into race conditions
-                // and the element may not be there.
-                try {
-                    current($optionnodes)->click();
-                } catch (Exception $e) {
-                    // We continue and return as this means that the element is not there or it is not the same.
-                    return;
-                }
-            }
-
-        } else {
-
-            // Wrapped in a try & catch as we can fall into race conditions
-            // and the element may not be there.
-            try {
-                // Multiple ones needs the click in the select.
-                $this->field->click();
-            } catch (Exception $e) {
-                // We continue and return as this means that the element is not there or it is not the same.
-                return;
-            }
-
-            // We also check that the option(s) are still there. We neither wait.
-            foreach ($options as $option) {
-                $optionxpath = $this->get_option_xpath($option, $selectxpath);
-                if (!$this->session->getDriver()->find($optionxpath)) {
-                    return;
-                }
+        if ($this->running_javascript()) {
+            // Trigger change event as this is needed by some drivers (Phantomjs). Don't do it for
+            // Singleselect as this will cause multiple event fire and lead to race-around condition.
+            $browser = \Moodle\BehatExtension\Driver\MoodleSelenium2Driver::getBrowser();
+            if (!$singleselect && ($browser == 'phantomjs')) {
+                $script = "Syn.trigger('change', {}, {{ELEMENT}})";
+                $this->session->getDriver()->triggerSynScript($this->field->getXpath(), $script);
             }
-
-            // Wait for all the possible AJAX requests that have been
-            // already triggered by clicking on the field to be finished.
             $this->session->wait(behat_base::TIMEOUT * 1000, behat_base::PAGE_READY_JS);
-
-            // Wrapped in a try & catch as we can fall into race conditions
-            // and the element may not be there.
-            try {
-
-                // Repeating the select(s) as some drivers (chrome that I know) are moving
-                // to another option after the general select field click above.
-                $afterfirstoption = false;
-                foreach ($options as $option) {
-                    $this->field->selectOption(trim($option), $afterfirstoption);
-                    $afterfirstoption = true;
-                }
-            } catch (Exception $e) {
-                // We continue and return as this means that the element is not there or it is not the same.
-                return;
-            }
         }
     }
 
@@ -233,9 +114,6 @@ class behat_form_select extends behat_form_field {
 
         // We are dealing with a multi-select.
 
-        // Can pass multiple comma separated, with valuable commas escaped with backslash.
-        $expectedarr = array(); // Array of passed text options to test.
-
         // Unescape + trim all options and flip it to have the expected values as keys.
         $expectedoptions = $this->get_unescaped_options($expectedvalue);
 
@@ -306,45 +184,23 @@ class behat_form_select extends behat_form_field {
 
         $selectedoptions = array(); // To accumulate found selected options.
 
-        // Selenium getValue() implementation breaks - separates - values having
-        // commas within them, so we'll be looking for options with the 'selected' attribute instead.
-        if ($this->running_javascript()) {
-            // Get all the options in the select and extract their value/text pairs.
-            $alloptions = $this->field->findAll('xpath', '//option');
-            foreach ($alloptions as $option) {
-                // Is it selected?
-                if ($option->hasAttribute('selected')) {
-                    if ($multiple) {
-                        // If the select is multiple, text commas must be encoded.
-                        $selectedoptions[] = trim(str_replace(',', '\,', $option->{$method}()));
-                    } else {
-                        $selectedoptions[] = trim($option->{$method}());
-                    }
-                }
-            }
-
-        } else {
-            // Goutte does not keep the 'selected' attribute updated, but its getValue() returns
-            // the selected elements correctly, also those having commas within them.
-
-            // Goutte returns the values as an array or as a string depending
-            // on whether multiple options are selected or not.
-            $values = $this->field->getValue();
-            if (!is_array($values)) {
-                $values = array($values);
-            }
+        // Driver returns the values as an array or as a string depending
+        // on whether multiple options are selected or not.
+        $values = $this->field->getValue();
+        if (!is_array($values)) {
+            $values = array($values);
+        }
 
-            // Get all the options in the select and extract their value/text pairs.
-            $alloptions = $this->field->findAll('xpath', '//option');
-            foreach ($alloptions as $option) {
-                // Is it selected?
-                if (in_array($option->getValue(), $values)) {
-                    if ($multiple) {
-                        // If the select is multiple, text commas must be encoded.
-                        $selectedoptions[] = trim(str_replace(',', '\,', $option->{$method}()));
-                    } else {
-                        $selectedoptions[] = trim($option->{$method}());
-                    }
+        // Get all the options in the select and extract their value/text pairs.
+        $alloptions = $this->field->findAll('xpath', '//option');
+        foreach ($alloptions as $option) {
+            // Is it selected?
+            if (in_array($option->getValue(), $values)) {
+                if ($multiple) {
+                    // If the select is multiple, text commas must be encoded.
+                    $selectedoptions[] = trim(str_replace(',', '\,', $option->{$method}()));
+                } else {
+                    $selectedoptions[] = trim($option->{$method}());
                 }
             }
         }
index 8757b29..478f50e 100644 (file)
@@ -15,14 +15,14 @@ Feature: Atto accessibility checker
     And I wait "2" seconds
     And I click on "Image" "button"
     And the field "Enter URL" matches value "/broken-image"
-    And I set the field "Describe this image" to "No more warning!"
+    And I set the field "Describe this image for someone who cannot see it" to "No more warning!"
     And I press "Save image"
     And I press "Accessibility checker"
     And I should see "Congratulations, no accessibility problems found!"
     And I click on ".moodle-dialogue-focused .closebutton" "css_element"
     And I select the text in the "Description" Atto editor
     And I click on "Image" "button"
-    And I set the field "Describe this image" to ""
+    And I set the field "Describe this image for someone who cannot see it" to ""
     And I set the field "Description not necessary" to "1"
     And I press "Save image"
     And I press "Accessibility checker"
index b889136..744831d 100644 (file)
@@ -17,7 +17,7 @@ Feature: Add images to Atto
     And I click on "Private files" "link"
     And I click on "moodle-logo.png" "link"
     And I click on "Select this file" "button"
-    And I set the field "Describe this image" to "It's the Moodle"
+    And I set the field "Describe this image for someone who cannot see it" to "It's the Moodle"
     # Wait for the page to "settle".
     And I wait until the page is ready
     And the field "Width" matches value "204"
@@ -45,7 +45,7 @@ Feature: Add images to Atto
     And I follow "Edit profile"
     And I select the text in the "Description" Atto editor
     And I click on "Image" "button"
-    Then the field "Describe this image" matches value "It's the Moodle"
+    Then the field "Describe this image for someone who cannot see it" matches value "It's the Moodle"
     And the field "Width" matches value "123"
     And the field "Height" matches value "456"
 
@@ -58,7 +58,7 @@ Feature: Add images to Atto
     And I select the text in the "Description" Atto editor
     When I click on "Image" "button"
     Then the field "Enter URL" matches value "/nothing/here"
-    And I set the field "Describe this image" to "Something"
+    And I set the field "Describe this image for someone who cannot see it" to "Something"
     And I set the field "Enter URL" to ""
     And I press "Save image"
     And I set the field "Description" to "<p>Image: <img src='/nothing/again' width='123' height='456' alt='Awesome!'>.</p>"
index d680313..8218293 100644 (file)
@@ -64,26 +64,6 @@ Feature: Using the activity grade form element
     And I press "Save and display"
     And I should not see "You must choose whether to rescale existing grades or not"
 
-  Scenario: Attempting to change the grade type when grades already exist
-    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" and I fill the form with:
-      | Assignment name | Test assignment name |
-      | Description | Test assignment description |
-    And I follow "Test assignment name"
-    And I follow "View/grade all submissions"
-    And I click on "Grade Student 1" "link" in the "Student 1" "table_row"
-    And I set the field "Grade out of 100" to "50"
-    And I press "Save changes"
-    And I press "Continue"
-    And I click on "Edit settings" "link" in the "Administration" "block"
-    When I expand all fieldsets
-    Then I should see "Some grades have already been awarded, so the grade type cannot be changed. If you wish to change the maximum grade, you must first choose whether or not to rescale existing grades."
-    And I set the field "grade[modgrade_type]" to "Scale"
-    And I press "Save and display"
-    And I should see "You cannot change the type, as grades already exist for this item"
-
   Scenario: Attempting to change the scale when grades already exist
     Given I log in as "admin"
     And I navigate to "Scales" node in "Site administration > Grades"
@@ -116,9 +96,6 @@ Feature: Using the activity grade form element
     And I click on "Edit settings" "link" in the "Administration" "block"
     When I expand all fieldsets
     Then I should see "Some grades have already been awarded, so the grade type and scale cannot be changed"
-    And I set the field "grade[modgrade_scale]" to "Letter scale"
-    And I press "Save and display"
-    And I should see "You cannot change the scale, as grades already exist for this item"
 
   Scenario: Attempting to change the maximum grade when ratings exist
     Given I log in as "teacher1"
index 495a486..1cc0672 100644 (file)
@@ -28,8 +28,8 @@
 require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
 
 use Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException,
-    Behat\Behat\Context\Step\Given as Given,
-    Behat\Behat\Context\Step\Then as Then,
+    Moodle\BehatExtension\Context\Step\Given as Given,
+    Moodle\BehatExtension\Context\Step\Then as Then,
     Behat\Gherkin\Node\TableNode as TableNode;
 
 /**
index f36571d..d750a8f 100644 (file)
 require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
 require_once(__DIR__ . '/../../../lib/behat/behat_field_manager.php');
 
-use Behat\Behat\Context\Step\Given as Given,
-    Behat\Behat\Context\Step\When as When,
-    Behat\Behat\Context\Step\Then as Then,
-    Behat\Gherkin\Node\TableNode as TableNode,
+use Behat\Gherkin\Node\TableNode as TableNode,
+    Moodle\BehatExtension\Context\Step\Given as Given,
+    Moodle\BehatExtension\Context\Step\When as When,
+    Moodle\BehatExtension\Context\Step\Then as Then,
     Behat\Gherkin\Node\PyStringNode as PyStringNode,
-    Behat\Mink\Element\NodeElement as NodeElement,
     Behat\Mink\Exception\ExpectationException as ExpectationException,
     Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
 
index 57c6355..6ac017c 100644 (file)
@@ -33,7 +33,7 @@ use Behat\Mink\Exception\ExpectationException as ExpectationException,
     WebDriver\Exception\NoSuchElement as NoSuchElement,
     WebDriver\Exception\StaleElementReference as StaleElementReference,
     Behat\Gherkin\Node\TableNode as TableNode,
-    Behat\Behat\Context\Step\Given as Given;
+    Moodle\BehatExtension\Context\Step\Given as Given;
 
 /**
  * Cross component steps definitions.
@@ -192,7 +192,7 @@ class behat_general extends behat_base {
         // unnamed window (presumably the main window) to some other named
         // window, then we first set the main window name to a conventional
         // value that we can later use this name to switch back.
-        $this->getSession()->evaluateScript(
+        $this->getSession()->executeScript(
                 'if (window.name == "") window.name = "' . self::MAIN_WINDOW_NAME . '"');
 
         $this->getSession()->switchToWindow($windowname);
@@ -1158,7 +1158,9 @@ class behat_general extends behat_base {
 
         // Check if value exists in specific row/column.
         // Get row xpath.
-        $rowxpath = $tablexpath."/tbody/tr[th[normalize-space(.)=" . $rowliteral . "] or td[normalize-space(.)=" . $rowliteral . "]]";
+        // GoutteDriver uses DomCrawler\Crawler and it is making XPath relative to the current context, so use descendant.
+        $rowxpath = $tablexpath."/tbody/tr[descendant::th[normalize-space(.)=" . $rowliteral .
+                    "] | descendant::td[normalize-space(.)=" . $rowliteral . "]]";
 
         $columnvaluexpath = $rowxpath . $columnpositionxpath . "[contains(normalize-space(.)," . $valueliteral . ")]";
 
@@ -1383,7 +1385,7 @@ class behat_general extends behat_base {
 
         $this->pageloaddetectionrunning = true;
 
-        $session->evaluateScript(
+        $session->executeScript(
                 'var span = document.createElement("span");
                 span.setAttribute("data-rel", "' . self::PAGE_LOAD_DETECTION_STRING . '");
                 span.setAttribute("style", "display: none;");
index cddbe93..449fcdc 100644 (file)
 
 require_once(__DIR__ . '/../../behat/behat_base.php');
 
-use Behat\Behat\Event\SuiteEvent as SuiteEvent,
-    Behat\Behat\Event\ScenarioEvent as ScenarioEvent,
-    Behat\Behat\Event\FeatureEvent as FeatureEvent,
-    Behat\Behat\Event\OutlineExampleEvent as OutlineExampleEvent,
-    Behat\Behat\Event\StepEvent as StepEvent,
+use Behat\Testwork\Hook\Scope\BeforeSuiteScope,
+    Behat\Testwork\Hook\Scope\AfterSuiteScope,
+    Behat\Behat\Hook\Scope\BeforeFeatureScope,
+    Behat\Behat\Hook\Scope\AfterFeatureScope,
+    Behat\Behat\Hook\Scope\BeforeScenarioScope,
+    Behat\Behat\Hook\Scope\AfterScenarioScope,
+    Behat\Behat\Hook\Scope\BeforeStepScope,
+    Behat\Behat\Hook\Scope\AfterStepScope,
     Behat\Mink\Exception\DriverException as DriverException,
     WebDriver\Exception\NoSuchWindow as NoSuchWindow,
     WebDriver\Exception\UnexpectedAlertOpen as UnexpectedAlertOpen,
@@ -104,7 +107,7 @@ class behat_hooks extends behat_base {
      * @throws Exception
      * @BeforeSuite
      */
-    public static function before_suite(SuiteEvent $event) {
+    public static function before_suite(BeforeSuiteScope $scope) {
         global $CFG;
 
         // Defined only when the behat CLI command is running, the moodle init setup process will
@@ -113,7 +116,6 @@ class behat_hooks extends behat_base {
         define('BEHAT_TEST', 1);
 
         define('CLI_SCRIPT', 1);
-
         // With BEHAT_TEST we will be using $CFG->behat_* instead of $CFG->dataroot, $CFG->prefix and $CFG->wwwroot.
         require_once(__DIR__ . '/../../../config.php');
 
@@ -169,7 +171,7 @@ class behat_hooks extends behat_base {
      * @param FeatureEvent $event event fired before feature.
      * @BeforeFeature
      */
-    public static function before_feature(FeatureEvent $event) {
+    public static function before_feature(BeforeFeatureScope $event) {
         if (!defined('BEHAT_FEATURE_TIMING_FILE')) {
             return;
         }
@@ -183,7 +185,7 @@ class behat_hooks extends behat_base {
      * @param FeatureEvent $event event fired after feature.
      * @AfterFeature
      */
-    public static function after_feature(FeatureEvent $event) {
+    public static function after_feature(AfterFeatureScope $event) {
         if (!defined('BEHAT_FEATURE_TIMING_FILE')) {
             return;
         }
@@ -201,7 +203,7 @@ class behat_hooks extends behat_base {
      * @param SuiteEvent $event event fired after suite.
      * @AfterSuite
      */
-    public static function after_suite(SuiteEvent $event) {
+    public static function after_suite(AfterSuiteScope $event) {
         if (!defined('BEHAT_FEATURE_TIMING_FILE')) {
             return;
         }
@@ -225,7 +227,7 @@ class behat_hooks extends behat_base {
      * @throws coding_exception If here we are not using the test database it should be because of a coding error
      * @BeforeScenario
      */
-    public function before_scenario($event) {
+    public function before_scenario(BeforeScenarioScope $scope) {
         global $DB, $SESSION, $CFG;
 
         // As many checks as we can.
@@ -244,19 +246,18 @@ class behat_hooks extends behat_base {
         } catch (CurlExec $e) {
             // Exception thrown by WebDriver, so only @javascript tests will be caugth; in
             // behat_util::is_server_running() we already checked that the server is running.
-            throw new Exception($driverexceptionmsg);
+            $this->stop_execution($driverexceptionmsg);
         } catch (DriverException $e) {
-            throw new Exception($driverexceptionmsg);
+            $this->stop_execution($driverexceptionmsg);
         } catch (UnknownError $e) {
             // Generic 'I have no idea' Selenium error. Custom exception to provide more feedback about possible solutions.
-            $this->throw_unknown_exception($e);
+            $this->stop_execution($e->getMessage());
         }
 
-
         // We need the Mink session to do it and we do it only before the first scenario.
         if (self::is_first_scenario()) {
             behat_selectors::register_moodle_selectors($session);
-            behat_context_helper::set_main_context($event->getContext()->getMainContext());
+            behat_context_helper::set_session($scope->getEnvironment());
         }
 
         // Reset mink session between the scenarios.
@@ -285,7 +286,7 @@ class behat_hooks extends behat_base {
             // Let's be conservative as we never know when new upstream issues will affect us.
             $session->visit($this->locate_path('/'));
         } catch (UnknownError $e) {
-            $this->throw_unknown_exception($e);
+            $this->stop_execution($e->getMessage());
         }
 
 
@@ -310,16 +311,18 @@ class behat_hooks extends behat_base {
      * default would be at framework level, which will stop the execution of
      * the run.
      *
-     * @param StepEvent $event event fired before step.
-     * @BeforeStep @javascript
+     * @BeforeStep
      */
-    public function before_step_javascript(StepEvent $event) {
+    public function before_step_javascript(BeforeStepScope $scope) {
+        self::$currentstepexception = null;
 
-        try {
-            $this->wait_for_pending_js();
-            self::$currentstepexception = null;
-        } catch (Exception $e) {
-            self::$currentstepexception = $e;
+        // Only run if JS.
+        if ($this->running_javascript()) {
+            try {
+                $this->wait_for_pending_js();
+            } catch (Exception $e) {
+                self::$currentstepexception = $e;
+            }
         }
     }
 
@@ -335,16 +338,36 @@ class behat_hooks extends behat_base {
      * default would be at framework level, which will stop the execution of
      * the run.
      *
-     * @param StepEvent $event event fired after step.
-     * @AfterStep @javascript
+     * @AfterStep
      */
-    public function after_step_javascript(StepEvent $event) {
-        global $CFG;
+    public function after_step_javascript(AfterStepScope $scope) {
+        global $CFG, $DB;
+
+        // Save the page content if the step failed.
+        if (!empty($CFG->behat_faildump_path) &&
+            $scope->getTestResult()->getResultCode() === Behat\Testwork\Tester\Result\TestResult::FAILED) {
+            $this->take_contentdump($scope);
+        }
+
+        // Abort any open transactions to prevent subsequent tests hanging.
+        // This does the same as abort_all_db_transactions(), but doesn't call error_log() as we don't
+        // want to see a message in the behat output.
+        if (($scope->getTestResult() instanceof \Behat\Behat\Tester\Result\ExecutedStepResult) &&
+            $scope->getTestResult()->hasException()) {
+            if ($DB && $DB->is_transaction_started()) {
+                $DB->force_transaction_rollback();
+            }
+        }
+
+        // Only run if JS.
+        if (!$this->running_javascript()) {
+            return;
+        }
 
         // Save a screenshot if the step failed.
         if (!empty($CFG->behat_faildump_path) &&
-                $event->getResult() === StepEvent::FAILED) {
-            $this->take_screenshot($event);
+            $scope->getTestResult()->getResultCode() === Behat\Testwork\Tester\Result\TestResult::FAILED) {
+            $this->take_screenshot($scope);
         }
 
         try {
@@ -366,42 +389,15 @@ class behat_hooks extends behat_base {
         }
     }
 
-    /**
-     * Execute any steps required after the step has finished.
-     *
-     * This includes creating an HTML dump of the content if there was a failure.
-     *
-     * @param StepEvent $event event fired after step.
-     * @AfterStep
-     */
-    public function after_step(StepEvent $event) {
-        global $CFG, $DB;
-
-        // Save the page content if the step failed.
-        if (!empty($CFG->behat_faildump_path) &&
-                $event->getResult() === StepEvent::FAILED) {
-            $this->take_contentdump($event);
-        }
-
-        // Abort any open transactions to prevent subsequent tests hanging.
-        // This does the same as abort_all_db_transactions(), but doesn't call error_log() as we don't
-        // want to see a message in the behat output.
-        if ($event->hasException()) {
-            if ($DB && $DB->is_transaction_started()) {
-                $DB->force_transaction_rollback();
-            }
-        }
-    }
-
     /**
      * Executed after scenario having switch window to restart session.
      * This is needed to close all extra browser windows and starting
      * one browser window.
      *
-     * @param ScenarioEvent $event event fired after scenario.
+     * @param AfterScenarioScope $event event fired after scenario.
      * @AfterScenario @_switch_window
      */
-    public function after_scenario_switchwindow(ScenarioEvent $event) {
+    public function after_scenario_switchwindow(AfterScenarioScope $event) {
         for ($count = 0; $count < self::EXTENDED_TIMEOUT; $count) {
             try {
                 $this->getSession()->restart();
@@ -428,15 +424,15 @@ class behat_hooks extends behat_base {
      * Take screenshot when a step fails.
      *
      * @throws Exception
-     * @param StepEvent $event
+     * @param AfterStepScope $scope
      */
-    protected function take_screenshot(StepEvent $event) {
+    protected function take_screenshot(AfterStepScope $scope) {
         // Goutte can't save screenshots.
         if (!$this->running_javascript()) {
             return false;
         }
 
-        list ($dir, $filename) = $this->get_faildump_filename($event, 'png');
+        list ($dir, $filename) = $this->get_faildump_filename($scope, 'png');
         $this->saveScreenshot($filename, $dir);
     }
 
@@ -444,10 +440,10 @@ class behat_hooks extends behat_base {
      * Take a dump of the page content when a step fails.
      *
      * @throws Exception
-     * @param StepEvent $event
+     * @param AfterStepScope $scope
      */
-    protected function take_contentdump(StepEvent $event) {
-        list ($dir, $filename) = $this->get_faildump_filename($event, 'html');
+    protected function take_contentdump(AfterStepScope $scope) {
+        list ($dir, $filename) = $this->get_faildump_filename($scope, 'html');
 
         $fh = fopen($dir . DIRECTORY_SEPARATOR . $filename, 'w');
         fwrite($fh, $this->getSession()->getPage()->getContent());
@@ -459,10 +455,10 @@ class behat_hooks extends behat_base {
      *
      * This is used for content such as the DOM, and screenshots.
      *
-     * @param StepEvent $event
+     * @param AfterStepScope $scope
      * @param String $filetype The file suffix to use. Limited to 4 chars.
      */
-    protected function get_faildump_filename(StepEvent $event, $filetype) {
+    protected function get_faildump_filename(AfterStepScope $scope, $filetype) {
         global $CFG;
 
         // All the contentdumps should be in the same parent dir.
@@ -482,72 +478,16 @@ class behat_hooks extends behat_base {
 
         // The scenario title + the failed step text.
         // We want a i-am-the-scenario-title_i-am-the-failed-step.$filetype format.
-        $filename = $event->getStep()->getParent()->getTitle() . '_' . $event->getStep()->getText();
+        $filename = $scope->getFeature()->getTitle() . '_' . $scope->getStep()->getText();
         $filename = preg_replace('/([^a-zA-Z0-9\_]+)/', '-', $filename);
 
-        // File name limited to 255 characters. Leaving 4 chars for the file
+        // File name limited to 255 characters. Leaving 5 chars for line number and 4 chars for the file.
         // extension as we allow .png for images and .html for DOM contents.
-        $filename = substr($filename, 0, 250) . '.' . $filetype;
+        $filename = substr($filename, 0, 245) . '_' . $scope->getStep()->getLine() . '.' . $filetype;
 
         return array($dir, $filename);
     }
 
-    /**
-     * Waits for all the JS to be loaded.
-     *
-     * @throws \Exception
-     * @throws NoSuchWindow
-     * @throws UnknownError
-     * @return bool True or false depending whether all the JS is loaded or not.
-     */
-    protected function wait_for_pending_js() {
-
-        // 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 =
-                    'if (typeof M === "undefined") {
-                        if (document.readyState === "complete") {
-                            return "";
-                        } else {
-                            return "incomplete";
-                        }
-                    } else if (' . self::PAGE_READY_JS . ') {
-                        return "";
-                    } else {
-                        return 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) {
-                // M is not defined when the window or the frame don't exist anymore.
-                if (strstr($e->getMessage(), 'M is not defined') != false) {
-                    $pending = '';
-                }
-            }
-
-            // If there are no pending JS we stop waiting.
-            if ($pending === '') {
-                return true;
-            }
-
-            // 0.1 seconds.
-            usleep(100000);
-        }
-
-        // Timeout waiting for JS to complete. It will be catched and forwarded to behat_hooks::i_look_for_exceptions().
-        // It is unlikely that Javascript code of a page or an AJAX request needs more than self::EXTENDED_TIMEOUT seconds
-        // to be loaded, although when pages contains Javascript errors M.util.js_complete() can not be executed, so the
-        // number of JS pending code and JS completed code will not match and we will reach this point.
-        throw new \Exception('Javascript code and/or AJAX requests are not ready after ' . self::EXTENDED_TIMEOUT .
-            ' seconds. There is a Javascript error or the code is extremely slow.');
-    }
-
     /**
      * Internal step definition to find exceptions, debugging() messages and PHP debug messages.
      *
@@ -666,15 +606,15 @@ class behat_hooks extends behat_base {
     }
 
     /**
-     * Throws an exception after appending an extra info text.
+     * Stops execution because of some exception.
      *
-     * @throws Exception
-     * @param UnknownError $exception
+     * @param string $exception
      * @return void
      */
-    protected function throw_unknown_exception(UnknownError $exception) {
+    protected function stop_execution($exception) {
         $text = get_string('unknownexceptioninfo', 'tool_behat');
-        throw new Exception($text . PHP_EOL . $exception->getMessage());
+        echo $text . PHP_EOL . $exception . PHP_EOL;
+        exit(1);
     }
 
 }
index 81392ea..995a510 100644 (file)
 
 require_once(__DIR__ . '/../../behat/behat_base.php');
 
-use Behat\Behat\Context\Step\Given as Given;
+use Moodle\BehatExtension\Context\Step\Given;
+use Moodle\BehatExtension\Context\Step\When;
 use Behat\Mink\Exception\ExpectationException as ExpectationException;
 use Behat\Mink\Exception\DriverException as DriverException;
-use Behat\Behat\Context\Step\When as When;
 
 /**
  * Steps definitions to navigate through the navigation tree nodes.
@@ -245,7 +245,8 @@ class behat_navigation extends behat_base {
             if ($parentnodes[0] === $siteadminstr) {
                 // We don't know if there if Site admin is already expanded so
                 // don't wait, it is non-JS and we already waited for the DOM.
-                if ($siteadminlink = $this->getSession()->getPage()->find('named', array('link', "'" . $siteadminstr . "'"))) {
+                $siteadminlink = $this->getSession()->getPage()->find('named_exact', array('link', "'" . $siteadminstr . "'"));
+                if ($siteadminlink) {
                     $siteadminlink->click();
                 }
             }
@@ -383,4 +384,35 @@ class behat_navigation extends behat_base {
         }
         return $node;
     }
+
+    /**
+     * Step to open the navigation bar if it is needed.
+     *
+     * The top log in and log out links are hidden when middle or small
+     * size windows (or devices) are used. This step returns a step definition
+     * clicking to expand the navbar if it is hidden.
+     *
+     * @Given /^I expand navigation bar$/
+     */
+    public function get_expand_navbar_step() {
+
+        // Checking if we need to click the navbar button to show the navigation menu, it
+        // is hidden by default when using clean theme and a medium or small screen size.
+
+        // The DOM and the JS should be all ready and loaded. Running without spinning
+        // as this is a widely used step and we can not spend time here trying to see
+        // a DOM node that is not always there (at the moment clean is not even the
+        // default theme...).
+        $navbuttonjs = "return (
+            Y.one('.btn-navbar') &&
+            Y.one('.btn-navbar').getComputedStyle('display') !== 'none'
+        )";
+
+        // Adding an extra click we need to show the 'Log in' link.
+        if (!$this->getSession()->getDriver()->evaluateScript($navbuttonjs)) {
+            return false;
+        }
+
+        return new Given('I click on ".btn-navbar" "css_element"');
+    }
 }
index 4b932b6..a9f05e4 100644 (file)
@@ -28,7 +28,7 @@
 require_once(__DIR__ . '/../../behat/behat_base.php');
 
 use Behat\Mink\Exception\ExpectationException as ExpectationException,
-    Behat\Behat\Context\Step\Given as Given,
+    Moodle\BehatExtension\Context\Step\Given as Given,
     Behat\Gherkin\Node\TableNode as TableNode;
 
 /**
index 738b829..a8f7454 100644 (file)
@@ -82,12 +82,25 @@ class behat_transformations extends behat_base {
      * Transformations applicable to TableNode arguments should also
      * be applied, adding them in a different method for Behat API restrictions.
      *
-     * @Transform /^table:(.*)/
+     * @Transform table:Surname,My Surname $NASTYSTRING2
      * @param TableNode $tablenode
      * @return TableNode The transformed table
      */
-    public function tablenode_transformations(TableNode $tablenode) {
+    public function prefixed_tablenode_transformations(TableNode $tablenode) {
+        return $this->tablenode_transformations($tablenode);
+    }
 
+    /**
+     * Transformations for TableNode arguments.
+     *
+     * Transformations applicable to TableNode arguments should also
+     * be applied, adding them in a different method for Behat API restrictions.
+     *
+     * @Transform table:Surname,$NASTYSTRING1
+     * @param TableNode $tablenode
+     * @return TableNode The transformed table
+     */
+    public function tablenode_transformations(TableNode $tablenode) {
         // Walk through all values including the optional headers.
         $rows = $tablenode->getRows();
         foreach ($rows as $rowkey => $row) {
@@ -101,7 +114,9 @@ class behat_transformations extends behat_base {
         }
 
         // Return the transformed TableNode.
-        $tablenode->setRows($rows);
+        unset($tablenode);
+        $tablenode = new TableNode($rows);
+
         return $tablenode;
     }
 
index 144983a..56c2573 100644 (file)
@@ -27,7 +27,7 @@
 
 require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
 
-use Behat\Behat\Context\Step\Given as Given,
+use Moodle\BehatExtension\Context\Step\Given as Given,
     Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
 
 /**
index 0e95f6c..745618d 100644 (file)
@@ -25,13 +25,13 @@ Feature: Check that messages can be deleted
     And "Delete" "link" should exist in the "#message_1" "css_element"
     And "Delete" "link" should exist in the "#message_2" "css_element"
     # Confirm that there is a confirmation box before deleting, and that when we cancel the messages remain.
-    And I hover "#message_2" "css_element"
+    And I click on "#message_2" "css_element"
     And I click on "Delete" "link" in the "#message_2" "css_element"
     And I press "Cancel"
     And I should see "Hey bud, what's happening?"
     And I should see "Whoops, forgot to mention that I drank all your beers. Lol."
     # Confirm we can delete a message and then can no longer see it.
-    And I hover "#message_2" "css_element"
+    And I click on "#message_2" "css_element"
     And I click on "Delete" "link" in the "#message_2" "css_element"
     And I press "Delete"
     And I should see "Hey bud, what's happening?"
@@ -69,10 +69,10 @@ Feature: Check that messages can be deleted
     And "Delete" "link" should exist in the "#message_3" "css_element"
     And "Delete" "link" should exist in the "#message_4" "css_element"
     # Now, delete one of the messages that User 1 sent and one by User 2.
-    And I hover "#message_1" "css_element"
+    And I click on "#message_1" "css_element"
     And I click on "Delete" "link" in the "#message_1" "css_element"
     And I press "Delete"
-    And I hover "#message_2" "css_element"
+    And I click on "#message_2" "css_element"
     And I click on "Delete" "link" in the "#message_2" "css_element"
     And I press "Delete"
     # Confirm that the messages are no longer listed.
@@ -111,7 +111,7 @@ Feature: Check that messages can be deleted
     # Send a message from the admin to User 1
     And I send "Hey there, this is the all-powerful administrator. Obey my commands." message to "User 1" user
     # Check the admin is still able to delete messages.
-    And I hover "#message_1" "css_element"
+    And I click on "#message_1" "css_element"
     And I click on "Delete" "link" in the "#message_1" "css_element"
     And I press "Delete"
     And I should not see "Hey there, this is the all-powerful administrator. Obey my commands."
index 4900960..b54baff 100644 (file)
@@ -51,6 +51,7 @@ Feature: In an assignment, teacher can annotate PDF files during grading
     And I follow "Course 1"
     And I follow "Test assignment name"
     And I follow "View/grade all submissions"
+    And I click on "Edit" "link" in the "Submitted for grading" "table_row"
     And I click on "Grade" "link" in the "Submitted for grading" "table_row"
     And I follow "Launch PDF editor..."
     And I change window size to "large"
@@ -129,6 +130,7 @@ Feature: In an assignment, teacher can annotate PDF files during grading
     And I follow "Course 1"
     And I follow "Test assignment name"
     And I follow "View/grade all submissions"
+    And I click on "Edit" "link" in the "Student 2" "table_row"
     And I click on "Grade" "link" in the "Student 2" "table_row"
     And I follow "Launch PDF editor..."
     And I change window size to "large"
index 41fab07..a35c867 100644 (file)
@@ -27,8 +27,6 @@
 
 require_once(__DIR__ . '/../../../../../../lib/behat/behat_base.php');
 
-use Behat\Behat\Context\Step\Given as Given;
-
 /**
  * Steps definitions related with the editpdf.
  *
index d63d098..13bebcf 100644 (file)
@@ -26,8 +26,8 @@ Feature: In an assignment, teachers can edit a students submission inline
       | assignsubmission_onlinetext_enabled | 1 |
       | assignsubmission_file_enabled | 0 |
       | assignfeedback_comments_enabled | 1 |
-      | assignfeedback_comments_commentinline | 1 |
       | assignfeedback_file_enabled | 1 |
+      | assignfeedback_comments_commentinline | 1 |
     And I log out
     And I log in as "student1"
     And I follow "Course 1"
index ba012aa..e370228 100644 (file)
@@ -27,7 +27,7 @@
 
 require_once(__DIR__ . '/../../../../lib/behat/behat_base.php');
 
-use Behat\Behat\Context\Step\Given as Given;
+use Moodle\BehatExtension\Context\Step\Given as Given;
 
 /**
  * Choice activity definitions.
@@ -67,14 +67,24 @@ class behat_mod_choice extends behat_base {
      * @return array
      */
     public function I_choose_options_from_activity($option, $choiceactivity) {
-        // Escaping again the strings as backslashes have been removed by the automatic transformation.
-        $return = array(new Given('I follow "' . $this->escape($choiceactivity) . '"'));
+        // Get Behat general and forms contexts.
+        $behatgeneral = behat_context_helper::get('behat_general');
+        $behatforms = behat_context_helper::get('behat_forms');
+
+        // Go to choice activity.
+        $behatgeneral->click_link($this->escape($choiceactivity));
+
+        // Wait for page to be loaded.
+        $this->wait_for_pending_js();
+
+        // Set all options.
         $options = explode('","', trim($option, '"'));
         foreach ($options as $option) {
-            $return[] = new Given('I set the field "' . $this->escape($option) . '" to "1"');
+            $behatforms->i_set_the_field_to($this->escape($option), '1');
         }
-        $return[] = new Given('I press "' . get_string('savemychoice', 'choice') . '"');
-        return $return;
+
+        // Save choice.
+        $behatforms->press_button(get_string('savemychoice', 'choice'));
     }
 
 }
index 3dc04c2..3fcc8d0 100644 (file)
@@ -27,8 +27,8 @@
 
 require_once(__DIR__ . '/../../../../lib/behat/behat_base.php');
 
-use Behat\Behat\Context\Step\Given as Given,
-    Behat\Behat\Context\Step\When as When,
+use Moodle\BehatExtension\Context\Step\Given as Given,
+    Moodle\BehatExtension\Context\Step\When as When,
     Behat\Gherkin\Node\TableNode as TableNode;
 /**
  * Database-related steps definitions.
index d5ef8e5..eab816c 100644 (file)
@@ -31,8 +31,7 @@ Feature: Users can be required to specify certain fields when adding entries to
       | Required | yes |
       | Options | Required Checkbox Option 1 |
     And I follow "Fields"
-    And I set the field "newtype" to "Checkbox"
-    And I click on "Go" "button" in the ".fieldadd" "css_element"
+    And I select "Checkbox" from the "newtype" singleselect
     And I set the following fields to these values:
       | Field name | Required Two-Option Checkbox |
       | Field description | Required Two-Option Checkbox |
@@ -79,8 +78,7 @@ Feature: Users can be required to specify certain fields when adding entries to
       | Required | yes |
       | Options | Option 1 |
     And I follow "Fields"
-    And I set the field "newtype" to "Multimenu"
-    And I click on "Go" "button" in the ".fieldadd" "css_element"
+    And I select "Multimenu" from the "newtype" singleselect
     And I set the following fields to these values:
       | Field name | Required Two-Option Multimenu |
       | Field description | Required Two-Option Multimenu |
index d6c4b52..abf47af 100644 (file)
@@ -54,14 +54,16 @@ class behat_mod_feedback extends behat_base {
         $additem = $this->escape(get_string('add_item', 'feedback'));
         $rv[] = new Given("I select \"{$questiontype}\" from the \"{$additem}\" singleselect");
 
-        $newdata = new TableNode();
         $rows = $questiondata->getRows();
+        $modifiedrows = array();
         foreach ($rows as $row) {
             foreach ($row as $key => $value) {
                 $row[$key] = preg_replace('|\\\\n|', "\n", $value);
             }
-            $newdata->addRow($row);
+            $modifiedrows[] = $row;
         }
+        $newdata = new TableNode($modifiedrows);
+
         $rv[] = new Given('I set the following fields to these values:', $newdata);
 
         $saveitem = $this->escape(get_string('save_item', 'feedback'));
index 585e993..d6dcbd6 100644 (file)
@@ -112,8 +112,6 @@ Feature: Test creating different types of feedback questions
     And I should see "C1" in the "(info)" "table"
     And I should see "my long answer" in the "(longertext)" "table"
     And I should see "lots of feedbacks" in the "(longertext)" "table"
-    #And I should see "1 (50.00 %)" in the "option a:" "table_row"  // TODO: MDL-46891
-    #And I should see "1 (50.00 %)" in the "option b:" "table_row"  // TODO: MDL-46891
     And I should see "2 (100.00 %)" in the "option d:" "table_row"
     And I should see "1 (50.00 %)" in the "option e:" "table_row"
     And I should see "1 (50.00 %)" in the "option f:" "table_row"
@@ -131,9 +129,12 @@ Feature: Test creating different types of feedback questions
     And I should see "Average: 53.00" in the "(numeric)" "table"
     And I should see "no way" in the "(shorttext)" "table"
     And I should see "hello" in the "(shorttext)" "table"
-    And I log out
 
   Scenario: Create different types of questions in feedback with javascript disabled
+    And I log out
 
   @javascript
   Scenario: Create different types of questions in feedback with javascript enabled
+    And I should see "1 (50.00 %)" in the "option a:" "table_row"
+    And I should see "1 (50.00 %)" in the "option b:" "table_row"
+    And I log out
index bb56542..f132e1f 100644 (file)
@@ -27,7 +27,7 @@
 
 require_once(__DIR__ . '/../../../../lib/behat/behat_base.php');
 
-use Behat\Behat\Context\Step\Given as Given,
+use Moodle\BehatExtension\Context\Step\Given as Given,
     Behat\Gherkin\Node\TableNode as TableNode;
 /**
  * Forum-related steps definitions.
index a4b904c..51c8eae 100644 (file)
@@ -27,7 +27,7 @@
 
 require_once(__DIR__ . '/../../../../lib/behat/behat_base.php');
 
-use Behat\Behat\Context\Step\Given as Given,
+use Moodle\BehatExtension\Context\Step\Given as Given,
     Behat\Gherkin\Node\TableNode as TableNode;
 
 /**
index aaa95ba..94467ce 100644 (file)
@@ -54,6 +54,7 @@ defined('MOODLE_INTERNAL') || die;
 use moodle\mod\lti as lti;
 
 require_once($CFG->dirroot.'/mod/lti/OAuth.php');
+require_once($CFG->libdir.'/weblib.php');
 
 define('LTI_URL_DOMAIN_REGEX', '/(?:https?:\/\/)?(?:www\.)?([^\/]+)(?:\/|$)/i');
 
@@ -503,6 +504,7 @@ function lti_build_standard_request($instance, $orgid, $islti2) {
     } else {
         $requestparams['tool_consumer_instance_name'] = get_site()->fullname;
     }
+    $requestparams['tool_consumer_instance_description'] = html_to_text(get_site()->summary, 0);
 
     return $requestparams;
 }
index 51cd7b9..00b64d7 100644 (file)
@@ -71,14 +71,16 @@ class mod_lti_external_testcase extends externallib_advanced_testcase {
      * Test view_lti
      */
     public function test_get_tool_launch_data() {
-        global $USER;
+        global $USER, $SITE;
+
+        $SITE->summary = "This is a <b>long</b> front page summary with html, it should exceed more than seventy-five characters.";
 
         $result = mod_lti_external::get_tool_launch_data($this->lti->id);
         $result = external_api::clean_returnvalue(mod_lti_external::get_tool_launch_data_returns(), $result);
 
         // Basic test, the function returns what it's expected.
         self::assertEquals($this->lti->toolurl, $result['endpoint']);
-        self::assertCount(35, $result['parameters']);
+        self::assertCount(36, $result['parameters']);
 
         // Check some parameters.
         $parameters = array();
@@ -93,6 +95,8 @@ class mod_lti_external_testcase extends externallib_advanced_testcase {
         self::assertEquals($USER->lastname, $parameters['lis_person_name_family']);
         self::assertEquals(fullname($USER), $parameters['lis_person_name_full']);
         self::assertEquals($USER->username, $parameters['ext_user_username']);
+        self::assertEquals("This is a LONG front page summary with html, it should exceed more than seventy-five characters.",
+                           $parameters['tool_consumer_instance_description']);
 
     }
 
index ac23865..4f4e7f8 100644 (file)
 require_once(__DIR__ . '/../../../../lib/behat/behat_base.php');
 require_once(__DIR__ . '/../../../../question/tests/behat/behat_question_base.php');
 
-use Behat\Behat\Context\Step\Given as Given,
-    Behat\Gherkin\Node\TableNode as TableNode,
-    Behat\Mink\Exception\ExpectationException as ExpectationException;
+use Moodle\BehatExtension\Context\Step\Given as Given,
+    Behat\Gherkin\Node\TableNode as TableNode;
+
+use Behat\Mink\Exception\ExpectationException as ExpectationException;
 
 /**
  * Steps definitions related to mod_quiz.
@@ -83,7 +84,7 @@ class behat_mod_quiz extends behat_question_base {
             }
             $rows = $data->getRows();
             array_unshift($rows, $headings);
-            $data->setRows($rows);
+            $data = new TableNode($rows);
         }
 
         // Add the questions.
index 0e7213e..587e84b 100644 (file)
@@ -59,6 +59,7 @@ Feature: Settings form fields disabled if not required
         | Attempts allowed | 3        |
     And I press "Save"
     And I navigate to "Edit settings" node in "Quiz administration"
+    And I expand all fieldsets
     And I set the field "Attempts allowed" to "1"
     Then the "Grading method" "field" should be enabled
     And the "Each attempt builds on the last" "field" should be enabled
@@ -71,6 +72,7 @@ Feature: Settings form fields disabled if not required
     And I set the field "Attempts allowed" to "2"
     And I press "Save"
     And I navigate to "Edit settings" node in "Quiz administration"
+    And I expand all fieldsets
     And I set the field "Attempts allowed" to "1"
     Then the "Grading method" "field" should be enabled
     And the "Each attempt builds on the last" "field" should be enabled
@@ -85,6 +87,7 @@ Feature: Settings form fields disabled if not required
         | Attempts allowed | Unlimited |
     And I press "Save"
     And I navigate to "Edit settings" node in "Quiz administration"
+    And I expand all fieldsets
     And I set the field "Attempts allowed" to "1"
     Then the "Grading method" "field" should be enabled
     And the "Each attempt builds on the last" "field" should be enabled
index 1aa157e..a6634c5 100644 (file)
@@ -395,4 +395,203 @@ class mod_wiki_external extends external_api {
         );
     }
 
+    /**
+     * Describes the parameters for get_subwiki_pages.
+     *
+     * @return external_function_parameters
+     * @since Moodle 3.1
+     */
+    public static function get_subwiki_pages_parameters() {
+        return new external_function_parameters (
+            array(
+                'wikiid' => new external_value(PARAM_INT, 'Wiki instance ID.'),
+                'groupid' => new external_value(PARAM_INT, 'Subwiki\'s group ID, -1 means current group. It will be ignored'
+                                        . ' if the wiki doesn\'t use groups.', VALUE_DEFAULT, -1),
+                'userid' => new external_value(PARAM_INT, 'Subwiki\'s user ID, 0 means current user. It will be ignored'
+                                        .' in collaborative wikis.', VALUE_DEFAULT, 0),
+                'options' => new external_single_structure(
+                            array(
+                                    'sortby' => new external_value(PARAM_ALPHA,
+                                            'Field to sort by (id, title, ...).', VALUE_DEFAULT, 'title'),
+                                    'sortdirection' => new external_value(PARAM_ALPHA,
+                                            'Sort direction: ASC or DESC.', VALUE_DEFAULT, 'ASC'),
+                                    'includecontent' => new external_value(PARAM_INT,
+                                            'Include each page contents or not.', VALUE_DEFAULT, 1),
+                            ), 'Options', VALUE_DEFAULT, array()),
+            )
+        );
+    }
+
+    /**
+     * Returns the list of pages from a specific subwiki.
+     *
+     * @param int $wikiid The wiki instance ID.
+     * @param int $groupid The group ID. If not defined, use current group.
+     * @param int $userid The user ID. If not defined, use current user.
+     * @param array $options Several options like sort by, sort direction, ...
+     * @return array Containing a list of warnings and a list of pages.
+     * @since Moodle 3.1
+     */
+    public static function get_subwiki_pages($wikiid, $groupid = -1, $userid = 0, $options = array()) {
+        global $USER, $DB;
+
+        $returnedpages = array();
+        $warnings = array();
+
+        $params = self::validate_parameters(self::get_subwiki_pages_parameters(),
+                                            array(
+                                                'wikiid' => $wikiid,
+                                                'groupid' => $groupid,
+                                                'userid' => $userid,
+                                                'options' => $options
+                                                )
+            );
+
+        // Get wiki instance.
+        if (!$wiki = wiki_get_wiki($params['wikiid'])) {
+            throw new moodle_exception('incorrectwikiid', 'wiki');
+        }
+        list($course, $cm) = get_course_and_cm_from_instance($wiki, 'wiki');
+        $context = context_module::instance($cm->id);
+        self::validate_context($context);
+
+        // Determine group.
+        $groupmode = groups_get_activity_groupmode($cm);
+        if ($groupmode == NOGROUPS) {
+            $groupid = 0;
+        } else if ($params['groupid'] == -1) {
+            // Use current group.
+            $groupid = groups_get_activity_group($cm);
+            $groupid = !empty($groupid) ? $groupid : 0;
+        } else {
+            $groupid = $params['groupid'];
+        }
+
+        // Determine user.
+        if ($wiki->wikimode == 'collaborative') {
+            // Collaborative wikis don't use userid in subwikis.
+            $userid = 0;
+        } else if (empty($params['userid'])) {
+            // Use current user.
+            $userid = $USER->id;
+        } else {
+            $userid = $params['userid'];
+        }
+
+        // Get subwiki based on group and user.
+        if (!$subwiki = wiki_get_subwiki_by_group($cm->instance, $groupid, $userid)) {
+            // The subwiki doesn't exist.
+            // Validate if user is valid.
+            if ($userid != 0 && $userid != $USER->id && !$user = $DB->get_record('user', array('id' => $userid))) {
+                throw new moodle_exception('invaliduserid', 'error');
+            }
+
+            // Validate that groupid is valid.
+            if ($groupid != 0 && !groups_group_exists($groupid)) {
+                throw new moodle_exception('cannotfindgroup', 'error');
+            }
+
+            // Valid data but subwiki not found. We'll simulate a subwiki object to check if the user would be able to see it
+            // if it existed. If he's able to see it then we'll return an empty array because the subwiki has no pages.
+            $subwiki = new stdClass();
+            $subwiki->wikiid = $wiki->id;
+            $subwiki->userid = $userid;
+            $subwiki->groupid = $groupid;
+
+            // Check that the user can view the subwiki. This function checks capabilities.
+            if (!wiki_user_can_view($subwiki, $wiki)) {
+                throw new moodle_exception('cannotviewpage', 'wiki');
+            }
+        } else {
+            // Check that the user can view the subwiki. This function checks capabilities.
+            if (!wiki_user_can_view($subwiki, $wiki)) {
+                throw new moodle_exception('cannotviewpage', 'wiki');
+            }
+
+            // Set sort param.
+            $options = $params['options'];
+            if (!empty($options['sortby'])) {
+                if ($options['sortdirection'] != 'ASC' && $options['sortdirection'] != 'DESC') {
+                    // Invalid sort direction. Use default.
+                    $options['sortdirection'] = 'ASC';
+                }
+                $sort = $options['sortby'] . ' ' . $options['sortdirection'];
+            }
+
+            $pages = wiki_get_page_list($subwiki->id, $sort);
+            $caneditpages = wiki_user_can_edit($subwiki);
+            $firstpage = wiki_get_first_page($subwiki->id);
+
+            foreach ($pages as $page) {
+                $retpage = array(
+                        'id' => $page->id,
+                        'subwikiid' => $page->subwikiid,
+                        'title' => external_format_string($page->title, $context->id),
+                        'timecreated' => $page->timecreated,
+                        'timemodified' => $page->timemodified,
+                        'timerendered' => $page->timerendered,
+                        'userid' => $page->userid,
+                        'pageviews' => $page->pageviews,
+                        'readonly' => $page->readonly,
+                        'caneditpage' => $caneditpages,
+                        'firstpage' => $page->id == $firstpage->id
+                    );
+
+                if ($options['includecontent']) {
+                    // Refresh page cached content if needed.
+                    if ($page->timerendered + WIKI_REFRESH_CACHE_TIME < time()) {
+                        if ($content = wiki_refresh_cachedcontent($page)) {
+                            $page = $content['page'];
+                        }
+                    }
+
+                    list($retpage['cachedcontent'], $retpage['contentformat']) = external_format_text(
+                                $page->cachedcontent, FORMAT_HTML, $context->id, 'mod_wiki', 'attachments', $subwiki->id);
+                }
+
+                $returnedpages[] = $retpage;
+            }
+
+        }
+
+        $result = array();
+        $result['pages'] = $returnedpages;
+        $result['warnings'] = $warnings;
+        return $result;
+    }
+
+    /**
+     * Describes the get_subwiki_pages return value.
+     *
+     * @return external_single_structure
+     * @since Moodle 3.1
+     */
+    public static function get_subwiki_pages_returns() {
+
+        return new external_single_structure(
+            array(
+                'pages' => new external_multiple_structure(
+                    new external_single_structure(
+                        array(
+                            'id' => new external_value(PARAM_INT, 'Page ID.'),
+                            'subwikiid' => new external_value(PARAM_INT, 'Page\'s subwiki ID.'),
+                            'title' => new external_value(PARAM_RAW, 'Page title.'),
+                            'timecreated' => new external_value(PARAM_INT, 'Time of creation.'),
+                            'timemodified' => new external_value(PARAM_INT, 'Time of last modification.'),
+                            'timerendered' => new external_value(PARAM_INT, 'Time of last renderization.'),
+                            'userid' => new external_value(PARAM_INT, 'ID of the user that last modified the page.'),
+                            'pageviews' => new external_value(PARAM_INT, 'Number of times the page has been viewed.'),
+                            'readonly' => new external_value(PARAM_INT, '1 if readonly, 0 otherwise.'),
+                            'caneditpage' => new external_value(PARAM_BOOL, 'True if user can edit the page.'),
+                            'firstpage' => new external_value(PARAM_BOOL, 'True if it\'s the first page.'),
+                            'cachedcontent' => new external_value(PARAM_RAW, 'Page contents.', VALUE_OPTIONAL),
+                            'contentformat' => new external_format_value('cachedcontent', VALUE_OPTIONAL),
+                        ), 'Pages'
+                    )
+                ),
+                'warnings' => new external_warnings(),
+            )
+        );
+    }
+
 }
index 76aa5ea..6894260 100644 (file)
@@ -61,5 +61,13 @@ $functions = array(
         'type'          => 'read',
         'capabilities'  => 'mod/wiki:viewpage',
         'services'      => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
+    ),
+    'mod_wiki_get_subwiki_pages' => array(
+        'classname'     => 'mod_wiki_external',
+        'methodname'    => 'get_subwiki_pages',
+        'description'   => 'Returns the list of pages for a specific subwiki.',
+        'type'          => 'read',
+        'capabilities'  => 'mod/wiki:viewpage',
+        'services'      => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
     )
 );
index 474246d..9162b38 100644 (file)
@@ -506,10 +506,11 @@ function wiki_get_missing_or_empty_pages($swid) {
 /**
  * Get pages list in wiki
  * @param int $swid sub wiki id
+ * @param string $sort How to sort the pages. By default, title ASC.
  */
-function wiki_get_page_list($swid) {
+function wiki_get_page_list($swid, $sort = 'title ASC') {
     global $DB;
-    $records = $DB->get_records('wiki_pages', array('subwikiid' => $swid), 'title ASC');
+    $records = $DB->get_records('wiki_pages', array('subwikiid' => $swid), $sort);
     return $records;
 }
 
index 06c3ace..ec1ddf8 100644 (file)
@@ -58,18 +58,90 @@ class mod_wiki_external_testcase extends externallib_advanced_testcase {
 
         // Create users.
         $this->student = self::getDataGenerator()->create_user();
+        $this->student2 = self::getDataGenerator()->create_user();
         $this->teacher = self::getDataGenerator()->create_user();
 
         // Users enrolments.
         $this->studentrole = $DB->get_record('role', array('shortname' => 'student'));
         $this->teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
         $this->getDataGenerator()->enrol_user($this->student->id, $this->course->id, $this->studentrole->id, 'manual');
+        $this->getDataGenerator()->enrol_user($this->student2->id, $this->course->id, $this->studentrole->id, 'manual');
         $this->getDataGenerator()->enrol_user($this->teacher->id, $this->course->id, $this->teacherrole->id, 'manual');
 
-        // Create first page.
+        // Create first pages.
         $this->firstpage = $this->getDataGenerator()->get_plugin_generator('mod_wiki')->create_first_page($this->wiki);
     }
 
+    /**
+     * Create two collaborative wikis (separate/visible groups), 2 groups and a first page for each wiki and group.
+     */
+    private function create_collaborative_wikis_with_groups() {
+        // Create groups and add student to one of them.
+        if (!isset($this->group1)) {
+            $this->group1 = $this->getDataGenerator()->create_group(array('courseid' => $this->course->id));
+            $this->getDataGenerator()->create_group_member(array('userid' => $this->student->id, 'groupid' => $this->group1->id));
+            $this->getDataGenerator()->create_group_member(array('userid' => $this->student2->id, 'groupid' => $this->group1->id));
+        }
+        if (!isset($this->group2)) {
+            $this->group2 = $this->getDataGenerator()->create_group(array('courseid' => $this->course->id));
+        }
+
+        // Create two collaborative wikis.
+        $this->wikisep = $this->getDataGenerator()->create_module('wiki',
+                                                        array('course' => $this->course->id, 'groupmode' => SEPARATEGROUPS));
+        $this->wikivis = $this->getDataGenerator()->create_module('wiki',
+                                                        array('course' => $this->course->id, 'groupmode' => VISIBLEGROUPS));
+
+        // Create pages.
+        $wikigenerator = $this->getDataGenerator()->get_plugin_generator('mod_wiki');
+        $this->fpsepg1 = $wikigenerator->create_first_page($this->wikisep, array('group' => $this->group1->id));
+        $this->fpsepg2 = $wikigenerator->create_first_page($this->wikisep, array('group' => $this->group2->id));
+        $this->fpsepall = $wikigenerator->create_first_page($this->wikisep, array('group' => 0)); // All participants.
+        $this->fpvisg1 = $wikigenerator->create_first_page($this->wikivis, array('group' => $this->group1->id));
+        $this->fpvisg2 = $wikigenerator->create_first_page($this->wikivis, array('group' => $this->group2->id));
+        $this->fpvisall = $wikigenerator->create_first_page($this->wikivis, array('group' => 0)); // All participants.
+    }
+
+    /**
+     * Create two individual wikis (separate/visible groups), 2 groups and a first page for each wiki and group.
+     */
+    private function create_individual_wikis_with_groups() {
+        // Create groups and add student to one of them.
+        if (!isset($this->group1)) {
+            $this->group1 = $this->getDataGenerator()->create_group(array('courseid' => $this->course->id));
+            $this->getDataGenerator()->create_group_member(array('userid' => $this->student->id, 'groupid' => $this->group1->id));
+            $this->getDataGenerator()->create_group_member(array('userid' => $this->student2->id, 'groupid' => $this->group1->id));
+        }
+        if (!isset($this->group2)) {
+            $this->group2 = $this->getDataGenerator()->create_group(array('courseid' => $this->course->id));
+        }
+
+        // Create two individual wikis.
+        $this->wikisepind = $this->getDataGenerator()->create_module('wiki', array('course' => $this->course->id,
+                                                        'groupmode' => SEPARATEGROUPS, 'wikimode' => 'individual'));
+        $this->wikivisind = $this->getDataGenerator()->create_module('wiki', array('course' => $this->course->id,
+                                                        'groupmode' => VISIBLEGROUPS, 'wikimode' => 'individual'));
+
+        // Create pages. Student can only create pages in his groups.
+        $wikigenerator = $this->getDataGenerator()->get_plugin_generator('mod_wiki');
+        $this->setUser($this->teacher);
+        $this->fpsepg1indt = $wikigenerator->create_first_page($this->wikisepind, array('group' => $this->group1->id));
+        $this->fpsepg2indt = $wikigenerator->create_first_page($this->wikisepind, array('group' => $this->group2->id));
+        $this->fpsepallindt = $wikigenerator->create_first_page($this->wikisepind, array('group' => 0)); // All participants.
+        $this->fpvisg1indt = $wikigenerator->create_first_page($this->wikivisind, array('group' => $this->group1->id));
+        $this->fpvisg2indt = $wikigenerator->create_first_page($this->wikivisind, array('group' => $this->group2->id));
+        $this->fpvisallindt = $wikigenerator->create_first_page($this->wikivisind, array('group' => 0)); // All participants.
+
+        $this->setUser($this->student);
+        $this->fpsepg1indstu = $wikigenerator->create_first_page($this->wikisepind, array('group' => $this->group1->id));
+        $this->fpvisg1indstu = $wikigenerator->create_first_page($this->wikivisind, array('group' => $this->group1->id));
+
+        $this->setUser($this->student2);
+        $this->fpsepg1indstu2 = $wikigenerator->create_first_page($this->wikisepind, array('group' => $this->group1->id));
+        $this->fpvisg1indstu2 = $wikigenerator->create_first_page($this->wikivisind, array('group' => $this->group1->id));
+
+    }
+
     /*
      * Test get wikis by courses
      */
@@ -372,4 +444,433 @@ class mod_wiki_external_testcase extends externallib_advanced_testcase {
 
     }
 
+    /**
+     * Test get_subwiki_pages using an invalid wiki instance.
+     */
+    public function test_get_subwiki_pages_invalid_instance() {
+        $this->setExpectedException('moodle_exception');
+        mod_wiki_external::get_subwiki_pages(0);
+    }
+
+    /**
+     * Test get_subwiki_pages using a user not enrolled in the course.
+     */
+    public function test_get_subwiki_pages_unenrolled_user() {
+        // Create and use the user.
+        $usernotenrolled = self::getDataGenerator()->create_user();
+        $this->setUser($usernotenrolled);
+
+        $this->setExpectedException('require_login_exception');
+        mod_wiki_external::get_subwiki_pages($this->wiki->id);
+    }
+
+    /**
+     * Test get_subwiki_pages using a hidden wiki as student.
+     */
+    public function test_get_subwiki_pages_hidden_wiki_as_student() {
+        // Create a hidden wiki and try to get the list of pages.
+        $hiddenwiki = $this->getDataGenerator()->create_module('wiki',
+                            array('course' => $this->course->id, 'visible' => false));
+
+        $this->setUser($this->student);
+        $this->setExpectedException('require_login_exception');
+        mod_wiki_external::get_subwiki_pages($hiddenwiki->id);
+    }
+
+    /**
+     * Test get_subwiki_pages without the viewpage capability.
+     */
+    public function test_get_subwiki_pages_without_viewpage_capability() {
+        // Prohibit capability = mod/wiki:viewpage on the course for students.
+        $contextcourse = context_course::instance($this->course->id);
+        assign_capability('mod/wiki:viewpage', CAP_PROHIBIT, $this->studentrole->id, $contextcourse->id);
+        accesslib_clear_all_caches_for_unit_testing();
+
+        $this->setUser($this->student);
+        $this->setExpectedException('moodle_exception');
+        mod_wiki_external::get_subwiki_pages($this->wiki->id);
+    }
+
+    /**
+     * Test get_subwiki_pages using an invalid userid.
+     */
+    public function test_get_subwiki_pages_invalid_userid() {
+        // Create an individual wiki.
+        $indwiki = $this->getDataGenerator()->create_module('wiki',
+                                array('course' => $this->course->id, 'wikimode' => 'individual'));
+
+        $this->setExpectedException('moodle_exception');
+        mod_wiki_external::get_subwiki_pages($indwiki->id, 0, -10);
+    }
+
+    /**
+     * Test get_subwiki_pages using an invalid groupid.
+     */
+    public function test_get_subwiki_pages_invalid_groupid() {
+        // Create testing data.
+        $this->create_collaborative_wikis_with_groups();
+
+        $this->setExpectedException('moodle_exception');
+        mod_wiki_external::get_subwiki_pages($this->wikisep->id, -111);
+    }
+
+    /**
+     * Test get_subwiki_pages, check that a student can't see another user pages in an individual wiki without groups.
+     */
+    public function test_get_subwiki_pages_individual_student_see_other_user() {
+        // Create an individual wiki.
+        $indwiki = $this->getDataGenerator()->create_module('wiki',
+                                array('course' => $this->course->id, 'wikimode' => 'individual'));
+
+        $this->setUser($this->student);
+        $this->setExpectedException('moodle_exception');
+        mod_wiki_external::get_subwiki_pages($indwiki->id, 0, $this->teacher->id);
+    }
+
+    /**
+     * Test get_subwiki_pages, check that a student can't get the pages from another group in
+     * a collaborative wiki using separate groups.
+     */
+    public function test_get_subwiki_pages_collaborative_separate_groups_student_see_other_group() {
+        // Create testing data.
+        $this->create_collaborative_wikis_with_groups();
+
+        $this->setUser($this->student);
+        $this->setExpectedException('moodle_exception');
+        mod_wiki_external::get_subwiki_pages($this->wikisep->id, $this->group2->id);
+    }
+
+    /**
+     * Test get_subwiki_pages, check that a student can't get the pages from another group in
+     * an individual wiki using separate groups.
+     */
+    public function test_get_subwiki_pages_individual_separate_groups_student_see_other_group() {
+        // Create testing data.
+        $this->create_individual_wikis_with_groups();
+
+        $this->setUser($this->student);
+        $this->setExpectedException('moodle_exception');
+        mod_wiki_external::get_subwiki_pages($this->wikisepind->id, $this->group2->id, $this->teacher->id);
+    }
+
+    /**
+     * Test get_subwiki_pages, check that a student can't get the pages from all participants in
+     * a collaborative wiki using separate groups.
+     */
+    public function test_get_subwiki_pages_collaborative_separate_groups_student_see_all_participants() {
+        // Create testing data.
+        $this->create_collaborative_wikis_with_groups();
+
+        $this->setUser($this->student);
+        $this->setExpectedException('moodle_exception');
+        mod_wiki_external::get_subwiki_pages($this->wikisep->id, 0);
+    }
+
+    /**
+     * Test get_subwiki_pages, check that a student can't get the pages from all participants in
+     * an individual wiki using separate groups.
+     */
+    public function test_get_subwiki_pages_individual_separate_groups_student_see_all_participants() {
+        // Create testing data.
+        $this->create_individual_wikis_with_groups();
+
+        $this->setUser($this->student);
+        $this->setExpectedException('moodle_exception');
+        mod_wiki_external::get_subwiki_pages($this->wikisepind->id, 0, $this->teacher->id);
+    }
+
+    /**
+     * Test get_subwiki_pages without groups and collaborative wiki.
+     */
+    public function test_get_subwiki_pages_collaborative() {
+
+        // Test user with full capabilities.
+        $this->setUser($this->student);
+
+        // Set expected result: first page.
+        $expectedpages = array();
+        $expectedfirstpage = (array) $this->firstpage;
+        $expectedfirstpage['caneditpage'] = true; // No groups and students have 'mod/wiki:editpage' capability.
+        $expectedfirstpage['firstpage'] = true;
+        $expectedfirstpage['contentformat'] = 1;
+        $expectedpages[] = $expectedfirstpage;
+
+        $result = mod_wiki_external::get_subwiki_pages($this->wiki->id);
+        $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+        $this->assertEquals($expectedpages, $result['pages']);
+
+        // Check that groupid param is ignored since the wiki isn't using groups.
+        $result = mod_wiki_external::get_subwiki_pages($this->wiki->id, 1234);
+        $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+        $this->assertEquals($expectedpages, $result['pages']);
+
+        // Check that userid param is ignored since the wiki is collaborative.
+        $result = mod_wiki_external::get_subwiki_pages($this->wiki->id, 1234, 1234);
+        $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+        $this->assertEquals($expectedpages, $result['pages']);
+
+        // Add a new page to the wiki and test again. We'll use a custom title so it's returned first if sorted by title.
+        $newpage = $this->getDataGenerator()->get_plugin_generator('mod_wiki')->create_page(
+                                $this->wiki, array('title' => 'AAA'));
+
+        $expectednewpage = (array) $newpage;
+        $expectednewpage['caneditpage'] = true; // No groups and students have 'mod/wiki:editpage' capability.
+        $expectednewpage['firstpage'] = false;
+        $expectednewpage['contentformat'] = 1;
+        array_unshift($expectedpages, $expectednewpage); // Add page to the beginning since it orders by title by default.
+
+        $result = mod_wiki_external::get_subwiki_pages($this->wiki->id);
+        $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+        $this->assertEquals($expectedpages, $result['pages']);
+
+        // Now we'll order by ID. Since first page was created first it'll have a lower ID.
+        $expectedpages = array($expectedfirstpage, $expectednewpage);
+        $result = mod_wiki_external::get_subwiki_pages($this->wiki->id, 0, 0, array('sortby' => 'id'));
+        $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+        $this->assertEquals($expectedpages, $result['pages']);
+
+        // Check that WS doesn't return page content if includecontent is false.
+        unset($expectedpages[0]['cachedcontent']);
+        unset($expectedpages[0]['contentformat']);
+        unset($expectedpages[1]['cachedcontent']);
+        unset($expectedpages[1]['contentformat']);
+        $result = mod_wiki_external::get_subwiki_pages($this->wiki->id, 0, 0, array('sortby' => 'id', 'includecontent' => 0));
+        $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+        $this->assertEquals($expectedpages, $result['pages']);
+    }
+
+    /**
+     * Test get_subwiki_pages without groups.
+     */
+    public function test_get_subwiki_pages_individual() {
+
+        // Create an individual wiki to test userid param.
+        $indwiki = $this->getDataGenerator()->create_module('wiki',
+                                array('course' => $this->course->id, 'wikimode' => 'individual'));
+
+        // Perform a request before creating any page to check that an empty array is returned if subwiki doesn't exist.
+        $result = mod_wiki_external::get_subwiki_pages($indwiki->id);
+        $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+        $this->assertEquals(array(), $result['pages']);
+
+        // Create first pages as student and teacher.
+        $this->setUser($this->student);
+        $indfirstpagestudent = $this->getDataGenerator()->get_plugin_generator('mod_wiki')->create_first_page($indwiki);
+        $this->setUser($this->teacher);
+        $indfirstpageteacher = $this->getDataGenerator()->get_plugin_generator('mod_wiki')->create_first_page($indwiki);
+
+        // Check that teacher can get his pages.
+        $expectedteacherpage = (array) $indfirstpageteacher;
+        $expectedteacherpage['caneditpage'] = true;
+        $expectedteacherpage['firstpage'] = true;
+        $expectedteacherpage['contentformat'] = 1;
+        $expectedpages = array($expectedteacherpage);
+
+        $result = mod_wiki_external::get_subwiki_pages($indwiki->id, 0, $this->teacher->id);
+        $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+        $this->assertEquals($expectedpages, $result['pages']);
+
+        // Check that the teacher can see the student's pages.
+        $expectedstudentpage = (array) $indfirstpagestudent;
+        $expectedstudentpage['caneditpage'] = true;
+        $expectedstudentpage['firstpage'] = true;
+        $expectedstudentpage['contentformat'] = 1;
+        $expectedpages = array($expectedstudentpage);
+
+        $result = mod_wiki_external::get_subwiki_pages($indwiki->id, 0, $this->student->id);
+        $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+        $this->assertEquals($expectedpages, $result['pages']);
+
+        // Now check that student can get his pages.
+        $this->setUser($this->student);
+
+        $result = mod_wiki_external::get_subwiki_pages($indwiki->id, 0, $this->student->id);
+        $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+        $this->assertEquals($expectedpages, $result['pages']);
+
+        // Check that not using userid uses current user.
+        $result = mod_wiki_external::get_subwiki_pages($indwiki->id, 0);
+        $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+        $this->assertEquals($expectedpages, $result['pages']);
+    }
+
+    /**
+     * Test get_subwiki_pages with groups and collaborative wikis.
+     */
+    public function test_get_subwiki_pages_separate_groups_collaborative() {
+
+        // Create testing data.
+        $this->create_collaborative_wikis_with_groups();
+
+        $this->setUser($this->student);
+
+        // Try to get pages from a valid group in separate groups wiki.
+
+        $expectedpage = (array) $this->fpsepg1;
+        $expectedpage['caneditpage'] = true; // User belongs to group and has 'mod/wiki:editpage' capability.
+        $expectedpage['firstpage'] = true;
+        $expectedpage['contentformat'] = 1;
+        $expectedpages = array($expectedpage);
+
+        $result = mod_wiki_external::get_subwiki_pages($this->wikisep->id, $this->group1->id);
+        $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+        $this->assertEquals($expectedpages, $result['pages']);
+
+        // Let's check that not using groupid returns the same result (current group).
+        $result = mod_wiki_external::get_subwiki_pages($this->wikisep->id);
+        $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+        $this->assertEquals($expectedpages, $result['pages']);
+
+        // Check that teacher can view a group pages without belonging to it.
+        $this->setUser($this->teacher);
+        $result = mod_wiki_external::get_subwiki_pages($this->wikisep->id, $this->group1->id);
+        $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+        $this->assertEquals($expectedpages, $result['pages']);
+
+        // Check that teacher can get the pages from all participants.
+        $expectedpage = (array) $this->fpsepall;
+        $expectedpage['caneditpage'] = true;
+        $expectedpage['firstpage'] = true;
+        $expectedpage['contentformat'] = 1;
+        $expectedpages = array($expectedpage);
+
+        $result = mod_wiki_external::get_subwiki_pages($this->wikisep->id, 0);
+        $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+        $this->assertEquals($expectedpages, $result['pages']);
+    }
+
+    /**
+     * Test get_subwiki_pages with groups and collaborative wikis.
+     */
+    public function test_get_subwiki_pages_visible_groups_collaborative() {
+
+        // Create testing data.
+        $this->create_collaborative_wikis_with_groups();
+
+        $this->setUser($this->student);
+
+        // Try to get pages from a valid group in visible groups wiki.
+
+        $expectedpage = (array) $this->fpvisg1;
+        $expectedpage['caneditpage'] = true; // User belongs to group and has 'mod/wiki:editpage' capability.
+        $expectedpage['firstpage'] = true;
+        $expectedpage['contentformat'] = 1;
+        $expectedpages = array($expectedpage);
+
+        $result = mod_wiki_external::get_subwiki_pages($this->wikivis->id, $this->group1->id);
+        $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+        $this->assertEquals($expectedpages, $result['pages']);
+
+        // Check that with visible groups a student can get the pages of groups he doesn't belong to.
+        $expectedpage = (array) $this->fpvisg2;
+        $expectedpage['caneditpage'] = false; // User doesn't belong to group so he can't edit the page.
+        $expectedpage['firstpage'] = true;
+        $expectedpage['contentformat'] = 1;
+        $expectedpages = array($expectedpage);
+
+        $result = mod_wiki_external::get_subwiki_pages($this->wikivis->id, $this->group2->id);
+        $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+        $this->assertEquals($expectedpages, $result['pages']);
+
+        // Check that with visible groups a student can get the pages of all participants.
+        $expectedpage = (array) $this->fpvisall;
+        $expectedpage['caneditpage'] = false;
+        $expectedpage['firstpage'] = true;
+        $expectedpage['contentformat'] = 1;
+        $expectedpages = array($expectedpage);
+
+        $result = mod_wiki_external::get_subwiki_pages($this->wikivis->id, 0);
+        $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+        $this->assertEquals($expectedpages, $result['pages']);
+    }
+
+    /**
+     * Test get_subwiki_pages with groups and individual wikis.
+     */
+    public function test_get_subwiki_pages_separate_groups_individual() {
+
+        // Create testing data.
+        $this->create_individual_wikis_with_groups();
+
+        $this->setUser($this->student);
+
+        // Check that student can retrieve his pages from separate wiki.
+        $expectedpage = (array) $this->fpsepg1indstu;
+        $expectedpage['caneditpage'] = true;
+        $expectedpage['firstpage'] = true;
+        $expectedpage['contentformat'] = 1;
+        $expectedpages = array($expectedpage);
+
+        $result = mod_wiki_external::get_subwiki_pages($this->wikisepind->id, $this->group1->id, $this->student->id);
+        $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+        $this->assertEquals($expectedpages, $result['pages']);
+
+        // Check that not using userid uses current user.
+        $result = mod_wiki_external::get_subwiki_pages($this->wikisepind->id, $this->group1->id);
+        $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+        $this->assertEquals($expectedpages, $result['pages']);
+
+        // Check that the teacher can see the student pages.
+        $this->setUser($this->teacher);
+        $result = mod_wiki_external::get_subwiki_pages($this->wikisepind->id, $this->group1->id, $this->student->id);
+        $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+        $this->assertEquals($expectedpages, $result['pages']);
+
+        // Check that a student can see pages from another user that belongs to his groups.
+        $this->setUser($this->student);
+        $expectedpage = (array) $this->fpsepg1indstu2;
+        $expectedpage['caneditpage'] = false;
+        $expectedpage['firstpage'] = true;
+        $expectedpage['contentformat'] = 1;
+        $expectedpages = array($expectedpage);
+
+        $result = mod_wiki_external::get_subwiki_pages($this->wikisepind->id, $this->group1->id, $this->student2->id);
+        $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+        $this->assertEquals($expectedpages, $result['pages']);
+    }
+
+    /**
+     * Test get_subwiki_pages with groups and individual wikis.
+     */
+    public function test_get_subwiki_pages_visible_groups_individual() {
+
+        // Create testing data.
+        $this->create_individual_wikis_with_groups();
+
+        $this->setUser($this->student);
+
+        // Check that student can retrieve his pages from visible wiki.
+        $expectedpage = (array) $this->fpvisg1indstu;
+        $expectedpage['caneditpage'] = true;
+        $expectedpage['firstpage'] = true;
+        $expectedpage['contentformat'] = 1;
+        $expectedpages = array($expectedpage);
+
+        $result = mod_wiki_external::get_subwiki_pages($this->wikivisind->id, $this->group1->id, $this->student->id);
+        $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+        $this->assertEquals($expectedpages, $result['pages']);
+
+        // Check that student can see teacher pages in visible groups, even if the user doesn't belong to the group.
+        $expectedpage = (array) $this->fpvisg2indt;
+        $expectedpage['caneditpage'] = false;
+        $expectedpage['firstpage'] = true;
+        $expectedpage['contentformat'] = 1;
+        $expectedpages = array($expectedpage);
+
+        $result = mod_wiki_external::get_subwiki_pages($this->wikivisind->id, $this->group2->id, $this->teacher->id);
+        $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+        $this->assertEquals($expectedpages, $result['pages']);
+
+        // Check that with visible groups a student can get the pages of all participants.
+        $expectedpage = (array) $this->fpvisallindt;
+        $expectedpage['caneditpage'] = false;
+        $expectedpage['firstpage'] = true;
+        $expectedpage['contentformat'] = 1;
+        $expectedpages = array($expectedpage);
+
+        $result = mod_wiki_external::get_subwiki_pages($this->wikivisind->id, 0, $this->teacher->id);
+        $result = external_api::clean_returnvalue(mod_wiki_external::get_subwiki_pages_returns(), $result);
+        $this->assertEquals($expectedpages, $result['pages']);
+    }
+
 }
diff --git a/mod/wiki/upgrade.txt b/mod/wiki/upgrade.txt
new file mode 100644 (file)
index 0000000..611e8a6
--- /dev/null
@@ -0,0 +1,5 @@
+This files describes API changes in /mod/wiki/*,
+information provided here is intended especially for developers.
+
+=== 3.1 ===
+ * Added a new param $sort to wiki_get_page_list function. Default value behaves exactly like before (sort by title ASC).
index 9d86bd4..bc40ab8 100644 (file)
@@ -28,8 +28,7 @@
 require_once(__DIR__ . '/../../../../../../lib/behat/behat_base.php');
 require_once(__DIR__ . '/../../../../../../lib/behat/behat_field_manager.php');
 
-use Behat\Behat\Context\Step\Given as Given,
-    Behat\Gherkin\Node\TableNode as TableNode,
+use Behat\Gherkin\Node\TableNode as TableNode,
     Behat\Mink\Exception\ElementTextException as ElementTextException;
 
 /**
index 4ff5d28..3c1c7b4 100644 (file)
@@ -27,7 +27,7 @@
 
 require_once(__DIR__ . '/../../../../lib/behat/behat_base.php');
 
-use Behat\Behat\Context\Step\Given as Given,
+use Moodle\BehatExtension\Context\Step\Given as Given,
     Behat\Gherkin\Node\TableNode as TableNode;
 
 /**
index adfcebc..eeb3a2e 100644 (file)
@@ -232,7 +232,7 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
         $this->check_current_mark(2);
         $this->check_current_output(
                 $this->get_contains_mark_summary(2),
-                $this->get_contains_submit_button_expectation(false),
+                $this->get_does_not_contain_submit_button_expectation(),
                 $this->get_contains_correct_expectation());
     }
 
@@ -310,7 +310,7 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
         $this->check_current_mark(0.8);
         $this->check_current_output(
                 $this->get_contains_mark_summary(0.8),
-                $this->get_contains_submit_button_expectation(false),
+                $this->get_does_not_contain_submit_button_expectation(),
                 $this->get_contains_correct_expectation(),
                 $this->get_does_not_contain_validation_error_expectation());
     }
@@ -393,7 +393,7 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
         $this->check_current_mark(4.00);
         $this->check_current_output(
                 $this->get_contains_mark_summary(4.00),
-                $this->get_contains_submit_button_expectation(false),
+                $this->get_does_not_contain_submit_button_expectation(),
                 $this->get_contains_incorrect_expectation(),
                 $this->get_does_not_contain_validation_error_expectation());
     }
@@ -475,7 +475,7 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
         $this->check_current_mark(0.66666667);
         $this->check_current_output(
                 $this->get_contains_mark_summary(0.67),
-                $this->get_contains_submit_button_expectation(false),
+                $this->get_does_not_contain_submit_button_expectation(),
                 $this->get_contains_incorrect_expectation(),
                 $this->get_does_not_contain_validation_error_expectation());
     }
@@ -532,7 +532,7 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
         $this->check_current_mark(1.0);
         $this->check_current_output(
                 $this->get_contains_mark_summary(1.0),
-                $this->get_contains_submit_button_expectation(false),
+                $this->get_does_not_contain_submit_button_expectation(),
                 $this->get_contains_correct_expectation(),
                 $this->get_does_not_contain_validation_error_expectation());
     }
@@ -646,7 +646,7 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
         $this->check_current_mark(1);
         $this->check_current_output(
                 $this->get_contains_mark_summary(1),
-                $this->get_contains_submit_button_expectation(false),
+                $this->get_does_not_contain_submit_button_expectation(),
                 $this->get_contains_incorrect_expectation(),
                 $this->get_does_not_contain_validation_error_expectation());
     }
@@ -749,7 +749,7 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
         $this->check_current_mark(0.9);
         $this->check_current_output(
                 $this->get_contains_mark_summary(0.9),
-                $this->get_contains_submit_button_expectation(false),
+                $this->get_does_not_contain_submit_button_expectation(),
                 $this->get_contains_incorrect_expectation(),
                 $this->get_does_not_contain_validation_error_expectation(),
                 $this->get_does_not_contain_disregarded_info_expectation());
@@ -848,7 +848,7 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
         $this->check_output_contains_text_input_with_class('sub1_answer', 'correct');
         $this->check_current_output(
                 $this->get_contains_mark_summary(8.00),
-                $this->get_contains_submit_button_expectation(false),
+                $this->get_does_not_contain_submit_button_expectation(),
                 $this->get_contains_correct_expectation(),
                 $this->get_does_not_contain_validation_error_expectation());
     }
index c8de438..42aef51 100644 (file)
@@ -213,7 +213,7 @@ class qbehaviour_adaptivenopenalty_walkthrough_test extends qbehaviour_walkthrou
         $this->check_current_mark(2);
         $this->check_current_output(
                 $this->get_contains_mark_summary(2),
-                $this->get_contains_submit_button_expectation(false),
+                $this->get_does_not_contain_submit_button_expectation(),
                 $this->get_contains_correct_expectation());
     }
 
@@ -305,7 +305,7 @@ class qbehaviour_adaptivenopenalty_walkthrough_test extends qbehaviour_walkthrou
         $this->check_current_mark(1.0);
         $this->check_current_output(
                 $this->get_contains_mark_summary(1.0),
-                $this->get_contains_submit_button_expectation(false),
+                $this->get_does_not_contain_submit_button_expectation(),
                 $this->get_contains_incorrect_expectation(),
                 $this->get_does_not_contain_validation_error_expectation());
     }
index 4bae37a..89c5d9c 100644 (file)
@@ -36,6 +36,9 @@ defined('MOODLE_INTERNAL') || die();
  */
 class qbehaviour_interactive_renderer extends qbehaviour_renderer {
     public function controls(question_attempt $qa, question_display_options $options) {
+        if ($options->readonly === qbehaviour_interactive::READONLY_EXCEPT_TRY_AGAIN) {
+            return '';
+        }
         return $this->submit_button($qa, $options);
     }
 
index 1e2d839..8485e6a 100644 (file)
@@ -95,7 +95,7 @@ class qbehaviour_interactive_walkthrough_test extends qbehaviour_walkthrough_tes
                 $this->get_contains_mc_radio_expectation($wrongindex, false, true),
                 $this->get_contains_mc_radio_expectation(($wrongindex + 1) % 3, false, false),
                 $this->get_contains_mc_radio_expectation(($wrongindex + 1) % 3, false, false),
-                $this->get_contains_submit_button_expectation(false),
+                $this->get_does_not_contain_submit_button_expectation(),
                 $this->get_contains_try_again_button_expectation(true),
                 $this->get_does_not_contain_correctness_expectation(),
                 new question_pattern_expectation('/Tries remaining: 2/'),
@@ -135,7 +135,7 @@ class qbehaviour_interactive_walkthrough_test extends qbehaviour_walkthrough_tes
                 $this->get_contains_mc_radio_expectation($rightindex, false, true),
                 $this->get_contains_mc_radio_expectation(($rightindex + 1) % 3, false, false),
                 $this->get_contains_mc_radio_expectation(($rightindex + 1) % 3, false, false),
-                $this->get_contains_submit_button_expectation(false),
+                $this->get_does_not_contain_submit_button_expectation(),
                 $this->get_contains_correct_expectation(),
                 $this->get_no_hint_visible_expectation());
 
@@ -219,7 +219,7 @@ class qbehaviour_interactive_walkthrough_test extends qbehaviour_walkthrough_tes
                 $this->get_contains_mc_radio_expectation($wrongindex, false, true),
                 $this->get_contains_mc_radio_expectation(($wrongindex + 1) % 3, false, false),
                 $this->get_contains_mc_radio_expectation(($wrongindex + 1) % 3, false, false),
-                $this->get_contains_submit_button_expectation(false),
+                $this->get_does_not_contain_submit_button_expectation(),
                 $this->get_contains_try_again_button_expectation(true),
                 $this->get_does_not_contain_correctness_expectation(),
                 new question_pattern_expectation('/Tries remaining: 1/'),
@@ -283,7 +283,7 @@ class qbehaviour_interactive_walkthrough_test extends qbehaviour_walkthrough_tes
         $this->check_current_mark(null);
         $this->check_current_output(
                 $this->get_contains_marked_out_of_summary(),
-                $this->get_contains_submit_button_expectation(false),
+                $this->get_does_not_contain_submit_button_expectation(),
                 $this->get_does_not_contain_validation_error_expectation(),
                 $this->get_contains_try_again_button_expectation(true),
                 new question_pattern_expectation('/Tries remaining: 2/'),
@@ -327,7 +327,7 @@ class qbehaviour_interactive_walkthrough_test extends qbehaviour_walkthrough_tes
         $this->check_current_mark(0.6666667);
         $this->check_current_output(
                 $this->get_contains_mark_summary(0.6666667),
-                $this->get_contains_submit_button_expectation(false),
+                $this->get_does_not_contain_submit_button_expectation(),
                 $this->get_contains_correct_expectation(),
                 $this->get_does_not_contain_validation_error_expectation(),
                 $this->get_no_hint_visible_expectation());
@@ -380,7 +380,7 @@ class qbehaviour_interactive_walkthrough_test extends qbehaviour_walkthrough_tes
                 $this->get_contains_mc_checkbox_expectation($right[1], false, false),
                 $this->get_contains_mc_checkbox_expectation($wrong[0], false, true),
                 $this->get_contains_mc_checkbox_expectation($wrong[1], false, false),
-                $this->get_contains_submit_button_expectation(false),
+                $this->get_does_not_contain_submit_button_expectation(),
                 $this->get_contains_try_again_button_expectation(true),
                 $this->get_does_not_contain_correctness_expectation(),
                 new question_pattern_expectation('/Tries remaining: 2/'),
index 8c87b3d..0523aa7 100644 (file)
@@ -87,7 +87,7 @@ class qbehaviour_interactivecountback_walkthrough_test extends qbehaviour_walkth
                 $this->get_contains_select_expectation('sub1', $choices, $orderforchoice[1], false),
                 $this->get_contains_select_expectation('sub2', $choices, $orderforchoice[1], false),
                 $this->get_contains_select_expectation('sub3', $choices, $orderforchoice[1], false),
-                $this->get_contains_submit_button_expectation(false),
+                $this->get_does_not_contain_submit_button_expectation(),
                 $this->get_contains_try_again_button_expectation(true),
                 $this->get_does_not_contain_correctness_expectation(),
                 new question_pattern_expectation('/Tries remaining: 2/'),
@@ -139,7 +139,7 @@ class qbehaviour_interactivecountback_walkthrough_test extends qbehaviour_walkth
                 $this->get_contains_select_expectation('sub1', $choices, $orderforchoice[2], false),
                 $this->get_contains_select_expectation('sub2', $choices, $orderforchoice[2], false),
                 $this->get_contains_select_expectation('sub3', $choices, $orderforchoice[1], false),
-                $this->get_contains_submit_button_expectation(false),
+                $this->get_does_not_contain_submit_button_expectation(),
                 $this->get_does_not_contain_try_again_button_expectation(),
                 $this->get_contains_correct_expectation(),
                 $this->get_contains_standard_correct_combined_feedback_expectation(),
index 41f58b2..cdcb2d2 100644 (file)
@@ -208,6 +208,9 @@ abstract class qbehaviour_renderer extends plugin_renderer_base {
      * @return string HTML fragment.
      */
     protected function submit_button(question_attempt $qa, question_display_options $options) {
+        if (!$qa->get_state()->is_active()) {
+            return '';
+        }
         $attributes = array(
             'type' => 'submit',
             'id' => $qa->get_behaviour_field_name('submit'),
index 5c530cd..235e953 100644 (file)
@@ -1,5 +1,15 @@
 This files describes API changes for question behaviour plugins.
 
+=== 3.1 ===
+
+1) The standard behaviours that use a 'Check' button have all been changed so
+   that they only show the button when the question is active. Your behaviour
+   may interit this behaviour, because the change was made in the base class,
+   and this is probably good for consistency. However, if your question behaviour
+   uses the Check button, your probably want to test it carefully, and you will
+   probably have to update your unit tests. See MDL-53304 for more details.
+
+
 === 2.9 ===
 
 1) There are new methods question_behaviour::can_finish_during_attempt and
index c472e1e..8a89633 100644 (file)
@@ -1103,11 +1103,27 @@ abstract class qbehaviour_walkthrough_test_base extends question_testcase {
         return new question_contains_tag_with_attributes('input', $expectedattributes, $forbiddenattributes);
     }
 
+    /**
+     * Returns an epectation that a string contains the HTML of a button with
+     * name {question-attempt prefix}-submit, and eiter enabled or not.
+     * @param bool $enabled if not null, check the enabled/disabled state of the button. True = enabled.
+     * @return question_contains_tag_with_attributes an expectation for use with check_current_output.
+     */
     protected function get_contains_submit_button_expectation($enabled = null) {
         return $this->get_contains_button_expectation(
             $this->quba->get_field_prefix($this->slot) . '-submit', null, $enabled);
     }
 
+    /**
+     * Returns an epectation that a string does not contain the HTML of a button with
+     * name {question-attempt prefix}-submit.
+     * @return question_contains_tag_with_attributes an expectation for use with check_current_output.
+     */
+    protected function get_does_not_contain_submit_button_expectation() {
+        return new question_no_pattern_expectation('/name="' .
+                $this->quba->get_field_prefix($this->slot) . '-submit"/');
+    }
+
     protected function get_tries_remaining_expectation($n) {
         return new question_pattern_expectation('/' .
             preg_quote(get_string('triesremaining', 'qbehaviour_interactive', $n), '/') . '/');
index 069d982..2c5e60c 100644 (file)
@@ -27,7 +27,7 @@
 
 require_once(__DIR__ . '/behat_question_base.php');
 
-use Behat\Behat\Context\Step\Given as Given,
+use Moodle\BehatExtension\Context\Step\Given as Given,
     Behat\Gherkin\Node\TableNode as TableNode,
     Behat\Mink\Exception\ExpectationException as ExpectationException,
     Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
index 9c7fdb4..b45b6ae 100644 (file)
@@ -27,7 +27,7 @@
 
 require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
 
-use Behat\Behat\Context\Step\Given as Given,
+use Moodle\BehatExtension\Context\Step\Given as Given,
     Behat\Gherkin\Node\TableNode as TableNode,
     Behat\Mink\Exception\ExpectationException as ExpectationException,
     Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
index 11b7ebc..89013c5 100644 (file)
@@ -98,7 +98,7 @@ class qtype_calculated_walkthrough_test extends qbehaviour_walkthrough_test_base
         $this->check_current_mark(3);
         $this->check_current_output(
                 $this->get_contains_mark_summary(3),
-                $this->get_contains_submit_button_expectation(false),
+                $this->get_does_not_contain_submit_button_expectation(),
                 $this->get_contains_correct_expectation(),
                 $this->get_does_not_contain_validation_error_expectation(),
                 $this->get_no_hint_visible_expectation());
index de31a58..00fddca 100644 (file)
@@ -98,7 +98,7 @@ class qtype_calculatedmulti_walkthrough_test extends qbehaviour_walkthrough_test
         $this->check_current_mark(3);
         $this->check_current_output(
                 $this->get_contains_mark_summary(3),
-                $this->get_contains_submit_button_expectation(false),
+                $this->get_does_not_contain_submit_button_expectation(),
                 $this->get_contains_correct_expectation(),
                 $this->get_does_not_contain_validation_error_expectation(),
                 $this->get_no_hint_visible_expectation());
index 63e639f..46af97e 100644 (file)
@@ -97,7 +97,7 @@ class qtype_calculatedsimple_walkthrough_test extends qbehaviour_walkthrough_tes
         $this->check_current_mark(3);
         $this->check_current_output(
                 $this->get_contains_mark_summary(3),
-                $this->get_contains_submit_button_expectation(false),
+                $this->get_does_not_contain_submit_button_expectation(),
                 $this->get_contains_correct_expectation(),
                 $this->get_does_not_contain_validation_error_expectation(),
                 $this->get_no_hint_visible_expectation());
index e7966d5..2e02529 100644 (file)
@@ -29,7 +29,8 @@ Feature: Preview a drag-drop onto image question
     When I click on "Preview" "link" in the "Drag onto image" "table_row"
     And I switch to "questionpreview" window
     # Increase window size and wait 2 seconds to ensure elements are placed properly by js.
-    And I change window size to "medium"
+    # Keep window large else drag will scroll the window to find element.
+    And I change window size to "large"
     And I wait "2" seconds
     # Odd, but the <br>s go to nothing, not a space.
     And I drag "mountainbelt" to place "1" in the drag and drop onto image question
@@ -50,7 +51,7 @@ Feature: Preview a drag-drop onto image question
     When I click on "Preview" "link" in the "Drag onto image" "table_row"
     And I switch to "questionpreview" window
     # Increase window size and wait 2 seconds to ensure elements are placed properly by js.
-    And I change window size to "medium"
+    And I change window size to "large"
     And I wait "2" seconds
     And I type "       " on place "1" in the drag and drop onto image question
     And I type "       " on place "2" in the drag and drop onto image question
index 71e3eef..932ac1b 100644 (file)
@@ -180,7 +180,7 @@ class qtype_ddimageortext_walkthrough_test extends qbehaviour_walkthrough_test_b
                 $this->quba->get_field_prefix($this->slot) . 'p3', '1'),
             $this->get_contains_hidden_expectation(
                 $this->quba->get_field_prefix($this->slot) . 'p4', '2'),
-            $this->get_contains_submit_button_expectation(false),
+            $this->get_does_not_contain_submit_button_expectation(),
             $this->get_contains_correct_expectation(),
             $this->get_no_hint_visible_expectation());
 
@@ -469,7 +469,7 @@ class qtype_ddimageortext_walkthrough_test extends qbehaviour_walkthrough_test_b
             $this->get_contains_drag_image_home_expectation(2, 2, 1),
             $this->get_contains_drag_image_home_expectation(3, 1, 2),
             $this->get_contains_drag_image_home_expectation(4, 2, 2),
-            $this->get_contains_submit_button_expectation(false),
+            $this->get_does_not_contain_submit_button_expectation(),
             $this->get_contains_try_again_button_expectation(true),
             $this->get_does_not_contain_correctness_expectation(),
             $this->get_contains_hint_expectation('This is the first hint'),
@@ -530,7 +530,7 @@ class qtype_ddimageortext_walkthrough_test extends qbehaviour_walkthrough_test_b
             $this->get_contains_drag_image_home_expectation(2, 2, 1),
             $this->get_contains_drag_image_home_expectation(3, 1, 2),
             $this->get_contains_drag_image_home_expectation(4, 2, 2),
-            $this->get_contains_submit_button_expectation(false),
+            $this->get_does_not_contain_submit_button_expectation(),
             $this->get_contains_try_again_button_expectation(true),
             $this->get_does_not_contain_correctness_expectation(),
             $this->get_contains_hint_expectation('This is the second hint'),
@@ -592,7 +592,7 @@ class qtype_ddimageortext_walkthrough_test extends qbehaviour_walkthrough_test_b
                 $this->quba->get_field_prefix($this->slot) . 'p3', 1),
             $this->get_contains_hidden_expectation(
                 $this->quba->get_field_prefix($this->slot) . 'p4', 2),
-            $this->get_contains_submit_button_expectation(false),
+            $this->get_does_not_contain_submit_button_expectation(),
             $this->get_does_not_contain_try_again_button_expectation(),
             $this->get_contains_correct_expectation(),
             $this->get_no_hint_visible_expectation(),
@@ -648,7 +648,7 @@ class qtype_ddimageortext_walkthrough_test extends qbehaviour_walkthrough_test_b
             $this->get_contains_drag_image_home_expectation(2, 2, 1),
             $this->get_contains_drag_image_home_expectation(3, 1, 2),
             $this->get_contains_drag_image_home_expectation(4, 2, 2),
-            $this->get_contains_submit_button_expectation(false),
+            $this->get_does_not_contain_submit_button_expectation(),
             $this->get_contains_correct_expectation(),
             $this->get_no_hint_visible_expectation());
 
@@ -710,7 +710,7 @@ class qtype_ddimageortext_walkthrough_test extends qbehaviour_walkthrough_test_b
             $this->get_contains_drag_image_home_expectation(2, 2, 1),
             $this->get_contains_drag_image_home_expectation(3, 1, 2),
             $this->get_contains_drag_image_home_expectation(4, 2, 2),
-            $this->get_contains_submit_button_expectation(false),
+            $this->get_does_not_contain_submit_button_expectation(),
             $this->get_contains_partcorrect_expectation(),
             $this->get_no_hint_visible_expectation());
 
@@ -769,7 +769,7 @@ class qtype_ddimageortext_walkthrough_test extends qbehaviour_walkthrough_test_b
             $this->get_contains_drag_image_home_expectation(2, 2, 1),
             $this->get_contains_drag_image_home_expectation(3, 1, 2),
             $this->get_contains_drag_image_home_expectation(4, 2, 2),
-            $this->get_contains_submit_button_expectation(false),
+            $this->get_does_not_contain_submit_button_expectation(),
             $this->get_contains_hint_expectation('This is the first hint'));
 
         // Do try again.
index b2be3cb..364f48b 100644 (file)
@@ -72,7 +72,7 @@ class behat_qtype_ddmarker extends behat_base {
         // Therefore to make it drag to the specified place, we have to add
         // a target div.
         $session = $this->getSession();
-        $session->evaluateScript("
+        $session->executeScript("
                 (function() {
                     if (document.getElementById('target-{$x}-{$y}')) {
                         return;
index 44bf892..dc78535 100644 (file)
@@ -29,7 +29,8 @@ Feature: Preview a drag-drop onto image question
     When I click on "Preview" "link" in the "Drag markers" "table_row"
     And I switch to "questionpreview" window
     # Increase window size and wait 2 seconds to ensure elements are placed properly by js.
-    And I change window size to "medium"
+    # Keep window large else drag will scroll the window to find element.
+    And I change window size to "large"
     And I wait "2" seconds
     # Odd, but the <br>s go to nothing, not a space.
     And I drag "OU" to "342,230" in the drag and drop markers question
@@ -46,7 +47,8 @@ Feature: Preview a drag-drop onto image question
     When I click on "Preview" "link" in the "Drag markers" "table_row"
     And I switch to "questionpreview" window
     # Increase window size and wait 2 seconds to ensure elements are placed properly by js.
-    And I change window size to "medium"
+    # Keep window large else drag will scroll the window to find element.
+    And I change window size to "large"
     And I wait "2" seconds
     And I type "up" "89" times on marker "Railway station" in the drag and drop markers question
     And I type "right" "21" times on marker "Railway station" in the drag and drop markers question
index ae2a734..afaf4a6 100644 (file)
@@ -168,7 +168,7 @@ class qtype_ddmarker_walkthrough_test extends qbehaviour_walkthrough_test_base {
                 $this->get_contains_hidden_expectation(1, '50,50'),
                 $this->get_contains_hidden_expectation(2, '150,50'),
                 $this->get_contains_hidden_expectation(3, '100,150'),
-                $this->get_contains_submit_button_expectation(false),
+                $this->get_does_not_contain_submit_button_expectation(),
                 $this->get_contains_correct_expectation(),
                 $this->get_no_hint_visible_expectation());
 
@@ -399,7 +399,7 @@ class qtype_ddmarker_walkthrough_test extends qbehaviour_walkthrough_test_base {
                 $this->get_contains_draggable_marker_home_expectation(1, false),
                 $this->get_contains_draggable_marker_home_expectation(2, false),
                 $this->get_contains_draggable_marker_home_expectation(3, false),
-                $this->get_contains_submit_button_expectation(false),
+                $this->get_does_not_contain_submit_button_expectation(),
                 $this->get_contains_try_again_button_expectation(true),
                 $this->get_does_not_contain_correctness_expectation(),
                 $this->get_contains_hint_expectation('This is the first hint'),
@@ -448,7 +448,7 @@ class qtype_ddmarker_walkthrough_test extends qbehaviour_walkthrough_test_base {
                 $this->get_contains_draggable_marker_home_expectation(1, false),
                 $this->get_contains_draggable_marker_home_expectation(2, false),
                 $this->get_contains_draggable_marker_home_expectation(3, false),
-                $this->get_contains_submit_button_expectation(false),
+                $this->get_does_not_contain_submit_button_expectation(),
                 $this->get_contains_try_again_button_expectation(true),
                 $this->get_does_not_contain_correctness_expectation(),
                 $this->get_contains_hint_expectation('This is the second hint'),
@@ -493,7 +493,7 @@ class qtype_ddmarker_walkthrough_test extends qbehaviour_walkthrough_test_base {
                 $this->get_contains_hidden_expectation(1, '50,50'),
                 $this->get_contains_hidden_expectation(2, '150,50'),
                 $this->get_contains_hidden_expectation(3, '100,150'),
-                $this->get_contains_submit_button_expectation(false),
+                $this->get_does_not_contain_submit_button_expectation(),
                 $this->get_does_not_contain_try_again_button_expectation(),
                 $this->get_contains_correct_expectation(),
                 $this->get_no_hint_visible_expectation(),
@@ -542,7 +542,7 @@ class qtype_ddmarker_walkthrough_test extends qbehaviour_walkthrough_test_base {
                 $this->get_contains_draggable_marker_home_expectation(1, false),
                 $this->get_contains_draggable_marker_home_expectation(2, false),
                 $this->get_contains_draggable_marker_home_expectation(3, false),
-                $this->get_contains_submit_button_expectation(false),
+                $this->get_does_not_contain_submit_button_expectation(),
                 $this->get_contains_correct_expectation(),
                 $this->get_no_hint_visible_expectation());
 
@@ -597,7 +597,7 @@ class qtype_ddmarker_walkthrough_test extends qbehaviour_walkthrough_test_base {
                 $this->get_contains_draggable_marker_home_expectation(1, false),
                 $this->get_contains_draggable_marker_home_expectation(2, false),
                 $this->get_contains_draggable_marker_home_expectation(3, false),
-                $this->get_contains_submit_button_expectation(false),
+                $this->get_does_not_contain_submit_button_expectation(),
                 $this->get_contains_partcorrect_expectation(),
                 $this->get_no_hint_visible_expectation());
 
@@ -651,7 +651,7 @@ class qtype_ddmarker_walkthrough_test extends qbehaviour_walkthrough_test_base {
                 $this->get_contains_draggable_marker_home_expectation(1, false),
                 $this->get_contains_draggable_marker_home_expectation(2, false),
                 $this->get_contains_draggable_marker_home_expectation(3, false),
-                $this->get_contains_submit_button_expectation(false),
+                $this->get_