Android and iOS under one API
Appium's headline promise is "one API for Android and iOS" — write a test once, run it on both. That is mostly true and genuinely valuable, but "mostly" hides real differences you must handle. Understanding what is shared and what is not keeps you from either over-trusting cross-platform reuse or needlessly writing two separate suites. This lesson is running tests across Android and iOS, and managing the differences honestly.
What is genuinely shared
A large amount really is common, because Appium sits on the WebDriver protocol:
- The test structure and API —
driver.$(...),click,setValue,waitForDisplayed, assertions — are identical across platforms. Your test logic — the steps of a flow, the things you assert — is the same for AgentPay's agent app whether on Android or iOS. - Accessibility ids — if developers set the same accessibility id on the equivalent element on both
platforms (Android
content-desc, iOSaccessibilityIdentifier), the same~sign-in-buttonlocator works on both. This is the single biggest lever for cross-platform reuse, and a strong reason to insist on accessibility ids. - The overall flow — log in, record a transaction, see confirmation — is the same user journey on both, so the test reads the same.
So a well-built mobile suite shares most of its code across platforms, changing mainly configuration. That is the promise delivering.
What actually differs
The differences are real and cluster in a few places:
- Capabilities and drivers — different per platform: Android uses
UiAutomator2and an.apk; iOS usesXCUITest, a.app/.ipa, and (for real devices) Apple signing. This is configuration, not test logic, but it is non-trivial (iOS on real devices needs provisioning profiles). - Locators when accessibility ids are missing — the fallback strategies differ: Android's
resource-idandUiSelectorversus iOS'sname/predicate/class-chain. If the app lacks accessibility ids, your locators diverge per platform. - Platform UI conventions — the platforms behave differently, and correctly so: the back button (Android has a hardware/system back; iOS does not — it uses on-screen back), permission dialogs look and behave differently, date pickers and selects differ, alerts differ. Tests that touch these need platform-specific branches.
- Setup and system dialogs — granting permissions, handling the keyboard, and OS prompts differ between Android and iOS.
So the honest picture: the test logic is shared; the configuration, some locators, and platform-convention touchpoints are not. You reuse the body of the test and branch at the edges.
Structuring for both platforms
The practical way to get cross-platform reuse without a mess:
-
Insist on accessibility ids in the app, matched across platforms — this alone makes most locators shared.
-
Keep capabilities in config, one profile per platform (WebdriverIO's
capabilitiesarray, or separate config files), so the test never mentions the platform. -
Isolate the differences — where behaviour genuinely differs (the back button, a permission dialog), put it behind a small helper or a page-object method that does the right thing per platform:
async function goBack(driver) { if (driver.isAndroid) await driver.back(); // Android system back else await driver.$('~back-button').click(); // iOS on-screen back }The test calls
goBack(driver)and stays platform-agnostic; the difference lives in one place (the same DRY principle as page objects). -
Run the same suite against both in CI with two configurations, so both platforms are covered every run.
This gives you the reuse Appium promises while handling the genuine differences cleanly, instead of pretending they do not exist or forking into two unrelated suites.
Don't over-promise "write once, run anywhere"
A note of realism the practices modules would insist on: "one test for both platforms" is a goal you approach, not a guarantee you get free. Teams that assume perfect reuse get surprised by the back button, the signing, the permission dialogs, and the missing accessibility ids — and end up with brittle branches everywhere. Teams that plan for the differences (accessibility ids up front, config per platform, differences isolated in helpers) get most of the reuse and keep the rest maintainable. Aim for "shared logic, isolated differences", and you get the real value without the disappointment.
Check your work
Shared: the test structure/API ($, click, setValue, waitForDisplayed, assertions), the flow, and
— if developers set matching accessibility ids on both platforms — the locators. Most of a well-built
suite's code is shared; mainly config changes.
Differs: capabilities/drivers (UiAutomator2 + .apk vs XCUITest + .app/signing), fallback locators when
accessibility ids are missing (resource-id/UiSelector vs name/predicate), and platform UI conventions
(Android back button vs iOS on-screen back, permission dialogs, pickers, alerts), plus system-dialog/keyboard
handling.
Structure for both: insist on matched accessibility ids; keep capabilities in per-platform config so
tests never name the platform; isolate genuine differences behind a helper/page-object method
(goBack(driver) branching on isAndroid); run the same suite against both in CI.
Realism: "write once, run anywhere" is approached, not free — plan for the differences (a11y ids up front, config per platform, differences in helpers) for "shared logic, isolated differences".
Practice
- List what is shared and what differs when running the AgentPay agent login test on Android and iOS.
- Explain why matching accessibility ids across platforms is the biggest lever for cross-platform reuse.
- Write a
goBack(driver)helper that does the right thing on Android vs iOS, and explain where it belongs. - Describe how you would keep the platform out of your test code (config, helpers) so one test runs on both.
- Give three platform-convention differences a test might have to branch on.
- Argue against the assumption that Appium gives perfect "write once, run anywhere", with an example.
Official documentation
- Appium — Drivers (Android & iOS) — UiAutomator2 and XCUITest and their differences.
- Appium — Capabilities — Per-platform configuration.
- WebdriverIO — Multiremote / capabilities — Running one suite against multiple platform configs.
Next: the reality of mobile automation — flakiness, device farms, and when it is worth it.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship