Merge pull request #20 from NoelDeMartin/MOBILE-3320

MOBILE-3320: Add launch step, browser tab assertion, and update choice tests
main
Dani Palou 2021-05-31 12:41:13 +02:00 committed by GitHub
commit df82285fb1
3 changed files with 225 additions and 132 deletions

View File

@ -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 Feature: Test basic usage of choice activity in app
In order to participate in the choice while using the mobile app In order to participate in the choice while using the mobile app
As a student As a student
@ -17,7 +17,6 @@ Feature: Test basic usage of choice activity in app
| teacher1 | C1 | editingteacher | | teacher1 | C1 | editingteacher |
| student1 | C1 | student | | student1 | C1 | student |
@app @3.8.0
Scenario: Answer a choice (multi or single, update answer) & View results Scenario: Answer a choice (multi or single, update answer) & View results
Given the following "activities" exist: Given the following "activities" exist:
| activity | name | intro | course | idnumber | option | allowmultiple | allowupdate | showresults | | 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 log in as "student1"
And I press "Course 1" near "Course overview" in the app And I press "Course 1" near "Course overview" in the app
And I press "Test single choice name" 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 press "Option 2" in the app And I select "Option 2" in the app
And I press "Save my choice" 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 When I press "OK" in the app
Then I should see "Option 1: 0" Then I should find "Option 1: 0" in the app
And I should see "Option 2: 1" And I should find "Option 2: 1" in the app
And I should see "Option 3: 0" And I should find "Option 3: 0" in the app
But I should not see "Remove my choice" But I should not find "Remove my choice" in the app
When I press the back button in the app When I press the back button in the app
And I press "Test single choice name" in the app And I press "Test single choice name" in the app
Then I should see "Option 1: 0" Then I should find "Option 1: 0" in the app
And I should see "Option 2: 1" And I should find "Option 2: 1" in the app
And I should see "Option 3: 0" 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 Scenario: Answer a choice (multi or single, update answer) & View results & Delete choice
Given the following "activities" exist: Given the following "activities" exist:
| activity | name | intro | course | idnumber | option | allowmultiple | allowupdate | showresults | | 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 log in as "student1"
And I press "Course 1" near "Course overview" in the app And I press "Course 1" near "Course overview" in the app
And I press "Test multi choice name" in the app And I press "Test multi choice name" in the app
And I press "Option 1" in the app And I select "Option 1" in the app
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 And I press "Save my choice" in the app
Then I should see "Option 1: 1" Then I should find "Option 1: 1" in the app
And I should see "Option 2: 1" And I should find "Option 2: 1" in the app
And I should see "Option 3: 0" And I should find "Option 3: 0" in the app
And I should see "Remove my choice" And I should find "Remove my choice" in the app
When I press "Option 1" in the app When I select "Option 1" in the app
And I press "Option 3" in the app And I select "Option 3" in the app
And I press "Save my choice" in the app And I press "Save my choice" in the app
Then I should see "Option 1: 0" Then I should find "Option 1: 0" in the app
And I should see "Option 2: 1" And I should find "Option 2: 1" in the app
And I should see "Option 3: 1" And I should find "Option 3: 1" in the app
When I press "Remove my choice" 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 When I press "Delete" in the app
Then I should see "The results are not currently viewable" Then I should find "The results are not currently viewable" in the app
But I should not see "Remove my choice" But I should not find "Remove my choice" in the app
@app @3.8.0
Scenario: Answer and change answer offline & Sync choice Scenario: Answer and change answer offline & Sync choice
Given the following "activities" exist: Given the following "activities" exist:
| activity | name | intro | course | idnumber | option | allowmultiple | allowupdate | showresults | | 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 log in as "student1"
And I press "Course 1" near "Course overview" in the app And I press "Course 1" near "Course overview" in the app
And I press "Test single choice name" 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 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 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 When I press "OK" in the app
And I press the back button 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 "Test single choice name" in the app
Then I should see "This Choice has offline data to be synchronised." Then I should find "This Choice has offline data to be synchronised." in the app
But I should not see "Option 1: 0" But I should not find "Option 1: 0" in the app
And I should not see "Option 2: 1" And I should not find "Option 2: 1" in the app
And I should not see "Option 3: 0" And I should not find "Option 3: 0" in the app
When I switch offline mode to "false" When I switch offline mode to "false"
And I press the back button 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 "Test single choice name" in the app
And I press "Display options" in the app Then I should find "Test single choice description" in the app
And I press "Refresh" in the app
Then I should see "Option 1: 0" When I press "Display options" in the app
And I should see "Option 2: 1" And I press "Refresh" in the app
And I should see "Option 3: 0" Then I should find "Option 1: 0" in the app
But I should not see "This Choice has offline data to be synchronised." 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 Scenario: Answer and change answer offline & Auto-sync choice
Given the following "activities" exist: Given the following "activities" exist:
| activity | name | intro | course | idnumber | option | allowmultiple | allowupdate | showresults | | 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 switch offline mode to "true"
And I press "Option 2" in the app And I press "Option 2" in the app
And I press "Save my choice" 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 When I press "OK" in the app
And I switch offline mode to "false" Then I should find "This Choice has offline data to be synchronised." in the app
Then I should see "This Choice has offline data to be synchronised." But I should not find "Option 1: 0" in the app
But I should not see "Option 1: 0" And I should not find "Option 2: 1" in the app
And I should not see "Option 2: 1" And I should not find "Option 3: 0" in the app
And I should not see "Option 3: 0"
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 And I wait loading to finish in the app
Then I should see "Option 1: 0" Then I should find "Option 1: 0" in the app
And I should see "Option 2: 1" And I should find "Option 2: 1" in the app
And I should see "Option 3: 0" And I should find "Option 3: 0" in the app
But I should not see "This Choice has offline data to be synchronised." But I should not find "This Choice has offline data to be synchronised." in the app
@app @3.8.0
Scenario: Prefetch Scenario: Prefetch
Given the following "activities" exist: Given the following "activities" exist:
| activity | name | intro | course | idnumber | option | allowmultiple | allowupdate | showresults | | 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 "Course 1" near "Course overview" in the app
And I press "Display options" in the app And I press "Display options" in the app
And I press "Show download 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 press "Download" near "Test single choice name" in the app
And I switch offline mode to "true" 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 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 When I press "OK" in the app
And I press the back button 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 "Test single choice name" in the app
And I press "Option 2" in the app And I press "Option 2" in the app
And I press "Save my choice" 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 When I press "OK" in the app
And I press the back button 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 "Test single choice name" in the app
Then I should see "This Choice has offline data to be synchronised." Then I should find "This Choice has offline data to be synchronised." in the app
But I should not see "Option 1: 0" But I should not find "Option 1: 0" in the app
And I should not see "Option 2: 1" And I should not find "Option 2: 1" in the app
And I should not see "Option 3: 0" And I should not find "Option 3: 0" in the app
When I switch offline mode to "false" When I switch offline mode to "false"
And I press the back button 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 "Test single choice name" in the app
Then I should see "Option 1: 0" Then I should find "Option 1: 0" in the app
And I should see "Option 2: 1" And I should find "Option 2: 1" in the app
And I should see "Option 3: 0" And I should find "Option 3: 0" in the app
But I should not see "This Choice has offline data to be synchronised." 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 Scenario: Download students choice in text format
# Submit answer as student # Submit answer as student
Given the following "activities" exist: 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 log in as "teacher1"
And I press "Course 1" near "Course overview" in the app And I press "Course 1" near "Course overview" in the app
And I press "Choice name" 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 press "Open in browser" in the app
And I switch to the browser tab opened by the app And I switch to the browser tab opened by the app
And I log in as "teacher1" And I log in as "teacher1"
And I press "Actions menu" And I press "Actions menu"
And I follow "View 1 responses" And I follow "View 1 responses"
And I press "Download in text format" And I press "Download in text format"
# TODO Then I should find "..." in the downloads folder

