2
0
Fork 0

MOBILE-3738 behat: Prepare 3.9.5 tests

main
Noel De Martin 2021-04-27 17:43:47 +02:00
parent d933421779
commit 17006dcc4e
19 changed files with 285 additions and 250 deletions

View File

@ -1,4 +1,4 @@
@mod @mod_assign @app @javascript @mod @mod_assign @app @app_upto3.9.4 @javascript
Feature: Test basic usage of assignment activity in app Feature: Test basic usage of assignment activity in app
In order to participate in the assignment while using the mobile app In order to participate in the assignment while using the mobile app
I need basic assignment functionality to work I need basic assignment functionality to work

View File

@ -1,4 +1,4 @@
@mod @mod_chat @app @javascript @mod @mod_chat @app @app_upto3.9.4 @javascript
Feature: Test basic usage of chat in app Feature: Test basic usage of chat in app
As a student As a student
I need basic chat functionality to work I need basic chat functionality to work

View File

@ -1,4 +1,4 @@
@mod @mod_choice @app @javascript @mod @mod_choice @app @app_upto3.9.4 @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

View File

@ -1,4 +1,4 @@
@mod @mod_comments @app @javascript @mod @mod_comments @app @app_upto3.9.4 @javascript
Feature: Test basic usage of comments in app Feature: Test basic usage of comments in app
In order to participate in the comments while using the mobile app In order to participate in the comments while using the mobile app
As a student As a student

View File

@ -1,4 +1,4 @@
@mod @mod_course @app @javascript @mod @mod_course @app @app_upto3.9.4 @javascript
Feature: Test basic usage of one course in app Feature: Test basic usage of one course in app
In order to participate in one course while using the mobile app In order to participate in one course while using the mobile app
As a student As a student

View File

@ -1,4 +1,4 @@
@core @core_course @app @javascript @core @core_course @app @app_upto3.9.4 @javascript
Feature: Check course completion feature. Feature: Check course completion feature.
In order to track the progress of the course on mobile device In order to track the progress of the course on mobile device
As a student As a student

View File

@ -22,17 +22,17 @@ Feature: Test course list shown on app start tab
Scenario: View courses (shortnames not displayed) Scenario: View courses (shortnames not displayed)
When I enter the app When I enter the app
And I log in as "student1" And I log in as "student1"
Then I should see "Course 1" Then I should find "Course 1" in the app
But I should not see "Course 2" But I should not find "Course 2" in the app
But I should not see "C1" But I should not find "C1" in the app
But I should not see "C2" But I should not find "C2" in the app
When I enter the app When I enter the app
And I log in as "student2" And I log in as "student2"
Then I should see "Course 1" Then I should find "Course 1" in the app
And I should see "Course 2" And I should find "Course 2" in the app
But I should not see "C1" But I should not find "C1" in the app
But I should not see "C2" But I should not find "C2" in the app
Scenario: Filter courses Scenario: Filter courses
Given the following config values are set as admin: Given the following config values are set as admin:
@ -78,26 +78,46 @@ Feature: Test course list shown on app start tab
| student2 | Z10 | student | | student2 | Z10 | student |
When I enter the app When I enter the app
And I log in as "student2" And I log in as "student2"
Then I press "Display options" near "Course overview" in the app Then I should find "C1" in the app
Then I should see "C1" And I should find "C2" in the app
And I should see "C2" And I should find "C3" in the app
And I should see "C3" And I should find "C4" in the app
And I should see "C4" And I should find "C5" in the app
And I should see "C5" And I should find "C6" in the app
And I should see "C6" And I should find "Course 1" in the app
Then I press "Filter my courses" in the app And I should find "Course 2" in the app
And I set the field "Filter my courses" to "fr" in the app And I should find "Frog 3" in the app
Then I should not see "C1" And I should find "Frog 4" in the app
And I should not see "C2" And I should find "Course 5" in the app
And I should see "C3" And I should find "Toad 6" in the app
And I should see "C4"
And I should not see "C5" When I press "Display options" near "Course overview" in the app
And I should not see "C6"
And I press "Display options" near "Course overview" in the app
And I press "Filter my courses" in the app And I press "Filter my courses" in the app
Then I should see "C1" And I set the field "Filter my courses" to "fr" in the app
And I should see "C2" Then I should find "C3" in the app
And I should see "C3" And I should find "C4" in the app
And I should see "C4" And I should find "Frog 3" in the app
And I should see "C5" And I should find "Frog 4" in the app
And I should see "C6" But I should not find "C1" in the app
And I should not find "C2" in the app
And I should not find "C5" in the app
And I should not find "C6" in the app
And I should not find "Course 1" in the app
And I should not find "Course 2" in the app
And I should not find "Course 5" in the app
And I should not find "Toad 6" in the app
When I press "Display options" near "Course overview" in the app
And I press "Filter my courses" in the app
Then I should find "C1" in the app
And I should find "C2" in the app
And I should find "C3" in the app
And I should find "C4" in the app
And I should find "C5" in the app
And I should find "C6" in the app
And I should find "Course 1" in the app
And I should find "Course 2" in the app
And I should find "Frog 3" in the app
And I should find "Frog 4" in the app
And I should find "Course 5" in the app
And I should find "Toad 6" in the app

