diff --git a/mod/choice/tests/behat/app_basic_usage.feature b/mod/choice/tests/behat/app_basic_usage.feature
index 22ea6aa69..a25265feb 100755
--- a/mod/choice/tests/behat/app_basic_usage.feature
+++ b/mod/choice/tests/behat/app_basic_usage.feature
@@ -1,4 +1,4 @@
-@mod @mod_choice @app @app_upto3.9.4 @javascript
+@mod @mod_choice @app @javascript
 Feature: Test basic usage of choice activity in app
   In order to participate in the choice while using the mobile app
   As a student
@@ -17,7 +17,6 @@ Feature: Test basic usage of choice activity in app
       | teacher1 | C1 | editingteacher |
       | student1 | C1 | student |
 
-  @app @3.8.0
   Scenario: Answer a choice (multi or single, update answer) & View results
     Given the following "activities" exist:
       | activity | name                    | intro                          | course | idnumber | option                       | allowmultiple | allowupdate | showresults |
@@ -26,24 +25,23 @@ Feature: Test basic usage of choice activity in app
     And I log in as "student1"
     And I press "Course 1" near "Course overview" in the app
     And I press "Test single choice name" in the app
-    And I press "Option 1" in the app
-    And I press "Option 2" in the app
+    And I select "Option 1" in the app
+    And I select "Option 2" in the app
     And I press "Save my choice" in the app
-    Then I should see "Are you sure"
+    Then I should find "Are you sure" in the app
 
     When I press "OK" in the app
-    Then I should see "Option 1: 0"
-    And I should see "Option 2: 1"
-    And I should see "Option 3: 0"
-    But I should not see "Remove my choice"
+    Then I should find "Option 1: 0" in the app
+    And I should find "Option 2: 1" in the app
+    And I should find "Option 3: 0" in the app
+    But I should not find "Remove my choice" in the app
 
     When I press the back button in the app
     And I press "Test single choice name" in the app
-    Then I should see "Option 1: 0"
-    And I should see "Option 2: 1"
-    And I should see "Option 3: 0"
+    Then I should find "Option 1: 0" in the app
+    And I should find "Option 2: 1" in the app
+    And I should find "Option 3: 0" in the app
 
-  @app @3.8.0
   Scenario: Answer a choice (multi or single, update answer) & View results & Delete choice
     Given the following "activities" exist:
       | activity | name                    | intro                          | course | idnumber | option                       | allowmultiple | allowupdate | showresults |
@@ -52,29 +50,28 @@ Feature: Test basic usage of choice activity in app
     And I log in as "student1"
     And I press "Course 1" near "Course overview" in the app
     And I press "Test multi choice name" in the app
-    And I press "Option 1" in the app
-    And I press "Option 2" in the app
+    And I select "Option 1" in the app
+    And I select "Option 2" in the app
     And I press "Save my choice" in the app
-    Then I should see "Option 1: 1"
-    And I should see "Option 2: 1"
-    And I should see "Option 3: 0"
-    And I should see "Remove my choice"
+    Then I should find "Option 1: 1" in the app
+    And I should find "Option 2: 1" in the app
+    And I should find "Option 3: 0" in the app
+    And I should find "Remove my choice" in the app
 
-    When I press "Option 1" in the app
-    And I press "Option 3" in the app
+    When I select "Option 1" in the app
+    And I select "Option 3" in the app
     And I press "Save my choice" in the app
-    Then I should see "Option 1: 0"
-    And I should see "Option 2: 1"
-    And I should see "Option 3: 1"
+    Then I should find "Option 1: 0" in the app
+    And I should find "Option 2: 1" in the app
+    And I should find "Option 3: 1" in the app
 
     When I press "Remove my choice" in the app
-    Then I should see "Are you sure"
+    Then I should find "Are you sure" in the app
 
     When I press "Delete" in the app
-    Then I should see "The results are not currently viewable"
-    But I should not see "Remove my choice"
+    Then I should find "The results are not currently viewable" in the app
+    But I should not find "Remove my choice" in the app
 
-  @app @3.8.0
   Scenario: Answer and change answer offline & Sync choice
     Given the following "activities" exist:
       | activity | name                    | intro                          | course | idnumber | option                       | allowmultiple | allowupdate | showresults |
