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

service-management.mddocs/

Service Management

Appium server lifecycle management including startup, shutdown, and connectivity validation for local and remote testing scenarios. These capabilities enable automated server management and testing infrastructure setup.

Capabilities

AppiumService Class

Service class for managing Appium server processes with startup, shutdown, and monitoring capabilities.

class AppiumService:
    def __init__(self):
        """Initialize Appium service instance."""
    
    def start(self, **kwargs):
        """
        Start Appium server with specified options.
        
        Args:
            **kwargs: Server startup options
                timeout (int): Startup timeout in seconds
                args (list): Additional command line arguments
                env (dict): Environment variables
                stdout (file): Stdout redirection
                stderr (file): Stderr redirection
        
        Raises:
            AppiumServiceError: If service fails to start
        """
    
    def stop(self, timeout: int = None):
        """
        Stop running Appium server.
        
        Args:
            timeout (int, optional): Shutdown timeout in seconds
        
        Raises:
            AppiumServiceError: If service fails to stop cleanly
        """
    
    @property
    def is_running(self) -> bool:
        """
        Check if Appium service is currently running.
        
        Returns:
            bool: True if service is running, False otherwise
        """
    
    @property
    def is_listening(self) -> bool:
        """
        Check if service is listening on configured port.
        
        Returns:
            bool: True if service is accepting connections, False otherwise
        """

Service Utility Functions

Standalone utility functions for service discovery, validation, and executable location.

def is_service_listening(url: str, timeout: int = None, custom_validator = None) -> bool:
    """
    Check if service is listening at specified URL.
    
    Args:
        url (str): Service URL to check
        timeout (int, optional): Connection timeout in seconds
        custom_validator (callable, optional): Custom validation function
    
    Returns:
        bool: True if service is listening and responsive
    """

def find_executable(executable: str) -> str:
    """
    Find executable in system PATH.
    
    Args:
        executable (str): Executable name to find
    
    Returns:
        str: Full path to executable
    
    Raises:
        FileNotFoundError: If executable not found in PATH
    """

def get_node() -> str:
    """
    Get Node.js executable path.
    
    Returns:
        str: Path to node executable
    
    Raises:
        FileNotFoundError: If Node.js not found
    """

def get_npm() -> str:
    """
    Get NPM executable path.
    
    Returns:
        str: Path to npm executable
    
    Raises:
        FileNotFoundError: If NPM not found
    """

Service Exceptions

Exception classes for service-related errors and diagnostics.

class AppiumServiceError(Exception):
    """General Appium service error."""
    pass

class AppiumStartupError(AppiumServiceError):
    """Exception raised when Appium service fails to start."""
    pass

Usage Examples

Basic Service Management

from appium.webdriver.appium_service import AppiumService
from appium import webdriver
from appium.options.android import UiAutomator2Options
import time

# Create and configure service
service = AppiumService()

try:
    # Start Appium server
    print("Starting Appium service...")
    service.start()
    
    # Wait for service to be ready
    timeout = 30
    start_time = time.time()
    
    while not service.is_listening and (time.time() - start_time) < timeout:
        time.sleep(1)
    
    if service.is_listening:
        print("Appium service is ready")
        
        # Create WebDriver session
        options = UiAutomator2Options()
        options.platform_name = "Android"
        options.device_name = "Android Emulator"
        
        driver = webdriver.Remote("http://localhost:4723", options=options)
        
        # Run tests
        perform_tests(driver)
        
        # Clean up
        driver.quit()
    else:
        print("Service failed to start within timeout")
        
except Exception as e:
    print(f"Error during testing: {e}")
    
finally:
    # Always stop service
    if service.is_running:
        print("Stopping Appium service...")
        service.stop()

def perform_tests(driver):
    """Placeholder for test logic."""
    pass

Advanced Service Configuration

import subprocess
import os

# Start service with custom configuration
def start_appium_with_config():
    service = AppiumService()
    
    # Custom environment variables
    env = os.environ.copy()
    env['APPIUM_LOG_LEVEL'] = 'debug'
    env['ANDROID_HOME'] = '/path/to/android/sdk'
    
    # Custom command line arguments
    args = [
        '--port', '4724',
        '--session-override',
        '--relaxed-security',
        '--log-timestamp',
        '--log', '/tmp/appium.log'
    ]
    
    # Start with custom configuration
    service.start(
        args=args,
        env=env,
        timeout=60
    )
    
    return service

# Usage
service = start_appium_with_config()

if service.is_listening:
    print("Custom Appium service started successfully")
    # Use service with WebDriver at http://localhost:4724
else:
    print("Failed to start custom service")

Service Discovery and Validation

from appium.webdriver.appium_service import is_service_listening, find_executable

# Check if Appium server is already running
def check_existing_service():
    urls_to_check = [
        "http://localhost:4723",
        "http://localhost:4724", 
        "http://127.0.0.1:4723"
    ]
    
    for url in urls_to_check:
        if is_service_listening(url, timeout=5):
            print(f"Found running Appium service at {url}")
            return url
    
    print("No running Appium service found")
    return None

# Find required executables
def check_prerequisites():
    """Check if required tools are available."""
    try:
        node_path = find_executable("node")
        print(f"Node.js found at: {node_path}")
        
        npm_path = find_executable("npm")
        print(f"NPM found at: {npm_path}")
        
        appium_path = find_executable("appium")
        print(f"Appium found at: {appium_path}")
        
        return True
    except FileNotFoundError as e:
        print(f"Missing prerequisite: {e}")
        return False

