Lesson 3 of 4

Lesson 03 — Test Design for Mobile

Title: Gestures, permissions, and real-world test scenarios
Description: Learn how to design comprehensive test cases for mobile apps, covering gestures, permissions, interruptions, network conditions, app lifecycle, and orientation — all the scenarios that separate a thorough mobile QA engineer from a beginner.
Why it matters for QA: A test suite that only checks the happy path will pass in CI and fail for real users. Mobile users live in the real world: they get phone calls mid-checkout, they rotate their phone sideways, they lose network connectivity in the subway. Your job as a QA engineer is to catch those failures before your users do.


1. Mobile-specific test types you must cover

Most QA beginners start by testing the happy path — the ideal scenario where everything works as expected. This is a good starting point, but mobile apps need much more coverage.

Here are the essential test categories for mobile:

Happy path (the baseline)

The happy path is the most common user scenario: the user does what they are supposed to do, everything works correctly.

Example for a login screen:

  • User opens the app
  • User enters a valid email and password
  • User taps "Login"
  • User sees the home screen

You must always write the happy path first. Everything else builds on it.

Permissions: grant, deny, and revoke

Mobile apps ask users for permissions: access to the camera, microphone, location, contacts, and more. Each permission has three important test scenarios:

Grant: The user allows the permission. The feature works as expected.

Deny: The user refuses the permission. The app must handle this gracefully — show a helpful message, offer an alternative, or disable the feature without crashing.

Revoke: The user initially granted the permission, but later goes into Settings and turns it off. The next time they use the feature, the app must detect the missing permission and respond appropriately.

In practice: Many apps crash or show a confusing blank screen when a permission is denied. Always test the deny path explicitly.

Interruptions: incoming calls, notifications, low battery

While your user is in the middle of filling out a form or completing a purchase, real life happens. Test these scenarios:

  • Incoming phone call: User receives a call while using the app. After the call, they return to the app. Is their progress saved? Is the app in the same state?
  • Push notification: A notification appears while the user is on a specific screen. They tap it and are taken somewhere else. They press Back. Where do they end up?
  • Low battery warning: The system shows a "Battery low (15%)" dialog. Does the app handle this correctly?
  • System update notification: The OS shows an update notification. Does it disrupt the user's flow?

Network conditions: offline, slow 3G, WiFi to cellular switch

Real users are not always on fast WiFi. Test:

  • No network: What does the app show when there is no internet? A friendly "No connection" message or a crash/empty screen?
  • Slow network (3G, 2G): Does the app time out gracefully? Are loading states shown?
  • Network loss mid-request: The user submits a form and the network drops exactly as the request is sent. Does the app show an error? Does it retry?
  • WiFi to cellular switch: The user moves out of WiFi range during a video stream or file upload. Does the app continue? Does it fail silently?

How to simulate network conditions:

  • Android emulator: Network throttling in emulator settings
  • iOS simulator: Network Link Conditioner (developer tool)
  • Real device: Enable Airplane Mode, or use device developer settings

App lifecycle: background, foreground, kill, and relaunch

Mobile operating systems manage app memory. An app can be:

  • Foreground: The user is actively using it
  • Background: The user pressed Home and the app is running behind the scenes
  • Killed: The OS terminated the app to free memory, or the user force-closed it

Test these transitions:

  • Background → Foreground: User presses Home, then returns to the app 1 minute later. Is the state preserved?
  • Background → Foreground (long time): User leaves the app for 30 minutes. Many apps refresh data or require re-login. Is this handled well?
  • Kill → Relaunch: User force-closes the app from the task switcher. On relaunch, is the app in a clean state?
  • OS kills background app: While the app is in the background, the OS kills it (common on low-RAM devices). When the user returns, what happens?

Orientation: portrait and landscape

Many apps support both portrait (vertical) and landscape (horizontal) orientation. Test:

  • Does the layout adjust correctly when rotated?
  • Does the content preserve state? (If the user typed text into a form, is it still there after rotation?)
  • On Android specifically: rotating the screen causes an Activity recreation, which is a very common source of bugs

In practice: Many developers only design for portrait mode. If your app supports landscape, rotation tests are essential.


2. Writing mobile test cases — a step-by-step approach

A good test case has four components:

  1. Precondition: The state of the app before the test begins
  2. Steps: Exactly what the user does, one action at a time
  3. Expected result: What should happen
  4. Actual result: What actually happened (filled in during test execution)

Process: from happy path to comprehensive coverage

Step 1: Write the happy path first Start with what a normal user does when everything works. This is your baseline.

Step 2: Add negative paths What if the user provides invalid data? What if a required service is down? What if the user skips a required step?

Step 3: Add edge cases What about empty states (no data to display), error states (server returned an error), first-launch states (app opened for the first time), and boundary values (password that is exactly 8 characters, the minimum)?