@@ -83,31 +80,32 @@ Feature: Test basic usage of choice activity in app
     And I log in as "student1"
     And I press "Course 1" near "Course overview" in the app
     And I press "Test single choice name" in the app
-    And I press "Option 1" in the app
+    And I select "Option 1" in the app
     And I switch offline mode to "true"
-    And I press "Option 2" in the app
+    And I select "Option 2" in the app
     And I press "Save my choice" in the app
-    Then I should see "Are you sure"
+    Then I should find "Are you sure" in the app
 
     When I press "OK" in the app
     And I press the back button in the app
     And I press "Test single choice name" in the app
-    Then I should see "This Choice has offline data to be synchronised."
-    But I should not see "Option 1: 0"
-    And I should not see "Option 2: 1"
-    And I should not see "Option 3: 0"
+    Then I should find "This Choice has offline data to be synchronised." in the app
+    But I should not find "Option 1: 0" in the app
+    And I should not find "Option 2: 1" in the app
+    And I should not find "Option 3: 0" in the app
 
     When I switch offline mode to "false"
     And I press the back button in the app
     And I press "Test single choice name" in the app
-    And I press "Display options" in the app
-    And I press "Refresh" in the app
-    Then I should see "Option 1: 0"
-    And I should see "Option 2: 1"
-    And I should see "Option 3: 0"
-    But I should not see "This Choice has offline data to be synchronised."
+    Then I should find "Test single choice description" in the app
+
+    When I press "Display options" in the app
+    And I press "Refresh" in the app
+    Then I should find "Option 1: 0" in the app
+    And I should find "Option 2: 1" in the app
+    And I should find "Option 3: 0" in the app
+    But I should not find "This Choice has offline data to be synchronised." in the app
 
-  @app @3.8.0
   Scenario: Answer and change answer offline & Auto-sync choice
     Given the following "activities" exist:
       | activity | name                    | intro                          | course | idnumber | option                       | allowmultiple | allowupdate | showresults |
@@ -120,23 +118,22 @@ Feature: Test basic usage of choice activity in app
     And I switch offline mode to "true"
     And I press "Option 2" in the app
     And I press "Save my choice" in the app
-    Then I should see "Are you sure"
+    Then I should find "Are you sure" in the app
 
     When I press "OK" in the app
-    And I switch offline mode to "false"
-    Then I should see "This Choice has offline data to be synchronised."
-    But I should not see "Option 1: 0"
-    And I should not see "Option 2: 1"
-    And I should not see "Option 3: 0"
+    Then I should find "This Choice has offline data to be synchronised." in the app
+    But I should not find "Option 1: 0" in the app
+    And I should not find "Option 2: 1" in the app
+    And I should not find "Option 3: 0" in the app
 
-    When I run cron tasks in the app
+    When I switch offline mode to "false"
+    And I run cron tasks in the app
     And I wait loading to finish in the app
-    Then I should see "Option 1: 0"
-    And I should see "Option 2: 1"
-    And I should see "Option 3: 0"
-    But I should not see "This Choice has offline data to be synchronised."
+    Then I should find "Option 1: 0" in the app
+    And I should find "Option 2: 1" in the app
+    And I should find "Option 3: 0" in the app
+    But I should not find "This Choice has offline data to be synchronised." in the app
 
-  @app @3.8.0
   Scenario: Prefetch
     Given the following "activities" exist:
       | activity | name                    | intro                          | course | idnumber | option                       | allowmultiple | allowupdate | showresults |
@@ -147,35 +144,36 @@ Feature: Test basic usage of choice activity in app
     And I press "Course 1" near "Course overview" in the app
     And I press "Display options" in the app
     And I press "Show download options" in the app
-    And I press "cloud download" near "Test single choice name" in the app
-    And I switch offline mode to "true"
+    And I press "Download" near "Test single choice name" in the app
+    Then I should find "Downloaded" near "Test single choice name" in the app
+
+    When I switch offline mode to "true"
     And I press "Test multi choice name" in the app
-    Then I should see "There was a problem connecting to the site. Please check your connection and try again."
+    Then I should find "There was a problem connecting to the site. Please check your connection and try again." in the app
 
     When I press "OK" in the app
     And I press the back button in the app
     And I press "Test single choice name" in the app
     And I press "Option 2" in the app
     And I press "Save my choice" in the app
