CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-appium-python-client

Python client library for Appium mobile automation framework extending Selenium WebDriver with iOS and Android testing capabilities

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

advanced-features.mddocs/

Advanced Features

Advanced capabilities including context switching, screen recording, image comparison, clipboard operations, settings management, and Flutter integration. These features enable sophisticated testing scenarios and cross-platform automation.

Capabilities

Context Management

Switch between native app and web contexts for hybrid app testing and WebView automation.

@property
def contexts(self) -> list:
    """
    Get list of available contexts (app and web views).
    
    Returns:
        list: List of context names (e.g., ['NATIVE_APP', 'WEBVIEW_1'])
    """

@property
def current_context(self) -> str:
    """
    Get current active context name.
    
    Returns:
        str: Current context name
    """

@property
def context(self) -> str:
    """
    Alias for current_context property.
    
    Returns:
        str: Current context name
    """

@context.setter
def context(self, context_name: str):
    """
    Switch to specified context.
    
    Args:
        context_name (str): Context name to switch to
    """

Screen Recording

Record device screen during test execution for debugging and documentation purposes.

def start_recording_screen(self, **options):
    """
    Start screen recording with specified options.
    
    Args:
        **options: Recording options
            time_limit (int): Maximum recording time in seconds (default: 180)
            video_type (str): Video format ('mp4', 'mov') - iOS only
            video_quality (str): Video quality ('low', 'medium', 'high')
            video_fps (int): Frames per second
            video_scale (str): Video scale factor
            video_size (str): Video dimensions (e.g., '1280x720')
            bit_rate (int): Video bit rate - Android only
            bug_report (bool): Include bug report - Android only
    """

def stop_recording_screen(self) -> str:
    """
    Stop screen recording and get video data.
    
    Returns:
        str: Base64 encoded video data
    """

Image Comparison

Compare images and find sub-images within larger images for visual testing and element location.

def compare_images(self, base64_image1: str, base64_image2: str, **options) -> dict:
    """
    Compare two images and get similarity metrics.
    
    Args:
        base64_image1 (str): First image as base64 string
        base64_image2 (str): Second image as base64 string
        **options: Comparison options
            mode (str): Comparison mode ('matchFeatures', 'getSimilarity', 'matchTemplate')
            threshold (float): Similarity threshold (0.0-1.0)
            visualize (bool): Return visualization of differences
    
    Returns:
        dict: Comparison results with similarity score and visualization data
    """

def find_image_occurrence(self, full_image: str, partial_image: str, **options) -> dict:
    """
    Find occurrence of partial image within full image.
    
    Args:
        full_image (str): Full image as base64 string
        partial_image (str): Partial image to find as base64 string
        **options: Search options
            threshold (float): Match threshold (0.0-1.0)
            multiple (bool): Find multiple occurrences
            visualize (bool): Return visualization of matches
    
    Returns:
        dict: Match results with coordinates and confidence scores
    """

Settings Management

Configure Appium server settings and behavior during test execution.

def get_settings(self) -> dict:
    """
    Get current Appium settings.
    
    Returns:
        dict: Dictionary of current setting name-value pairs
    """

def update_settings(self, settings: dict):
    """
    Update Appium settings.
    
    Args:
        settings (dict): Dictionary of setting name-value pairs to update
            Examples:
                {'waitForIdleTimeout': 1000}
                {'shouldUseCompactResponses': False}
                {'elementResponseAttributes': 'type,label'}
    """

Execute Mobile Commands

Execute platform-specific mobile commands not available through standard WebDriver API.

def execute_mobile_command(self, command: str, **params):
    """
    Execute mobile-specific command.
    
    Args:
        command (str): Mobile command name (e.g., 'shell', 'deepLink', 'startPerfRecord')
        **params: Command-specific parameters
    
    Returns:
        Any: Command execution result (varies by command)
    """

Flutter Integration

Specialized finders and commands for Flutter app testing.