Step 4: Add mobile-specific scenarios Now add the scenarios from Section 1: permissions, interruptions, network conditions, orientation, and lifecycle.

Example: login screen test cases

#Test casePreconditionStepsExpected result
1Happy path loginApp installed, user has a valid account, no active session1. Open app. 2. Enter valid email. 3. Enter valid password. 4. Tap "Login"User is navigated to the home screen. Welcome message shown
2Wrong passwordSame as above1. Open app. 2. Enter valid email. 3. Enter wrong password. 4. Tap "Login"Error message shown: "Incorrect password". User stays on login screen
3Empty email fieldApp on login screen1. Leave email empty. 2. Enter any password. 3. Tap "Login"Validation error appears under email field. Login not attempted
4Empty password fieldApp on login screen1. Enter valid email. 2. Leave password empty. 3. Tap "Login"Validation error appears under password field. Login not attempted
5Invalid email formatApp on login screen1. Enter "notanemail" in email field. 2. Enter any password. 3. Tap "Login"Validation error: "Please enter a valid email address"
6Biometric loginUser has fingerprint enrolled, biometric login enabled in settings1. Open app. 2. Tap "Use fingerprint". 3. Authenticate with fingerprintUser is logged in and navigated to home screen
7Biometric deniedSame as above1. Open app. 2. Tap "Use fingerprint". 3. Cancel biometric promptUser remains on login screen. "Login cancelled" message shown
8Login with no networkNo internet connection1. Disable WiFi and cellular. 2. Open app. 3. Enter valid credentials. 4. Tap "Login"Error message: "No internet connection. Please check your network."
9App backgrounded mid-loginUser on login screen with email entered1. Type email. 2. Press Home button. 3. Wait 1 minute. 4. Return to appEmail field still contains the typed text. App is on login screen
10Screen rotation on loginApp on login screen with email entered1. Type email. 2. Rotate device to landscape. 3. Rotate back to portraitEmail text is preserved. UI layout adjusts correctly in both orientations
11Login interrupted by callUser mid-login process1. Enter credentials. 2. Simulate incoming phone call (accept or decline). 3. Return to appApp resumes on login screen with data intact
12Account locked (too many attempts)Account exists1. Enter valid email. 2. Enter wrong password 5 times in a rowAccount locked message shown. Clear instructions for how to unlock

3. Gestures in automation

Mobile apps rely on gestures. When writing automated tests, you need to simulate these gestures correctly.

The four main gestures

Tap: The equivalent of a mouse click. Most basic interaction.

Long press: Hold a finger on an element for 1–2 seconds. Often reveals a context menu or additional options.

Swipe: Drag a finger across the screen in one direction. Used for scrolling, dismissing cards, navigating between pages.

Pinch and zoom: Two fingers moving apart (zoom in) or together (zoom out). Common in maps, image viewers, and PDF readers.

Why pixel coordinates are dangerous

Beginners often write gesture tests using exact pixel coordinates:

pythonpython
# BAD APPROACH — do not do this
driver.tap([(540, 960)])  # Tap at x=540, y=960

This seems straightforward, but it breaks for several reasons:

  1. Different screen sizes: A Pixel 6 has a different resolution than a Galaxy S22. The button at coordinates (540, 960) on one device is in a different position on another.
  2. Different font sizes: If the user has large text enabled in accessibility settings, elements shift position.
  3. Different screen densities: The same physical button is at different pixel coordinates on different DPI screens.
  4. Dynamic content: If the layout changes (a banner appears, content loads), all your hardcoded coordinates are wrong.

Use accessibility IDs and semantic locators instead

The right approach is to find elements by their semantic meaning, not their position:

pythonpython
# GOOD APPROACH — find by accessibility ID
login_button = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "login-button")
login_button.click()

# GOOD APPROACH — find by text content
submit = driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR,
    'new UiSelector().text("Login")')
submit.click()

# GOOD APPROACH — find by XPath with descriptive attribute
username_field = driver.find_element(AppiumBy.XPATH,
    '//android.widget.EditText[@content-desc="email-input"]')

Ask developers to add data-testid or accessibilityLabel attributes to important interactive elements. This is a common practice in teams that take testing seriously, and it makes your tests much more stable.

Swipe gestures in automation

Swipe is one of the trickier gestures to automate. The key is to swipe from one meaningful point to another, not from hardcoded coordinates:

pythonpython
from appium.webdriver.common.appiumby import AppiumBy
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.actions.pointer_input import PointerInput

# A simple scroll down — swipe from center-bottom to center-top
window_size = driver.get_window_size()
start_x = window_size['width'] // 2
start_y = int(window_size['height'] * 0.8)
end_y = int(window_size['height'] * 0.2)

