Android and iOS for Testers

Android and iOS are the two operating systems almost every mobile app ships on. Android is made by Google on an open-source core and shipped by many manufacturers, each with its own system layer; iOS is made by Apple and runs only on Apple devices. A requirement such as "the user scans a QR code to pay" is the same on both, but the operating system decides how the camera permission is asked, what the back gesture does, how the build reaches testers and users, and which OS versions are still in use.

This lesson teaches the four platform differences that change test design most often: permissions, back navigation, the stores and OS versions, and then the toolkit you use on each platform to install test builds, read device logs and record what you saw. Treat each platform as a separate product that shares requirements, and write the expected result per platform wherever the platform, not your team, defines the behaviour.

Why it matters

Defects that show up on only one platform are seldom in the business logic both platforms share. They appear where the app meets the operating system: a permission denied on iOS that the app keeps asking for, an Android back press that skips the confirmation step, a build that behaves differently on an OS version the team never installed, or a release blocked by store review the day before launch.

For the tester, knowing the platform rules turns vague scenarios into precise expected results. "Test permissions" becomes one concrete case per permission state on each platform; "check back navigation" becomes a list of screens and gestures with an expected destination for each. It also tells you what you can control from a test device and what only the platform decides, so you report defects to the right place.

Android and iOS from the tester’s seat

Android (Google) runs on devices from many manufacturers. The system is open source at its core, manufacturers add their own layers, and apps can be installed from Google Play, from other stores or directly from an APK file, which is why testers usually receive debug builds as files. The platform exposes a system-wide back action, a runtime permission model and a very wide spread of OS versions and hardware.

iOS (Apple) runs only on iPhones, with iPadOS on iPads. Apple controls the hardware, the OS, the build tools (Xcode, which needs a Mac) and the main distribution channel, the App Store (alternative app marketplaces are allowed only in some regions, such as the EU). Apps reach testers through TestFlight or through provisioning profiles tied to registered devices. There is no system back button, protected resources such as the camera or location need a stated purpose, and a new iOS version is released for every supported device on the same day.

What stays the same is everything you learned earlier: requirements, test design techniques, bug reports. What changes is the oracle for platform behaviour. The four differences below cover most platform-specific test cases, and the last section equips you to install, inspect and record on each platform; device sizes and the device matrix are the subject of the next lesson.

  • Permissions: how and how often the system asks, and what happens after a denial.
  • Back navigation: whether the system or the app owns "back", and what each gesture means.
  • Stores: how a build reaches testers, how it reaches users, and what the store checks.
  • OS versions: how many are live at once, and what makes behaviour change between them.
  • Toolkit: how a build gets onto a device, which developer settings help, and where the logs and crash reports are.
Same requirement, two rulebooks: Android adds a system back action and allows a second permission prompt after a denial; iOS asks once per resource, leaves back navigation to the app and distributes mainly through the App Store.

Android

Open-source core, many device makers, apps from Google Play and beyond

Who ships it:
Google and many manufacturers, each with its own system layer
Permissions:
Runtime dialog; may ask again after one denial, silent after two; “Only this time” grants
Back navigation:
System back button or gesture on every screen; back on the root leaves the app
Reaching testers and users:
APK installed directly; Play internal, closed and open tracks; staged rollout
OS versions:
Many versions live for years; behaviour depends on the OS and on the target API level
Developer settings:
Developer options: USB debugging, “Don’t keep activities”, “Show taps”, download rate limit; emulator network controls
Logs and crash reports:
adb logcat and adb bugreport with ANR traces; crash and ANR rates in Android vitals

iOS

One vendor for hardware, OS, build tools and store

Who ships it:
Apple only: its devices, its tools (Xcode on a Mac), its store
Permissions:
One system dialog per resource, then Settings only; purpose string required
Back navigation:
No system back: the app’s back button, edge swipe and swipe-down sheets
Reaching testers and users:
TestFlight builds that expire; App Review; phased release over seven days
OS versions:
One version ships to all supported devices on the same day; some changes wait for a rebuild with the new SDK
Developer settings:
Developer Mode to run development builds; Network Link Conditioner for slow or lossy networks
Logs and crash reports:
Console app and Xcode on a Mac; crash logs on the device and in Xcode Organizer; a watchdog instead of ANR