-    Then I should see "Are you sure"
+    Then I should find "Are you sure" in the app
 
     When I press "OK" in the app
     And I press the back button in the app
     And I press "Test single choice name" in the app
-    Then I should see "This Choice has offline data to be synchronised."
-    But I should not see "Option 1: 0"
-    And I should not see "Option 2: 1"
-    And I should not see "Option 3: 0"
+    Then I should find "This Choice has offline data to be synchronised." in the app
+    But I should not find "Option 1: 0" in the app
+    And I should not find "Option 2: 1" in the app
+    And I should not find "Option 3: 0" in the app
 
     When I switch offline mode to "false"
     And I press the back button in the app
     And I press "Test single choice name" in the app
-    Then I should see "Option 1: 0"
-    And I should see "Option 2: 1"
-    And I should see "Option 3: 0"
-    But I should not see "This Choice has offline data to be synchronised."
+    Then I should find "Option 1: 0" in the app
+    And I should find "Option 2: 1" in the app
+    And I should find "Option 3: 0" in the app
+    But I should not find "This Choice has offline data to be synchronised." in the app
 
-  @app @3.8.0
   Scenario: Download students choice in text format
     # Submit answer as student
     Given the following "activities" exist:
@@ -194,10 +192,13 @@ Feature: Test basic usage of choice activity in app
     And I log in as "teacher1"
     And I press "Course 1" near "Course overview" in the app
     And I press "Choice name" in the app
-    And I press "Display options" in the app
+    Then I should find "Test choice description" in the app
+
+    When I press "Display options" in the app
     And I press "Open in browser" in the app
     And I switch to the browser tab opened by the app
     And I log in as "teacher1"
     And I press "Actions menu"
     And I follow "View 1 responses"
     And I press "Download in text format"
+    # TODO Then I should find "..." in the downloads folder
diff --git a/tests/behat/app_behat_runtime.js b/tests/behat/app_behat_runtime.js
index 7da3164a0..db9f68695 100644
--- a/tests/behat/app_behat_runtime.js
+++ b/tests/behat/app_behat_runtime.js
@@ -326,6 +326,32 @@
         return elements;
     };
 
+    /**
+     * Given a list of elements, get the top ancestors among all of them.
+     *
+     * This will remote duplicates and drop any elements nested within each other.
+     *
+     * @param {Array} elements Elements list.
+     * @return {Array} Top ancestors.
+     */
+    var getTopAncestors = function(elements) {
+        const uniqueElements = new Set(elements);
+
+        for (const element of uniqueElements) {
+            for (otherElement of uniqueElements) {
+                if (otherElement === element) {
+                    continue;
+                }
+
+                if (element.contains(otherElement)) {
+                    uniqueElements.delete(otherElement);
+                }
+            }
+        }
+
+        return [...uniqueElements];
+    };
+
     /**
      * Function to find elements based on their text or Aria label.
      *
@@ -343,10 +369,16 @@
             if (nearElements.length === 0) {
                 throw new Error('There was no match for near text')
             } else if (nearElements.length > 1) {
-                throw new Error('Too many matches for near text');
-            }
+                const nearElementsAncestors = getTopAncestors(nearElements);
 
-            container = nearElements[0].parentElement;
+                if (nearElementsAncestors.length > 1) {
+                    throw new Error('Too many matches for near text');
+                }
+
+                container = nearElementsAncestors[0].parentElement;
+            } else {
+                container = nearElements[0].parentElement;
+            }
         }
 
         do {
diff --git a/tests/behat/behat_app.php b/tests/behat/behat_app.php
index c2b753d0a..3d52a63c5 100644
--- a/tests/behat/behat_app.php
+++ b/tests/behat/behat_app.php
@@ -46,6 +46,9 @@ class behat_app extends behat_base {
     /** @var string URL for running Ionic server */
     protected $ionicurl = '';
 