actions = ActionChains(driver)
actions.w3c_actions.devices = [PointerInput('touch', 'finger')]
actions.w3c_actions.pointer_action.move_to_location(start_x, start_y)
actions.w3c_actions.pointer_action.pointer_down()
actions.w3c_actions.pointer_action.move_to_location(start_x, end_y)
actions.w3c_actions.pointer_action.release()
actions.perform()

Notice that the scroll uses percentages of the screen size, not hardcoded pixels. This works on any screen size.


4. Permission handling in tests

Permission dialogs are a common source of test failures because they appear unexpectedly and block the test from proceeding.

How to grant permissions before tests (setup)

The best approach is to grant permissions before the test starts, not during it. This is cleaner and more reliable.

On Android, use adb to grant permissions:

bashbash
# Grant camera permission before running tests
adb shell pm grant com.your.app.package android.permission.CAMERA

# Grant location permission
adb shell pm grant com.your.app.package android.permission.ACCESS_FINE_LOCATION

# Grant all dangerous permissions at once (useful for setup scripts)
adb shell pm grant com.your.app.package android.permission.CAMERA
adb shell pm grant com.your.app.package android.permission.READ_CONTACTS
adb shell pm grant com.your.app.package android.permission.RECORD_AUDIO

In Appium capabilities, you can auto-grant permissions:

pythonpython
desired_caps = {
    "platformName": "Android",
    "app": "/path/to/app.apk",
    "autoGrantPermissions": True  # Grant all permissions automatically
}

How to test the deny scenario

To test what happens when a user denies a permission, you need to:

  1. Start the test with the permission NOT granted (the default for a fresh install)
  2. Let the permission dialog appear
  3. Tap "Deny" (or "Don't allow")
  4. Verify the app handles the denial gracefully
pythonpython
# When the permission dialog appears, tap "Deny"
deny_button = driver.find_element(
    AppiumBy.ID, "com.android.permissioncontroller:id/permission_deny_button"
)
deny_button.click()

# Now verify the app shows a helpful message
error_message = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "camera-denied-message")
assert error_message.is_displayed()

How to reset permissions between runs

This is crucial for tests that check first-launch behavior. If permissions are already granted from a previous run, the permission dialog will not appear, and your test will not work correctly.

Reset permissions by clearing app data:

bashbash
# Clear all app data (this resets permissions too)
adb shell pm clear com.your.app.package

Or revoke specific permissions:

bashbash
# Revoke camera permission before a specific test
adb shell pm revoke com.your.app.package android.permission.CAMERA

In pytest, use a fixture to reset permissions before each test:

pythonpython
import pytest
import subprocess

@pytest.fixture(autouse=True)
def reset_app_permissions():
    """Reset camera permission before each test that needs it."""
    subprocess.run([
        "adb", "shell", "pm", "revoke",
        "com.your.app.package",
        "android.permission.CAMERA"
    ])
    yield
    # Cleanup after test if needed

5. Creating a mobile test plan

A test plan is a document that describes what you will test, how you will test it, and in what priority order. For mobile testing, it has some specific components.

Step 1: Define your device matrix

A device matrix is a table of devices and OS versions you will run your tests on. You cannot test on every device, so you choose based on your users.

Sample device matrix for a banking app:

DeviceOSPriorityWhy
Samsung Galaxy S23Android 13P1Most popular Android in target market
Google Pixel 7Android 14P1Reference Android device, clean OS
Xiaomi Redmi Note 12Android 12P2Budget device, very common, tests low-end performance
iPhone 14iOS 16P1Most popular iPhone model
iPhone 12iOS 15P2Older but still widely used
iPad AiriPadOS 16P3Tablet layout, lower priority

How to determine your matrix:

  • Use your app's analytics (Crashlytics, Firebase, Google Analytics)
  • Check which OS versions and devices have the most users
  • Include at least one budget device
  • Include the latest OS + one version behind

Step 2: Assign priority levels to test scenarios

Not all tests are equally important. Assign priorities:

P1 — Critical (must pass before release):

  • User registration and login
  • Core user flows (place order, make payment, etc.)
  • Data loss scenarios
  • Security-sensitive features

P2 — Important (should pass before release):

  • Secondary user flows (edit profile, change settings)
  • Push notifications
  • Deep links
  • Permission handling

P3 — Nice to have (check when time allows):

  • Edge cases and rare scenarios
  • Cosmetic issues
  • Orientation for less-used screens

Step 3: Decide what to automate vs what to test manually

Test typeAutomateManualNotes
Critical happy pathsYesNoRun in CI on every build
Regression tests (things that used to break)YesNoPrevents regressions
Permission flowsYesNoCan be scripted with adb
Gesture-heavy flows (complex swipes)SometimesYesGestures are brittle in automation
Visual design reviewNoYesRequires human judgment
Exploratory testing (finding unknown bugs)NoYesBy definition cannot be automated
Interruptions (calls, notifications)PartiallyYesHard to automate fully
Performance testingYesYesTools exist, but human observation adds value
Real-world conditions (subway, one hand)NoYesCannot be automated

