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

View File

@ -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,11 +369,17 @@
if (nearElements.length === 0) {
throw new Error('There was no match for near text')
} else if (nearElements.length > 1) {
const nearElementsAncestors = getTopAncestors(nearElements);
if (nearElementsAncestors.length > 1) {
throw new Error('Too many matches for near text');
}
container = nearElementsAncestors[0].parentElement;
} else {
container = nearElements[0].parentElement;
}
}
do {
const elements = findElementsBasedOnTextWithin(container, text);

View File

@ -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,72 +349,89 @@ 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) {
$this->getSession()->visit($this->ionicurl);
$this->spin(function($context) {
$title = $context->getSession()->getPage()->find('xpath', '//title');
if ($title) {
$text = $title->getHtml();
if (
($islegacy && $text === 'Moodle Desktop') ||
(!$islegacy && $text === 'Moodle App')
($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) {
// Assert initial page.
$this->spin(function($context) {
$page = $context->getSession()->getPage();
$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
$element = $this->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');
}
// 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();
}
return 'login';
protected function enter_site() {
if (!$this->is_in_login_page()) {
// Already in the site.
return;
}
$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);
global $CFG;
// 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');
}
// Continue only after JS finishes.
$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();
}
@ -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.
*