MDL-63977 Behat: Prefer ionic approot over wwwroot
[moodle.git] / lib / tests / behat / behat_app.php
index f5d723d..b2251c3 100644 (file)
@@ -29,7 +29,6 @@ require_once(__DIR__ . '/../../behat/behat_base.php');
 
 use Behat\Mink\Exception\DriverException;
 use Behat\Mink\Exception\ExpectationException;
-use Behat\Behat\Hook\Scope\BeforeScenarioScope;
 
 /**
  * Mobile/desktop app steps definitions.
@@ -40,12 +39,12 @@ use Behat\Behat\Hook\Scope\BeforeScenarioScope;
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class behat_app extends behat_base {
-    /** @var bool True if the current scenario has the app tag */
-    protected $apptag = false;
-
     /** @var stdClass Object with data about launched Ionic instance (if any) */
     protected static $ionicrunning = null;
 
+    /** @var string URL for running Ionic server */
+    protected $ionicurl = '';
+
     /**
      * Checks if the current OS is Windows, from the point of view of task-executing-and-killing.
      *
@@ -56,15 +55,15 @@ class behat_app extends behat_base {
     }
 
     /**
-     * Checks tags before each scenario.
+     * Called from behat_hooks when a new scenario starts, if it has the app tag.
      *
-     * @BeforeScenario
-     * @param BeforeScenarioScope $scope Scope information
+     * This updates Moodle configuration and starts Ionic running, if it isn't already.
      */
-    public function check_tags(BeforeScenarioScope $scope) {
-        $this->apptag = in_array('app', $scope->getScenario()->getTags()) ||
-                in_array('app', $scope->getFeature()->getTags());
-    }
+    public function start_scenario() {
+        $this->check_behat_setup();
+        $this->fix_moodle_setup();
+        $this->ionicurl = $this->start_or_reuse_ionic();
+}
 
     /**
      * Opens the Moodle app in the browser.
@@ -77,19 +76,17 @@ class behat_app extends behat_base {
      * @throws ExpectationException Problem with resizing window
      */
     public function i_enter_the_app() {
+        // Check the app tag was set.
+        if (!$this->has_tag('app')) {
+            throw new DriverException('Requires @app tag on scenario or feature.');
+        }
+
         // Restart the browser and set its size.
         $this->getSession()->restart();
         $this->resize_window('360x720', true);
 
-        // Prepare setup.
-        $this->check_behat_setup();
-        $this->fix_moodle_setup();
-
-        // Start Ionic server (or use existing one).
-        $url = $this->start_or_reuse_ionic();
-
         // Go to page and prepare browser for app.
-        $this->prepare_browser($url);
+        $this->prepare_browser($this->ionicurl);
     }
 
     /**
@@ -100,19 +97,14 @@ class behat_app extends behat_base {
     protected function check_behat_setup() {
         global $CFG;
 
-        // Check the app tag was set.
-        if (!$this->apptag) {
-            throw new DriverException('Requires @app tag on scenario or feature.');
-        }
-
         // Check JavaScript is enabled.
         if (!$this->running_javascript()) {
             throw new DriverException('The app requires JavaScript.');
         }
 
         // Check the config settings are defined.
-        if (empty($CFG->behat_ionicaddress) && empty($CFG->behat_approot)) {
-            throw new DriverException('$CFG->behat_ionicaddress or $CFG->behat_approot must be defined.');
+        if (empty($CFG->behat_ionic_wwwroot) && empty($CFG->behat_ionic_dirroot)) {
+            throw new DriverException('$CFG->behat_ionic_wwwroot or $CFG->behat_ionic_dirroot must be defined.');
         }
     }
 
@@ -184,15 +176,15 @@ class behat_app extends behat_base {
     protected function start_or_reuse_ionic() {
         global $CFG;
 
-        if (!empty($CFG->behat_ionicaddress)) {
+        if (empty($CFG->behat_ionic_dirroot) && !empty($CFG->behat_ionic_wwwroot)) {
             // Use supplied Ionic server which should already be running.
-            $url = $CFG->behat_ionicaddress;
+            $url = $CFG->behat_ionic_wwwroot;
         } else if (self::$ionicrunning) {
             // Use existing Ionic instance launched previously.
             $url = self::$ionicrunning->url;
         } else {
             // Open Ionic process in relevant path.
-            $path = realpath($CFG->behat_approot);
+            $path = realpath($CFG->behat_ionic_dirroot);
             $stderrfile = $CFG->dataroot . '/behat/ionic-stderr.log';
             $prefix = '';
             // Except on Windows, use 'exec' so that we get the pid of the actual Node process
@@ -329,20 +321,18 @@ class behat_app extends behat_base {
     }
 
     /**
-     * Logs in as the given user in the app's login screen.
-     *
-     * Must be run from the app login screen (i.e. immediately after first 'I enter the app').
+     * Carries out the login steps for the app, assuming the user is on the app login page. Called
+     * from behat_auth.php.
      *
-     * @Given /^I log in as "(?P<username_string>(?:[^"]|\\")*)" in the app$/
      * @param string $username Username (and password)
-     * @throws DriverException If the main page doesn't load
+     * @throws Exception Any error
      */
