# Choose a Device Matrix That Matches Real Risk

> A bigger device list often pays several times for the same screen, OS and memory risk. This guide builds a matrix whose rows are named failures rather than handset models: a low-memory Android row backed by the user-perceived LMK rate, a foldable row for configuration-change sequences, a tablet row that Android 16's ignored orientation locks made mandatory, and newest- and oldest-OS iOS rows constrained by what Xcode 27 can still simulate. It covers what usage data Play Console and App Store Connect actually expose, a Crashlytics BigQuery query for crash concentration, the matrix as Gradle Managed Devices groups and xcodebuild destinations, why recreate() does not test process death, and when to retire a device versus a row.

- Author: [Shashank Rawlani](https://shashank.rawlani.com)

- Published: 2026-10-15T13:30:00.000Z

- Updated: 2026-10-14T11:09:52.936Z

- Category: Automation Tutorials

- Canonical source: https://automationtester.in/blog/automation-tutorials/mobile-device-matrix-real-risk

- Tags: mobile-testing, device-matrix, android, ios, gradle-managed-devices, xcode, test-strategy

**Quick answer:** Write rows as risks, then pick hardware to carry them. Each row names one failure: a low-memory kill, state lost on fold or unfold, a broken large-screen layout, a newest-OS behaviour change, a missing API at your oldest supported OS. Derive rows from your own Play Console, App Store Connect and crash data, not a list of popular phones. Run each on the cheapest runtime that reproduces its risk: emulators and simulators per pull request, a few risk-specific devices nightly, physical hardware before release. Retire a device when another carries its rows; retire a row only when its risk is gone.

A familiar sequence: the device farm bill doubles, the release suite now runs on thirty handsets, and the next production incident is a checkout screen that loses its basket when a user unfolds their phone. Every one of the thirty devices was a slab phone. Most were mid-range models on the same Android version, and all fell into the same width class. The suite was paying thirty times for one kind of coverage.

Adding devices did not add coverage, because nobody had asked what each device was *for*. This article asks that question row by row. Which checks belong on a device at all was the first part of this series; performance gets its own part later.

**Versions checked on 2026-09-30.** Google Play: since 31 August 2026, new apps and updates must target Android 16 (API level 36), with extensions available to 1 November 2026. Xcode 27 (27A266a, released 14 September 2026) uploads apps with iOS deployment targets from 15 to 27, but its Simulator and on-device debugging support only iOS 17 or later. Current iOS and iPadOS: 27.0.1. Android Gradle Plugin 9.4.1, where the managed-device property `apiLevel` is deprecated in favour of `sdkVersion`. Automated Test Device emulator images are published for API levels 30 to 36.

## A handset model carries risk; it is not risk

Google's own consoles refuse to treat a model name as one configuration. Android vitals warns that "a single device model may have variants with different Android versions, RAM, storage, or System on Chip (SoC)", and the Play Console device catalog says the model alone "may not be enough to specify these attributes uniquely."

So a row that reads "Samsung mid-range, 2024" is not a test condition. The condition is something like *3 GB of RAM or less, Android 14, compact width*, and a model is one way to get hold of it. Write the risk column first and the hardware column last. Two devices that land in the same bucket on every attribute your app cares about are one row, and the second adds cost without information. The reverse also holds: an unfolded foldable is an expanded-width screen too, so one carrier can serve two rows.

Bucket on the attributes the platforms use to change behaviour: OS API level, width size class, fold state, RAM tier and, with native code, memory page size. Android's width classes are compact below 600dp, medium to 840dp, expanded to 1200dp, then large and extra-large; width matters more than height "due to the ubiquity of vertical scrolling."

## The usage data each store actually gives you

The two platforms give you very different data, and the difference shapes the matrix.

### Android: Reach and devices, then vitals

There is no longer a public chart of Android version shares to borrow. Google's distribution dashboard shows only Vulkan and OpenGL ES support, from a seven-day snapshot ending 24 November 2025, and for deciding "what to test" it points to Reach and devices in the Play Console. That page splits your own install base by Android version, RAM, SoC, CPU, GPU and OpenGL ES version. Its overview "only shows the top three values for any attribute", so the tail lives on the Attribute details page, and RAM ranges are inclusive at the bottom and exclusive at the top, which matters when you pick the lowest tier to cover.

Android vitals adds the defect side. Every metric can be broken down by Android version, form factor and device model, and where they exist, the crash or ANR clusters for each breakdown are one click away. One metric maps straight onto a matrix row: the **user-perceived LMK rate**, "the percentage of your daily active users who experienced at least one user-perceived low memory kill". If it is not trivial, your own data justifies a low-memory row. One blind spot: vitals "exclude technical issues that occur on uncertified device models, or on versions of your app that were not installed through Google Play", so a sideloaded or enterprise fleet needs another crash source.

### iOS: a dated two-number page and App Store Connect

Apple publishes an "iOS and iPadOS usage" figure on its App Store support page, measured from "devices that transacted on the App Store on June 7, 2026." It gives the share on iOS 26 and iPadOS 26 and nothing else. It predates iOS 27 and says nothing about how the rest splits across older versions, which is the split that decides your oldest-OS row.

App Store Connect analytics filter by *Platform Version*, meaning "the OS version on which the app was downloaded from the App Store or used", and by *Device*. But Device means iPhone, iPad, Apple TV and similar product families, not models. Usage data also comes only "from users who have agreed to share diagnostics and usage information with app developers", so check the opt-in rate before trusting a small bucket.

### Crash clusters by attribute, not by brand

If you export Crashlytics to BigQuery, the schema already holds the attributes you need to bucket on: `device.model`, `operating_system.display_version`, `operating_system.device_type` (for example `MOBILE` or `TABLET`), `device_orientation`, `memory.free` and `error_type`. Ask which issues are *concentrated* in one bucket; those are the risks your matrix is missing:

```
-- Issues whose affected installs sit mostly in one form factor.
-- Table name: app id with dots as underscores, plus _ANDROID or _IOS.
WITH per_bucket AS (
  SELECT
    issue_id,
    operating_system.device_type AS form_factor,
    COUNT(DISTINCT installation_uuid) AS installs
  FROM `shop-prod.firebase_crashlytics.com_example_shop_ANDROID`
  WHERE event_timestamp >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY)
    AND error_type IN ('FATAL', 'ANR')
  GROUP BY issue_id, form_factor
)
SELECT
  issue_id,
  form_factor,
  installs,
  ROUND(installs / SUM(installs) OVER (PARTITION BY issue_id), 2) AS share_of_issue
FROM per_bucket
WHERE installs >= 20
ORDER BY share_of_issue DESC, installs DESC;
```

Rerun it with `operating_system.display_version` or a bucketed `memory.free` in place of the form factor. An issue concentrated on tablets justifies a tablet row. An issue spread evenly across every bucket is a code bug that a host test should catch, and another device will not help.

Clustering thousands of events by attribute is work a script or a model does well. Dropping a row is not: it turns on support commitments, accessibility needs and who sits in a small bucket, so product and support make that call.

## Five rows, each with a named failure

A worked matrix for a shopping app with an iOS 17 deployment target and an Android `minSdk` of 28. A row whose risk column cannot be filled in does not belong.

| Row | Risk it exists to catch | Every pull request | Nightly | Before release |
| --- | --- | --- | --- | --- |
| Low-memory Android | Process killed in the background; basket and form state gone on return | Kill-and-restore test on a phone emulator | Physical device that reports `isLowRamDevice()` true | Same device, release build |
| Foldable | State lost across fold and unfold, and across a rotation followed by a fold | Tagged subset on a Pixel Fold emulator | Full configuration suite on that emulator | Physical foldable |
| Tablet | Layout broken at expanded width once orientation locks stop applying | None | Expanded-width suite on a tablet emulator | Physical tablet |
| Current iPhone, iOS 27 | Behaviour change in the newest OS the app runs on | Simulator, iOS 27 runtime | Same | Physical iPhone on the current point release |
| Oldest supported iOS | API or behaviour missing at the deployment-target floor | None | Simulator, iOS 17 runtime | Physical device on the floor version |

Three of these rows are usually justified wrongly.

**The tablet row became mandatory on 31 August.** For apps targeting Android 16, "orientation, resizability, and aspect ratio restrictions no longer apply on displays with smallest width >= 600dp", so portrait locks, `resizeableActivity="false"` and `maxAspectRatio` are ignored there. Play now requires API 36 for every update, so an app that leaned on a portrait lock gets resized on tablets and unfolded foldables with its next release. The temporary opt-out, `PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY`, is documented as one that "won't apply when targeting API level 37." The page names the knock-on cost too: "more activity re-creation, which can result in losing user state if not properly preserved."

**The foldable row tests combinations.** Android's adaptive app quality guidelines, at the most basic tier, expect the app to retain or restore state "as the device goes through configuration changes such as device rotation, folding and unfolding". Their `T-Config_Combinations` test asks for sequences such as "rotation followed by device folding or unfolding." A single fold test passes on apps that fail the sequence.

**The oldest-iOS row depends on your Xcode.** Xcode 27 accepts deployment targets back to iOS 15. Its Simulator column reads "iOS 17 or later", and its Device Support column, defined as the range "for installing and debugging applications on device", says the same. With a floor of iOS 15 or 16, the current toolchain ships your app to OS versions it cannot simulate or debug on. Either keep a CI host on Xcode 26.6, which supports iOS 15 in both columns but needs macOS Tahoe 26.x, or cover the floor with a physical device running a distributed build. Both cost money; choose one deliberately.

## Recreating an activity is not process death

The low-memory row is the one most often "covered" by a test that cannot fail. Android's saved-state guidance separates the two events clearly. ViewModel state survives a configuration change but does not survive "system-initiated process death"; only saved state and persistent storage do. `ActivityScenario.recreate()` simulates the first event. The documentation notes that the scenario "maintains the activity's saved instance state", and the process, with every singleton and in-memory cache, stays alive.

```
// Passes even when the basket lives only in a process-wide singleton.
@Test
fun basketSurvivesRecreate_provesLittle() {
    launchActivity<CheckoutActivity>().use { scenario ->
        addItemToBasket("sku-4411")
        scenario.recreate()
        onView(withId(R.id.basket_count)).check(matches(withText("1")))
    }
}
```

Reaching for force-stop is no better. The same guidance lists "killing or force-quitting the app from the Settings screen" as a user-initiated dismissal, where the user does not expect state back, so it tests a different promise. The event that matches a low-memory kill is killing a backgrounded process. `adb shell am kill` does exactly that, and its documentation says it "kills only processes that are safe to kill and that will not impact the user experience", which in practice means the app must be in the background first:

```
#!/usr/bin/env bash
# Low-memory row, every PR: background the app, kill it, return to it.
set -euo pipefail
PKG=com.example.shop

adb shell am start -n "$PKG/.CheckoutActivity" --es seed_basket sku-4411
sleep 2
adb shell input keyevent KEYCODE_HOME        # move to background first
adb shell am kill "$PKG"                     # system-style kill, not force-stop
adb shell pidof "$PKG" && { echo "process still alive"; exit 1; } || true
adb shell am start -n "$PKG/.CheckoutActivity"   # user returns
# The UI test that follows asserts the basket count is still 1.
```

That runs on any emulator. The nightly device exists for what an emulator will not show: how often the kill happens under real memory pressure. The platform's own flag is `isLowRamDevice()`. Its reference page says that "currently it generally means something with 1GB or less of RAM", with device configuration having the final say. Pick the carrier from the lowest RAM bucket in Reach and devices that still holds a meaningful share of installs, and check the flag on the unit itself rather than the spec sheet.

## The matrix in build files

A matrix kept in a spreadsheet drifts away from what CI actually runs. On Android, Gradle Managed Devices let the build declare the emulator rows and the groups that run together:

```
// app/build.gradle.kts, AGP 9.4
import com.android.build.api.dsl.ManagedVirtualDevice

android {
    testOptions {
        managedDevices {
            localDevices {
                create("phoneApi36") {
                    device = "Pixel 2"
                    sdkVersion = 36                 // apiLevel is deprecated
                    systemImageSource = "aosp-atd"
                }
                create("foldApi36") {
                    device = "Pixel Fold"
                    sdkVersion = 36
                    systemImageSource = "aosp-atd"
                }
                create("tabletApi36") {
                    device = "Nexus 9"
                    sdkVersion = 36
                    systemImageSource = "google-atd"  // needs Play services
                }
                create("phoneMinSdk") {
                    device = "Pixel 2"
                    sdkVersion = 28                 // the minSdk floor
                    systemImageSource = "aosp"      // no ATD image below 30
                }
                create("phone16kPages") {
                    device = "Pixel 2"
                    sdkVersion = 36
                    systemImageSource = "google"
                    pageAlignment = ManagedVirtualDevice.PageAlignment.FORCE_16KB_PAGES
                }
            }
            groups {
                create("pullRequest") {
                    targetDevices.add(localDevices["phoneApi36"])
                    targetDevices.add(localDevices["foldApi36"])
                }
                create("nightly") {
                    targetDevices.add(localDevices["foldApi36"])
                    targetDevices.add(localDevices["tabletApi36"])
                    targetDevices.add(localDevices["phoneMinSdk"])
                    targetDevices.add(localDevices["phone16kPages"])
                }
            }
        }
    }
}
```

Run a group with `./gradlew pullRequestGroupDebugAndroidTest`, or one device with `./gradlew foldApi36DebugAndroidTest`.

The managed-devices page still comments its Automated Test Device example with "ATDs currently support only API level 30", but the SDK repository lists `aosp_atd` and `google_atd` images for API 30 through 36. The floor is real: nothing below 30. ATD images "disable hardware rendering", so keep screenshot tests on a full image, and managed devices take "only API levels 27 and higher", so a `minSdk` of 24 needs its floor row elsewhere.

The last device is the row most matrices lack. Since Android 15, AOSP supports 16 KB memory-page devices, and apps with native code, even code arriving through an SDK, must be rebuilt for them. Play requires that support for apps targeting API 35 and up and blocks non-compliant updates from 1 February 2027. No model name reveals page size, hence the row. No `.so` files in your APK, no row.

Sharding multiplies cost: shards are provisioned per device profile, so three devices at two shards each start six emulators. Espresso Device's `@RequiresDisplay` has a quieter cost: when requirements are not met "the tests are skipped", and a skipped row reads as green, so alert on skip counts per group.

On iOS, a test plan says *what* runs and under which configurations, while the destination list says *where*. Swift Testing tags carry the cadence. Apple's test-plan guide suggests tags for "cadence (presubmission, daily, prerelease)", and test plans can include tests by tag from Xcode 16 onward:

```
import Testing

extension Tag {
    @Tag static var presubmission: Self
    @Tag static var daily: Self
}

@Test(.tags(.presubmission))
func basketSurvivesBackgroundAndReturn() async throws { /* ... */ }

@Test(.tags(.daily))
func checkoutLayoutAtRegularWidth() async throws { /* ... */ }
```

```
# Floor runtime for the nightly row; Xcode 27 simulators start at iOS 17.
xcodebuild -downloadPlatform iOS -buildVersion 17.5

# Pull request: current iPhone only.
xcodebuild test -scheme Shop -testPlan PullRequest \
  -destination 'platform=iOS Simulator,name=iPhone 17 Pro,OS=27.0'

# Nightly: the same plan structure on the newest and oldest runtimes.
xcodebuild test -scheme Shop -testPlan Nightly \
  -destination 'platform=iOS Simulator,name=iPhone 17 Pro,OS=27.0' \
  -destination 'platform=iOS Simulator,name=iPhone SE (3rd generation),OS=17.5'
```

Repeating `-destination` runs the same tests on each destination in one command; the names must match simulator device types installed with your Xcode. For physical Android release rows on hardware you do not own, Test Lab takes the same shape as repeated `--device model=…,version=…` flags on `gcloud firebase test android run`, with models chosen from `gcloud firebase test android models list` by the attribute the row needs.

## When a device, or a row, leaves

Remove a *device* when another device already carries every row it carried. Remove a *row* only when its risk is gone: you raised `minSdk` or the deployment target past it, the bucket has shown negligible installs and no concentrated crash clusters for several releases, or the platform behaviour it guarded no longer exists. Popularity never retires a row on its own; a small low-memory bucket can still produce most of your LMK rate.

Farms remove devices for you, and that happens quietly. Firebase Test Lab keeps deprecated devices "for at least one month before being removed". After that, requests for the device "are marked as Skipped", not failed. Test Lab also tags devices with a `reduced_stability` label after 30 days or more of high inconclusive rates, and it describes low-capacity models as "not suitable for test sharding". Read all three signals as prompts to re-pick the carrier for the row. None of them is a reason to drop the row.

Escapes push the other way. For each production defect a device run should have caught, ask which row should have seen it. If none, add one and name its failure. If a row should have and did not, fix the carrier or the test; the matrix was right. A monthly review, next to the crash-cluster query, keeps growth tied to real failures.

## Rebuild one row from last month's crashes

Add a column to your current device list headed "risk this device exists to catch" and fill it in honestly. Any device whose cell says "popular", or repeats the cell above, is a cut candidate, and its cost funds the rows you lack.

Then run the concentration query over the last 30 days, or sort vitals crash clusters by form factor and Android version. Take the most concentrated issue and name the row that should have caught it. If there is none, add it today with a test that fails on the shipped build; for low memory that means the kill-and-return script, not `recreate()`. Tag each device result with the bucket its carrier stood for, such as "RAM under 2 GB" or "expanded width", so a green run can be traced to the risk it ruled out.

## Questions about sizing a device matrix

### How many physical devices does a release gate need?

As many as you have distinct rows whose risk an emulator or simulator cannot reproduce, which is usually fewer than teams own. Android's testing strategy page illustrates a release tier with eight phones, a foldable and a tablet. Treat that as an illustration of shape, not a quota. If three of your eight phones share an OS, a RAM tier and a width class, two of them are redundant.

### Should every test run on every row?

No. Android's guidance on screen testing puts it bluntly: "most of your UI tests don't need to run on multiple devices." Run the bulk on one phone configuration and give each other row only the tests that exercise its risk: configuration-change tests on the foldable, expanded-width layout tests on the tablet, restore tests on the low-memory device.

### Does a cloud device farm replace owning devices?

For breadth, mostly. Farms are weak for rows needing one specific, stable unit, such as a device whose low-RAM flag you verified or hardware with an enrolled biometric, and owned devices never turn into "Skipped" overnight. Own the few units for rows that must never go dark; rent the rest.

## Primary references

- [Android Developers — Meet Google Play's target API level requirement](https://developer.android.com/google/play/requirements/target-sdk). That starting 31 August 2026 new apps and app updates must target Android 16 (API level 36), with lower levels for Wear OS, Automotive, TV and XR, that existing apps must target API 35 to stay available to new users on newer OS versions, and that extensions can be requested to 1 November 2026.

- [Android Developers — Behavior changes: apps targeting Android 16](https://developer.android.com/about/versions/16/behavior-changes-16). That for apps targeting API 36 "orientation, resizability, and aspect ratio restrictions no longer apply on displays with smallest width >= 600dp"; the list of ignored manifest attributes and APIs; the warning about extra activity re-creation and lost state; the `PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY` opt-out; and that the opt-out "won't apply when targeting API level 37."

- [Android Developers — Adaptive app quality guidelines, Tier 3 (Adaptive ready)](https://developer.android.com/docs/quality-guidelines/adaptive-app-quality/tier-3). The state-retention requirement across "device rotation, folding and unfolding, and window resizing", and the `T-Config_Combinations` test covering sequences such as rotation followed by folding.

- [Android Developers — Adaptive app quality guidelines](https://developer.android.com/docs/quality-guidelines/large-screen-app-quality). The three tiers and the recommended test device types and emulators, including the 7.6-inch fold-in foldable emulator and a tablet emulator.

- [Android Developers — Use window size classes](https://developer.android.com/develop/ui/compose/layouts/adaptive/use-window-size-classes). The width breakpoints (compact below 600dp, medium to 840dp, expanded to 1200dp, then large and extra large) and that width "is usually more important than available height".

- [Android Developers — Distribution dashboard](https://developer.android.com/about/dashboards). That the page now carries only Vulkan and OpenGL ES distribution, as a seven-day snapshot ending 24 November 2025, and recommends Reach and devices in Play Console for deciding "what to test".

- [Play Console Help — View and understand your app's quality and reach](https://support.google.com/googleplay/android-developer/answer/10770882?hl=en). The Reach and devices attributes (Android version, RAM, SoC and others), that the overview "only shows the top three values for any attribute", and the inclusive-lower, exclusive-upper RAM range rule.

- [Play Console Help — View and restrict your app's compatible devices](https://support.google.com/googleplay/android-developer/answer/7353455?hl=en). That the model alone "may not be enough to specify these attributes uniquely", and the device catalog's model and rule-based exclusions.

- [Play Console Help — Monitor your app's technical quality with Android vitals](https://support.google.com/googleplay/android-developer/answer/9844486?hl=en). The breakdowns by Android version, form factor and device model, the definition of a device model with variants, crash and ANR clusters per breakdown, the user-perceived LMK rate, and the exclusion of uncertified devices and non-Play installs.

- [Android Developers — ActivityManager reference](https://developer.android.com/reference/android/app/ActivityManager). `isLowRamDevice()`, which "generally means something with 1GB or less of RAM" and depends on device configuration.

- [Android Developers — Save UI states](https://developer.android.com/topic/libraries/architecture/saving-states). That ViewModel state does not survive system-initiated process death while saved state and persistent storage do, and that force-quitting from Settings is a user-initiated dismissal.

- [Android Developers — Test your app's activities](https://developer.android.com/guide/components/activities/testing). `ActivityScenario.recreate()` and that the scenario "maintains the activity's saved instance state".

- [Android Developers — Android Debug Bridge (adb)](https://developer.android.com/tools/adb). The `am kill` command, which "kills only processes that are safe to kill and that will not impact the user experience", as distinct from `force-stop`.

- [Android Studio user guide: Scale your tests with build-managed devices](https://developer.android.com/studio/test/gradle-managed-devices). The `localDevices` and `groups` DSL, the task naming pattern, "only API levels 27 and higher", shard provisioning per device profile, the ATD image sources and their removed services, the disabled hardware rendering and unsupported screenshot tests, and the stale "only API level 30" comment.

- [Android Gradle Plugin 9.4 API — ManagedVirtualDevice](https://developer.android.com/reference/tools/gradle-api/9.4/com/android/build/api/dsl/ManagedVirtualDevice). That `apiLevel` is deprecated in favour of `sdkVersion` (added in 8.9.0), the four `systemImageSource` values, and the `pageAlignment` property.

- [Android SDK repository — AOSP ATD system image manifest](https://dl.google.com/android/repository/sys-img/aosp_atd/sys-img2-3.xml). The published ATD images for API levels 30 through 36; the matching `google_atd` manifest lists the same range.

- [Android Developers — Support 16 KB page sizes](https://developer.android.com/guide/practices/page-sizes). AOSP support for 16 KB devices since Android 15, the rebuild requirement for apps with native code including through SDKs, and the Play requirement with its 1 February 2027 update cut-off.

- [Android Developers — Libraries and tools to test different screen sizes](https://developer.android.com/training/testing/different-screens/tools). The Pixel Fold ATD managed-device example, that "most of your UI tests don't need to run on multiple devices", annotation filtering, and Espresso Device's `@RequiresDisplay` skipping tests whose requirements are not met.

- [Android Developers — Testing strategies](https://developer.android.com/training/testing/fundamentals/strategies). The example release-candidate tier of eight phones, one foldable and one tablet.

- [Firebase — Crashlytics BigQuery dataset schema](https://firebase.google.com/docs/crashlytics/bigquery-dataset-schema). The `firebase_crashlytics` dataset and per-app table naming, and the `device.model`, `operating_system.*`, `device_orientation`, `memory.free`, `error_type`, `issue_id` and `installation_uuid` columns used in the query.

- [Firebase — Test Lab available devices](https://firebase.google.com/docs/test-lab/android/available-testing-devices). `gcloud firebase test android models list`, the `reduced_stability` tag after 30 or more days of inconclusive results, low-capacity devices being "not suitable for test sharding", and deprecated devices being removed after at least a month, with requests then "marked as Skipped".

- [Google Cloud SDK — gcloud firebase test android run](https://cloud.google.com/sdk/gcloud/reference/firebase/test/android/run). The repeatable `--device` flag with model, version, locale and orientation dimensions.

- [Apple Developer — Xcode SDKs and system requirements](https://developer.apple.com/support/xcode/). Xcode 27's iOS deployment targets of 15 to 27 against Simulator and Device Support of iOS 17 or later, the column definitions, and Xcode 26.6's iOS 15 support in both columns on macOS Tahoe 26.x.

- [Apple Developer — Releases](https://developer.apple.com/news/releases/). Xcode 27 (27A266a) on 14 September 2026 and iOS and iPadOS 27.0.1 on 28 September 2026.

- [Apple Developer — Upcoming requirements](https://developer.apple.com/news/upcoming-requirements/). That since 28 April 2026 uploads must be built with Xcode 26 or later against a 26 SDK, which keeps Xcode 26.6 a valid build host.

- [Apple Developer — App Store support, iOS and iPadOS usage](https://developer.apple.com/support/app-store/). The published usage figure and its measurement date of 7 June 2026, reporting only the share on iOS 26 and iPadOS 26.

- [App Store Connect Help — App analytics filters and dimensions](https://developer.apple.com/help/app-store-connect/reference/reporting/app-analytics-filters-and-dimensions/). That the Device dimension is the product family (iPhone, iPad and others) and the definition of Platform Version.

- [App Store Connect Analytics Help — App usage](https://developer.apple.com/help/app-store-connect-analytics/engagement/app-usage/). Filtering by app version, device and platform version, and that usage data comes only from users who agreed to share diagnostics with developers.

- [Apple Developer — Improving code assessment by organizing tests into test plans](https://developer.apple.com/documentation/xcode/organizing-tests-to-improve-feedback). Tags for "cadence (presubmission, daily, prerelease)", tag-based inclusion in test plans from Xcode 16, and `xcodebuild test -testPlan`.

- [Apple Developer — Downloading and installing additional Xcode components](https://developer.apple.com/documentation/xcode/downloading-and-installing-additional-xcode-components). `xcodebuild -downloadPlatform iOS -buildVersion` for installing an older simulator runtime.

- [Apple Developer Archive — Automating the test process](https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/testing_with_xcode/chapters/08-automation.html). That simulator destinations take name and OS keys and that several `-destination` arguments can be chained in one `xcodebuild test` command.

## Continue reading on AutomationTester.in

- [Choose Vitest, Browser Mode, or Playwright by Risk](https://automationtester.in/blog/automation-tutorials/choose-vitest-browser-mode-playwright-by-risk)
- [Mock or Integrate? Playwright Network Testing Boundaries](https://automationtester.in/blog/automation-tutorials/playwright-network-mocking-integration-boundaries)
- [Playwright Test Architecture: Start with Risk, Not Pages](https://automationtester.in/blog/automation-tutorials/playwright-test-architecture-risk-not-page-objects)
- [Make TypeScript Type-Level Tests Earn Their Place](https://automationtester.in/blog/automation-tutorials/typescript-type-level-tests-contract-value)
- [Test TypeScript Error Handling Without Losing Causes](https://automationtester.in/blog/automation-tutorials/typescript-error-handling-preserve-causes-tests)

Source: [Choose a Device Matrix That Matches Real Risk](https://automationtester.in/blog/automation-tutorials/mobile-device-matrix-real-risk) by Shashank Rawlani.