class FlutterFinder:
    @staticmethod
    def by_key(value: str):
        """
        Find Flutter element by key.
        
        Args:
            value (str): Flutter key value
            
        Returns:
            FlutterFinder: Finder instance for element location
        """
    
    @staticmethod  
    def by_text(value: str):
        """
        Find Flutter element by text content.
        
        Args:
            value (str): Text content to find
            
        Returns:
            FlutterFinder: Finder instance for element location
        """
    
    @staticmethod
    def by_semantics_label(value: str):
        """
        Find Flutter element by semantics label.
        
        Args:
            value (str): Semantics label value
            
        Returns:
            FlutterFinder: Finder instance for element location
        """
    
    @staticmethod
    def by_type(value: str):
        """
        Find Flutter element by widget type.
        
        Args:
            value (str): Flutter widget type name
            
        Returns:
            FlutterFinder: Finder instance for element location
        """
    
    @staticmethod
    def by_text_containing(value: str):
        """
        Find Flutter element by partial text match.
        
        Args:
            value (str): Partial text to match
            
        Returns:
            FlutterFinder: Finder instance for element location
        """

Usage Examples

Context Switching for Hybrid Apps

from appium import webdriver
from appium.options.android import UiAutomator2Options
from selenium.webdriver.common.by import By

# Setup for hybrid app testing
options = UiAutomator2Options()
options.platform_name = "Android"
options.device_name = "Android Emulator"
options.app = "/path/to/hybrid/app.apk"
driver = webdriver.Remote("http://localhost:4723", options=options)

# Check available contexts
print("Available contexts:", driver.contexts)
print("Current context:", driver.current_context)

# Switch to WebView context
webview_contexts = [ctx for ctx in driver.contexts if 'WEBVIEW' in ctx]
if webview_contexts:
    driver.context = webview_contexts[0]
    print(f"Switched to context: {driver.current_context}")
    
    # Now use standard web element finding
    web_element = driver.find_element(By.ID, "web-button")
    web_element.click()
    
    # Switch back to native context
    driver.context = "NATIVE_APP"
    print(f"Switched back to: {driver.current_context}")
    
    # Use mobile locators again
    from appium.webdriver.common.appiumby import AppiumBy
    native_element = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "native-button")
    native_element.click()

Screen Recording for Test Documentation

import base64
import time

def record_test_scenario(driver, test_name):
    """Record a test scenario with automatic file saving."""
    
    try:
        # Start recording with options
        driver.start_recording_screen(
            time_limit=300,  # 5 minutes max
            video_quality="medium",
            video_fps=10
        )
        
        print(f"Started recording for {test_name}")
        
        # Perform test actions
        yield  # This allows the calling code to run test steps
        
    finally:
        # Stop recording and save
        try:
            video_data = driver.stop_recording_screen()
            
            # Decode and save video
            video_bytes = base64.b64decode(video_data)
            filename = f"{test_name}_{int(time.time())}.mp4"
            
            with open(filename, 'wb') as f:
                f.write(video_bytes)
                
            print(f"Recording saved as {filename}")
            
        except Exception as e:
            print(f"Failed to save recording: {e}")

# Usage with context manager pattern
with record_test_scenario(driver, "login_test"):
    # Perform login test steps
    username_field = driver.find_element("id", "username")
    username_field.send_keys("testuser")
    
    password_field = driver.find_element("id", "password") 
    password_field.send_keys("password123")
    
    login_button = driver.find_element("id", "login")
    login_button.click()
    
    time.sleep(2)  # Wait for login to complete

Image-Based Testing and Validation

import base64
import cv2
import numpy as np

def capture_element_screenshot(driver, element):
    """Capture screenshot of specific element."""
    # Take full screenshot
    screenshot = driver.get_screenshot_as_png()
    
    # Get element location and size
    location = element.location
    size = element.size
    
    # Convert to numpy array
    nparr = np.frombuffer(screenshot, np.uint8)
    img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
    
    # Crop to element bounds
    x, y = location['x'], location['y']
    w, h = size['width'], size['height']
    element_img = img[y:y+h, x:x+w]
    
    # Convert back to base64
    _, buffer = cv2.imencode('.png', element_img)
    return base64.b64encode(buffer).decode('utf-8')

