Skip to main content

Mobile Testing Milestones & Mini-Projects

In short: six projects. The mobile-web ones (Milestone 2) run on your machine and are shown with real, passing tests; the native ones use real commands and config you run when you have an emulator, device or device cloud.

How to use this page

First read each milestone in the Roadmap. For the native milestones, a free device-cloud trial works if you don't have Android Studio or Xcode installed.

Contents​


Milestone 1: Build a device matrix​

Practises: turning usage data into a test plan.

#TaskExpected result
1Pick an app and a market (e.g. a shopping app in India)Chosen
2Find current OS-version and device-share data (StatCounter, your analytics)A short data list
3Build a matrix with "must / should / edge" tiersA table
4Justify each device in one lineReasons written
5State the OS-version support rangee.g. iOS 16–18, Android API 30–35
Example matrix (illustrative β€” use current data)
TierDeviceWhy
MustLatest iPhone, iOS latest & latestβˆ’1Large, high-spending share
MustSamsung Galaxy A-series (mid-range), Android latest & βˆ’1Best-selling Android tier in the market
ShouldA 3-year-old iPhone on the oldest supported iOSStill common; oldest we support
ShouldGoogle Pixel (clean Android)Reference Android behaviour
EdgeA low-end Android (1–2 GB RAM)Performance bugs appear here first
EdgeAn iPad / a foldableDifferent layouts

Support: iOS 16–18; Android API 30–35. Review each release.

Try it: how would the matrix differ for a banking app used mostly by older users? (Hint: older devices, larger fonts, accessibility.)


Milestone 2: Mobile-web tests​

Practises: device emulation, touch, orientation β€” runnable now.

#TaskExpected result
1Playwright project with iPhone 15, Pixel 7 and iPad Pro 113 projects
2Test: the device is emulated as mobile, narrow viewportPasses on all 3
3Test: add todos via the keyboard; the counter updatesPasses
4Test: rotate to landscape; the app stays usablePasses
5Run all9 passed (3 tests Γ— 3 devices)
6Add Galaxy S9+ (320px); check nothing breaks at the narrowest widthPasses or a finding

Check: npx playwright test β€” 9 passed.

Solution
playwright.config.ts
// playwright.config.ts β€” mobile-web testing with device emulation (real devices need a device cloud)
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
testDir: './tests',
use: { baseURL: 'https://demo.playwright.dev/' },
projects: [
{ name: 'iPhone 15', use: { ...devices['iPhone 15'] } },
{ name: 'Pixel 7', use: { ...devices['Pixel 7'] } },
{ name: 'iPad Pro 11', use: { ...devices['iPad Pro 11'] } },
],
});
tests/mobileweb.spec.ts
// tests/mobileweb.spec.ts β€” the same web app, checked on emulated phones and a tablet
import { test, expect } from '@playwright/test';

test('device is emulated as mobile with touch', async ({ page, isMobile }) => {
await page.goto('/todomvc/');
const width = page.viewportSize()!.width;
console.log(`viewport ${width}px, isMobile=${isMobile}`);
expect(width).toBeLessThan(900); // all our devices are narrow
});

test('add todos with the on-screen keyboard', async ({ page }) => {
await page.goto('/todomvc/');
const box = page.getByPlaceholder('What needs to be done?');

await box.fill('Buy milk');
await box.press('Enter');
await box.fill('Walk the dog');
await box.press('Enter');

await expect(page.getByText('Buy milk')).toBeVisible();
await expect(page.getByText('Walk the dog')).toBeVisible();
await expect(page.getByText('2 items left')).toBeVisible();
});

test('rotating to landscape changes the viewport', async ({ page }) => {
await page.goto('/todomvc/');
const portrait = page.viewportSize()!;
await page.setViewportSize({ width: portrait.height, height: portrait.width }); // rotate
const landscape = page.viewportSize()!;
expect(landscape.width).toBeGreaterThan(landscape.height); // now wider than tall
await expect(page.getByPlaceholder('What needs to be done?')).toBeVisible(); // still usable
});

Real result: 9 passed. This exercises a real browser engine at each device's size and touch settings. It is mobile web β€” not a native app, not real hardware.

Watch out for: baseURL with a path plus page.goto('/') β€” / goes to the site root and drops the path. Use the full path in goto, as here.

Try it: the TodoMVC toggle checkbox is hidden until hover β€” try to tap() it on the iPhone project. It times out. Why is that a real mobile bug?


Milestone 3: Manual mobile pass​

Practises: the manual mobile layer β€” do this on a real phone (your own app, or any app you use).

#TaskExpected result
1Interruptions: take a call / get a notification mid-flow; switch apps; lock/unlockState preserved each time
2Permissions: deny camera/location, then grant, then revoke in SettingsGraceful handling, no crash
3Network: turn on airplane mode mid-action; then reconnectClear message; actions sync, no duplicates
4Orientation: rotate on key screens and mid-typingNo lost data, sensible layout
5Large font: max out display/font size in SettingsLayout still works
6Log each issue as a mobile bug reportA list of findings
What good findings look like
  • "Rotating during checkout clears the address form (Android 14, Pixel 7)."
  • "Denying location shows a blank map with no explanation (iOS 18)."
  • "Actions taken in airplane mode are lost on reconnect instead of syncing."
  • "At the largest font size, the 'Pay' button is pushed off-screen."

