Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MOBILE-4616 behat: Fix flaky tests #4164

Merged
merged 2 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions local_moodleappbehat/tests/behat/behat_app_helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -738,25 +738,36 @@ protected function resize_app_window(int $width = 500, int $height = 720) {
* This function is similar to the arg_time_to_string transformation, but it allows the time to be a sub-text of the string.
*
* @param string $text
* @return string Transformed text.
* @return string|string[] Transformed text.
*/
protected function transform_time_to_string(string $text): string {
protected function transform_time_to_string(string $text): string|array {
if (!preg_match('/##(.*)##/', $text, $matches)) {
// No time found, return the original text.
return $text;
}

$timepassed = explode('##', $matches[1]);
$basetime = time();

// If not a valid time string, then just return what was passed.
if ((($timestamp = strtotime($timepassed[0])) === false)) {
if ((($timestamp = strtotime($timepassed[0], $basetime)) === false)) {
return $text;
}

$count = count($timepassed);
if ($count === 2) {
// If timestamp with specified strftime format, then return formatted date string.
return str_replace($matches[0], userdate($timestamp, $timepassed[1]), $text);
$result = [str_replace($matches[0], userdate($timestamp, $timepassed[1]), $text)];

// If it's a relative date, allow a difference of 1 minute for the base time used to calculate the timestampt.
if ($timestamp !== strtotime($timepassed[0], 0)) {
$timestamp = strtotime($timepassed[0], $basetime - 60);
$result[] = str_replace($matches[0], userdate($timestamp, $timepassed[1]), $text);
}

$result = array_unique($result);

return count($result) == 1 ? $result[0] : $result;
} else if ($count === 1) {
return str_replace($matches[0], $timestamp, $text);
} else {
Expand Down
12 changes: 12 additions & 0 deletions src/testing/services/behat-blocking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,18 @@ export class TestingBehatBlockingService {
this.unblock(key);
}

/**
* Adds a pending key to the array, and remove it after some time.
*
* @param milliseconds Number of milliseconds to wait before the key is removed.
* @returns Promise resolved after the time has passed.
*/
async wait(milliseconds: number): Promise<void> {
const key = this.block();
await CoreWait.wait(milliseconds);
this.unblock(key);
}

/**
* It would be really beautiful if you could detect CSS transitions and animations, that would
* cover almost everything, but sadly there is no way to do this because the transitionstart
Expand Down
19 changes: 17 additions & 2 deletions src/testing/services/behat-dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,13 @@ export class TestingBehatDomUtilsService {
*/
protected findElementsBasedOnTextWithinWithExact(
container: HTMLElement,
text: string,
text: string | string[],
options: TestingBehatFindOptions,
): ElementsWithExact[] {
if (Array.isArray(text)) {
return text.map((text) => this.findElementsBasedOnTextWithinWithExact(container, text, options)).flat();
}

// Escape double quotes to prevent breaking the query selector.
const escapedText = text.replace(/"/g, '\\"');
const attributesSelector = `[aria-label*="${escapedText}"], a[title*="${escapedText}"], ` +
Expand Down Expand Up @@ -266,7 +270,7 @@ export class TestingBehatDomUtilsService {
*/
protected findElementsBasedOnTextWithin(
container: HTMLElement,
text: string,
text: string | string[],
options: TestingBehatFindOptions,
): HTMLElement[] {
const elements = this.findElementsBasedOnTextWithinWithExact(container, text, options);
Expand Down Expand Up @@ -495,6 +499,17 @@ export class TestingBehatDomUtilsService {
locator: TestingBehatElementLocator,
options: TestingBehatFindOptions = {},
): HTMLElement | undefined {
if (Array.isArray(locator.text)) {
for (const text of locator.text) {
const element = this.findElementBasedOnText({ ...locator, text });
if (element) {
return element;
}
}

return undefined;
}

// Remove extra spaces.
const treatedText = locator.text.trim().replace(/\s\s+/g, ' ');
if (treatedText !== locator.text) {
Expand Down
8 changes: 7 additions & 1 deletion src/testing/services/behat-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,9 @@ export class TestingBehatRuntimeService {
// Click button
await TestingBehatDomUtils.pressElement(foundButton);

// Block Behat for at least 500ms, WS calls or DOM changes might not begin immediately.
TestingBehatBlocking.wait(500);

return 'OK';
}

Expand Down Expand Up @@ -446,6 +449,9 @@ export class TestingBehatRuntimeService {

await TestingBehatDomUtils.pressElement(found);

// Block Behat for at least 500ms, WS calls or DOM changes might not begin immediately.
TestingBehatBlocking.wait(500);

return 'OK';
} catch (error) {
return 'ERROR: ' + error.message;
Expand Down Expand Up @@ -803,7 +809,7 @@ export type TestingBehatFindOptions = {
};

export type TestingBehatElementLocator = {
text: string;
text: string | string[];
within?: TestingBehatElementLocator;
near?: TestingBehatElementLocator;
selector?: string;
Expand Down