mattermost-mobile/detox/e2e/support/utils/detoxhelpers.ts

108 lines
4.3 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {by, element, waitFor} from 'detox';
export async function waitForPartialText(text: string, timeout = 10000): Promise<void> {
// Detox does not support partial text matching directly; use exact match or implement a workaround.
await waitFor(element(by.text(new RegExp(text, 'i')))).toBeVisible().withTimeout(timeout);
}
export async function expectPartialText(text: string, timeout = 10000): Promise<void> {
// Detox does not support partial text matching directly; use exact match or implement a workaround.
await waitFor(element(by.text(new RegExp(text, 'i')))).toBeVisible().withTimeout(timeout);
}
export async function scrollUntilVisible(testID: string, text: string): Promise<void> {
// Detox does not support partial text matching directly; use exact match or implement a workaround.
await waitFor(element(by.text(new RegExp(text, 'i')))).
toBeVisible().
whileElement(by.id(testID)).
scroll(100, 'down');
}
export async function waitForLoadingSpinner(testID: string, timeout = 10000): Promise<void> {
await waitFor(element(by.id(testID))).not.toBeVisible().withTimeout(timeout);
}
/**
* Safely dismiss keyboard with retry logic
* Prevents keyboard input session invalidation errors
* @param maxAttempts - Maximum number of dismissal attempts
*/
export async function dismissKeyboardSafely(maxAttempts = 3): Promise<void> {
/* eslint-disable no-await-in-loop, no-console */
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
await device.pressBack(); // Works on both iOS (dismisses keyboard) and Android
// Wait a bit for keyboard to dismiss
await new Promise((resolve) => setTimeout(resolve, 300));
return;
} catch (error) {
if (attempt === maxAttempts) {
// On final attempt, log warning but don't throw to prevent test failures
console.warn(`[dismissKeyboardSafely] Failed to dismiss keyboard after ${maxAttempts} attempts:`, error);
} else {
await new Promise((resolve) => setTimeout(resolve, 200));
}
}
}
/* eslint-enable no-await-in-loop, no-console */
}
/**
* Perform text input with keyboard state verification
* Ensures text input session is valid before typing
* @param element - Detox element to type into
* @param text - Text to type
*/
export async function typeTextSafely(elementToType: Detox.IndexableNativeElement, text: string): Promise<void> {
try {
// Tap element to ensure it's focused
await elementToType.tap();
await new Promise((resolve) => setTimeout(resolve, 300));
// Type text
await elementToType.typeText(text);
} catch (error) {
// eslint-disable-next-line no-console
console.warn('[typeTextSafely] Text input failed, retrying:', error);
// Retry once with dismissal and refocus
await dismissKeyboardSafely();
await new Promise((resolve) => setTimeout(resolve, 500));
await elementToType.tap();
await new Promise((resolve) => setTimeout(resolve, 300));
await elementToType.typeText(text);
}
}
/**
* Retry an element visibility check with exponential backoff
* Helps handle race conditions during navigation and UI transitions
* @param elementToCheck - Detox element to check visibility
* @param timeout - Timeout for each attempt in milliseconds
* @param maxAttempts - Maximum number of retry attempts
*/
export async function waitForVisibilityWithRetry(
elementToCheck: Detox.NativeElement,
timeout = 10000,
maxAttempts = 3,
): Promise<void> {
/* eslint-disable no-await-in-loop, no-console */
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
await waitFor(elementToCheck).toBeVisible().withTimeout(timeout);
return;
} catch (error) {
if (attempt === maxAttempts) {
throw error;
}
console.warn(`[waitForVisibilityWithRetry] Attempt ${attempt}/${maxAttempts} failed, retrying...`);
// Exponential backoff: 1s, 2s, 4s
await new Promise((resolve) => setTimeout(resolve, 1000 * attempt));
}
}
/* eslint-enable no-await-in-loop, no-console */
}