March 2021 Hot fix Product Update: Turn your Appium into a Visual Test
Adam Creamer
Most Appium errors are not mysterious. They are the same twenty problems, wearing different stack traces, and the message you get back rarely names the actual cause.
This is a reference. Find your error, read what it actually means, apply the fix. Examples use the Appium Python client against Appium 3, with notes where Appium 2 behaves differently.
One habit first, because it saves more time than anything else here: run the server with appium –log-level debug and read the log, not the client exception. The client shows you the last thing that failed. The server log shows you the request that caused it.
Disclosure: Kobiton is a real-device testing cloud. The nineteen fixes below need no account and no vendor; the section near the end explains which of these errors are infrastructure problems rather than code problems.
Appium requires Node.js version ^20.19.0 || ^22.12.0 || >=24.0.0Appium 3 raised the floor. The minimum is now Node 20.19.0, with npm 10 or newer. Appium 2 accepted Node 14.17.0, so plenty of CI images that worked in June will fail on upgrade.
Check what you have, then upgrade the runtime, not Appium:
node -v
npm -vThis is the single most common Appium 3 upgrade failure, and it usually shows up in CI first because build images lag behind developer laptops.
Could not find a driver for automationName 'UiAutomator2' and platformName 'Android'Since Appium 2, drivers do not ship with the server. You install them separately, and a fresh machine or a fresh CI container has none.
appium driver install uiautomator2 # Android
appium driver install xcuitest # iOS
appium driver list --installedIf the driver is installed and you still get this, check your capability spelling. automationName is case sensitive and UiAutomator2 is not UIAutomator2.
Error: listen EADDRINUSE: address already in use 0.0.0.0:4723An earlier Appium process did not exit. Common after a crashed test run or a killed IDE.
lsof -ti:4723 | xargs kill -9 # macOS and Linux
netstat -ano | findstr :4723 # Windows, then taskkill /PID <pid> /FFor parallel runs, do not fight over one port. Give each server instance its own with appium -p 4724, and see error 20 for the device-side ports that also collide.
A session is either terminated or not startedNine times out of ten this is the base path. Appium 1.x served at /wd/hub. Appium 2 and 3 serve at /. Any tutorial or old test file written before 2023 has the wrong URL.
# Correct for Appium 2 and 3
driver = webdriver.Remote("http://127.0.0.1:4723", options=options)
# Wrong, unless you started the server with --base-path=/wd/hub
driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", options=options)Note that many device clouds still expose /wd/hub on their endpoint. That is their gateway, not your local server, so keep the two straight.
Feature 'adb_shell' is not scoped to a driverAppium 3 made the scope prefix on –allow-insecure mandatory. What worked in Appium 2 now throws.
# Appium 2
appium --allow-insecure=adb_shell
# Appium 3, scoped to one driver
appium --allow-insecure=uiautomator2:adb_shell
# Appium 3, all drivers that support it
appium --allow-insecure=*:adb_shellServer-scope features such as session_discovery need the wildcard prefix too. The full list of changes is in the Appium 3 migration guide.
An unknown server-side error occurred... Could not find app at /path/to/app.apkAlmost always a relative path resolved against the wrong working directory, or a path that exists on your laptop and not on the CI runner. Use an absolute path or an HTTP URL, and confirm the file is where you think:
options.app = os.path.abspath("builds/app-debug.apk")On iOS, remember that .app is a simulator build and .ipa is a real-device build. Handing a simulator build to a real device produces this same unhelpful message.
Command failed: Failure [INSTALL_FAILED_UPDATE_INCOMPATIBLE]In CI, make the uninstall an explicit step before the session starts rather than relying on fullReset, which is slower and does more than you usually want.
Error executing adbExec... device unauthorizedThe USB debugging prompt on the device was never accepted, or the host key changed. Reconnect and accept the prompt, and if it never appears:
adb kill-server && adb start-server
adb devicesA device showing offline usually means a cable or hub problem before it means a software one. Swap the cable first.
Unable to start WebDriverAgent session because of xcodebuild failureThis is the single largest source of iOS setup pain, and it is nearly always signing. WebDriverAgent is an app that has to be built, signed, and trusted on the device before Appium can talk to it.
Work through it in order:
The XCUITest driver documentation covers the signing options in more depth. If you would rather not maintain provisioning profiles, this is the strongest argument for running iOS tests on a device cloud where the signing is handled for you.
Bad parameters: 'desiredCapabilities' is not a recognized parameterAppium 3 removed JSONWP parameter handling from POST /session. Only the W3C capabilities object is accepted now. Old client libraries that still send desiredCapabilities will fail at session creation.
Upgrade your client library, then use typed Options objects rather than a raw dictionary:
from appium.options.android import UiAutomator2Options
options = UiAutomator2Options()
options.platform_name = "Android"
options.automation_name = "UiAutomator2"
options.device_name = "Pixel 7"If you build capabilities from a dict, non-standard keys need the appium: prefix. The Options classes add it for you, which is the main reason to use them. The same pattern applies in every client library — see our Appium Java tutorial for the UiAutomator2Options equivalent.
An element could not be located on the page using the given search parametersThree causes, in order of how often they turn out to be the real one.
The element has not rendered yet. Add an explicit wait rather than a sleep:
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
wait = WebDriverWait(driver, 15)
el = wait.until(EC.visibility_of_element_located(
(AppiumBy.ACCESSIBILITY_ID, "login_button")))The locator is wrong. Dump the current tree and look, rather than guessing:
print(driver.page_source)Or the element is inside a webview and you are still in native context. See error 19.
For a fuller method of telling these three apart from the artifacts of a failed run, see how to triage Appium locator failures in ten minutes.
The element reference is stale; either the element is no longer attached to the DOM...You found an element, the screen re-rendered, and your reference now points at something that no longer exists. Lists that refresh, screens that reload after an API call, and anything with a loading spinner produce this constantly.
Do not cache element references across actions. Find the element immediately before you use it, and if a screen is known to re-render, wrap the interaction in a short retry.
Element is not currently interactable and may not be manipulatedThe element exists in the tree but cannot receive the action. Usually it is off screen, behind an overlay or keyboard, or still animating.
Scroll it into view first:
driver.execute_script("mobile: scrollGesture", {
"left": 100, "top": 300, "width": 400, "height": 1200,
"direction": "down", "percent": 1.0
})If a keyboard is covering it, hide it with driver.hide_keyboard() before the tap. If an animation is the cause, wait on element_to_be_clickable rather than presence_of_element_located.
No exception, just a failing assertion or a tap that lands somewhere unexpected. This is nearly always an XPath with a positional index:
# Brittle. Breaks the moment a row is added.
driver.find_element(AppiumBy.XPATH, "//android.widget.TextView[3]")
# Stable
driver.find_element(AppiumBy.ACCESSIBILITY_ID, "settings_row")Positional XPath is the leading cause of tests that pass on one device and fail on another, because view hierarchies differ across OEM skins and screen sizes. Ask your developers for accessibility IDs. It improves automation stability and real accessibility at the same time.
Locator Strategy 'X' is not supported for this sessionLocator strategies are driver specific. -android uiautomator does not exist on iOS. -ios predicate string and -ios class chain do not exist on Android. A cross-platform test that shares one locator file will hit this the first time it runs on the other platform.
Keep platform-specific locators separate, and lean on accessibility IDs, which work on both.
A session is either terminated or not startedThe session timed out while your test was doing something else. Appium closes a session after newCommandTimeout seconds with no incoming command, which defaults to 60.
If you have a legitimately slow step, such as waiting on a backend job, raise it:
options.new_command_timeout = 300If you are seeing this without a slow step, the server or the device crashed. Check the server log for the real failure above this message.
AttributeError: module has no attribute 'TouchAction'TouchAction and MultiTouchAction were deprecated in Appium 2 and the underlying endpoints are removed in Appium 3. Replace them with either the W3C Actions API or, more simply, driver gesture commands:
# Android
driver.execute_script("mobile: swipeGesture", {
"left": 100, "top": 800, "width": 200, "height": 400,
"direction": "up", "percent": 0.75
})
driver.execute_script("mobile: longClickGesture",
{"elementId": el.id, "duration": 1000})
# iOS
driver.execute_script("mobile: swipe", {"elementId": el.id, "direction": "up"})
driver.execute_script("mobile: pinch",
{"elementId": el.id, "scale": 2.0, "velocity": 1.0})Appium 3 removed a long list of legacy endpoints this way, including screen recording, clipboard, keyevent, and fingerprint, all of which moved to driver-specific mobile: execute methods. If a helper method in your suite suddenly 404s after upgrading, check the removed endpoints list in the Appium 3 migration guide. The full command reference lives in the UiAutomator2 and XCUITest driver docs.
No Chromedriver found that can automate Chrome '<version>'Android webview automation runs through Chromedriver, and the Chromedriver version has to match the Chrome or webview version on the device. Device fleets with mixed OS versions hit this constantly.
Let Appium fetch the right one instead of pinning it by hand:
options.set_capability("appium:chromedriverAutodownload", True)That requires the chromedriver_autodownload insecure feature on the server, which in Appium 3 needs the scope prefix from error 5.
NoSuchContextException: No such context foundTwo separate problems produce this.
The app is not built with webview debugging enabled. WebView.setWebContentsDebuggingEnabled(true) has to be set in a debug build, and no capability on your side can work around a release build without it.
Or the context list has not populated yet. Print it before switching:
print(driver.contexts) # ['NATIVE_APP', 'WEBVIEW_com.example.app']
driver.switch_to.context("WEBVIEW_com.example.app")Setting appium:ensureWebviewsHavePages to true filters out webview entries that have no pages attached, which removes a common source of switching into a context that does nothing.
Original error: socket hang upOr sessions that pass alone and fail in parallel. Each Android session needs its own systemPort for the UiAutomator2 server, and each iOS session needs its own wdaLocalPort for WebDriverAgent. Leave them at the default and two sessions on the same host fight for the same port.
options.set_capability("appium:systemPort", 8201) # Android, unique per session
options.set_capability("appium:wdaLocalPort", 8101) # iOS, unique per sessionAssign them from your test runner’s worker index so they never repeat. This is the most common reason a suite is stable at one thread and unusable at four, and it is also the point where most teams decide that running parallel sessions on their own hardware is not worth maintaining.
Work down the stack in this order and you will find almost anything:
Most Appium debugging time is spent reproducing a failure, not fixing it. Anything that captures state automatically at the moment of the failure pays for itself in a week.
Several errors above come from the environment rather than your test code: signing WebDriverAgent, matching Chromedriver to a device’s webview version, keeping ports apart across parallel sessions, and keeping devices charged, connected, and authorized.
Kobiton runs your Appium tests on real iOS and Android devices in the cloud, on-prem, or in your own managed device lab, so that class of error stops being your team’s problem. Point webdriver.Remote at the Kobiton endpoint and your existing test code runs unchanged, in parallel, across the device and OS mix your users actually have.
For the errors that are in your test code, Session Explorer replays the run with the device logs, network payloads, crash logs, and system metrics lined up against the timeline, so you can see the screen state at the moment the element lookup failed. The built-in element inspector surfaces locators straight from a cloud session, which is useful mid-debug when you are already in a replay. For local development against a debug build, a standalone Appium Inspector install is still the right tool.
And when a UI change breaks a locator, self-healing repairs the element lookup so the run continues instead of handing your team a morning of maintenance.
Ready to stop debugging your device lab and start debugging your app? Start testing for free or book a demo.
The most common causes are a Node version below 20.19.0 on the build image, drivers not installed in a fresh container, relative app paths that resolve differently, and port collisions between parallel sessions. Check those four before anything else.
Node 20.19.0 is now the minimum, many deprecated endpoints were removed in favour of driver-specific mobile: execute methods, –allow-insecure requires a scope prefix, GET /sessions moved to GET /appium/sessions behind a feature flag, and POST /session no longer accepts desiredCapabilities.
Use explicit waits rather than sleeps, verify the locator against driver.page_source instead of guessing, and check whether the element is inside a webview while your session is still in native context.
Run the server with –log-level debug and read the last request before the error. The client exception tells you what broke; the server log tells you why.