MOBILE-3947 reminders: Fix tests

main
Noel De Martin 2024-01-11 15:25:49 +01:00
parent 21d0dc6821
commit 44606242fd
5 changed files with 139 additions and 74 deletions

View File

@ -781,13 +781,10 @@ class behat_app extends behat_app_helper {
/** /**
* Sets a field to the given text value in the app. * Sets a field to the given text value in the app.
* *
* Currently this only works for input fields which must be identified using a partial or
* exact match on the placeholder text.
*
* @Given /^I set the field "((?:[^"]|\\")+)" to "((?:[^"]|\\")*)" in the app$/ * @Given /^I set the field "((?:[^"]|\\")+)" to "((?:[^"]|\\")*)" in the app$/
* @param string $field Text identifying field * @param string $field Text identifying the field.
* @param string $value Value for field * @param string $value Value to set. In select fields, this can be either the value or text included in the select option.
* @throws DriverException If the field set doesn't work * @throws DriverException If the field set doesn't work.
*/ */
public function i_set_the_field_in_the_app(string $field, string $value) { public function i_set_the_field_in_the_app(string $field, string $value) {
$field = addslashes_js($field); $field = addslashes_js($field);

View File

@ -16,52 +16,48 @@ Feature: Set a new reminder on activity
| assign | C1 | assign01 | Assignment 01 | ## yesterday ## | ## now +70 minutes ## | | assign | C1 | assign01 | Assignment 01 | ## yesterday ## | ## now +70 minutes ## |
| assign | C1 | assign02 | Assignment 02 | ## yesterday ## | ## 1 January 2050 ## | | assign | C1 | assign02 | Assignment 02 | ## yesterday ## | ## 1 January 2050 ## |
@ionic7_failure
Scenario: Add, delete and update reminder on activity Scenario: Add, delete and update reminder on activity
Given I entered the assign activity "Assignment 01" on course "Course 1" as "student1" in the app Given I entered the assign activity "Assignment 01" on course "Course 1" as "student1" in the app
Then I should not find "Set a reminder for \"Assignment 01\" (Opened)" in the app Then I should not find "Set a reminder for \"Assignment 01\" (Opened)" in the app
And I should find "Set a reminder for \"Assignment 01\" (Due)" in the app And I should not find "Reminder set for" in the app
And "Set a reminder for \"Assignment 01\" (Due)" should not be selected in the app But I should find "Set a reminder for \"Assignment 01\" (Due)" in the app
# Default set # Default set
When I press "Set a reminder for \"Assignment 01\" (Due)" in the app When I press "Set a reminder for \"Assignment 01\" (Due)" in the app
Then I should find "Reminder set for " in the app Then I should find "Reminder set for" in the app
And "Set a reminder for \"Assignment 01\" (Due)" should be selected in the app
# Set from list # Set from list
When I press "Set a reminder for \"Assignment 01\" (Due)" in the app When I press "Set a reminder for \"Assignment 01\" (Due)" in the app
Then I should find "Set a reminder" in the app Then I should find "Set a reminder" in the app
And "At the time of the event" should be selected in the app And "At the time of the event" should be selected in the app
And "1 hour before" should not be selected in the app But "1 hour before" should not be selected in the app
When I press "1 hour before" in the app When I press "1 hour before" in the app
Then I should find "Reminder set for " in the app Then I should find "Reminder set for" in the app
And "Set a reminder for \"Assignment 01\" (Due)" should be selected in the app
# Custom set # Custom set
When I press "Set a reminder for \"Assignment 01\" (Due)" in the app When I press "Set a reminder for \"Assignment 01\" (Due)" in the app
Then I should find "Set a reminder" in the app Then I should find "Set a reminder" in the app
And "At the time of the event" should not be selected in the app
And "1 hour before" should be selected in the app And "1 hour before" should be selected in the app
But "At the time of the event" should not be selected in the app
When I press "Custom..." in the app When I press "Custom..." in the app
Then I should find "Custom reminder" in the app Then I should find "Custom reminder" in the app
When I set the following fields to these values in the app: When I set the following fields to these values in the app:
| Value | 4 | | Value | 4 |
| Units | minutes | | Units | minutes |
And I press "Set reminder" in the app And I press "Set reminder" in the app
Then I should find "Reminder set for " in the app Then I should find "Reminder set for" in the app
And "Set a reminder for \"Assignment 01\" (Due)" should be selected in the app
# Remove # Remove
When I press "Set a reminder for \"Assignment 01\" (Due)" in the app When I press "Set a reminder for \"Assignment 01\" (Due)" in the app
Then "4 minutes before" should be selected in the app Then "4 minutes before" should be selected in the app
When I press "Delete reminder" in the app When I press "Delete reminder" in the app
Then I should find "Reminder deleted" in the app Then I should find "Reminder deleted" in the app
And "Set a reminder for \"Assignment 01\" (Due)" should not be selected in the app But I should not find "Reminder set for" in the app
# Set and check reminder # Set and check reminder
When I press "Set a reminder for \"Assignment 01\" (Due)" in the app When I press "Set a reminder for \"Assignment 01\" (Due)" in the app
Then I should find "Reminder set for " in the app Then I should find "Reminder set for" in the app
When I press "Set a reminder for \"Assignment 01\" (Due)" in the app When I press "Set a reminder for \"Assignment 01\" (Due)" in the app
And I press "Custom..." in the app And I press "Custom..." in the app
Then I should find "Custom reminder" in the app Then I should find "Custom reminder" in the app
@ -69,7 +65,7 @@ Feature: Set a new reminder on activity
| Value | 69 | | Value | 69 |
| Units | minutes | | Units | minutes |
And I press "Set reminder" in the app And I press "Set reminder" in the app
Then I should find "Reminder set for " in the app Then I should find "Reminder set for" in the app
When I wait "50" seconds When I wait "50" seconds
Then a notification with title "Due: Assignment 01" is present in the app Then a notification with title "Due: Assignment 01" is present in the app
And I close a notification with title "Due: Assignment 01" in the app And I close a notification with title "Due: Assignment 01" in the app
@ -82,9 +78,9 @@ Feature: Set a new reminder on activity
| Value | 68 | | Value | 68 |
| Units | minutes | | Units | minutes |
And I press "Set reminder" in the app And I press "Set reminder" in the app
Then I should find "Reminder set for " in the app Then I should find "Reminder set for" in the app
When I press "Set a reminder for \"Assignment 01\" (Due)" in the app When I press "Set a reminder for \"Assignment 01\" (Due)" in the app
Then I should find "Reminder set for " in the app Then I should find "Reminder set for" in the app
When I press "Delete reminder" in the app When I press "Delete reminder" in the app
Then I should find "Reminder deleted" in the app Then I should find "Reminder deleted" in the app
When I wait "50" seconds When I wait "50" seconds

View File

@ -12,34 +12,33 @@ Feature: Set a new reminder on course
| user | course | role | | user | course | role |
| student1 | C1 | student | | student1 | C1 | student |
@ionic7_failure
Scenario: Add, delete and update reminder on course Scenario: Add, delete and update reminder on course
Given I entered the course "Course 1" as "student1" in the app Given I entered the course "Course 1" as "student1" in the app
And I press "Course summary" in the app And I press "Course summary" in the app
Then I should not find "Set a reminder for \"Course 1\" (Course start date)" in the app Then I should not find "Set a reminder for \"Course 1\" (Course start date)" in the app
And I should find "Set a reminder for \"Course 1\" (Course end date)" in the app And I should not find "Reminder set for" in the app
And "Set a reminder for \"Course 1\" (Course end date)" should not be selected in the app But I should find "Set a reminder for \"Course 1\" (Course end date)" in the app
# Default set # Default set
When I press "Set a reminder for \"Course 1\" (Course end date)" in the app When I press "Set a reminder for \"Course 1\" (Course end date)" in the app
Then I should find "Reminder set for " in the app Then I should find "Reminder set for " in the app
And "Set a reminder for \"Course 1\" (Course end date)" should be selected in the app And I should find "Reminder set for" in the app
# Set from list # Set from list
When I press "Set a reminder for \"Course 1\" (Course end date)" in the app When I press "Set a reminder for \"Course 1\" (Course end date)" in the app
Then I should find "Set a reminder" in the app Then I should find "Set a reminder" in the app
And "At the time of the event" should be selected in the app And "At the time of the event" should be selected in the app
And "12 hours before" should not be selected in the app But "12 hours before" should not be selected in the app
When I press "12 hours before" in the app When I press "12 hours before" in the app
Then I should find "Reminder set for " in the app Then I should find "Reminder set for " in the app
And "Set a reminder for \"Course 1\" (Course end date)" should be selected in the app And I should find "Reminder set for" in the app
# Custom set # Custom set
When I press "Set a reminder for \"Course 1\" (Course end date)" in the app When I press "Set a reminder for \"Course 1\" (Course end date)" in the app
Then I should find "Set a reminder" in the app Then I should find "Set a reminder" in the app
And "At the time of the event" should not be selected in the app And "At the time of the event" should not be selected in the app
And "12 hours before" should be selected in the app But "12 hours before" should be selected in the app
When I press "Custom..." in the app When I press "Custom..." in the app
Then I should find "Custom reminder" in the app Then I should find "Custom reminder" in the app
When I set the following fields to these values in the app: When I set the following fields to these values in the app:
@ -47,11 +46,11 @@ Feature: Set a new reminder on course
| Units | hours | | Units | hours |
And I press "Set reminder" in the app And I press "Set reminder" in the app
Then I should find "Reminder set for " in the app Then I should find "Reminder set for " in the app
And "Set a reminder for \"Course 1\" (Course end date)" should be selected in the app And I should find "Reminder set for" in the app
# Remove # Remove
When I press "Set a reminder for \"Course 1\" (Course end date)" in the app When I press "Set a reminder for \"Course 1\" (Course end date)" in the app
Then "2 hours before" should be selected in the app Then "2 hours before" should be selected in the app
When I press "Delete reminder" in the app When I press "Delete reminder" in the app
Then I should find "Reminder deleted" in the app Then I should find "Reminder deleted" in the app
And "Set a reminder for \"Course 1\" (Course end date)" should not be selected in the app But I should not find "Reminder set for" in the app

View File

@ -276,7 +276,7 @@ export class TestingBehatDomUtilsService {
/** /**
* Given a list of elements, get the top ancestors among all of them. * 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. * This will remove duplicates and drop any elements nested within each other.
* *
* @param elements Elements list. * @param elements Elements list.
* @returns Top ancestors. * @returns Top ancestors.
@ -480,6 +480,34 @@ export class TestingBehatDomUtilsService {
return this.findElementsBasedOnText(locator, options)[0]; return this.findElementsBasedOnText(locator, options)[0];
} }
/**
* Wait until an element with the given selector is found.
*
* @param selector Element selector.
* @param timeout Timeout after which an error is thrown.
* @param retryFrequency Frequency for retries when the element is not found.
* @returns Element.
*/
async waitForElement<T extends HTMLElement = HTMLElement>(
selector: string,
timeout: number = 2000,
retryFrequency: number = 100,
): Promise<T> {
const element = document.querySelector<T>(selector);
if (!element) {
if (timeout < retryFrequency) {
throw new Error(`Element with '${selector}' selector not found`);
}
await new Promise(resolve => setTimeout(resolve, retryFrequency));
return this.waitForElement<T>(selector, timeout - retryFrequency, retryFrequency);
}
return element;
}
/** /**
* Function to find elements based on their text or Aria label. * Function to find elements based on their text or Aria label.
* *
@ -515,7 +543,7 @@ export class TestingBehatDomUtilsService {
protected findElementsBasedOnTextInContainer( protected findElementsBasedOnTextInContainer(
locator: TestingBehatElementLocator, locator: TestingBehatElementLocator,
topContainer: HTMLElement, topContainer: HTMLElement,
options: TestingBehatFindOptions, options: TestingBehatFindOptions = {},
): HTMLElement[] { ): HTMLElement[] {
let container: HTMLElement | null = topContainer; let container: HTMLElement | null = topContainer;
@ -667,37 +695,26 @@ export class TestingBehatDomUtilsService {
} }
/** /**
* Set an element value. * Set an input element value.
* *
* @param element HTML to set. * @param element Input element.
* @param value Value to be set. * @param value Value.
*/ */
async setElementValue(element: HTMLInputElement | HTMLElement, value: string): Promise<void> { async setInputValue(element: HTMLInputElement | HTMLElement, value: string): Promise<void> {
await NgZone.run(async () => { await NgZone.run(async () => {
const promise = new CorePromisedValue<void>();
// Functions to get/set value depending on field type. // Functions to get/set value depending on field type.
const setValue = (text: string) => { const setValue = async (text: string) => {
if (! ('value' in element)) {
element.innerHTML = text;
return;
}
if (element.tagName === 'ION-SELECT') { if (element.tagName === 'ION-SELECT') {
value = value.trim(); this.setIonSelectInputValue(element, value);
const optionValue = Array.from(element.querySelectorAll('ion-select-option')) } else if ('value' in element) {
.find((option) => option.innerHTML.trim() === value);
if (optionValue) {
element.value = optionValue.value;
}
} else {
element.value = text; element.value = text;
} else {
element.innerHTML = text;
} }
element.dispatchEvent(new Event('ionChange')); element.dispatchEvent(new Event('ionChange'));
}; };
const getValue = () => { const getValue = () => {
if ('value' in element) { if ('value' in element) {
return element.value; return element.value;
@ -707,38 +724,79 @@ export class TestingBehatDomUtilsService {
}; };
// Pretend we have cut and pasted the new text. // Pretend we have cut and pasted the new text.
let event: InputEvent; if (element.tagName !== 'ION-SELECT' && getValue() !== '') {
if (getValue() !== '') { await CoreUtils.nextTick();
event = new InputEvent('input', { await setValue('');
element.dispatchEvent(new InputEvent('input', {
bubbles: true, bubbles: true,
view: window, view: window,
cancelable: true, cancelable: true,
inputType: 'deleteByCut', inputType: 'deleteByCut',
}); }));
await CoreUtils.nextTick();
setValue('');
element.dispatchEvent(event);
} }
if (value !== '') { if (value !== '') {
event = new InputEvent('input', { await CoreUtils.nextTick();
await setValue(value);
element.dispatchEvent(new InputEvent('input', {
bubbles: true, bubbles: true,
view: window, view: window,
cancelable: true, cancelable: true,
inputType: 'insertFromPaste', inputType: 'insertFromPaste',
data: value, data: value,
}); }));
}
});
}
await CoreUtils.nextTick(); /**
setValue(value); * Select an option in an ion-select element.
element.dispatchEvent(event); *
* @param element IonSelect element.
* @param value Value.
*/
protected async setIonSelectInputValue(element: HTMLElement, value: string): Promise<void> {
// Press select.
await TestingBehatDomUtils.pressElement(element);
// Press option.
type IonSelectInterface = 'alert' | 'action-sheet' | 'popover';
const selectInterface = element.getAttribute('interface') as IonSelectInterface ?? 'alert';
const containerSelector = ({
'alert': 'ion-alert.select-alert',
'action-sheet': 'ion-action-sheet.select-action-sheet',
'popover': 'ion-popover.select-popover',
})[selectInterface];
const optionSelector = ({
'alert': 'button',
'action-sheet': 'button',
'popover': 'ion-radio',
})[selectInterface] ?? '';
const optionsContainer = await TestingBehatDomUtils.waitForElement(containerSelector);
const options = this.findElementsBasedOnTextInContainer(
{ text: value, selector: optionSelector },
optionsContainer,
{},
);
if (options.length === 0) {
throw new Error('Couldn\'t find ion-select option.');
}
await TestingBehatDomUtils.pressElement(options[0]);
// Press options submit.
if (selectInterface === 'alert') {
const submitButton = optionsContainer.querySelector<HTMLElement>('.alert-button-group button:last-child');
if (!submitButton) {
throw new Error('Couldn\'t find ion-select submit button.');
} }
promise.resolve(); await TestingBehatDomUtils.pressElement(submitButton);
}
return promise;
});
} }
} }

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { TestingBehatDomUtils } from './behat-dom'; import { TestingBehatDomUtils, TestingBehatDomUtilsService } from './behat-dom';
import { TestingBehatBlocking } from './behat-blocking'; import { TestingBehatBlocking } from './behat-blocking';
import { CoreCustomURLSchemes, CoreCustomURLSchemesProvider } from '@services/urlschemes'; import { CoreCustomURLSchemes, CoreCustomURLSchemesProvider } from '@services/urlschemes';
import { ONBOARDING_DONE } from '@features/login/constants'; import { ONBOARDING_DONE } from '@features/login/constants';
@ -63,6 +63,10 @@ export class TestingBehatRuntimeService {
return CoreNavigator.instance; return CoreNavigator.instance;
} }
get domUtils(): TestingBehatDomUtilsService {
return TestingBehatDomUtils.instance;
}
/** /**
* Init behat functions and set options like skipping onboarding. * Init behat functions and set options like skipping onboarding.
* *
@ -468,11 +472,22 @@ export class TestingBehatRuntimeService {
?? options.find(option => option.text === value)?.value ?? options.find(option => option.text === value)?.value
?? options.find(option => option.text.includes(value))?.value ?? options.find(option => option.text.includes(value))?.value
?? value; ?? value;
} else if (input.tagName === 'ION-SELECT') {
const options = Array.from(input.querySelectorAll('ion-select-option'));
value = options.find(option => option.value?.toString() === value)?.textContent?.trim()
?? options.find(option => option.textContent?.trim() === value)?.textContent?.trim()
?? options.find(option => option.textContent?.includes(value))?.textContent?.trim()
?? value;
} }
await TestingBehatDomUtils.setElementValue(input, value); try {
await TestingBehatDomUtils.setInputValue(input, value);
return 'OK'; return 'OK';
} catch (error) {
return `ERROR: ${error.message ?? 'Unknown error'}`;
}
} }
/** /**