QC Mobile Testing
When to Use
- Testing a native iOS or Android application
- Testing a React Native or Flutter cross-platform app
- Planning device matrix coverage for a release
- Testing mobile-specific behaviors (gestures, push notifications, deep links)
- Testing app behavior on poor network conditions
Core Jobs
1. Device Matrix Strategy
Not feasible to test on every device. Use risk-based device matrix:
Tier 1 — Must test (every release):
iOS: Latest iPhone (current + N-1 iOS), iPad latest
Android: Samsung Galaxy (latest), Google Pixel (latest), latest Android version
Coverage: ~60% of your actual user base (check analytics)
Tier 2 — Test before major releases:
iOS: iPhone SE (small screen), older iPhone (N-2)
Android: Mid-range phone (Xiaomi, Oppo), older Android (N-2)
Coverage: additional ~25% of user base
Tier 3 — Spot check quarterly:
Edge devices, very old Android, tablets
Coverage: remaining long tail
Prioritize based on:
- Your analytics: which devices/OS versions do YOUR users have?
- Market data: iOS vs Android split in your region
- Device capabilities: features like camera, NFC, biometrics
Tools for device access:
- BrowserStack / Sauce Labs: real device cloud (subscription)
- Firebase Test Lab: free tier for Android
- Physical devices: maintain small pool of Tier 1 devices
2. iOS Testing with XCUITest
// XCUITest — Apple's native UI testing framework
import XCTest
class LoginUITests: XCTestCase {
var app: XCUIApplication!
override func setUpWithError() throws {
continueAfterFailure = false
app = XCUIApplication()
app.launchArguments = ["--uitesting"] // flag to use test data
app.launch()
}
func testSuccessfulLogin() throws {
// Find elements by accessibility identifier
let emailField = app.textFields["email-input"]
let passwordField = app.secureTextFields["password-input"]
let loginButton = app.buttons["login-button"]
emailField.tap()
emailField.typeText("test@example.com")
passwordField.tap()
passwordField.typeText("Test1234!")
loginButton.tap()
// Verify navigation to home screen
XCTAssertTrue(app.navigationBars["Home"].waitForExistence(timeout: 5))
}
func testLoginWithBiometrics() throws {
// Test Face ID / Touch ID
app.buttons["biometric-login"].tap()
// Simulate biometric in simulator
let coordinator = XCUIDevice.shared
coordinator.biometricEnrollment(enrolled: true)
coordinator.performBiometricAuthentication(success: true)
XCTAssertTrue(app.navigationBars["Home"].waitForExistence(timeout: 3))
}
}
3. Android Testing with Espresso
// Espresso — Android's native UI testing framework
@RunWith(AndroidJUnit4::class)
class LoginInstrumentedTest {
@get:Rule
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
@Test
fun testSuccessfulLogin() {
onView(withId(R.id.email_input))
.perform(typeText("test@example.com"), closeSoftKeyboard())
onView(withId(R.id.password_input))
.perform(typeText("Test1234!"), closeSoftKeyboard())
onView(withId(R.id.login_button))
.perform(click())
onView(withId(R.id.home_toolbar))
.check(matches(isDisplayed()))
}
@Test
fun testValidationError() {
onView(withId(R.id.login_button)).perform(click())
onView(withText("Email is required"))
.check(matches(isDisplayed()))
}
}
4. Cross-Platform Testing with Appium
# Appium — cross-platform (iOS + Android from same test code)
from appium import webdriver
from appium.options import XCUITestOptions, UiAutomator2Options
# iOS configuration
ios_options = XCUITestOptions()
ios_options.platform_name = "iOS"
ios_options.device_name = "iPhone 15"
ios_options.bundle_id = "com.myapp.ios"
# Android configuration
android_options = UiAutomator2Options()
android_options.platform_name = "Android"
android_options.device_name = "Pixel 7"
android_options.app_package = "com.myapp.android"
# Test using MobileBy locators
from appium.webdriver.common.appiumby import AppiumBy
def test_login(driver):
email = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "email-input")
email.send_keys("test@example.com")
password = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "password-input")
password.send_keys("Test1234!")
login_btn = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "login-button")
login_btn.click()
home = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "home-screen")
assert home.is_displayed()
5. Mobile-Specific Test Cases
Gesture testing:
- Swipe left/right (carousels, delete actions)
- Pinch to zoom (maps, images)
- Long press (context menus)
- Pull to refresh
- Scroll to bottom (infinite scroll, load more)
App lifecycle testing:
- Background then foreground: does app restore state correctly?
- Incoming call during operation: does app pause/resume gracefully?
- Low memory warning: does app release memory, handle gracefully?
- App update: does data persist across update?
- Force kill: does app recover session on relaunch?
- Deep links: does myapp://screen/123 open correct screen?
- Push notifications: tapping notification navigates to correct screen?
Network testing:
- Offline: does app show appropriate offline message?
- Slow 3G (use Network Link Conditioner on iOS, Android emulator settings):
- Do timeouts work correctly?
- Does loading indicator show?
- Does retry work?
- Network switch (WiFi → 4G): does app handle gracefully?
Device-specific:
- Orientation change: portrait ↔ landscape maintains state?
- Screen size: small screen (SE) shows all content without truncation?
- Dark mode: all screens readable and correct?
- Accessibility: VoiceOver (iOS) / TalkBack (Android) works on key flows?
- Keyboard: does keyboard obscure input fields? Scroll to show?
6. Mobile Performance Testing
# iOS performance — use Instruments (Xcode)
# Metrics to check:
# - App launch time: cold launch < 2s, warm launch < 1s
# - Memory usage: should not grow unboundedly during use
# - CPU: should not spike > 80% during normal use
# - Battery: excessive background activity drains battery
# Android — use Android Profiler (Android Studio)
adb shell am start -W -n com.myapp/.MainActivity
# Output: ThisTime, TotalTime (cold start)
# Frame rate: 60fps target (16ms per frame budget)
adb shell dumpsys gfxinfo com.myapp | grep "Total frames"
Key Concepts
- Device matrix — curated set of devices/OS versions representing significant user segments
- XCUITest — Apple's native UI test framework for iOS; fastest, most reliable for iOS
- Espresso — Google's native UI test framework for Android; same advantages
- Appium — cross-platform mobile automation; write once, run on iOS+Android
- App lifecycle — background/foreground transitions, memory warnings, deep links, push notifications
- Network Link Conditioner — iOS tool to simulate poor network conditions in testing
Checklist
Key Outputs
- Device matrix document with tier assignments and rationale
- Mobile test checklist covering app lifecycle, gestures, network conditions
- Automated mobile tests for critical flows (Appium or native framework)
- Mobile performance baseline (launch time, memory usage)
Output Format
- 🔴 Critical — testing only on latest iPhone/Pixel (misses 60%+ of real device issues), no app lifecycle testing (background/foreground bugs reach production)
- 🟡 Warning — no network condition testing (app hangs on slow network), no orientation testing, only manual mobile testing with no automation
- 🟢 Suggestion — use Firebase Test Lab for automated device farm testing, add Network Link Conditioner tests to CI, document device matrix based on actual analytics
Anti-Patterns
- Testing on emulator/simulator only (miss real device issues: memory pressure, real network, real battery)
- Testing on developer's own device only (misses other screen sizes, OS versions)
- Ignoring accessibility testing on mobile (VoiceOver/TalkBack used by significant user segment)
- No performance baseline (can't detect performance regressions)
Integration
qc-test-design — same test design techniques apply to mobile (EP, BVA, exploratory)
qc-automation — Appium/XCUITest/Espresso replace Playwright for mobile
qa-risk-management — device matrix is a risk-based decision
1---2name: qc-mobile-testing3description: Use when testing mobile applications — device matrix strategy, iOS and Android testing tools (XCUITest, Espresso, Appium), gesture and interaction testing, network condition testing, app lifecycle testing, and mobile-specific quality concerns.4---56# QC Mobile Testing78## When to Use9- Testing a native iOS or Android application10- Testing a React Native or Flutter cross-platform app11- Planning device matrix coverage for a release12- Testing mobile-specific behaviors (gestures, push notifications, deep links)13- Testing app behavior on poor network conditions1415## Core Jobs1617### 1. Device Matrix Strategy18```19Not feasible to test on every device. Use risk-based device matrix:2021Tier 1 — Must test (every release):22 iOS: Latest iPhone (current + N-1 iOS), iPad latest23 Android: Samsung Galaxy (latest), Google Pixel (latest), latest Android version24 Coverage: ~60% of your actual user base (check analytics)2526Tier 2 — Test before major releases:27 iOS: iPhone SE (small screen), older iPhone (N-2)28 Android: Mid-range phone (Xiaomi, Oppo), older Android (N-2)29 Coverage: additional ~25% of user base3031Tier 3 — Spot check quarterly:32 Edge devices, very old Android, tablets33 Coverage: remaining long tail3435Prioritize based on:36 - Your analytics: which devices/OS versions do YOUR users have?37 - Market data: iOS vs Android split in your region38 - Device capabilities: features like camera, NFC, biometrics3940Tools for device access:41 - BrowserStack / Sauce Labs: real device cloud (subscription)42 - Firebase Test Lab: free tier for Android43 - Physical devices: maintain small pool of Tier 1 devices44```4546### 2. iOS Testing with XCUITest47```swift48// XCUITest — Apple's native UI testing framework49import XCTest5051class LoginUITests: XCTestCase {52 var app: XCUIApplication!5354 override func setUpWithError() throws {55 continueAfterFailure = false56 app = XCUIApplication()57 app.launchArguments = ["--uitesting"] // flag to use test data58 app.launch()59 }6061 func testSuccessfulLogin() throws {62 // Find elements by accessibility identifier63 let emailField = app.textFields["email-input"]64 let passwordField = app.secureTextFields["password-input"]65 let loginButton = app.buttons["login-button"]6667 emailField.tap()68 emailField.typeText("test@example.com")69 passwordField.tap()70 passwordField.typeText("Test1234!")71 loginButton.tap()7273 // Verify navigation to home screen74 XCTAssertTrue(app.navigationBars["Home"].waitForExistence(timeout: 5))75 }7677 func testLoginWithBiometrics() throws {78 // Test Face ID / Touch ID79 app.buttons["biometric-login"].tap()80 // Simulate biometric in simulator81 let coordinator = XCUIDevice.shared82 coordinator.biometricEnrollment(enrolled: true)83 coordinator.performBiometricAuthentication(success: true)8485 XCTAssertTrue(app.navigationBars["Home"].waitForExistence(timeout: 3))86 }87}88```8990### 3. Android Testing with Espresso91```kotlin92// Espresso — Android's native UI testing framework93@RunWith(AndroidJUnit4::class)94class LoginInstrumentedTest {95 @get:Rule96 val activityRule = ActivityScenarioRule(LoginActivity::class.java)9798 @Test99 fun testSuccessfulLogin() {100 onView(withId(R.id.email_input))101 .perform(typeText("test@example.com"), closeSoftKeyboard())102103 onView(withId(R.id.password_input))104 .perform(typeText("Test1234!"), closeSoftKeyboard())105106 onView(withId(R.id.login_button))107 .perform(click())108109 onView(withId(R.id.home_toolbar))110 .check(matches(isDisplayed()))111 }112113 @Test114 fun testValidationError() {115 onView(withId(R.id.login_button)).perform(click())116117 onView(withText("Email is required"))118 .check(matches(isDisplayed()))119 }120}121```122123### 4. Cross-Platform Testing with Appium124```python125# Appium — cross-platform (iOS + Android from same test code)126from appium import webdriver127from appium.options import XCUITestOptions, UiAutomator2Options128129# iOS configuration130ios_options = XCUITestOptions()131ios_options.platform_name = "iOS"132ios_options.device_name = "iPhone 15"133ios_options.bundle_id = "com.myapp.ios"134135# Android configuration136android_options = UiAutomator2Options()137android_options.platform_name = "Android"138android_options.device_name = "Pixel 7"139android_options.app_package = "com.myapp.android"140141# Test using MobileBy locators142from appium.webdriver.common.appiumby import AppiumBy143144def test_login(driver):145 email = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "email-input")146 email.send_keys("test@example.com")147148 password = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "password-input")149 password.send_keys("Test1234!")150151 login_btn = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "login-button")152 login_btn.click()153154 home = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "home-screen")155 assert home.is_displayed()156```157158### 5. Mobile-Specific Test Cases159```160Gesture testing:161 - Swipe left/right (carousels, delete actions)162 - Pinch to zoom (maps, images)163 - Long press (context menus)164 - Pull to refresh165 - Scroll to bottom (infinite scroll, load more)166167App lifecycle testing:168 - Background then foreground: does app restore state correctly?169 - Incoming call during operation: does app pause/resume gracefully?170 - Low memory warning: does app release memory, handle gracefully?171 - App update: does data persist across update?172 - Force kill: does app recover session on relaunch?173 - Deep links: does myapp://screen/123 open correct screen?174 - Push notifications: tapping notification navigates to correct screen?175176Network testing:177 - Offline: does app show appropriate offline message?178 - Slow 3G (use Network Link Conditioner on iOS, Android emulator settings):179 - Do timeouts work correctly?180 - Does loading indicator show?181 - Does retry work?182 - Network switch (WiFi → 4G): does app handle gracefully?183184Device-specific:185 - Orientation change: portrait ↔ landscape maintains state?186 - Screen size: small screen (SE) shows all content without truncation?187 - Dark mode: all screens readable and correct?188 - Accessibility: VoiceOver (iOS) / TalkBack (Android) works on key flows?189 - Keyboard: does keyboard obscure input fields? Scroll to show?190```191192### 6. Mobile Performance Testing193```bash194# iOS performance — use Instruments (Xcode)195# Metrics to check:196# - App launch time: cold launch < 2s, warm launch < 1s197# - Memory usage: should not grow unboundedly during use198# - CPU: should not spike > 80% during normal use199# - Battery: excessive background activity drains battery200201# Android — use Android Profiler (Android Studio)202adb shell am start -W -n com.myapp/.MainActivity203# Output: ThisTime, TotalTime (cold start)204205# Frame rate: 60fps target (16ms per frame budget)206adb shell dumpsys gfxinfo com.myapp | grep "Total frames"207```208209## Key Concepts210- **Device matrix** — curated set of devices/OS versions representing significant user segments211- **XCUITest** — Apple's native UI test framework for iOS; fastest, most reliable for iOS212- **Espresso** — Google's native UI test framework for Android; same advantages213- **Appium** — cross-platform mobile automation; write once, run on iOS+Android214- **App lifecycle** — background/foreground transitions, memory warnings, deep links, push notifications215- **Network Link Conditioner** — iOS tool to simulate poor network conditions in testing216217## Checklist218- [ ] Device matrix defined based on user analytics (not just latest/greatest)?219- [ ] Critical flows tested on at least Tier 1 devices before release?220- [ ] App lifecycle tested (background/foreground, incoming call, force kill)?221- [ ] Network conditions tested (offline, slow 3G, network switch)?222- [ ] Gesture interactions tested (swipe, pinch, long press) where applicable?223- [ ] Orientation change tested for key screens?224- [ ] Dark mode tested if supported?225226## Key Outputs227- Device matrix document with tier assignments and rationale228- Mobile test checklist covering app lifecycle, gestures, network conditions229- Automated mobile tests for critical flows (Appium or native framework)230- Mobile performance baseline (launch time, memory usage)231232## Output Format233- 🔴 **Critical** — testing only on latest iPhone/Pixel (misses 60%+ of real device issues), no app lifecycle testing (background/foreground bugs reach production)234- 🟡 **Warning** — no network condition testing (app hangs on slow network), no orientation testing, only manual mobile testing with no automation235- 🟢 **Suggestion** — use Firebase Test Lab for automated device farm testing, add Network Link Conditioner tests to CI, document device matrix based on actual analytics236237## Anti-Patterns238- Testing on emulator/simulator only (miss real device issues: memory pressure, real network, real battery)239- Testing on developer's own device only (misses other screen sizes, OS versions)240- Ignoring accessibility testing on mobile (VoiceOver/TalkBack used by significant user segment)241- No performance baseline (can't detect performance regressions)242243## Integration244- `qc-test-design` — same test design techniques apply to mobile (EP, BVA, exploratory)245- `qc-automation` — Appium/XCUITest/Espresso replace Playwright for mobile246- `qa-risk-management` — device matrix is a risk-based decision