View File

@ -1,4 +1,4 @@
@mod @mod_courses @app @javascript @mod @mod_courses @app @app_upto3.9.4 @javascript
Feature: Test basic usage of courses in app Feature: Test basic usage of courses in app
In order to participate in the courses while using the mobile app In order to participate in the courses while using the mobile app
As a student As a student

View File

@ -1,4 +1,4 @@
@mod @mod_data @app @javascript @mod @mod_data @app @app_upto3.9.4 @javascript
Feature: Users can manage entries in database activities Feature: Users can manage entries in database activities
In order to populate databases In order to populate databases
As a user As a user

View File

@ -1,4 +1,4 @@
@mod @mod_data @app @javascript @mod @mod_data @app @app_upto3.9.4 @javascript
Feature: Users can store entries in database activities when offline and sync when online Feature: Users can store entries in database activities when offline and sync when online
In order to populate databases while offline In order to populate databases while offline
As a user As a user

View File

@ -1,4 +1,4 @@
@mod @mod_forum @app @javascript @mod @mod_forum @app @app_upto3.9.4 @javascript
Feature: Test basic usage of forum activity in app Feature: Test basic usage of forum activity in app
In order to participate in the forum while using the mobile app In order to participate in the forum while using the mobile app
As a student As a student

View File

@ -1,4 +1,4 @@
@mod @mod_glossary @app @javascript @mod @mod_glossary @app @app_upto3.9.4 @javascript
Feature: Test basic usage of glossary in app Feature: Test basic usage of glossary in app
In order to participate in the glossaries while using the mobile app In order to participate in the glossaries while using the mobile app
As a student As a student

View File

@ -1,4 +1,4 @@
@mod @mod_login @app @javascript @mod @mod_login @app @app_upto3.9.4 @javascript
Feature: Test basic usage of login in app Feature: Test basic usage of login in app
I need basic login functionality to work I need basic login functionality to work

View File

@ -1,4 +1,4 @@
@mod @mod_messages @app @javascript @mod @mod_messages @app @app_upto3.9.4 @javascript
Feature: Test basic usage of messages in app Feature: Test basic usage of messages in app
In order to participate with messages while using the mobile app In order to participate with messages while using the mobile app
As a student As a student

View File

@ -1,4 +1,4 @@
@mod @mod_quiz @app @javascript @mod @mod_quiz @app @app_upto3.9.4 @javascript
Feature: Attempt a quiz in app Feature: Attempt a quiz in app
As a student As a student
In order to demonstrate what I know In order to demonstrate what I know

View File

@ -1,4 +1,4 @@
@mod @mod_quiz @app @javascript @mod @mod_quiz @app @app_upto3.9.4 @javascript
Feature: Attempt a quiz in app Feature: Attempt a quiz in app
As a student As a student
In order to demonstrate what I know In order to demonstrate what I know

View File

@ -1,4 +1,4 @@
@mod @mod_survey @app @javascript @mod @mod_survey @app @app_upto3.9.4 @javascript
Feature: Test basic usage of survey activity in app Feature: Test basic usage of survey activity in app
In order to participate in surveys while using the mobile app In order to participate in surveys while using the mobile app
As a student As a student

View File