Rules as of the current platform versions; read each new OS release’s behaviour changes.

Try it: Test Scenario Generator

Permissions: one dialog, two sets of rules

On both platforms a protected resource (camera, location, microphone, contacts, photos, notifications) needs the user’s consent through a system dialog that the app cannot style or skip. The differences are in the sequence. On Android, permissions have been requested at runtime since Android 6.0; the app can ask again after a first denial, but from Android 11 a second denial makes the system stop showing the dialog, so the app must offer a route to Settings. Android 11 also added "Only this time" grants for location, microphone and camera, Android 12 added approximate location, and Android 13 made posting notifications a runtime permission. Android can also reset the permissions of an app that has not been used for months.

On iOS the system shows each permission dialog once. After a denial the app can never show it again; the user has to change the setting in the Settings app, and the app has to explain that. Resources such as the camera, microphone, location, photos and contacts need a purpose string in the app’s configuration (for example NSCameraUsageDescription); if it is missing, access fails and can crash the app, and App Review rejects the build. Location adds "Allow Once", which expires when the app is no longer in use so that the app may ask again later, and a precise-location switch; photos can be limited to selected items, notifications have always required permission, and tracking across apps needs the separate App Tracking Transparency prompt.

Changing a permission in Settings while the app is in the background can end the app’s process: Android ends it when a permission is revoked, and iOS ends it when many privacy settings, such as the camera, change in either direction. The return to the app is then a cold start (the app launches again in a new process) that must restore the user’s place. The companion guide on mobile app test scenarios lists the generic permission scenarios; the platform rules above give you the expected result for each.

  • Ask in context: the dialog should appear when the feature is used, after a short explanation, not at first launch.
  • Denied: the feature degrades gracefully with a clear message and a working "Open Settings" action; no loop of dialogs.
  • Granted later in Settings: the feature works on return without a reinstall.
  • Store compliance: the reason the app gives for each permission matches what the feature does (the purpose string on iOS; on Google Play, the declaration required for sensitive permissions such as background location); reviewers on both stores reject vague or missing explanations.

Try it: State Transition Generator

Worked example: the camera permission in a QR payment flow

Requirement: "On the Pay screen, the user taps Scan and points the camera at a merchant QR code. If the camera is unavailable, the user can enter the merchant code manually." The team ships the same feature on Android and iOS. Below is the permission behaviour the tester expects from each platform, which becomes the expected-result column of the test cases.

  • TC-1 (both): fresh install, tap Scan: explanation sheet, then the system dialog; grant: the camera opens within the same flow.
  • TC-2 (Android 11 and later): deny once, tap Scan again: the system dialog appears again; deny again, tap Scan a third time: no dialog, the app shows the manual entry option and an "Open Settings" button that lands on the app’s permission page.
  • TC-3 (iOS): deny once, tap Scan again: no system dialog; the app shows manual entry and "Open Settings"; grant in Settings and return: the app has restarted and Scan opens the camera.
  • TC-4 (Android 11 and later): choose "Only this time", pay, leave the app for a while, return and tap Scan: the dialog appears again instead of a silent failure.
  • TC-5 (both): revoke the camera permission in Settings while the Pay screen is open, return: the app restarts cleanly without a crash, and the pending payment state is either restored or clearly cancelled.
  • TC-6 (both): with the permission denied, manual code entry completes a payment, so the denial never blocks the business goal.
Camera permission for "Scan a QR code to pay"

Permission state               Android 11+                   iOS
--------------------------------------------------------------------------------------
Never asked                    system dialog on request      system dialog on request
Denied once, asked again       dialog shown again            no dialog: route to Settings
Denied twice, asked again      no dialog: route to Settings  no dialog: route to Settings
"Only this time" granted       expires soon after leaving    not offered for the camera
Revoked in Settings while open process ends, relaunch        process ends, relaunch
Purpose text                   explain before the dialog     NSCameraUsageDescription

