or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

assertions.mdconfiguration.mddevice-management.mdelement-interaction.mdelement-selection.mdindex.mdsynchronization.mdweb-view-testing.md
tile.json

synchronization.mddocs/

Synchronization

Advanced waiting and synchronization utilities for handling asynchronous operations, ensuring test stability, and managing timing-sensitive scenarios in mobile app testing.

Capabilities

WaitFor API

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);

Action-Based Waiting

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);

Automatic Synchronization Control

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.*'
]);

Wait Conditions

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);

Timeout Management

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);

Error Handling and Debugging

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);
  }
}

Advanced Synchronization Patterns

Common patterns for complex synchronization scenarios.

Progressive Loading

// 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));
  }
}

State Transitions

// 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);
}

Conditional Waiting

// 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');
  }
}

Best Practices

Timeout Selection

// 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 Use Timeouts

// 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

Prefer waitFor Over Manual Delays

// 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)); // ❌ Unreliable

Types

interface 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';