Each names the device and OS, and says what should happen instead.

Try it: put your phone in battery-saver mode and use a social app. Does anything stop working (background refresh, notifications)?


Milestone 4: Command-line device control​

Practises: adb / simctl to install, set conditions and fire deep links.

Needs an Android emulator/device (adb) or an iOS simulator (simctl).

#TaskExpected result
1List connected devicesYour device/emulator shown
2Install an APK / .appInstalled
3Grant, then revoke, a permission from the command lineApp reflects the change
4Fire a deep link to a specific screenThe app opens that screen
5Simulate low battery (Android) or set locationApp responds
Commands
# Android
adb devices
adb install -r app.apk
adb shell pm grant <pkg> android.permission.CAMERA
adb shell pm revoke <pkg> android.permission.CAMERA
adb shell am start -a android.intent.action.VIEW -d "myapp://order/12"
adb shell dumpsys battery set level 5
adb shell dumpsys battery reset # undo

# iOS simulator
xcrun simctl boot "iPhone 15"
xcrun simctl install booted App.app
xcrun simctl privacy booted grant location <bundle-id>
xcrun simctl openurl booted "myapp://order/12"

Full list: quick reference.

Try it: fire a deep link to a screen that needs login, while logged out. Does the app send you to login and then to the right screen?


Milestone 5: Appium native automation​

Practises: capabilities, accessibility-id locators, a screen object, a gesture.

Needs an emulator/device, or a device-cloud session. Use a sample app (e.g. the Sauce Labs demo app, or your own).

#TaskExpected result
1Install Appium + the uiautomator2 (or xcuitest) driver; start the serverServer on :4723
2Write capabilities for your device + appSession starts
3A LoginScreen object using accessibility idsReusable
4Test: log in and reach the products screenPasses
5Add a swipe/scroll gesture to reach an item lower in a listItem found
6Background the app 5 s and resume; state preservedPasses
Solution shape

Capabilities:

capabilities/android.json
{
"platformName": "Android",
"appium:automationName": "UiAutomator2",
"appium:deviceName": "Pixel_7_API_34",
"appium:app": "/path/to/app.apk",
"appium:autoGrantPermissions": true
}

Screen object + test (Java, abbreviated):

class LoginScreen {
private final AppiumDriver driver;
LoginScreen(AppiumDriver d) { this.driver = d; }
void loginAs(String user, String pass) {
driver.findElement(AppiumBy.accessibilityId("username")).sendKeys(user);
driver.findElement(AppiumBy.accessibilityId("password")).sendKeys(pass);
driver.findElement(AppiumBy.accessibilityId("login_button")).click();
}
}

@Test void userCanLogIn() {
new LoginScreen(driver).loginAs("standard_user", "secret_sauce");
assertTrue(driver.findElement(AppiumBy.accessibilityId("products_title")).isDisplayed());
}

Backgrounding for interruptions:

driver.executeScript("mobile: backgroundApp", Map.of("seconds", 5));

Full walkthrough with a runnable project: the Appium guide.

Watch out for: locating by text β€” it breaks under translation. Ask developers to add accessibility ids (they double as screen-reader labels).


Milestone 6: Cloud run + report​

Practises: device-cloud coverage, performance, store readiness, reporting.

#TaskExpected result
1Run your Milestone 5 tests on a device cloud across 3 devicesResults on 3 devices
2Measure cold-start time on a flagship and a low-end deviceTwo numbers; low-end slower
3Run the store-readiness checklistiOS + Android checks
4Do a VoiceOver/TalkBack pass on the main journeyNotes
5Write a mobile test reportVerdict + coverage + findings
Report outline
Verdict:   GO WITH RISKS β€” 1 major open (form clears on rotation, Android)
Coverage: iOS 16–18 (iPhone 12, 15), Android 12–15 (Pixel 7, Galaxy A54, low-end X)
emulator for functional; 3 real/cloud devices for sign-off
Performance: cold start 1.2s (flagship) vs 3.8s (low-end) β€” investigate on low-end
Store: iOS review checklist βœ“; Android data-safety form needs updating
Accessibility: TalkBack completes checkout; 2 unlabeled icons
Findings: <list, each with device + OS>

Try it: compare your cold-start numbers with the app's competitors on the same device. Where do you stand?


Final project​

Test a mobile app end to end β€” your own, a React Native/Flutter sample, or a public demo app on a device cloud.

Done when:

  • Device matrix from current data, with tiers and reasons
  • Mobile-web (if applicable) tested with emulation across 3 devices
  • Manual pass on a real device: interruptions, permissions, networks, orientation, large font
  • Core journeys automated with Appium and run on β‰₯ 3 devices (cloud is fine)
  • Update-from-live-version path tested
  • Performance measured on a low-end device vs a baseline
  • Store-readiness checklist complete; a11y pass with a screen reader
  • A report with a verdict, coverage and findings β€” proof you can deliver mobile testing