@ -174,6 +174,26 @@
var observer = new MutationObserver(mutationCallback); var observer = new MutationObserver(mutationCallback);
observer.observe(document, {attributes: true, childList: true, subtree: true}); observer.observe(document, {attributes: true, childList: true, subtree: true});
/**
* Check if an element is visible.
*
* @param {HTMLElement} element Element
* @param {HTMLElement} container Container
* @returns {boolean} Whether the element is visible or not
*/
var isElementVisible = (element, container) => {
if (element.getAttribute('aria-hidden') === 'true' || getComputedStyle(element).display === 'none')
return false;
if (element.parentElement === container)
return true;
if (!element.parentElement)
return false;
return isElementVisible(element.parentElement, container);
};
/** /**
* Generic shared function to find possible xpath matches within the document, that are visible, * Generic shared function to find possible xpath matches within the document, that are visible,
* and then process them using a callback function. * and then process them using a callback function.
@ -199,194 +219,107 @@
} }
}; };
/**
* Finds an element within a given container.
*
* @param {HTMLElement} container Parent element to search the element within
* @param {string} text Text to look for
* @return {HTMLElement} Found element
*/
var findElementBasedOnTextWithin = (container, text) => {
const attributesSelector = `[aria-label*="${text}"], a[title*="${text}"], img[alt*="${text}"]`;
for (const foundByAttributes of container.querySelectorAll(attributesSelector)) {
if (isElementVisible(foundByAttributes, container))
return foundByAttributes;
}
const treeWalker = document.createTreeWalker(
container,
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_DOCUMENT_FRAGMENT | NodeFilter.SHOW_TEXT,
{
acceptNode: node => {
if (
node instanceof HTMLStyleElement ||
node instanceof HTMLLinkElement ||
node instanceof HTMLScriptElement
)
return NodeFilter.FILTER_REJECT;
if (
node instanceof HTMLElement && (
node.getAttribute('aria-hidden') === 'true' || getComputedStyle(node).display === 'none'
)
)
return NodeFilter.FILTER_REJECT;
return NodeFilter.FILTER_ACCEPT;
}
},
);
let currentNode;
while (currentNode = treeWalker.nextNode()) {
if (currentNode instanceof Text) {
if (currentNode.textContent.includes(text)) {
return currentNode.parentElement;
}
continue;
}
const labelledBy = currentNode.getAttribute('aria-labelledby');
if (labelledBy && container.querySelector(`#${labelledBy}`)?.innerText?.includes(text))
return currentNode;
if (currentNode.shadowRoot) {
for (const childNode of currentNode.shadowRoot.childNodes) {
if (!childNode) {
continue;
}
if (childNode.matches(attributesSelector)) {
return childNode;
}
const foundByText = findElementBasedOnTextWithin(childNode, text);
if (foundByText) {
return foundByText;
}
}
}
}
};
/** /**
* Function to find an element based on its text or Aria label. * Function to find an element based on its text or Aria label.
* *
* @param {string} text Text (full or partial) * @param {string} text Text (full or partial)
* @param {string} [near] Optional 'near' text - if specified, must have a single match on page * @param {string} [near] Optional 'near' text - if specified, must have a single match on page
* @return {HTMLElement} Found element * @return {HTMLElement} Found element
* @throws {string} Error message beginning 'ERROR:' if something went wrong
*/ */
var findElementBasedOnText = function(text, near) { var findElementBasedOnText = function(text, near) {
// Find all the elements that contain this text (and don't have a child element that const topContainer = document.querySelector('ion-alert, ion-popover, ion-action-sheet, core-ion-tab.show-tab ion-page.show-page, ion-page.show-page, html');
// contains it - i.e. the most specific elements). let container = topContainer;
var escapedText = text.replace('"', '""');
var exactMatches = [];
var anyMatches = [];
findPossibleMatches('//*[contains(normalize-space(.), "' + escapedText +
'") and not(child::*[contains(normalize-space(.), "' + escapedText + '")])]',
function(match) {
// Get the text. Note that innerText returns capitalised values for Android buttons
// for some reason, so we'll have to do a case-insensitive match.
var matchText = match.innerText.trim().toLowerCase();
// Let's just check - is this actually a label for something else? If so we will click if (topContainer && near) {
// that other thing instead. const nearElement = findElementBasedOnText(near);
var labelId = document.evaluate('string(ancestor-or-self::ion-label[@id][1]/@id)', match).stringValue;
if (labelId) {
var target = document.querySelector('*[aria-labelledby=' + labelId + ']');
if (target) {
match = target;
}
}
// Add to array depending on if it's an exact or partial match. if (!nearElement) {
if (matchText === text.toLowerCase()) { return;
exactMatches.push(match);
} else {
anyMatches.push(match);
}
});
// Find all the Aria labels that contain this text.
var exactLabelMatches = [];
var anyLabelMatches = [];
findPossibleMatches('//*[@aria-label and contains(@aria-label, "' + escapedText + '")]' +
'| //a[@title and contains(@title, "' + escapedText + '")]' +
'| //img[@alt and contains(@alt, "' + escapedText + '")]', function(match) {
// Add to array depending on if it's an exact or partial match.
var attributeData = match.getAttribute('aria-label') ||
match.getAttribute('title') ||
match.getAttribute('alt');
if (attributeData.trim() === text) {
exactLabelMatches.push(match);
} else {
anyLabelMatches.push(match);
}
});
// If the 'near' text is set, use it to filter results.
var nearAncestors = [];
if (near !== undefined) {
escapedText = near.replace('"', '""');
var exactNearMatches = [];
var anyNearMatches = [];
findPossibleMatches('//*[contains(normalize-space(.), "' + escapedText +
'") and not(child::*[contains(normalize-space(.), "' + escapedText +
'")])]', function(match) {
// Get the text.
var matchText = match.innerText.trim();
// Add to array depending on if it's an exact or partial match.
if (matchText === text) {
exactNearMatches.push(match);
} else {
anyNearMatches.push(match);
}
});
var nearFound = null;
// If there is an exact text match, use that (regardless of other matches).
if (exactNearMatches.length > 1) {
throw new Error('Too many exact matches for near text');
} else if (exactNearMatches.length) {
nearFound = exactNearMatches[0];
} }
if (nearFound === null) { container = nearElement.parentElement;
// If there is one partial text match, use that.
if (anyNearMatches.length > 1) {
throw new Error('Too many partial matches for near text');
} else if (anyNearMatches.length) {
nearFound = anyNearMatches[0];
}
}
if (!nearFound) {
throw new Error('No matches for near text');
}
while (nearFound) {
nearAncestors.push(nearFound);
nearFound = nearFound.parentNode;
}
/**
* Checks the number of steps up the tree from a specified node before getting to an
* ancestor of the 'near' item
*
* @param {HTMLElement} node HTML node
* @returns {number} Number of steps up, or Number.MAX_SAFE_INTEGER if it never matched
*/
var calculateNearDepth = function(node) {
var depth = 0;
while (node) {
var ancestorDepth = nearAncestors.indexOf(node);
if (ancestorDepth !== -1) {
return depth + ancestorDepth;
}
node = node.parentNode;
depth++;
}
return Number.MAX_SAFE_INTEGER;
};
/**
* Reduces an array to include only the nearest in each category.
*
* @param {Array} arr Array to
* @return {Array} Array including only the items with minimum 'near' depth
*/
var filterNonNearest = function(arr) {
var nearDepth = arr.map(function(node) {
return calculateNearDepth(node);
});
var minDepth = Math.min.apply(null, nearDepth);
return arr.filter(function(element, index) {
return nearDepth[index] == minDepth;
});
};
// Filter all the category arrays.
exactMatches = filterNonNearest(exactMatches);
exactLabelMatches = filterNonNearest(exactLabelMatches);
anyMatches = filterNonNearest(anyMatches);
anyLabelMatches = filterNonNearest(anyLabelMatches);
} }
// Select the resulting match. Note this 'do' loop is not really a loop, it is just so we
// can easily break out of it as soon as we find a match.
var found = null;
do { do {
// If there is an exact text match, use that (regardless of other matches). const node = findElementBasedOnTextWithin(container, text);
if (exactMatches.length > 1) {
throw new Error('Too many exact matches for text'); if (node) {
} else if (exactMatches.length) { return node;
found = exactMatches[0];
break;
} }
} while ((container = container.parentElement) && container !== topContainer);
// If there is an exact label match, use that.
if (exactLabelMatches.length > 1) {
throw new Error('Too many exact label matches for text');
} else if (exactLabelMatches.length) {
found = exactLabelMatches[0];
break;
}
// If there is one partial text match, use that.
if (anyMatches.length > 1) {
throw new Error('Too many partial matches for text');
} else if (anyMatches.length) {
found = anyMatches[0];
break;
}
// Finally if there is one partial label match, use that.
if (anyLabelMatches.length > 1) {
throw new Error('Too many partial label matches for text');
} else if (anyLabelMatches.length) {
found = anyLabelMatches[0];
break;
}
} while (false);
if (!found) {
throw new Error('No matches for text');
}
return found;
}; };
/** /**
@ -476,6 +409,29 @@
return 'OK'; return 'OK';
}; };
/**
* Function to find an arbitrary item based on its text or aria label.
*
* @param {string} text Text (full or partial)
* @param {string} [near] Optional 'near' text
* @return {string} OK if successful, or ERROR: followed by message
*/
var behatFind = function(text, near) {
log(`Action - Find ${text}`);
try {
const element = findElementBasedOnText(text, near);
if (!element) {
return 'ERROR: No matches for text';
}
return 'OK';
} catch (error) {
return 'ERROR: ' + error.message;
}
};
/** /**
* Get main navigation controller. * Get main navigation controller.
* *
@ -497,7 +453,7 @@
* Function to press arbitrary item based on its text or Aria label. * Function to press arbitrary item based on its text or Aria label.
* *
* @param {string} text Text (full or partial) * @param {string} text Text (full or partial)
* @param {string} near Optional 'near' text - if specified, must have a single match on page * @param {string} near Optional 'near' text
* @return {string} OK if successful, or ERROR: followed by message * @return {string} OK if successful, or ERROR: followed by message
*/ */
var behatPress = function(text, near) { var behatPress = function(text, near) {
@ -506,28 +462,37 @@
var found; var found;
try { try {
found = findElementBasedOnText(text, near); found = findElementBasedOnText(text, near);
if (!found) {
return 'ERROR: No matches for text';
}
} catch (error) { } catch (error) {
return 'ERROR: ' + error.message; return 'ERROR: ' + error.message;
} }
var mainContent = getNavCtrl().getActive().contentRef().nativeElement; if (window.BehatMoodleAppLegacy) {
var rect = found.getBoundingClientRect(); var mainContent = getNavCtrl().getActive().contentRef().nativeElement;
var rect = found.getBoundingClientRect();
// Scroll the item into view. // Scroll the item into view.
mainContent.scrollTo(rect.x, rect.y); mainContent.scrollTo(rect.x, rect.y);
// Simulate a mouse click on the button. // Simulate a mouse click on the button.
var eventOptions = {clientX: rect.left + rect.width / 2, clientY: rect.top + rect.height / 2, var eventOptions = {clientX: rect.left + rect.width / 2, clientY: rect.top + rect.height / 2,
bubbles: true, view: window, cancelable: true}; bubbles: true, view: window, cancelable: true};
setTimeout(function() { setTimeout(function() {
found.dispatchEvent(new MouseEvent('mousedown', eventOptions)); found.dispatchEvent(new MouseEvent('mousedown', eventOptions));
}, 0); }, 0);
setTimeout(function() { setTimeout(function() {
found.dispatchEvent(new MouseEvent('mouseup', eventOptions)); found.dispatchEvent(new MouseEvent('mouseup', eventOptions));
}, 0); }, 0);
setTimeout(function() { setTimeout(function() {
found.dispatchEvent(new MouseEvent('click', eventOptions)); found.dispatchEvent(new MouseEvent('click', eventOptions));
}, 0); }, 0);
} else {
found.scrollIntoView();
setTimeout(() => found.click(), 300);
}
// Mark busy until the button click finishes processing. // Mark busy until the button click finishes processing.
addPendingDelay(); addPendingDelay();
@ -547,7 +512,10 @@
var resultCount = 0; var resultCount = 0;
var titles = Array.from(document.querySelectorAll('ion-header ion-title')); var titles = Array.from(document.querySelectorAll('ion-header ion-title'));
titles.forEach(function(title) { titles.forEach(function(title) {
if (title.offsetParent) { if (
(window.BehatMoodleAppLegacy && title.offsetParent) ||
(!window.BehatMoodleAppLegacy && isElementVisible(title, document.body))
) {
result = title.innerText.trim(); result = title.innerText.trim();
resultCount++; resultCount++;
} }
@ -670,6 +638,7 @@
window.behat = { window.behat = {
pressStandard : behatPressStandard, pressStandard : behatPressStandard,
closePopup : behatClosePopup, closePopup : behatClosePopup,
find : behatFind,
press : behatPress, press : behatPress,
setField : behatSetField, setField : behatSetField,
getHeader : behatGetHeader, getHeader : behatGetHeader,

View File

@ -25,7 +25,7 @@
// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php. // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
require_once(__DIR__ . '/../../behat/behat_base.php'); require_once(__DIR__ . '/../../../../lib/behat/behat_base.php');
use Behat\Mink\Exception\DriverException; use Behat\Mink\Exception\DriverException;
use Behat\Mink\Exception\ExpectationException; use Behat\Mink\Exception\ExpectationException;
@ -63,7 +63,7 @@ class behat_app extends behat_base {
$this->check_behat_setup(); $this->check_behat_setup();
$this->fix_moodle_setup(); $this->fix_moodle_setup();
$this->ionicurl = $this->start_or_reuse_ionic(); $this->ionicurl = $this->start_or_reuse_ionic();
} }
/** /**
* Opens the Moodle app in the browser. * Opens the Moodle app in the browser.
@ -93,6 +93,33 @@ class behat_app extends behat_base {
$this->prepare_browser($this->ionicurl); $this->prepare_browser($this->ionicurl);
} }
/**
* 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 $text
*/
public function i_find_in_the_app($not, $text='', $near='') {
$not = !empty($not);
$text = addslashes_js($text);
$near = addslashes_js($near);
$this->spin(function() use ($not, $text, $near) {
$result = $this->evaluate_script("return window.behat.find(\"$text\", \"$near\");");
if ($not && $result === 'OK') {
throw new DriverException('Error, found an item that should not be found');
}
if (!$not && $result !== 'OK') {
throw new DriverException('Error finding item - ' . $result);
}
return true;
});
$this->wait_for_pending_js();
}
/** /**
* Checks the Behat setup - tags and configuration. * Checks the Behat setup - tags and configuration.
* *
@ -282,14 +309,23 @@ class behat_app extends behat_base {
protected function prepare_browser(string $url) { protected function prepare_browser(string $url) {
global $CFG; global $CFG;
// Check whether the app is running a legacy version.
$json = @file_get_contents("$url/assets/env.json") ?: @file_get_contents("$url/config.json");
$data = json_decode($json);
$appversion = $data->build->version ?? str_replace('-dev', '', $data->versionname);
$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($url);
$this->spin( $this->spin(
function($context, $args) { 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 ($text === 'Moodle Desktop') { if (
($islegacy && $text === 'Moodle Desktop') ||
(!$islegacy && $text === 'Moodle App')
) {
return true; return true;
} }
} }
@ -297,20 +333,25 @@ class behat_app extends behat_base {
}, false, 60); }, false, 60);
// Run the scripts to install Moodle 'pending' checks. // Run the scripts to install Moodle 'pending' checks.
$islegacyboolean = $islegacy ? 'true' : 'false';
$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. // Wait until the site login field appears OR the main page.
$situation = $this->spin( $situation = $this->spin(
function($context, $args) { 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 = $page->find('xpath', '//page-core-login-site-onboarding'); $element = $islegacy
? $page->find('xpath', '//page-core-login-site-onboarding')
: $page->find('xpath', '//core-login-site-onboarding');
if ($element) { if ($element) {
$this->i_press_in_the_app('Skip'); $this->i_press_in_the_app('Skip');
$this->wait_for_pending_js();
} }
return 'login'; return 'login';
@ -327,7 +368,7 @@ class behat_app extends behat_base {
// page. If it's the main page, we just leave it there. // page. If it's the main page, we just leave it there.
if ($situation === 'login') { if ($situation === 'login') {
$this->i_set_the_field_in_the_app('campus.example.edu', $CFG->wwwroot); $this->i_set_the_field_in_the_app('campus.example.edu', $CFG->wwwroot);
$this->i_press_in_the_app('Connect!'); $this->i_press_in_the_app($islegacy ? 'Connect!' : 'Connect to your site');
} }
// Continue only after JS finishes. // Continue only after JS finishes.
@ -491,18 +532,23 @@ class behat_app extends behat_base {
* @throws ExpectationException If the header text is different to the expected value * @throws ExpectationException If the header text is different to the expected value
*/ */
public function the_header_should_be_in_the_app(string $text) { public function the_header_should_be_in_the_app(string $text) {
$result = $this->spin(function($context, $args) { $this->spin(function() use ($text) {
$result = $this->evaluate_script('return window.behat.getHeader();'); $result = $this->evaluate_script('return window.behat.getHeader();');
if (substr($result, 0, 3) !== 'OK:') { if (substr($result, 0, 3) !== 'OK:') {
throw new DriverException('Error getting header - ' . $result); throw new DriverException('Error getting header - ' . $result);
} }
return $result;
$header = substr($result, 3);
if (trim($header) !== trim($text)) {
throw new ExpectationException(
"The header text was not as expected: '$header'",
$this->getSession()->getDriver()
);
}
return true;
}); });
$header = substr($result, 3);
if (trim($header) !== trim($text)) {
throw new ExpectationException('The header text was not as expected: \'' . $header . '\'',
$this->getSession()->getDriver());
}
} }
/** /**