Advanced waiting and synchronization utilities for handling asynchronous operations, ensuring test stability, and managing timing-sensitive scenarios in mobile app testing.
Primary waiting mechanism for elements to reach expected states before proceeding with test execution.
/**
* Create waiting context for element state changes
* @param element - Element to wait for
* @returns WaitFor interface with assertion methods
*/
function waitFor(element: NativeElement): Expect<WaitFor>;
interface WaitFor {
/**
* Set maximum wait timeout
* @param millis - Timeout in milliseconds
* @returns Promise that resolves when condition is met or rejects on timeout
*/
withTimeout(millis: number): Promise<void>;
/**
* Perform actions while waiting for condition
* @param matcher - Matcher for element to interact with during wait
* @returns Action interface for wait-time interactions
*/
whileElement(matcher: NativeMatcher): NativeElementWaitableActions & WaitFor;
}
interface NativeElementWaitableActions {
/**
* Scroll element while waiting for condition
* @param pixels - Distance to scroll in pixels
* @param direction - Scroll direction
* @param startPositionX - Starting X position (0-1, NaN for auto)
* @param startPositionY - Starting Y position (0-1, NaN for auto)
*/
scroll(
pixels: number,
direction: Direction,
startPositionX?: number,
startPositionY?: number
): Promise<void>;
/**
* Tap element while waiting for condition
* @param point - Optional coordinates within element
*/
tap(point?: Point2D): Promise<void>;
/**
* Long press element while waiting for condition
* @param point - Optional coordinates within element
* @param duration - Optional press duration in milliseconds
*/
longPress(point?: Point2D, duration?: number): Promise<void>;
/**
* Multiple tap element while waiting for condition
* @param times - Number of taps to perform
*/
multiTap(times: number): Promise<void>;
/**
* Type text into element while waiting for condition
* @param text - Text to type
*/
typeText(text: string): Promise<void>;
/**
* Replace text in element while waiting for condition
* @param text - Replacement text
*/
replaceText(text: string): Promise<void>;
/**
* Clear text from element while waiting for condition
*/
clearText(): Promise<void>;
/**
* Tap backspace key while waiting for condition
*/
tapBackspaceKey(): Promise<void>;
/**
* Tap return key while waiting for condition
*/
tapReturnKey(): Promise<void>;
/**
* Scroll element to edge while waiting for condition
* @param edge - Target edge to scroll to
* @param startPositionX - Starting X position (0-1, NaN for auto)
* @param startPositionY - Starting Y position (0-1, NaN for auto)
*/
scrollTo(
edge: Direction,
startPositionX?: number,
startPositionY?: number
): Promise<void>;
/**
* Swipe element while waiting for condition
* @param direction - Swipe direction
* @param speed - Swipe speed
* @param percentage - Screen percentage to swipe (0-1)
* @param normalizedStartingPointX - Starting X position (0-1)
* @param normalizedStartingPointY - Starting Y position (0-1)
*/
swipe(
direction: Direction,
speed?: Speed,
percentage?: number,
normalizedStartingPointX?: number,
normalizedStartingPointY?: number
): Promise<void>;
/**
* Set picker column value while waiting for condition (iOS only)
* @param column - Column index
* @param value - Target value string
*/
setColumnToValue(column: number, value: string): Promise<void>;
/**
* Set date picker date while waiting for condition
* @param dateString - Date in specified format
* @param dateFormat - Format specification
*/
setDatePickerDate(dateString: string, dateFormat: string): Promise<void>;
/**
* Perform accessibility action while waiting for condition
* @param actionName - Name of accessibility action
*/
performAccessibilityAction(actionName: string): Promise<void>;
/**
* Pinch element while waiting for condition (iOS only)
* @param scale - Scale factor
* @param speed - Pinch speed
* @param angle - Rotation angle in radians
*/
pinch(scale: number, speed?: Speed, angle?: number): Promise<void>;
}Usage Examples:
// Basic waiting with timeout
await waitFor(element(by.id('loading_complete')))
.toBeVisible()
.withTimeout(10000);
// Wait for element to disappear
await waitFor(element(by.id('loading_spinner')))
.not.toBeVisible()
.withTimeout(5000);
// Wait for element existence
await waitFor(element(by.id('dynamic_content')))
.toExist()
.withTimeout(15000);
// Wait for specific text to appear
await waitFor(element(by.id('status_message')))
.toHaveText('Process Complete')
.withTimeout(30000);Perform actions while waiting for conditions to be met, useful for infinite scroll or progressive loading scenarios.
/**
* Perform scrolling actions while waiting for target condition
*/
interface NativeElementWaitableActions {
scroll(
pixels: number,
direction: Direction,
startPositionX?: number,
startPositionY?: number
): Promise<void>;
}Usage Examples:
// Scroll down while waiting for element to appear
await waitFor(element(by.text('Load More Button')))
.toBeVisible()
.whileElement(by.id('content_list'))
.scroll(50, 'down');
// Scroll horizontally while looking for specific item
await waitFor(element(by.id('target_item')))
.toBeVisible()
.whileElement(by.id('horizontal_scroll'))
.scroll(100, 'right')
.withTimeout(20000);
// Multiple scroll actions
await waitFor(element(by.text('End of List')))
.toBeVisible()
.whileElement(by.id('product_list'))
.scroll(200, 'down')
.withTimeout(60000);
// Tap while waiting for condition
await waitFor(element(by.id('submit_button')))
.toBeVisible()
.whileElement(by.id('refresh_button'))
.tap()
.withTimeout(10000);
// Swipe while waiting
await waitFor(element(by.text('Target Item')))
.toBeVisible()
.whileElement(by.id('carousel'))
.swipe('left', 'fast', 0.8)
.withTimeout(15000);
// Type text while waiting for condition
await waitFor(element(by.id('search_results')))
.toBeVisible()
.whileElement(by.id('search_input'))
.typeText('query text')
.withTimeout(8000);Control Detox's automatic synchronization system that monitors app state and waits for idle conditions.
/**
* Disable automatic synchronization with app
* WARNING: May require manual sleep() calls - use with caution
*/
async function disableSynchronization(): Promise<void>;
/**
* Re-enable automatic synchronization with app
* IMPORTANT: Only call when app can become idle again
*/
async function enableSynchronization(): Promise<void>;
/**
* Configure URL patterns to ignore during synchronization
* @param urls - Array of URL patterns (regex strings) to blacklist
*/
async function setURLBlacklist(urls: string[]): Promise<void>;Usage Examples:
// Handle continuous animations or timers
await device.disableSynchronization();
try {
// Perform actions in area with continuous activity
await element(by.id('animated_element')).tap();
await new Promise(resolve => setTimeout(resolve, 2000)); // Manual wait
await expect(element(by.id('result'))).toBeVisible();
} finally {
// Always re-enable synchronization
await device.enableSynchronization();
}
// Ignore specific network endpoints
await device.setURLBlacklist([
'.*analytics\\..*',
'.*tracking\\..*',
'.*127\\.0\\.0\\.1.*'
]);All standard assertion methods can be used with waitFor for flexible waiting conditions.
/**
* Available wait conditions (same as assertion methods)
*/
interface Expect<WaitFor> {
toBeVisible(pct?: number): WaitFor;
toExist(): WaitFor;
toBeFocused(): WaitFor;
toHaveText(text: string): WaitFor;
toHaveLabel(label: string): WaitFor;
toHaveId(id: string): WaitFor;
toHaveValue(value: any): WaitFor;
toHaveToggleValue(value: boolean): WaitFor;
not: Expect<WaitFor>;
}Usage Examples:
// Wait for visibility with specific percentage
await waitFor(element(by.id('partial_modal')))
.toBeVisible(50)
.withTimeout(8000);
// Wait for focus state
await waitFor(element(by.id('auto_focus_input')))
.toBeFocused()
.withTimeout(3000);
// Wait for specific value
await waitFor(element(by.id('counter_display')))
.toHaveValue('100')
.withTimeout(15000);
// Wait for toggle state change
await waitFor(element(by.id('network_status')))
.toHaveToggleValue(true)
.withTimeout(10000);
// Wait for text update
await waitFor(element(by.id('status_label')))
.toHaveText('Sync Complete')
.withTimeout(20000);Configure appropriate timeouts based on expected operation duration and system performance.
Timeout Guidelines:
// Quick UI updates (1-3 seconds)
await waitFor(element(by.id('button_press_feedback')))
.toBeVisible()
.withTimeout(3000);
// Standard network operations (5-10 seconds)
await waitFor(element(by.id('api_response_data')))
.toBeVisible()
.withTimeout(10000);
// Complex operations or slow networks (15-30 seconds)
await waitFor(element(by.id('file_upload_complete')))
.toBeVisible()
.withTimeout(30000);
// Background processing (30+ seconds)
await waitFor(element(by.id('batch_processing_done')))
.toBeVisible()
.withTimeout(60000);Handle timeout errors and provide meaningful debugging information.
// Timeout error handling
try {
await waitFor(element(by.id('expected_element')))
.toBeVisible()
.withTimeout(5000);
} catch (error) {
console.log('Element failed to appear:', error.message);
// Take screenshot for debugging
const screenshot = await device.takeScreenshot('timeout_failure');
console.log('Screenshot saved:', screenshot);
// Check if element exists but not visible
try {
await expect(element(by.id('expected_element'))).toExist();
console.log('Element exists but not visible');
} catch {
console.log('Element does not exist in hierarchy');
}
}
// Gradual timeout strategy
async function waitWithFallback(elementMatcher, primaryTimeout = 10000, fallbackTimeout = 30000) {
try {
await waitFor(element(elementMatcher))
.toBeVisible()
.withTimeout(primaryTimeout);
} catch (error) {
console.log('Primary timeout reached, trying longer wait...');
await waitFor(element(elementMatcher))
.toBeVisible()
.withTimeout(fallbackTimeout);
}
}Common patterns for complex synchronization scenarios.
// Handle progressive content loading
async function waitForAllContent() {
let previousCount = 0;
let stableCount = 0;
while (stableCount < 3) { // Wait for 3 stable counts
await waitFor(element(by.id('content_list')))
.toBeVisible()
.withTimeout(5000);
const items = await element(by.type('ListItem')).getAttributes();
const currentCount = items.elements ? items.elements.length : 1;
if (currentCount === previousCount) {
stableCount++;
} else {
stableCount = 0;
previousCount = currentCount;
}
if (stableCount === 0) {
// Trigger more loading
await element(by.id('content_list')).scroll(200, 'down');
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
}// Wait for multi-state transitions
async function waitForCompleteStateTransition() {
// Wait for loading to start
await waitFor(element(by.id('loading_indicator')))
.toBeVisible()
.withTimeout(5000);
// Wait for loading to complete
await waitFor(element(by.id('loading_indicator')))
.not.toBeVisible()
.withTimeout(30000);
// Wait for results to appear
await waitFor(element(by.id('results_container')))
.toBeVisible()
.withTimeout(10000);
}// Wait for one of multiple possible outcomes
async function waitForAnyOutcome(timeoutMs = 15000) {
const outcomes = [
{ matcher: by.id('success_message'), type: 'success' },
{ matcher: by.id('error_message'), type: 'error' },
{ matcher: by.id('warning_message'), type: 'warning' }
];
const promises = outcomes.map(outcome =>
waitFor(element(outcome.matcher))
.toBeVisible()
.withTimeout(timeoutMs)
.then(() => outcome.type)
.catch(() => null)
);
const results = await Promise.allSettled(promises);
const successfulResult = results.find(result =>
result.status === 'fulfilled' && result.value !== null
);
if (successfulResult) {
return successfulResult.value;
} else {
throw new Error('None of the expected outcomes appeared within timeout');
}
}// Use appropriate timeouts for different scenarios
const TIMEOUTS = {
IMMEDIATE: 1000, // UI feedback, button press response
SHORT: 3000, // Simple animations, transitions
STANDARD: 10000, // Network requests, data loading
LONG: 30000, // File uploads, complex processing
VERY_LONG: 60000 // Background sync, batch operations
};
await waitFor(element(by.id('spinner')))
.not.toBeVisible()
.withTimeout(TIMEOUTS.STANDARD);// Always specify timeout - never wait indefinitely
await waitFor(element(by.id('element')))
.toBeVisible()
.withTimeout(10000); // Required!
// This will do nothing without withTimeout():
// waitFor(element(by.id('element'))).toBeVisible(); // ❌ Wrong// Good: Wait for actual condition
await waitFor(element(by.id('data_loaded')))
.toBeVisible()
.withTimeout(15000);
// Bad: Arbitrary sleep
await new Promise(resolve => setTimeout(resolve, 5000)); // ❌ Unreliableinterface WaitFor {
withTimeout(millis: number): Promise<void>;
whileElement(matcher: NativeMatcher): NativeElementWaitableActions & WaitFor;
}
interface NativeElementWaitableActions {
scroll(pixels: number, direction: Direction, startPositionX?: number, startPositionY?: number): Promise<void>;
tap(point?: Point2D): Promise<void>;
longPress(point?: Point2D, duration?: number): Promise<void>;
multiTap(times: number): Promise<void>;
typeText(text: string): Promise<void>;
replaceText(text: string): Promise<void>;
clearText(): Promise<void>;
tapBackspaceKey(): Promise<void>;
tapReturnKey(): Promise<void>;
scrollTo(edge: Direction, startPositionX?: number, startPositionY?: number): Promise<void>;
swipe(direction: Direction, speed?: Speed, percentage?: number, normalizedStartingPointX?: number, normalizedStartingPointY?: number): Promise<void>;
setColumnToValue(column: number, value: string): Promise<void>;
setDatePickerDate(dateString: string, dateFormat: string): Promise<void>;
performAccessibilityAction(actionName: string): Promise<void>;
pinch(scale: number, speed?: Speed, angle?: number): Promise<void>;
}
type Direction = 'left' | 'right' | 'top' | 'bottom' | 'up' | 'down';