Improve stability of your App Automate tests
Learn how to prevent common causes of flaky or failed Appium tests on BrowserStack App Automate real devices.
Flaky or failing Appium tests on BrowserStack App Automate usually trace back to a handful of root causes: races between your test code and the app UI, unreliable locators, capability misconfiguration, or infrastructure limits like session timeouts and parallel caps. This guide collects best practices across the test lifecycle, from writing stable test code to debugging a failed session, so you can build a reliable Appium suite on App Automate real devices. Where a topic already has a dedicated deep-dive page, this guide keeps its own treatment short and links out to the full page.
Jump to a section:
- Write stable tests
- Locate elements reliably
- Handle timeouts
- Configure capabilities
- App upload and install errors
- Fix session start failures
- Parallel test and queuing errors
- Test WebViews and hybrid apps
- Handle permissions and system pop-ups
- Network and connection issues
- iOS-specific guidance
- Debug failed sessions
Write stable tests
Replace fixed sleeps with explicit waits
A fixed sleep either wastes time when the app responds quickly, or fails outright when the app is slower than the sleep duration. Use an explicit wait that ends as soon as the condition you’re waiting for is met.
The same principle applies in Java, using WebDriverWait with ExpectedConditions instead of Thread.sleep:
Always quit the driver in teardown
End every test with a clean driver.quit(), wrapped in try/finally in Python or an @AfterMethod in Java, rather than letting the test runner exit without closing the session. An unclosed session keeps consuming a parallel slot until it times out. See Fix the idle timeout error for the full teardown pattern and how BrowserStack handles idle sessions.
Add waits after app state transitions
After you background, foreground, or relaunch the app, wait for a known element before you interact with the screen instead of assuming the transition finished instantly:
Locate elements reliably
Choose your locator strategy in the following order:
| Priority | Locator strategy | Use case |
|---|---|---|
| 1 | Accessibility ID | Elements that expose an accessibility label on Android or iOS. |
| 2 | ID or resource-id | Android elements with a stable resource ID. |
| 3 | iOS predicate string or class chain | Native iOS elements without a stable accessibility label. |
| 4 | XPath | Last resort only. Fragile across OS versions and device sizes. |
Prefer attribute-based XPath over index-based XPath
When XPath is your only option, avoid index-based paths that break the moment the view hierarchy shifts. Use a stable attribute instead:
Locate elements in Jetpack Compose apps
Enable testTagsAsResourceId in your test build so Compose test tags are exposed as resource IDs:
Once you enable this setting, locate the tagged element by its resource-id, the same way you locate any other Android element.
Work with deep iOS UI hierarchies
Deeply nested iOS view hierarchies slow down element lookups and page source generation. Appium’s generic snapshotMaxDepth setting controls how far into the hierarchy the driver traverses:
BrowserStack also offers its own grid-sampling mechanism for this same problem. See Find deeply nested elements in iOS for how bstackPageSource works and when to use it instead of, or alongside, snapshotMaxDepth.
Re-query elements after navigation
Never cache an element reference across a screen transition. The reference goes stale the moment the underlying view is recreated:
Handle timeouts
Keep a session alive during long-running steps
When a step waits on an external system, such as a CI deployment, a payment gateway, or file processing, send a periodic no-op command from a background thread so the session doesn’t hit an idle timeout:
For idle-timeout capability details, including the configurable range and default value, see Fix the idle timeout error.
Know the session hard limit
App Automate sessions have a default maximum duration of 2 hours. Extended limits are available on enterprise plans. Otherwise, split long test suites into shorter parallel runs.
See the timeouts reference for the complete list of configurable and fixed timeouts on App Automate.
Configure capabilities
Build your base capability set with the BrowserStack capability generator. Device names, OS versions, and Appium version strings must match BrowserStack’s supported values exactly.
Avoid the two most common capability mistakes
A device name that doesn’t exist, such as Samsung S24 instead of Samsung Galaxy S24, fails the session immediately with no useful error. Mixing legacy browserstack.* keys with bstack:options in the same capability set is also a common source of confusion. Use bstack:options consistently:
Start from a minimal baseline when debugging
When you’re debugging a failure, strip your capabilities down to the minimum needed to reproduce it. Experimental or undocumented capabilities are a common source of session start failures, and a minimal baseline rules them out quickly.
App upload and install errors
Upload your app through the REST API as a CI step before your test run, not from inside the test itself, and store the resulting app_url as an environment variable:
Understand common upload errors
An upload error’s HTTP status code doesn’t always point to the same cause. Check which of the following applies:
| HTTP status | Cause | What to do |
|---|---|---|
| 422 Unprocessable Entity | The uploaded file isn’t accepted, for example an unsupported file type, a corrupt file, or an unsupported build variant. | Check the BROWSERSTACK_* error code in the response body and fix the file accordingly. |
| 403 Forbidden | Your account doesn’t have App Management permission. | Ask your organization admin to grant App Management permission. |
Handle large app uploads from CI
For APK or IPA files above 200 MB, up to the 1 GB limit, a 504 Gateway Timeout usually means the upload needs more time than your test framework’s runner allows. Upload the app in a dedicated CI step with a generous timeout instead of relying on an SDK upload helper:
If uploads still time out, use curl directly with --max-time 300 rather than an SDK upload helper with a lower internal timeout, and consider maintaining a smaller app variant for smoke tests.
Fix session start failures
When a session never starts, work through this checklist:
Validate your device and OS strings
Check them against the https://api-cloud.browserstack.com/app-automate/devices.json endpoint.
Check your credentials
Confirm BROWSERSTACK_USERNAME and BROWSERSTACK_ACCESS_KEY are set correctly in your environment.
Verify the app_url
Confirm the app_url belongs to your account and hasn’t expired.
Try a minimal capability set
Strip capabilities down to the minimum, then add them back one at a time until the session starts, or the failure reappears.
Check hub connectivity from your CI environment
hub-cloud.browserstack.com on port 443 must be reachable. If you’re behind a corporate VPN or firewall, test with curl -v https://hub-cloud.browserstack.com.
Parallel test and queuing errors
Understand why sessions queue
Sessions queue when you hit your plan’s parallel session limit, or when the specific device you requested is temporarily unavailable. See All parallels in use issues and Queue your tests for the full detail on queuing behavior.
Check your shard configuration
Make sure every shard has at least one test assigned after filtering. An empty shard errors out immediately, and that error can be mistaken for a stability failure rather than a configuration problem.
Use one driver instance per thread
Sharing an Appium driver instance across threads is the most common cause of intermittent parallel failures. Use a ThreadLocal driver instead of a shared static field:
Test WebViews and hybrid apps
Enable WebView debugging in your test build
Use the no-rebuild alternative on Android
If you can’t rebuild your app, the enableWebviewDebug capability patches it at install time on Android:
This capability is Android only, it fails with BROWSERSTACK_INCOMPATIBLE_OS on iOS. It isn’t supported for .apks files, where it fails with BROWSERSTACK_UNSUPPORTED_CAPABILITY_WITH_APKS. It also can’t be combined with resignApp: false.
Wait for the WebView context before switching
Poll for the WebView context with an explicit wait instead of switching the instant the WebView renders, and switch back to the native context when you finish:
Pin the ChromeDriver version
Pin appium:chromedriverVersion to match your app’s embedded Chromium build. Check the build with adb shell dumpsys package com.android.webview | grep versionName:
Watch for views outside the accessibility tree
Native views rendered outside the accessibility tree, such as Google Maps or Metal and OpenGL layers, don’t appear in Appium’s page source even when they’re visible on screen. Use coordinate-based taps for these views only as a last resort.
See App WebView issues for the full WebView troubleshooting page.
Handle permissions and system pop-ups
Pre-grant permissions at install
See Handle permission pop-ups for the full permission-handling page.
Handle first-run dialogs explicitly
Don’t assume onboarding screens, update prompts, or first-run dialogs won’t appear. Handle them explicitly in your test setup:
Expect third-party system dialogs
Third-party system dialogs, such as Google Play Services prompts, Apple ID sign-in, or OEM overlays on devices like Samsung or Xiaomi, are injected by the device OS. BrowserStack can’t suppress them. Handle these dialogs explicitly in your test flow, or use a pre-authenticated test account.
Network and connection issues
Keep networkLogs set to false, its default, unless you’re actively debugging network traffic. When enabled, BrowserStack routes traffic through an internal proxy, which can interfere with SSL or certificate pinning, Firebase and FCM push notifications, OAuth and SSO flows, and apps that check the network interface name.
Exclude specific hosts from network log capture
If only a few hosts cause issues when networkLogs is enabled, such as a pinned-certificate API or an FCM endpoint, exclude them from the proxy instead of disabling network logs entirely:
These rules are enforced at session start:
-
networkLogsExcludeHostsrequiresnetworkLogsto betrue. OmittingnetworkLogs: truefails the session withtls_passthrough_networklogs_cap_not_set. -
networkLogsExcludeHostsandnetworkLogsIncludeHostsare mutually exclusive. Passing both fails the session. - Each list accepts a maximum of 10 hosts, and each host entry, including a regex string, is capped at 200 characters.
Reach non-public backends with BrowserStack Local
For apps that connect to internal staging environments, localhost, or allowlisted IP ranges, use BrowserStack Local with a unique localIdentifier per CI build. Don’t share one tunnel identifier across concurrent CI pipelines:
Fix SSL pinning failures in order of preference
Try these in order: disable network logs first, and if that doesn’t resolve the failure, disable SSL pinning in test builds only, never in production. See Accept insecure certificates and Network logs and insecure certs issues for the full detail.
iOS-specific guidance
Setting resignApp to false preserves entitlements that re-signing would otherwise strip, such as push notifications, universal links, iCloud and keychain access, and Apple Pay. It comes with trade-offs: enableWebviewDebug, biometric injection, and camera or video injection stop working, and your app needs an enterprise-signed build with a valid, non-expired provisioning profile. See Re-sign iOS apps for the full re-signing behavior and iOS app entitlement issues for the entitlement-loss detail.
Debug failed sessions
Work through this sequence before you change any test code:
Watch the session video
Open the session in the App Automate dashboard and watch the video from the point where the failure started.
Check the Appium logs
Find the last command before the failure, and look for 502 or 504 status codes or a SessionNotCreatedException.
Check the device logs
In Android logcat or the iOS device logs, look for FATAL EXCEPTION, Terminating app, permission denials, or memory warnings.
Isolate the variable
Check whether the failure happens on all devices or just one, on every run or intermittently, with networkLogs set to false, or with a fresh capability set.
For the full detail on each log type, see Appium logs, Device logs, Video recording, Crash logs, Network logs, and Text logs.
Recognize common error patterns
Match the error you see against the following common patterns:
| Error | Likely cause | What to do |
|---|---|---|
SessionNotCreatedException |
Wrong device name, wrong OS version, or an expired app_url. |
Validate your capabilities with the capability generator. |
NoSuchElementException with the element visible in the video |
Timing. The element isn’t interactive yet. | Add an explicit wait for the element to become clickable. |
StaleElementReferenceException |
The element was queried before a navigation and reused after it. | Re-find the element after every navigation. |
| App crashes on launch | An app-side issue such as an ANR, a signing problem, or an incompatible OS version. | Test on a local physical device first. |
Diagnose “works in App Live, fails in App Automate”
This gap is almost always caused by one of these environment differences between App Live and App Automate:
| Cause | Fix |
|---|---|
| The network log proxy intercepts traffic. | Set networkLogs to false. |
| Re-signing strips entitlements. | Set resignApp to false. |
| Permissions aren’t granted automatically. | Add the appium:permissions capability. |
| The test expects a logged-out state, but the device has cached login or app state. | Use a test account that starts from a logged-out state. |
Related topics
We're sorry to hear that. Please share your feedback so we can do better
Contact our Support team for immediate help while we work on improving our docs.
We're continuously improving our docs. We'd love to know what you liked
We're sorry to hear that. Please share your feedback so we can do better
Contact our Support team for immediate help while we work on improving our docs.
We're continuously improving our docs. We'd love to know what you liked
Thank you for your valuable feedback!