+    /** @var bool Checks whether the app is runing a legacy version (ionic 3) */
+    protected $islegacy;
+
     /**
      * Checks if the current OS is Windows, from the point of view of task-executing-and-killing.
      *
@@ -67,9 +70,7 @@ class behat_app extends behat_base {
     }
 
     /**
-     * Opens the Moodle app in the browser.
-     *
-     * Requires JavaScript.
+     * Opens the Moodle app in the browser and introduces the enters the site.
      *
      * @Given /^I enter the app$/
      * @throws DriverException Issue with configuration or feature file
@@ -77,28 +78,35 @@ class behat_app extends behat_base {
      * @throws ExpectationException Problem with resizing window
      */
     public function i_enter_the_app() {
+        $this->i_launch_the_app();
+        $this->enter_site();
+    }
+
+    /**
+     * Opens the Moodle app in the browser.
+     *
+     * @Given /^I launch the app$/
+     * @throws DriverException Issue with configuration or feature file
+     * @throws dml_exception Problem with Moodle setup
+     * @throws ExpectationException Problem with resizing window
+     */
+    public function i_launch_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);
-
-        if (empty($this->ionicurl)) {
-            $this->ionicurl = $this->start_or_reuse_ionic();
-        }
-
         // Go to page and prepare browser for app.