def visual_regression_test(driver, expected_image_path, element):
    """Perform visual regression testing on element."""
    
    # Capture current element appearance
    current_image = capture_element_screenshot(driver, element)
    
    # Load expected image
    with open(expected_image_path, 'rb') as f:
        expected_image = base64.b64encode(f.read()).decode('utf-8')
    
    # Compare images
    comparison_result = driver.compare_images(
        expected_image, 
        current_image,
        mode='getSimilarity',
        threshold=0.95
    )
    
    similarity = comparison_result.get('score', 0)
    print(f"Visual similarity: {similarity:.2%}")
    
    if similarity < 0.95:
        print("Visual regression detected!")
        # Save diff image if available
        if 'visualization' in comparison_result:
            save_diff_image(comparison_result['visualization'])
        return False
    
    return True

def find_ui_element_by_image(driver, template_image_path):
    """Find UI element using image template matching."""
    
    # Take screenshot
    screenshot = driver.get_screenshot_as_base64()
    
    # Load template image
    with open(template_image_path, 'rb') as f:
        template_image = base64.b64encode(f.read()).decode('utf-8')
    
    # Find image occurrence
    result = driver.find_image_occurrence(
        screenshot,
        template_image,
        threshold=0.8,
        visualize=True
    )
    
    if result.get('matches'):
        match = result['matches'][0]  # Get first match
        center_x = match['x'] + match['width'] // 2
        center_y = match['y'] + match['height'] // 2
        
        # Tap at found location
        driver.tap([(center_x, center_y)])
        return True
    
    return False

Settings and Performance Optimization

def optimize_test_performance(driver):
    """Optimize Appium settings for faster test execution."""
    
    # Get current settings
    current_settings = driver.get_settings()
    print("Current settings:", current_settings)
    
    # Update settings for performance
    performance_settings = {
        'waitForIdleTimeout': 1000,  # Reduce idle wait time
        'waitForSelectorTimeout': 5000,  # Reduce selector timeout
        'shouldUseCompactResponses': True,  # Use compact JSON responses
        'elementResponseAttributes': 'type,name,label,enabled,visible',  # Limit response data
        'mjpegServerScreenshotQuality': 50,  # Lower screenshot quality for speed
        'mjpegScalingFactor': 50  # Scale down screenshots
    }
    
    driver.update_settings(performance_settings)
    print("Applied performance optimizations")
    
    # Verify settings were applied
    updated_settings = driver.get_settings()
    print("Updated settings:", updated_settings)

def configure_debug_settings(driver):
    """Configure settings for debugging and troubleshooting."""
    
    debug_settings = {
        'waitForIdleTimeout': 10000,  # Longer idle timeout for debugging
        'waitForSelectorTimeout': 30000,  # Longer selector timeout
        'shouldUseCompactResponses': False,  # Full responses for debugging
        'elementResponseAttributes': '*',  # All element attributes
        'screenshotQuality': 100,  # High quality screenshots
        'mjpegScalingFactor': 100  # Full resolution screenshots
    }
    
    driver.update_settings(debug_settings)
    print("Applied debug settings")

Mobile Command Execution

def execute_platform_commands(driver):
    """Execute various platform-specific mobile commands."""
    
    # Android shell commands
    if driver.capabilities.get('platformName').lower() == 'android':
        # Execute shell command
        result = driver.execute_mobile_command('shell', command='getprop ro.build.version.release')
        print(f"Android version: {result}")
        
        # Deep link
        driver.execute_mobile_command('deepLink', 
                                    url='myapp://profile/123',
                                    package='com.example.myapp')
        
        # Start performance recording
        driver.execute_mobile_command('startPerfRecord', 
                                    timeout=30000,
                                    profileName='cpu')
        
        # Perform actions to measure
        perform_cpu_intensive_actions()
        
        # Stop performance recording
        perf_data = driver.execute_mobile_command('stopPerfRecord')
        save_performance_data(perf_data)
    
    # iOS specific commands
    elif driver.capabilities.get('platformName').lower() == 'ios':
        # Siri command
        driver.execute_mobile_command('siri', text='What time is it?')
        
        # Face ID simulation
        driver.execute_mobile_command('enrollBiometric', isEnabled=True)
        driver.execute_mobile_command('sendBiometricMatch', type='faceId', match=True)
        
        # Pasteboard access
        driver.execute_mobile_command('setPasteboard', content='Test clipboard content')
        clipboard_content = driver.execute_mobile_command('getPasteboard')
        print(f"Clipboard: {clipboard_content}")