Try it: Test Case Generator

Back navigation: system back versus app-owned back

Android has a system-wide back action: a button in three-button navigation and, since Android 10, an edge-swipe gesture in gesture navigation. It is delivered to whatever is on screen, and the app decides what it means: close the dialog, pop the previous screen, go back a page inside a WebView, or, on the app’s first screen, leave the app. Android also distinguishes Up (the arrow in the app bar, which moves up the app’s hierarchy) from Back (which retraces the user’s steps); after a deep link (a link that opens a specific screen inside the app) the two can lead to different places. Newer Android versions can also preview where back will lead before the gesture completes (predictive back) for apps that support it, which makes a wrong destination visible at once.

iOS has no system back. Going back is the app’s job: the back button at the top left of a navigation bar, the interactive swipe from the left edge inside a navigation stack (both move to the right side in right-to-left languages such as Arabic), swiping a sheet down to dismiss it, and tabs that each keep their own history. When an app was opened from another app, the status bar offers a "Back to" link to that app. A custom navigation bar that silently disables the edge swipe, or a sheet that can be dismissed with unsaved form data, are typical iOS-only defects.

  • For every screen, write down what back should do on each platform, then do it: the destination, and what happens to unsaved data.
  • Back on the root screen: Android leaves the app (or shows "press back again to exit"); on iOS the equivalent is the home gesture, which only backgrounds the app.
  • Back while a request is in flight (Pay, Submit): the action must not be sent twice and the result must not be lost.
  • Back after rotation, after a permission dialog, after returning from the background and after a deep link: the stack must still be sane.
  • WebView screens on Android: back must first move through the page history, then leave the WebView.

Try it: Test Scenario Generator Try it: State Transition Generator

Stores, releases and OS versions

Google Play publishes an app from an Android App Bundle and offers internal, closed and open testing tracks before production, staged rollouts to a percentage of users that can be halted, and a pre-launch report that runs the build on a set of devices and reports crashes. Outside Play, an APK can be installed directly, and some devices ship without Google services at all. The App Store takes a build uploaded from Xcode; TestFlight distributes it to up to 100 internal and 10,000 external testers (builds expire after 90 days, and the first build sent to external testers passes a beta review), App Review checks the production build, and a phased release spreads an update over seven days and can be paused. Neither store moves users who already updated back to the previous build: you halt or pause the rollout and ship a fixed build with a higher version number.

Both stores check the listing against the app: privacy declarations (Play’s Data safety section, the App Store’s App Privacy labels), the reasons given for permissions, screenshots and the features the reviewer can actually reach. Review rejections are release defects, so test the listing and a reviewer’s path through the app before submission, including the test account the reviewer will use.

OS versions behave differently on the two platforms. Android identifies releases by API level (Android 15 is API 35); updates reach devices through manufacturers, so several versions stay in use for years. Your app declares the lowest version it installs on (minSdkVersion) and the version whose behaviour it opts into (targetSdkVersion); Google Play requires a recent target, and raising it switches on behaviour changes for your app, which makes a target bump a test event. iOS ships one version to all supported devices on the same day, with no manufacturer in between and a public beta each summer; apps declare a deployment target, and some behaviour changes apply only once the app is rebuilt with the new SDK, so a new Xcode is a test event too.

  • Install path: test the build the users will get (store track or TestFlight), not only the debug build from the developer.
  • Update path: install the previous production version, use it, then update; data and sessions must survive.
  • OS set: newest version and its beta, oldest supported version, and the manufacturer layers that carry most of your traffic; your analytics, the store consoles and crash reports show which OS versions really arrive, while User-Agent strings from web traffic help less, because Chrome on Android and Safari since iOS 26 report a frozen OS version (the next lesson turns this into a matrix).
  • Forced update: when the back end drops an old version, the app must say so and link to the store; Android also offers in-app updates.