-        $this->prepare_browser($this->ionicurl);
+        $this->prepare_browser();
     }
 
     /**
      * Finds elements in the app.
      *
      * @Then /^I should(?P<not_boolean> not)? find "(?P<text_string>(?:[^"]|\\")*)"(?: near "(?P<near_string>(?:[^"]|\\")*)")? in the app$/
+     * @param string $not
      * @param string $text
+     * @param string $near
      */
     public function i_find_in_the_app($not, $text='', $near='') {
         $not = !empty($not);
@@ -341,75 +349,92 @@ class behat_app extends behat_base {
      * @param string $url App URL
      * @throws DriverException If the app fails to load properly
      */
-    protected function prepare_browser(string $url) {
-        global $CFG;
+    protected function prepare_browser() {
+        // Restart the browser and set its size.
+        $this->getSession()->restart();
+        $this->resize_window('360x720', true);
+
+        if (empty($this->ionicurl)) {
+            $this->ionicurl = $this->start_or_reuse_ionic();
+        }
 
         // Check whether the app is running a legacy version.
-        $json = @file_get_contents("$url/assets/env.json") ?: @file_get_contents("$url/config.json");
+        $json = @file_get_contents("{$this->ionicurl}/assets/env.json") ?: @file_get_contents("{$this->ionicurl}/config.json");
         $data = json_decode($json);
         $appversion = $data->build->version ?? str_replace('-dev', '', $data->versionname);
-        $islegacy = version_compare($appversion, '3.9.5', '<');
+
+        $this->islegacy = version_compare($appversion, '3.9.5', '<');
 
         // Visit the Ionic URL and wait for it to load.
-        $this->getSession()->visit($url);
-        $this->spin(
-                function($context) use ($islegacy) {
-                    $title = $context->getSession()->getPage()->find('xpath', '//title');
-                    if ($title) {
-                        $text = $title->getHtml();
-                        if (
-                            ($islegacy && $text === 'Moodle Desktop') ||
-                            (!$islegacy && $text === 'Moodle App')
-                        ) {
-                            return true;
-                        }
-                    }
-                    throw new DriverException('Moodle app not found in browser');
-                }, false, 60);
+        $this->getSession()->visit($this->ionicurl);
+        $this->spin(function($context) {
+            $title = $context->getSession()->getPage()->find('xpath', '//title');
+
+            if ($title) {
+                $text = $title->getHtml();
+
+                if (
+                    ($this->islegacy && $text === 'Moodle Desktop') ||
+                    (!$this->islegacy && $text === 'Moodle App')
+                ) {
+                    return true;
+                }
+            }
+
+            throw new DriverException('Moodle app not found in browser');
+        }, false, 60);
 
         // Run the scripts to install Moodle 'pending' checks.
-        $islegacyboolean = $islegacy ? 'true' : 'false';
+        $islegacyboolean = $this->islegacy ? 'true' : 'false';
         $this->execute_script("window.BehatMoodleAppLegacy = $islegacyboolean;");
         $this->execute_script(file_get_contents(__DIR__ . '/app_behat_runtime.js'));
 
-        // Wait until the site login field appears OR the main page.
-        $situation = $this->spin(
-                function($context) use ($islegacy) {
-                    $page = $context->getSession()->getPage();
+        // Assert initial page.
+        $this->spin(function($context) {
+            $page = $context->getSession()->getPage();
+            $element = $page->find('xpath', '//page-core-login-site//input[@name="url"]');
 
-                    $element = $page->find('xpath', '//page-core-login-site//input[@name="url"]');
-                    if ($element) {
-                        // Wait for the onboarding modal to open, if any.
-                        $this->wait_for_pending_js();
-                        $element = $islegacy
-                            ? $page->find('xpath', '//page-core-login-site-onboarding')
-                            : $page->find('xpath', '//core-login-site-onboarding');
-                        if ($element) {
-                            $this->i_press_in_the_app('Skip');
-                            $this->wait_for_pending_js();
-                        }
+            if ($element) {
+                // Wait for the onboarding modal to open, if any.
+                $this->wait_for_pending_js();
 
-                        return 'login';
-                    }
+                $element = $this->islegacy
+                    ? $page->find('xpath', '//page-core-login-site-onboarding')
+                    : $page->find('xpath', '//core-login-site-onboarding');
 
-                    $element = $page->find('xpath', '//page-core-mainmenu');
-                    if ($element) {
-                        return 'mainpage';
-                    }
-                    throw new DriverException('Moodle app login URL prompt not found');
-                }, behat_base::get_extended_timeout(), 60);
+                if ($element) {
+                    $this->i_press_in_the_app('Skip');
+                }
 
-        // If it's the login page, we automatically fill in the URL and leave it on the user/pass
-        // page. If it's the main page, we just leave it there.
-        if ($situation === 'login') {
-            $this->i_set_the_field_in_the_app($islegacy ? 'campus.example.edu' : 'Your site', $CFG->wwwroot);
-            $this->i_press_in_the_app($islegacy ? 'Connect!' : 'Connect to your site');
-        }
+                // Login screen found.
+                return true;
+            }
+
+            if ($page->find('xpath', '//page-core-mainmenu')) {
+                // Main menu found.
+                return true;
+            }
+
+            throw new DriverException('Moodle app not launched properly');
+        }, false, 60);
 
         // Continue only after JS finishes.
         $this->wait_for_pending_js();
     }
 
+    protected function enter_site() {
+        if (!$this->is_in_login_page()) {
+            // Already in the site.
+            return;
+        }
+
+        global $CFG;
+
+        $this->i_set_the_field_in_the_app($this->islegacy ? 'campus.example.edu' : 'Your site', $CFG->wwwroot);
+        $this->i_press_in_the_app($this->islegacy ? 'Connect!' : 'Connect to your site');
+        $this->wait_for_pending_js();
+    }
+
     /**
      * Carries out the login steps for the app, assuming the user is on the app login page. Called
      * from behat_auth.php.
@@ -536,6 +561,16 @@ class behat_app extends behat_base {
         $this->press($text, $near);
     }
 
+    /**
+     * Check whether the current page is the login form.
+     */
+    protected function is_in_login_page(): bool {
+        $page = $this->getSession()->getPage();
+        $logininput = $page->find('xpath', '//page-core-login-site//input[@name="url"]');
+
+        return !is_null($logininput);
+    }
+
     /**
      * Clicks on / touches something that is visible in the app, near some other text.
      *
@@ -618,6 +653,31 @@ class behat_app extends behat_base {
         });
     }
 
+    /**
+     * Check that the app opened a new browser tab.
+     *
+     * @Given /^the app should(?P<not_boolean> not)? have opened a browser tab$/
+     * @param string $not
+     */
+    public function the_app_should_have_opened_a_browser_tab($not = '') {
+        $not = !empty($not);
+
+        $this->spin(function() use ($not) {
+            $openedbrowsertab = count($this->getSession()->getWindowNames()) === 2;
+
+            if ($not === $openedbrowsertab) {
+                throw new ExpectationException(
+                    $not
+                        ? 'Did not expect the app to have opened a browser tab'
+                        : 'Expected the app to have opened a browser tab',
+                    $this->getSession()->getDriver()
+                );
+            }
+
+            return true;
+        });
+    }
+
     /**
      * Switches to a newly-opened browser tab.
      *