Appium tests become hard to read when every locator carries its own timeout guess or fixed sleep. A shared wait policy gives the test suite one implicit locator timeout, one screen-level explicit wait, and one polling interval so mobile screen transitions fail with a locator-specific timeout message instead of a stalled run.
The Appium server receives the implicit wait through the W3C WebDriver session timeouts command. Explicit waits stay in the client code, where WebdriverIO can wait for an element state such as displayed, enabled, or a custom condition before the next action runs.
Keep implicit waits short when explicit waits are part of the same suite. A long implicit wait applies to every element lookup and can stretch explicit waits, while a short implicit wait plus page-specific explicit waits keeps slow screen loads visible in the failing step.
Related: How to run an Appium test in JavaScript
Related: How to configure Appium capabilities
Related: How to troubleshoot stale Appium elements
$ cd appium-js-smoke
The examples use WebdriverIO because the Appium quickstart uses it for JavaScript tests. Use the same timeout pattern with the equivalent wait and timeout methods in another Appium client.
$ npm ls webdriverio mocha appium-js-smoke@1.0.0 ├── mocha@11.7.2 └── webdriverio@9.27.2
Install the packages first if the command reports an empty tree: npm install --save-dev webdriverio mocha.
Related: How to run an Appium test in JavaScript
$ mkdir -p test/support
const WAIT = Object.freeze({ implicit: Number(process.env.APPIUM_IMPLICIT_WAIT_MS || 1000), screen: Number(process.env.APPIUM_SCREEN_WAIT_MS || 15000), interval: Number(process.env.APPIUM_WAIT_INTERVAL_MS || 500), }); async function applySessionTimeouts(driver) { await driver.setTimeout({ implicit: WAIT.implicit }); } module.exports = { WAIT, applySessionTimeouts };
implicit is sent to the Appium session through WebDriver timeouts. screen and interval stay client-side and are passed to explicit waits.
{
"scripts": {
"test": "mocha \"test/**/*.test.js\" --timeout 30000"
},
"devDependencies": {
"mocha": "^11.7.2",
"webdriverio": "^9.27.2"
}
}
A framework timeout shorter than the explicit wait can fail the test runner before WebdriverIO prints the locator-specific timeout message.
const assert = require('node:assert/strict'); const { remote } = require('webdriverio'); const { WAIT, applySessionTimeouts } = require('./support/waits'); const capabilities = { platformName: 'Android', 'appium:automationName': 'UiAutomator2', 'appium:deviceName': 'Android', 'appium:appPackage': 'com.android.settings', 'appium:appActivity': '.Settings', }; describe('Android Settings Appium waits', function () { let driver; afterEach(async function () { if (driver) { await driver.deleteSession(); driver = undefined; } }); it('opens Battery settings with configured waits', async function () { driver = await remote({ hostname: process.env.APPIUM_HOST || '127.0.0.1', port: Number(process.env.APPIUM_PORT || 4723), path: process.env.APPIUM_PATH || '/', logLevel: 'error', waitforTimeout: WAIT.screen, waitforInterval: WAIT.interval, capabilities, }); await applySessionTimeouts(driver); const battery = await driver.$('//*[@text="Battery"]'); await battery.waitForDisplayed({ timeout: WAIT.screen, interval: WAIT.interval, timeoutMsg: 'Battery menu item did not appear', }); await battery.click(); const title = await driver.$('//*[@text="Battery"]'); assert.equal(await title.getText(), 'Battery'); }); });
Use a locator that appears on the target device. The Android Settings app is a small smoke-test target; a product app should wait for its own screen label, button, dialog, or activity boundary.
$ appium --address 127.0.0.1 --port 4723 --log-level info [Appium] Welcome to Appium v3.5.0 [Appium] Appium REST http interface listener started on http://127.0.0.1:4723
The server must have the platform driver installed and a matching emulator, simulator, or device available before the test can create a session.
Related: How to start the Appium server
Related: How to install the Appium Android driver
$ npm test > test > mocha "test/**/*.test.js" --timeout 30000 Android Settings Appium waits ✔ opens Battery settings with configured waits (841ms) 1 passing (848ms)
A passing run shows the client created the Appium session, sent the implicit timeout, waited for the Battery item, clicked it, and cleaned up the session.
$ APPIUM_SCREEN_WAIT_MS=20000 APPIUM_WAIT_INTERVAL_MS=750 npm test
Keep the environment override close to the CI job, device farm job, or slow emulator profile that needs it instead of changing the default wait policy for every developer run.
Battery menu item did not appear ##### snipped ##### [HTTP] --> POST /session/9f4c1f0a-72b8-4d8d-a3b4-4c09a8f042d6/element
If the locator is wrong, the explicit wait times out with the message from the test. If the session itself stalls before the locator runs, troubleshoot server, driver, or device timeouts instead.
Related: How to troubleshoot Appium timeout errors
Related: How to debug Appium session logs