6. Common mobile QA mistakes beginners make

Learning from other people's mistakes is faster than learning from your own. Here are the most common errors new mobile QA engineers make:

Mistake 1: Testing only on high-end flagship devices

The problem: You test on the latest iPhone 15 Pro and Samsung Galaxy S24. Your app works perfectly. But 60% of your users have budget devices from 2021 with 3GB of RAM. The app is slow and crashes for them.

The fix: Always include at least one budget or mid-range device in your test matrix. Test on devices that represent your actual users, not the most expensive devices your company owns.

Mistake 2: Ignoring orientation changes

The problem: You test the app in portrait mode only. On Android, rotating the screen destroys and recreates the Activity, which often causes crashes or data loss. On landscape, buttons may be hidden behind the keyboard.

The fix: For every screen you test, rotate the device and confirm the layout works. Pay special attention to screens with text input.

Mistake 3: Not testing with slow network

The problem: In the office on WiFi, everything loads instantly. On a commute with poor 3G signal, the app shows a blank screen and the user does not know if anything is happening.

The fix: Test with throttled network (3G, offline). Check that: loading states are shown, errors are communicated clearly, and the app does not crash when the network is slow or absent.

Mistake 4: Hardcoding timeouts (magic numbers)

The problem: A developer writes time.sleep(3) or driver.implicitly_wait(5) in tests, assuming the app will always respond within that time. On a slow emulator or a cloud farm with high load, the test fails intermittently.

The fix: Use explicit waits that wait for a specific condition, not a specific duration:

pythonpython
# BAD — hardcoded timeout
time.sleep(3)

# GOOD — wait for the element to be visible
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

wait = WebDriverWait(driver, timeout=10)
element = wait.until(
    EC.visibility_of_element_located((AppiumBy.ACCESSIBILITY_ID, "home-screen"))
)

Mistake 5: Not resetting state between tests

The problem: Test 1 logs in. Test 2 assumes the app is on the login screen — but the user from Test 1 is still logged in. Test 2 fails.

The fix: Each test must start from a known, clean state. Use adb shell pm clear or app-level logout in your test setup to ensure a fresh start.

Mistake 6: Only testing with ideal content

The problem: You test with a normal-length name like "John Smith" and a standard profile photo. But your real users might have names in Arabic or Chinese, or upload a 20MB photo.

The fix: Test with edge case content:

  • Very long names (100+ characters)
  • Names with special characters and emoji
  • Very large images
  • Empty states (no data to show)
  • Content in multiple languages (localization)

Official references


Quick recap

TopicKey takeaway
Test typesAlways cover: happy path, permissions, interruptions, network, lifecycle, orientation
Test case structurePrecondition → Steps → Expected result
GesturesUse accessibility IDs and semantic locators, never hardcoded pixel coordinates
PermissionsTest grant, deny, and revoke; reset permissions between test runs with adb shell pm clear
Test planDevice matrix driven by analytics, priorities P1/P2/P3, clear automate vs manual split
Common mistakesHigh-end devices only, ignoring rotation, no slow network tests, hardcoded timeouts

Homework

  1. Write a test plan: Choose a simple app (a calculator, a notes app, or a login screen). Write a test plan that includes:

    • A device matrix (at least 4 devices/OS combinations)
    • At least 15 test cases in table format with preconditions, steps, and expected results
    • At least 3 permission-related test cases
    • At least 2 network condition test cases
    • At least 2 lifecycle test cases (background/foreground)
  2. Find gesture bugs: Install any app that uses swipe gestures (a dating app, a news reader, a weather app). Test swipe gestures in both portrait and landscape mode. Rotate the device while a swipe is in progress. Note any unexpected behavior.

  3. Permission denial test: On any phone you own, find an app that uses the camera. Go to Settings → Apps → [App Name] → Permissions and deny the camera permission. Now open the app and try to use the camera feature. Document exactly what happens. Is the error message clear? Does the app crash? Does it offer a way to re-enable the permission?

  4. Network resilience check: Open any app that loads data from the internet. Start the app, let it load some content, then immediately switch Airplane Mode on. Navigate through the app. What happens? Does it show cached data? Does it show error messages? Does it crash?

  5. Device matrix research: Go to your app's analytics (if you have access) or use public data for a category of app (e.g., "top food delivery apps"). Identify the top 5 Android OS versions and top 5 iOS versions. Design a minimum viable 6-device test matrix based on this data. Write a short justification for each device choice.


Next: learn to automate these scenarios with Appium in the next track.