Your Appium test suite takes 45 minutes. The same functional coverage in Espresso runs in 8 minutes. Your CI pipeline is the bottleneck, and developers have stopped trusting the results because “the tests are always running.”
This is normal. Appium tests typically run 3-6x slower than native frameworks. But most of that slowdown is preventable.
Here are 7 fixes that reduced our test suite execution time by 60%, based on optimizations we’ve made running thousands of Appium tests across real devices.
Fix 1: Stop Using XPath
Impact: 50-80% faster element location (source, Appium best practices, Android UI testing docs)
XPath is the biggest performance killer in Appium tests. Here’s why:
iOS and Android don’t use XML. When you call findElement(By.xpath("...")), Appium must:
- Walk the entire UI hierarchy recursively
- Serialize it to XML (hundreds of elements on complex screens)
- Execute your XPath query against that XML
- Deserialize matching elements back to native references
- Return WebDriver element handles
This process takes 1-5 seconds on complex screens. Every. Single. Time.
The Fix
Replace XPath with native locator strategies:
// ❌ SLOW: XPath (1-5 seconds per lookup)
driver.findElement(By.xpath("//android.widget.Button[@text='Login']"));
// ✅ FAST: Resource ID (50-100ms)
driver.findElement(By.id("com.example:id/login_button"));
// ✅ FAST: Accessibility ID (50-100ms, cross-platform)
driver.findElement(MobileBy.accessibilityId("login_button"));
// ✅ FAST: UiAutomator selector (Android, 30-80ms)
driver.findElement(MobileBy.AndroidUIAutomator(
"new UiSelector().resourceId(\"com.example:id/login_button\")"
));
// ✅ FAST: iOS Predicate (iOS, 30-80ms)
driver.findElement(MobileBy.iOSNsPredicateString(
"type == 'XCUIElementTypeButton' AND name == 'Login'"
));
Locator Speed Comparison
| Locator Strategy | Average Time | Relative Speed |
|---|---|---|
| Resource ID / Accessibility ID | 50-100ms | Baseline |
| iOS Predicate / Class Chain | 30-80ms | 20% faster |
| UiAutomator (Android) | 30-80ms | 20% faster |
| CSS Selector (WebView only) | 100-200ms | 2x slower |
| XPath | 1,000-5,000ms | 10-50x slower |
Action item: Audit your test code for XPath usage. Every XPath locator is a performance bug.
Fix 2: Reduce Session Startup Overhead
Impact: 2-5 minutes saved per test run
Appium session creation is expensive. The default fullReset capability reinstalls the app for every test, adding 30-90 seconds.
The Fix
Use noReset for most tests, fullReset only when necessary:
// ❌ SLOW: Full reset every test (30-90 seconds startup)
capabilities.setCapability("fullReset", true);
// ✅ FAST: No reset, reuse app state (5-10 seconds startup)
capabilities.setCapability("noReset", true);
// ✅ BALANCED: Clear app data without reinstall (10-20 seconds)
capabilities.setCapability("fullReset", false);
capabilities.setCapability("noReset", false);
Session Strategy by Test Type
| Test Type | Reset Strategy | Startup Time |
|---|---|---|
| Smoke tests | noReset | 5-10s |
| Feature tests | noReset + manual logout | 5-10s |
| Data isolation tests | fullReset=false | 10-20s |
| Fresh install tests | fullReset=true | 30-90s |
Pro tip: Structure your test suite so tests don’t depend on fresh state. Use @BeforeEach to navigate to a known state rather than reinstalling.
Fix 3: Replace Implicit Waits with Explicit Waits
Impact: 30-50% reduction in flaky test time
Implicit waits are a performance trap. When set to 30 seconds, every failed element lookup waits the full duration before throwing.
// ❌ SLOW: Implicit wait affects ALL lookups
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(30));
// Finding a non-existent element? Wait 30 seconds. Every time.
driver.findElement(By.id("element_that_might_not_exist"));
The Fix
Use explicit waits targeted to specific conditions:
// ✅ FAST: Explicit wait for specific element
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
// Wait only when needed, with specific conditions
WebElement loginButton = wait.until(
ExpectedConditions.elementToBeClickable(By.id("login_button"))
);
// For elements that should exist immediately, use short timeout
WebDriverWait shortWait = new WebDriverWait(driver, Duration.ofSeconds(2));
WebElement header = shortWait.until(
ExpectedConditions.presenceOfElementLocated(By.id("header"))
);
Wait Strategy Guide
| Scenario | Wait Type | Timeout |
|---|---|---|
| Navigation complete | Explicit | 10-15s |
| Element clickable | Explicit | 5-10s |
| Element present | Explicit | 2-5s |
| Element NOT present | Explicit with catch | 1-2s |
| Page load | Explicit | 15-30s |
Never use: Thread.sleep(). It always waits the full duration even if the condition is met immediately.
Fix 4: Batch Actions to Reduce Round Trips
Impact: 40-60% faster for action-heavy tests
Every Appium command is a network round trip. On cloud platforms, that’s 100-500ms per command. Ten commands = 1-5 seconds of pure latency.
The Fix
Batch operations where possible:
// ❌ SLOW: 6 round trips
driver.findElement(By.id("username")).click();
driver.findElement(By.id("username")).clear();
driver.findElement(By.id("username")).sendKeys("[email protected]");
driver.findElement(By.id("password")).click();
driver.findElement(By.id("password")).clear();
driver.findElement(By.id("password")).sendKeys("password123");
// ✅ FASTER: 4 round trips using clear+sendKeys
WebElement username = driver.findElement(By.id("username"));
username.clear();
username.sendKeys("[email protected]");
WebElement password = driver.findElement(By.id("password"));
password.clear();
password.sendKeys("password123");
// ✅ FASTEST: 2 round trips using setValue (if supported)
driver.findElement(By.id("username")).sendKeys("[email protected]");
driver.findElement(By.id("password")).sendKeys("password123");
Use TouchAction for Gestures
Individual swipe commands are slow. Use TouchAction for complex gestures:
// ❌ SLOW: Multiple separate swipe calls
// ... 5 separate swipe commands
// ✅ FAST: Single TouchAction chain
TouchAction action = new TouchAction(driver);
action
.press(PointOption.point(500, 1500))
.waitAction(WaitOptions.waitOptions(Duration.ofMillis(500)))
.moveTo(PointOption.point(500, 500))
.release()
.perform();
Fix 5: Optimize Capability Configuration
Impact: 10-30% faster session creation and execution
Wrong capabilities slow everything down. Here are the optimizations that matter:
Android Optimizations
DesiredCapabilities caps = new DesiredCapabilities();
// Use UiAutomator2 (faster than deprecated UiAutomator)
caps.setCapability("automationName", "UiAutomator2");
// Skip screenshot comparison (faster)
caps.setCapability("disableWindowAnimation", true);
// Skip unneeded checks
caps.setCapability("skipDeviceInitialization", true);
caps.setCapability("skipServerInstallation", true);
// Faster element lookup
caps.setCapability("ignoreUnimportantViews", true);
// Reduce logging overhead
caps.setCapability("enablePerformanceLogging", false);
iOS Optimizations
DesiredCapabilities caps = new DesiredCapabilities();
// Use XCUITest driver
caps.setCapability("automationName", "XCUITest");
// Faster launch
caps.setCapability("useNewWDA", false); // Reuse WebDriverAgent
caps.setCapability("wdaStartupRetries", 2);
// Skip screenshot overhead
caps.setCapability("screenshotQuality", 0);
// Reduce element tree serialization
caps.setCapability("snapshotMaxDepth", 50);
caps.setCapability("customSnapshotTimeout", 15);
Fix 6: Run Tests on Real Devices (Properly)
Impact: 50-200% faster than emulators for complex tests
Emulators look fast for simple tests but choke on complex scenarios. See our emulator vs real device guide for when to use each. They struggle with:
- Complex UI rendering
- Heavy animations
- Multi-touch gestures
- Performance-sensitive flows
Real Device Advantages
| Scenario | Emulator | Real Device | Difference |
|---|---|---|---|
| Simple click test | 2s | 1.5s | 25% faster |
| Form with 10 fields | 15s | 8s | 47% faster |
| Scroll + load test | 25s | 10s | 60% faster |
| Animation-heavy flow | 40s | 12s | 70% faster |
The Catch: Cloud Latency
Cloud device farms (BrowserStack, LambdaTest) add network latency that can negate real device benefits:
Local emulator: 50ms command latency
Cloud real device: 150-300ms command latency
Local real device: 10-30ms command latency
Best setup: Real devices on your local network. You get real device accuracy with local network speed.
Fix 7: Parallelize Intelligently
Impact: Linear speedup (5 devices = 5x faster)
If you have 500 tests taking 50 minutes sequentially, running on 10 devices in parallel takes ~5 minutes.
Parallelization Strategies
Strategy 1: Test-level parallelism (TestNG/JUnit)
<!-- testng.xml -->
<suite name="Mobile Tests" parallel="tests" thread-count="10">
<test name="Login Tests">
<classes>
<class name="com.example.LoginTest"/>
</classes>
</test>
<!-- More test classes... -->
</suite>
Strategy 2: Method-level parallelism
<suite name="Mobile Tests" parallel="methods" thread-count="10">
Strategy 3: Data-driven parallelism
@DataProvider(parallel = true)
public Object[][] loginData() {
return new Object[][] {
{"[email protected]", "pass1"},
{"[email protected]", "pass2"},
// ...
};
}
Parallelization Requirements
Each parallel execution needs:
- Separate Appium server instance (or cloud session)
- Separate device
- Isolated test data
- Thread-safe test code
Cost Comparison
| Approach | 10 Parallel Tests | Monthly Cost |
|---|---|---|
| BrowserStack | 10 parallel sessions | ~$2,000/month |
| LambdaTest | 10 parallel sessions | ~$1,600/month |
| Own Devices + DeviceLab | 10 real devices | ~$1,000/month |
| Own Devices + Appium Grid | 10 devices + maintenance | ~$500/month + time |
Bonus: Consider Alternatives for Speed-Critical Tests
Sometimes the answer isn’t “optimize Appium” but “use something faster.”
Framework Speed Comparison
| Framework | Relative Speed | Cross-Platform | Learning Curve |
|---|---|---|---|
| Espresso (Android) | Baseline (fastest) | No | Medium |
| XCUITest (iOS) | Baseline (fastest) | No | Medium |
| Maestro | 2-3x Espresso | Yes | Low |
| Appium (optimized) | 3-5x Espresso | Yes | High |
| Appium (unoptimized) | 10-20x Espresso | Yes | High |
Recommendation: Use Appium for cross-platform coverage. Use Maestro or native frameworks for your critical-path smoke tests that run on every commit. Compare costs in our BrowserStack pricing analysis.
Implementation Checklist
- Audit codebase for XPath usage—replace with accessibility IDs
- Switch from implicit to explicit waits
- Set
noReset: trueas default,fullResetonly when needed - Add UiAutomator2/XCUITest specific capabilities
- Batch element interactions where possible
- Set up parallel execution infrastructure
- Profile your slowest tests and optimize them first
Measuring Your Progress
Before and after each optimization, measure:
# Total suite time
time ./gradlew connectedAndroidTest
# Individual test time
# Add timing annotations or use test framework reporting
Target improvements:
- XPath removal: 50-80% faster element finding
- Session optimization: 2-5 minute reduction per run
- Wait optimization: 30-50% reduction in flaky retries
- Parallelization: Linear scaling with device count