Try it: Pairwise Testing Generator Try it: User-Agent Parser

The tester’s toolkit on each platform

Emulator, simulator or real device is a device-strategy decision that the mobile testing guide covers; this section assumes you have a build and at least one device or emulator per platform. On Android the toolkit is adb (Android Debug Bridge, in the SDK platform tools), used over USB once Developer options are enabled by tapping the build number seven times in Settings and USB debugging is switched on. An APK installs with adb install; an Android App Bundle (AAB) cannot be installed directly, so ask for a universal APK, build device APKs from the bundle with bundletool, or upload it to Play’s internal app sharing, which returns an install link. Play testing tracks carry release candidates signed exactly as production.

On iOS a build reaches a real device through TestFlight (an invitation or a public link), or as a development or ad hoc build whose provisioning profile lists the device’s UDID (its unique device identifier), installed from Xcode’s Devices and Simulators window; since iOS 16, running a development-signed build needs Developer Mode switched on under Privacy & Security. The iOS Simulator is driven from a Mac with xcrun simctl: install an app, open a deep link, reset a permission, record the screen. Two kinds of developer setting earn a place in every test plan. The first is Android’s "Don’t keep activities", which destroys each screen the moment you leave it and so exposes lost form state and crashes on return. The second is network throttling: iOS’s Network Link Conditioner (Settings > Developer, present once the device has been used with Xcode) simulates slow, lossy or failing networks, the Android emulator’s network controls set a slower network type, a weak signal or no data connection, and recent Android versions also add a network download rate limit to Developer options on a real device.

Logs are your evidence. Android writes to logcat: every line has a time, a process id, a priority from V (verbose) to F (fatal) and a tag; adb logcat streams it, --pid narrows it to your app, and adb bugreport packs the full logs with the ANR traces into one zip for the developer. An ANR (Application Not Responding) is the "isn’t responding" dialog Android shows when the app’s main thread (the one that draws the screen and handles taps) stays blocked, for example about five seconds without handling input. Play tracks crash and ANR rates in Android vitals, where a user-perceived crash rate above 1.09% or ANR rate above 0.47% counts as bad behaviour and can reduce the app’s visibility on Google Play. iOS keeps a unified log that you read live in the Console app on a Mac with the device connected, or in Xcode’s console when Xcode launched the app. Crash logs (.ips files) stay on the device under Settings > Privacy & Security > Analytics & Improvements > Analytics Data, and Xcode’s Organizer collects symbolicated crashes (memory addresses translated into function names) from TestFlight and App Store users who share them. iOS has no ANR dialog: a watchdog terminates an app that blocks the main thread or launches too slowly, which the crash log shows as exception code 0x8badf00d.

  • Permission states in one command: adb shell pm grant and pm revoke on Android (revoking ends the app process, as in Settings), simctl privacy grant, revoke or reset on the iOS Simulator; simctl privacy has no camera service, so camera cases such as the worked example need a real iPhone; "never asked" on a real device means uninstall and reinstall.
  • Screen recording: adb shell screenrecord or the built-in recorder in Quick Settings (Android 11 and later), with "Show taps" enabled in Developer options so every tap is visible; the Control Center recorder on iOS, QuickTime on a Mac over a cable, or simctl io recordVideo on the Simulator.
  • Attach to every platform bug: the logcat excerpt or bug report on Android, the crash log or a Console excerpt on iOS, and the recording of the gesture or dialog. TestFlight testers can also send a screenshot with a comment from inside the beta app.
  • A logcat capture is long, and its lines carry a process id rather than your package name; a regular expression that keeps only your app’s process id and the priorities W, E and F turns it into a readable attachment.