# Custom service validator
def custom_validator(response):
    """Custom validation for service health."""
    if response.status_code == 200:
        data = response.json()
        return data.get('status') == 0  # Appium returns status 0 for success
    return False

# Check service with custom validation
def validate_service_health(url):
    return is_service_listening(
        url, 
        timeout=10, 
        custom_validator=custom_validator
    )

# Usage
if check_prerequisites():
    existing_service = check_existing_service()
    if existing_service:
        if validate_service_health(existing_service):
            print("Service is healthy and ready")
        else:
            print("Service is running but not healthy")

Multi-Service Management

class MultiServiceManager:
    """Manage multiple Appium services for parallel testing."""
    
    def __init__(self):
        self.services = {}
    
    def start_service(self, name, port, **kwargs):
        """Start named service on specific port."""
        service = AppiumService()
        
        args = kwargs.get('args', [])
        args.extend(['--port', str(port)])
        kwargs['args'] = args
        
        try:
            service.start(**kwargs)
            
            # Wait for service to be ready
            service_url = f"http://localhost:{port}"
            if self._wait_for_service(service_url):
                self.services[name] = {
                    'service': service,
                    'port': port,
                    'url': service_url
                }
                print(f"Service '{name}' started on port {port}")
                return True
            else:
                service.stop()
                return False
                
        except Exception as e:
            print(f"Failed to start service '{name}': {e}")
            return False
    
    def stop_service(self, name):
        """Stop named service."""
        if name in self.services:
            service_info = self.services[name]
            service_info['service'].stop()
            del self.services[name]
            print(f"Service '{name}' stopped")
    
    def stop_all(self):
        """Stop all managed services."""
        for name in list(self.services.keys()):
            self.stop_service(name)
    
    def get_service_url(self, name):
        """Get URL for named service."""
        return self.services.get(name, {}).get('url')
    
    def _wait_for_service(self, url, timeout=30):
        """Wait for service to be ready."""
        import time
        start_time = time.time()
        
        while (time.time() - start_time) < timeout:
            if is_service_listening(url, timeout=2):
                return True
            time.sleep(1)
        
        return False

# Usage example
manager = MultiServiceManager()

try:
    # Start services for parallel testing
    manager.start_service("android_service", 4723)
    manager.start_service("ios_service", 4724)
    
    # Run parallel tests
    android_url = manager.get_service_url("android_service")
    ios_url = manager.get_service_url("ios_service")
    
    if android_url and ios_url:
        # Create drivers for both platforms
        android_driver = create_android_driver(android_url)
        ios_driver = create_ios_driver(ios_url)
        
        # Run tests in parallel
        run_parallel_tests(android_driver, ios_driver)
        
        # Clean up drivers
        android_driver.quit()
        ios_driver.quit()

finally:
    # Stop all services
    manager.stop_all()

Error Handling and Recovery

from appium.webdriver.appium_service import AppiumServiceError, AppiumStartupError
import time

def robust_service_start(max_retries=3, base_port=4723):
    """Start Appium service with retry logic and port selection."""
    
    for attempt in range(max_retries):
        port = base_port + attempt
        service = AppiumService()
        
        try:
            print(f"Attempt {attempt + 1}: Starting service on port {port}")
            
            args = ['--port', str(port), '--session-override']
            service.start(args=args, timeout=60)
            
            # Verify service is responding
            service_url = f"http://localhost:{port}"
            if wait_for_service_ready(service_url, timeout=30):
                print(f"Service successfully started on port {port}")
                return service, service_url
            else:
                print(f"Service started but not responding on port {port}")
                service.stop()
                
        except AppiumStartupError as e:
            print(f"Startup failed on port {port}: {e}")
            try:
                service.stop()
            except:
                pass
                
        except Exception as e:
            print(f"Unexpected error on port {port}: {e}")
            try:
                service.stop()
            except:
                pass
        
        # Wait before next attempt
        if attempt < max_retries - 1:
            time.sleep(5)
    
    raise AppiumServiceError(f"Failed to start service after {max_retries} attempts")

def wait_for_service_ready(url, timeout=30):
    """Wait for service to be ready with health check."""
    start_time = time.time()
    
    while (time.time() - start_time) < timeout:
        try:
            if is_service_listening(url, timeout=5):
                # Additional health check
                response = requests.get(f"{url}/status", timeout=5)
                if response.status_code == 200:
                    return True
        except:
            pass
        
        time.sleep(2)
    
    return False

# Usage with error handling
try:
    service, service_url = robust_service_start()
    
    # Use service for testing
    options = UiAutomator2Options()
    driver = webdriver.Remote(service_url, options=options)
    
    # Run tests
    run_tests(driver)
    
except AppiumServiceError as e:
    print(f"Service management error: {e}")
    # Handle service error (e.g., use remote service)
    
except Exception as e:
    print(f"Test execution error: {e}")
    
finally:
    # Clean up
    if 'driver' in locals():
        driver.quit()
    if 'service' in locals() and service.is_running:
        service.stop()

Types

# Service types
ServiceURL = str  # Format: "http://hostname:port"
Port = int       # Port number (typically 4723)
ExecutablePath = str  # Full path to executable
Timeout = int    # Timeout in seconds

# Service configuration
ServiceArgs = List[str]  # Command line arguments
ServiceEnv = Dict[str, str]  # Environment variables
ServiceOptions = Dict[str, Union[int, List[str], Dict[str, str]]]

# Service state
ServiceStatus = bool  # Running/stopped state
ConnectionStatus = bool  # Listening/not listening

# Exception types
ServiceError = AppiumServiceError
StartupError = AppiumStartupError

# Validator function type
ValidatorFunction = Callable[[Any], bool]

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