View File

@ -326,6 +326,32 @@
return elements; 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. * Function to find elements based on their text or Aria label.
* *
@ -343,10 +369,16 @@
if (nearElements.length === 0) { if (nearElements.length === 0) {
throw new Error('There was no match for near text') throw new Error('There was no match for near text')
} else if (nearElements.length > 1) { } 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 { do {

View File

@ -46,6 +46,9 @@ class behat_app extends behat_base {
/** @var string URL for running Ionic server */ /** @var string URL for running Ionic server */
protected $ionicurl = ''; 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. * 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. * Opens the Moodle app in the browser and introduces the enters the site.
*
* Requires JavaScript.
* *
* @Given /^I enter the app$/ * @Given /^I enter the app$/
* @throws DriverException Issue with configuration or feature file * @throws DriverException Issue with configuration or feature file
@ -77,28 +78,35 @@ class behat_app extends behat_base {
* @throws ExpectationException Problem with resizing window * @throws ExpectationException Problem with resizing window
*/ */
public function i_enter_the_app() { 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. // Check the app tag was set.
if (!$this->has_tag('app')) { if (!$this->has_tag('app')) {
throw new DriverException('Requires @app tag on scenario or feature.'); 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. // Go to page and prepare browser for app.
$this->prepare_browser($this->ionicurl); $this->prepare_browser();
} }
/** /**
* Finds elements in the app. * Finds elements in the app.
* *
* @Then /^I should(?P<not_boolean> not)? find "(?P<text_string>(?:[^"]|\\")*)"(?: near "(?P<near_string>(?:[^"]|\\")*)")? 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 $text
* @param string $near
*/ */
public function i_find_in_the_app($not, $text='', $near='') { public function i_find_in_the_app($not, $text='', $near='') {
$not = !empty($not); $not = !empty($not);
@ -341,75 +349,92 @@ class behat_app extends behat_base {
* @param string $url App URL * @param string $url App URL
* @throws DriverException If the app fails to load properly * @throws DriverException If the app fails to load properly
*/ */
protected function prepare_browser(string $url) { protected function prepare_browser() {
global $CFG; // 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. // 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); $data = json_decode($json);
$appversion = $data->build->version ?? str_replace('-dev', '', $data->versionname); $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. // Visit the Ionic URL and wait for it to load.
$this->getSession()->visit($url); $this->getSession()->visit($this->ionicurl);
$this->spin( $this->spin(function($context) {
function($context) use ($islegacy) { $title = $context->getSession()->getPage()->find('xpath', '//title');
$title = $context->getSession()->getPage()->find('xpath', '//title');
if ($title) { if ($title) {
$text = $title->getHtml(); $text = $title->getHtml();
if (
($islegacy && $text === 'Moodle Desktop') || if (
(!$islegacy && $text === 'Moodle App') ($this->islegacy && $text === 'Moodle Desktop') ||
) { (!$this->islegacy && $text === 'Moodle App')
return true; ) {
} return true;
} }
throw new DriverException('Moodle app not found in browser'); }
}, false, 60);
throw new DriverException('Moodle app not found in browser');
}, false, 60);
// Run the scripts to install Moodle 'pending' checks. // 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("window.BehatMoodleAppLegacy = $islegacyboolean;");
$this->execute_script(file_get_contents(__DIR__ . '/app_behat_runtime.js')); $this->execute_script(file_get_contents(__DIR__ . '/app_behat_runtime.js'));
// Wait until the site login field appears OR the main page. // Assert initial page.
$situation = $this->spin( $this->spin(function($context) {
function($context) use ($islegacy) { $page = $context->getSession()->getPage();
$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) {
if ($element) { // Wait for the onboarding modal to open, if any.
// Wait for the onboarding modal to open, if any. $this->wait_for_pending_js();
$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();
}
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) {
if ($element) { $this->i_press_in_the_app('Skip');
return 'mainpage'; }
}
throw new DriverException('Moodle app login URL prompt not found');
}, behat_base::get_extended_timeout(), 60);
// If it's the login page, we automatically fill in the URL and leave it on the user/pass // Login screen found.
// page. If it's the main page, we just leave it there. return true;
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'); 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. // Continue only after JS finishes.
$this->wait_for_pending_js(); $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 * Carries out the login steps for the app, assuming the user is on the app login page. Called
* from behat_auth.php. * from behat_auth.php.
@ -536,6 +561,16 @@ class behat_app extends behat_base {
$this->press($text, $near); $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. * 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. * Switches to a newly-opened browser tab.
* *