-    public function i_log_in_as_username_in_the_app(string $username) {
+    public function login(string $username) {
         $this->i_set_the_field_in_the_app('Username', $username);
         $this->i_set_the_field_in_the_app('Password', $username);
 
         // Note there are two 'Log in' texts visible (the title and the button) so we have to use
-        // the 'near' syntax here.
+        // a 'near' value here.
         $this->i_press_near_in_the_app('Log in', 'Forgotten');
 
         // Wait until the main page appears.
@@ -368,7 +358,7 @@ class behat_app extends behat_base {
      */
     public function i_press_the_standard_button_in_the_app(string $button) {
         $this->spin(function($context, $args) use ($button) {
-            $result = $this->getSession()->evaluateScript('return window.behatPressStandard("' .
+            $result = $this->getSession()->evaluateScript('return window.behat.pressStandard("' .
                     $button . '");');
             if ($result !== 'OK') {
                 throw new DriverException('Error pressing standard button - ' . $result);
@@ -386,7 +376,7 @@ class behat_app extends behat_base {
      */
     public function i_close_the_popup_in_the_app() {
         $this->spin(function($context, $args)  {
-            $result = $this->getSession()->evaluateScript('return window.behatClosePopup();');
+            $result = $this->getSession()->evaluateScript('return window.behat.closePopup();');
             if ($result !== 'OK') {
                 throw new DriverException('Error closing popup - ' . $result);
             }
@@ -406,15 +396,7 @@ class behat_app extends behat_base {
      * @throws DriverException If the press doesn't work
      */
     public function i_press_in_the_app(string $text) {
-        $this->spin(function($context, $args) use ($text) {
-            $result = $this->getSession()->evaluateScript('return window.behatPress("' .
-                    addslashes_js($text) . '");');
-            if ($result !== 'OK') {
-                throw new DriverException('Error pressing item - ' . $result);
-            }
-            return true;
-        });
-        $this->wait_for_pending_js();
+        $this->press($text);
     }
 
     /**
@@ -430,9 +412,30 @@ class behat_app extends behat_base {
      * @throws DriverException If the press doesn't work
      */
     public function i_press_near_in_the_app(string $text, string $near) {
+        $this->press($text, $near);
+    }
+
+    /**
+     * Clicks on / touches something that is visible in the app, near some other text.
+     *
+     * If the $near is specified then when there are multiple matches, it picks the one
+     * nearest (in DOM terms) $near. $near should be an exact match, or a partial match that only
+     * has one result.
+     *
+     * @param behat_base $base Behat context
+     * @param string $text Text identifying click target
+     * @param string $near Text identifying a nearby unique piece of text
+     * @throws DriverException If the press doesn't work
+     */
+    protected function press(string $text, string $near = '') {
         $this->spin(function($context, $args) use ($text, $near) {
-            $result = $this->getSession()->evaluateScript('return window.behatPress("' .
-                    addslashes_js($text) . '", "' . addslashes_js($near) . '");');
+            if ($near !== '') {
+                $nearbit = ', "' . addslashes_js($near) . '"';
+            } else {
+                $nearbit = '';
+            }
+            $result = $context->getSession()->evaluateScript('return window.behat.press("' .
+                    addslashes_js($text) . '"' . $nearbit .');');
             if ($result !== 'OK') {
                 throw new DriverException('Error pressing item - ' . $result);
             }
@@ -454,7 +457,7 @@ class behat_app extends behat_base {
      */
     public function i_set_the_field_in_the_app(string $field, string $value) {
         $this->spin(function($context, $args) use ($field, $value) {
-            $result = $this->getSession()->evaluateScript('return window.behatSetField("' .
+            $result = $this->getSession()->evaluateScript('return window.behat.setField("' .
                     addslashes_js($field) . '", "' . addslashes_js($value) . '");');
             if ($result !== 'OK') {
                 throw new DriverException('Error setting field - ' . $result);
@@ -476,7 +479,7 @@ class behat_app extends behat_base {
      */
     public function the_header_should_be_in_the_app(string $text) {
         $result = $this->spin(function($context, $args) {
-            $result = $this->getSession()->evaluateScript('return window.behatGetHeader();');
+            $result = $this->getSession()->evaluateScript('return window.behat.getHeader();');
             if (substr($result, 0, 3) !== 'OK:') {
                 throw new DriverException('Error getting header - ' . $result);
             }