# Android (adb from the SDK platform tools; USB debugging on)
adb devices                                  # the phone is listed as "device"
adb install -r app-debug.apk                 # install or update the build
adb shell pm grant com.example.pay android.permission.CAMERA
adb shell pm revoke com.example.pay android.permission.CAMERA
adb logcat --pid=$(adb shell pidof -s com.example.pay)   # your running app only
adb bugreport bugreport.zip                  # full logs and ANR traces
adb shell screenrecord /sdcard/scan.mp4      # Ctrl+C stops it; then:
adb pull /sdcard/scan.mp4

# iOS Simulator (xcrun simctl, from Xcode on a Mac)
xcrun simctl install booted Pay.app
xcrun simctl privacy booted reset location com.example.pay   # back to "never asked"
xcrun simctl openurl booted "pay://scan"
xcrun simctl io booted recordVideo scan.mp4

Try it: Regex Tester

Common mistakes

  • Writing one expected result for both platforms where the platform defines the behaviour, so the iOS case "deny, then ask again" is marked failed because no dialog appeared.
  • Testing permissions only on the happy path (granted at first ask) and never the denial, the second denial, "Only this time" and revocation in Settings.
  • Checking back navigation only with the on-screen back button on Android and never with the gesture, or never swiping from the edge on iOS.
  • Testing only the developer’s debug build on devices left on whatever OS they came with, so the store or TestFlight build that users receive, the newest OS, its beta and the oldest supported version are never exercised.
  • Reporting a platform rule as an app defect, for example that iOS does not show the permission dialog twice, instead of reporting the missing route to Settings.
  • Reporting a crash or a freeze without the device log, so the developer has to guess: attach the logcat lines or the bug report with ANR traces on Android, and the crash log from the device or Xcode on iOS.

The QA perspective

On a real team, a tester covers both platforms even when developers are split into an Android and an iOS group. During refinement, ask for every feature that touches a protected resource, navigation or the release: what is the expected behaviour on each platform, and who decided it. Record the answers as platform-specific expected results, and keep a short platform checklist (permission states, back on every screen, install and update paths, OS versions) that every release runs through.

Keep at least one Android and one iOS device on the newest OS and one on the oldest supported version, and install each new OS beta on a spare device as soon as it is published. In the bug report, name the platform, OS version, device, build type (debug, internal track, TestFlight, production) and the exact permission state or gesture, and attach the logcat excerpt or crash log and a short screen recording: that is what lets a developer reproduce a platform-specific defect in minutes.

Practice exercise

Write platform-specific test cases for a location-based store finder

Your task

A retail app adds "Stores near me": the Nearby tab asks for the location permission and shows stores sorted by distance. If location is unavailable, the user can type a city. Write the permission test cases for Android and iOS, with the expected result for each platform, and then add the back-navigation checks for the Nearby tab and the store detail screen.

Finish with two release questions you would ask before the build goes to the stores.

Steps
  1. List the permission states on each platform: never asked, granted, denied once, denied twice (Android), "Only this time" (Android) or "Allow Once" (iOS), precise or approximate (iOS, and Android 12 and later), revoked in Settings.
  2. For each state, write the expected behaviour of the Nearby tab, including the fallback to typing a city.
  3. Write what back (Android gesture and button, iOS edge swipe and back button) should do on the Nearby tab and on a store detail screen opened from it.
  4. Note the two release questions.
  5. Write the commands that put the app into each permission state on an Android device and on the iOS Simulator without tapping through Settings.
Show the model answer
Model answer

Permissions, Android: first tap on Nearby: short explanation, then the system dialog with precise and approximate options and "Only this time"; approximate: the list sorts by distance with a note that precision is reduced; deny once: the next visit asks again; deny twice: no dialog, the tab shows the city field and an "Open Settings" button; "Only this time": works now, asks again on a later visit; revoke in Settings while on the tab: the app restarts and the tab opens on the city field without a crash.

Permissions, iOS: first tap: explanation, then the system dialog with "Allow Once", "Allow While Using App", "Don’t Allow" and the precise switch; "Allow Once": works in this session and asks again at the next launch; "Don’t Allow": the tab shows the city field and "Open Settings", and the system dialog never returns; precise switch off: the list sorts by distance with a note that precision is reduced; grant in Settings and return: the list sorts by distance without a reinstall, even if the app was relaunched in between; revoke in Settings while the app is in the background: on return the tab shows the city field without a crash.