def perform_cpu_intensive_actions():
    """Placeholder for CPU intensive test actions."""
    pass

def save_performance_data(data):
    """Save performance data for analysis."""
    pass

Flutter Integration Testing

from appium.webdriver.extensions.flutter_integration.flutter_finder import FlutterFinder

def flutter_testing_example(driver):
    """Example of Flutter-specific testing."""
    
    # Find elements using Flutter finders
    submit_button = driver.find_element(
        AppiumBy.FLUTTER_INTEGRATION_KEY, 
        FlutterFinder.by_key('submit_button')
    )
    submit_button.click()
    
    # Find by text content
    welcome_text = driver.find_element(
        AppiumBy.FLUTTER_INTEGRATION_TEXT,
        FlutterFinder.by_text('Welcome to the app')
    )
    assert welcome_text.is_displayed()
    
    # Find by widget type
    text_fields = driver.find_elements(
        AppiumBy.FLUTTER_INTEGRATION_TYPE,
        FlutterFinder.by_type('TextField')
    )
    
    # Fill in all text fields
    test_data = ['username', 'password', 'email@example.com']
    for i, field in enumerate(text_fields):
        if i < len(test_data):
            field.send_keys(test_data[i])
    
    # Find by semantics label
    navigation_button = driver.find_element(
        AppiumBy.FLUTTER_INTEGRATION_SEMANTICS_LABEL,
        FlutterFinder.by_semantics_label('Navigate to next page')
    )
    navigation_button.click()
    
    # Find by partial text
    help_elements = driver.find_elements(
        AppiumBy.FLUTTER_INTEGRATION_TEXT_CONTAINING,
        FlutterFinder.by_text_containing('Help')
    )
    
    for help_element in help_elements:
        print(f"Help element found: {help_element.text}")

Types

# Context types
ContextList = List[str]  # List of context names
ContextName = str       # Context identifier (e.g., 'NATIVE_APP', 'WEBVIEW_1')

# Recording types
RecordingOptions = Dict[str, Union[int, str, bool]]
VideoData = str         # Base64 encoded video data
RecordingTimeLimit = int  # Maximum recording time in seconds

# Image types
Base64Image = str       # Base64 encoded image data
ImagePath = str         # File path to image
SimilarityScore = float # 0.0 to 1.0 similarity score
MatchThreshold = float  # 0.0 to 1.0 match threshold

# Comparison results
ComparisonResult = Dict[str, Union[float, str, bool]]
OccurrenceResult = Dict[str, Union[List[dict], str, bool]]

# Settings types
SettingsDict = Dict[str, Union[str, int, bool]]
SettingName = str
SettingValue = Union[str, int, bool]

# Mobile command types
MobileCommand = str     # Command name
CommandParams = Dict[str, Union[str, int, bool, List]]
CommandResult = Union[str, dict, bool, None]

# Flutter types
FlutterKey = str        # Flutter widget key
FlutterText = str       # Flutter widget text
FlutterType = str       # Flutter widget type
SemanticsLabel = str    # Flutter semantics label

Install with Tessl CLI

npx tessl i tessl/pypi-appium-python-client

docs

advanced-features.md

android-platform.md

application-management.md

configuration-options.md

device-interaction.md

element-location.md

index.md

service-management.md

webdriver-core.md

tile.json