Back navigation, Android: on a store detail screen, back and Up both return to the Nearby list at the same scroll position; on the Nearby tab (a root tab), back leaves the app or returns to the previous tab according to the agreed rule, never to a blank screen; after a deep link to a store, Up goes to the Nearby list and back returns to where the user came from. iOS: the detail screen has a back button and the edge swipe works; the Nearby tab keeps its own history when the user switches tabs and returns.

Release questions: does the purpose text for location in the store listing and in the app match the feature (stores near me only, no background tracking), and on which OS versions and which build (internal track, TestFlight) will the release candidate be tested before submission?

Commands, Android: adb uninstall com.example.retail then adb install -r app.apk gives "never asked"; adb shell pm grant com.example.retail android.permission.ACCESS_COARSE_LOCATION together with ACCESS_FINE_LOCATION gives precise, coarse alone gives approximate; adb shell pm revoke gives revoked and ends the app process; for "denied once", "denied twice" and "Only this time", answer the dialog itself. iOS Simulator: xcrun simctl privacy booted reset location com.example.retail returns to "never asked", and grant location or revoke location set granted and denied; "Allow Once" and the precise switch are set by answering the dialog; on a real iPhone the permission changes only in Settings or by reinstalling.

Frequently asked questions

Do I need a Mac to test iOS apps?

To run an app on an iPhone you need a build produced with Xcode on a Mac, usually distributed through TestFlight or a provisioning profile. A tester does not need a Mac to install and test a TestFlight build on a device, but the build itself is produced with Xcode on macOS, on a team Mac or a cloud build service, and the iOS Simulator runs only on macOS.

Why does iOS not show the permission dialog again after I denied it?

That is the platform rule: iOS shows each permission dialog once. The app must detect the denied state, explain what is missing and offer a route to the Settings app. The defect to report is a missing or broken route to Settings, not the absent dialog.

Can an Android app be tested without Google Play?

Yes. A debug or release APK can be installed directly on a device or emulator, which is how most test builds are delivered. Before release, also test the build from a Play testing track, because the signing, the APKs that Play generates from the app bundle and Play features such as in-app updates and integrity checks behave differently from a hand-installed APK.

Is a rejected store review a defect?

Treat it as one: it blocks the release. Typical causes are a crash on the reviewer’s device, a feature the reviewer cannot reach, a vague permission purpose text or a listing that does not match the app. Testing the reviewer’s path and the listing before submission prevents most of them.

References

  • Certified Tester Mobile Application Testing (CT-MAT)ISTQB · 2019ISTQB's specialist syllabus on mobile testing: app types, device and platform challenges, test types and tools.
  • Core app quality guidelinesAndroid Developers (Google)Google's baseline quality criteria for Android apps — visual experience, functionality, performance, privacy and security — with the tests used to check them.
  • Test apps on AndroidAndroid Developers (Google)Android's official testing guidance: test types and scopes, local vs instrumented tests, and testing best practices.
  • Android vitalsAndroid Developers (Google)The app-quality metrics Google Play collects from users' devices — user-perceived crash and ANR rates among the core vitals — and the bad-behaviour thresholds that affect an app's visibility.

Practise with Mutqan tools

Define states and events, then generate a transition table, valid/invalid transition tests and 0-switch/1-switch coverage.

Test Design

Generate structured test cases (id, title, preconditions, steps, expected result, priority) from a feature description and field list.

Test Design

Generate high-level test scenarios covering functional, negative, security, usability and compatibility angles for a feature.

Test Design

Parse User-Agent strings into browser, version, engine, OS, device type and bot detection.

Web

Test regular expressions live with match highlighting, capture groups and replace preview.

Utilities

Go deeper

Your progress on this topic

Part of the QA Roadmap