Regression Testing for iOS Apps: Complete Guide (2026)
Regression Testing for iOS Apps: Complete Guide (2026) is the definitive resource for ensuring that each new build of an iOS application retains the behavior users expect. This guide walks you through
Regression Testing for iOS Apps: Complete Guide (2026) is the definitive resource for ensuring that each new build of an iOS application retains the behavior users expect. This guide walks you through the exact meaning of regression testing for iOS, where it fits among other test types, when to trigger it, and how to construct a reliable process that scales from small teams to enterprise releases. You will find a concrete test matrix, step‑by‑step automation instructions, real‑world examples, metrics that matter, common pitfalls, CI/CD patterns, and a short checklist you can copy into your wiki. Throughout, we reference the autonomous QA platform SUSA only where it naturally adds value—showing how self‑driving exploration can augment traditional regression suites without replacing them.
1. Understanding Regression Testing in the iOS Context
Regression testing verifies that recent code changes have not introduced unintended side‑effects in existing functionality. For iOS apps, this means confirming that a new build still works on the range of devices and OS versions your users run, and that core user flows—login, navigation, in‑app purchases, data persistence—behave exactly as before.
1.1 How Regression Differs from Adjacent Test Types
| Test Type | Goal | Typical Scope | Frequency |
|---|---|---|---|
| Smoke / Sanity | Verify basic stability after a build | Critical path, 5‑10% of tests | Every CI build |
| Functional | Validate specific feature against spec | Feature‑level test cases | Per sprint |
| Regression | Ensure no new bugs in existing features | Broad coverage of previously tested areas | Nightly / pre‑release |
| Exploratory | Discover unknown issues via ad‑hoc | Unscripted, tester‑driven | As needed |
| Performance | Measure responsiveness, resource use | Load, memory, battery impact | Weekly / release |
Regression testing sits between smoke (which is too shallow) and full functional suites (which may be too heavy for every commit). It re‑executes a curated subset of tests that have historically caught regressions, focusing on areas most likely to be affected by change.
1.2 When to Run Regression for iOS
- After every merge to main – catch integration‑level issues early.
- Before a TestFlight or App Store submit – guarantee release quality.
- When a dependency updates (e.g., a third‑party SDK, Xcode toolchain ) – validate compatibility.
- Post‑hotfix – ensure the fix didn’t break elsewhere.
- Nightly for long‑running projects – monitor flakiness and drift.
2. Building a Regression Test Matrix for iOS
A test matrix captures the combination of features, devices, OS versions, and configuration variables you need to cover. It serves as the backbone for both manual and automated regression planning.
2.1 Core Dimensions
| Dimension | Values to Consider | Rationale |
|---|---|---|
| Feature Area | Login, Onboarding, Settings, Payments, Push Notifications, Offline Sync | High‑risk, frequently changed |
| Device Generation | iPhone SE (2022), iPhone 13, iPhone 14 Pro, iPhone 15 Pro Max, iPad Air (5th) | Represent different screen sizes, chipsets |
| iOS Version | iOS 16.4, 16.5, 16.6, 17.0, 17.1, 17.2 | Capture OS‑specific behavior |
| Orientation | Portrait, Landscape | UI layout changes |
| Accessibility | VoiceOver on/off, Larger Text sizes, Reduce Motion | WCAG compliance |
| Network Condition | Wi‑Fi, 4G, 3G, Offline | Behavior under varying connectivity |
| Locale/Language | en-US, es-ES, ja-JP, ar-SA (right‑to‑left) | Internationalization |
2.2 Example Matrix (Condensed)
Below is a trimmed matrix showing how you might prioritize combos for a nightly regression run. Each cell indicates whether the combo is Required (R), Optional (O), or Excluded (X) for that run.
| Feature \ Device‑OS | iPhone SE 16.4 | iPhone 13 16.6 | iPhone 14 Pro 17.0 | iPad Air 17.1 |
|---|---|---|---|---|
| Login | R | R | R | O |
| Settings | O | R | R | R |
| Payments | R | R | O | O |
| Push Notifications | O | O | R | R |
| Offline Sync | R | O | R | O |
*Interpretation*: For login, you test on every device/OS combo because authentication touches multiple system layers (Keychain, Face ID, network). Settings is less critical on older phones, so you mark it optional there. This matrix lets you generate a concise test plan without exhaustive combinatorial explosion.
2.3 Maintaining the Matrix
- Store it as a CSV in version control (e.g.,
regression_matrix.csv). - Write a small script (Python or Swift) that reads the CSV and expands it into a list of test targets for your CI pipeline.
- Review the matrix quarterly: add new device models, retire OS versions that fall below your support threshold, and adjust feature priorities based on release data.
3. Manual Regression Testing Approaches
Even with strong automation, manual regression remains valuable for exploratory checks, UI polish, and validating scenarios that are hard to script (e.g., gesture‑driven interactions, AR sessions).
3.1 Structured Manual Checklist
Create a lightweight checklist that testers can run on a physical device or a TestFlight build. Example items:
- Launch – app opens within 2 s, no splash‑screen freeze.
- Login Flow – valid/invalid credentials, biometric fallback, password reset link.
- Navigation – tab bar switches, deep link opens correct screen, back gesture works.
- Data Entry – text fields accept max length, keyboard types, autocorrect behavior.
- Payments – test card succeeds, declined card shows proper error, Apple Pay sheet appears.
- Push Notifications – receive notification while app is foreground/background/tapped.
- Offline – disable Wi‑Fi/cellular, perform core actions, verify queue and retry.
- Accessibility – enable VoiceOver, navigate all screens, verify labels and hints.
- Orientation – rotate device, ensure layout adapts, no clipped content.
- Crash Check – open console, verify no unexpected exceptions.
Each item can be marked PASS, FAIL, or BLOCKED (e.g., missing feature flag). Keep the checklist in a shared Confluence page or a Markdown file in the repo.
3.2 Using TestFlight for Distributed Manual Regression
- Upload a build to TestFlight with a specific internal testing group labeled “Regression”.
- Enable automatic notifications so testers know when a new build is available.
- Collect feedback via the built‑in TestFlight feedback button or integrate a third‑party tool like Instabug for screenshots and logs.
- After the test window, export the feedback CSV and correlate failures with specific device/OS combos from your matrix.
3.3 When Manual Adds Value
- New UI components (custom transitions, Canvas‑based drawings) where visual regression is needed.
- Hardware‑specific features (LiDAR scanning, Bluetooth peripherals) that simulators cannot emulate.
- Accessibility audits – manual testers with assistive technology can catch nuanced issues that automated checks miss.
- Ad‑hoc exploratory sessions after a risky refactor – let a senior QA engineer wander the app for 15 minutes and note anything odd.
4. Automated Regression Testing Foundations
Automation provides repeatability, speed, and the ability to run the same matrix on every commit. For iOS, the primary automation stack centers on XCTest and XCUITest, complemented by CI orchestration tools.
4.1 Unit Testing with XCTest
Unit tests validate isolated logic—view models, networking parsers, persistence layers. They run fast (sub‑second) and are ideal for guarding against regressions in business logic.
import XCTest
@testable import MyApp
final class LoginViewModelTests: XCTestCase {
func testValidCredentialsProducesToken() {
let viewModel = LoginViewModel(authService: MockAuthService())
viewModel.username = "user@example.com"
viewModel.password = "Secret123"
XCTAssertNoThrow(try viewModel.login())
XCTAssertEqual(viewModel.authToken, "mock-token-123")
}
func testInvalidPasswordShowsError() {
let viewModel = LoginViewModel(authService: MockAuthService(fail: true))
viewModel.username = "user@example.com"
viewModel.password = "wrong"
viewModel.login()
XCTAssertEqual(viewModel.errorMessage, "Invalid credentials")
}
}
Run unit tests locally with:
xcodebuild test -scheme MyAppUnitTests -destination 'platform=iOS Simulator,name=iPhone 15,OS=17.2'
4.2 UI Testing with XCUITest
XCUITest drives the actual UI, making it suitable for regression checks of navigation flows, form submissions, and system alert handling.
import XCTest
final class LoginUITests: XCTestCase {
let app = XCUIApplication()
override func setUp() {
continueAfterFailure = false
app.launch()
}
func testSuccessfulLoginFlow() {
let usernameField = app.textFields["Username"]
usernameField.tap()
usernameField.typeText("tester@example.com")
let passwordField = app.secureTextFields["Password"]
passwordField.tap()
passwordField.typeText("SecurePass!")
app.buttons["Log In"].tap()
// Expect the home screen to appear
let homeLabel = app.staticTexts["Welcome"]
XCTAssertTrue(homeLabel.waitForExistence(timeout: 5))
}
}
Execute UI tests on a specific device/OS combo:
xcodebuild test -scheme MyAppUITests \
-destination 'platform=iOS Simulator,name=iPhone 14 Pro,OS=17.0'
4.3 Speeding Up UI Test Execution
- Parallel Simulators: Use
xcodebuildwithparallel-testingenabled (Xcode 15+). - Test Selection: Run only tests tagged with a custom
@Regressionattribute via a test plan. - Snapshot Testing: Tools like
iOSSnapshotTestCase(FBSnapshotTestCore) catch visual regressions without interacting with the app.
import FBSnapshotTestCase
class SnapshotLoginTests: FBSnapshotTestCase {
override func setUp() {
super.setUp()
recordMode = false
}
func testLoginScreenSnapshot() {
let vc = LoginViewController()
FBSnapshotVerifyView(vc.view, identifier: "login_screen")
}
}
4.4 Leveraging Fastlane for Automation
Fastlane abstracts common iOS CI tasks—building, testing, uploading to TestFlight—into simple lanes.
# Fastfile
default_platform(:ios)
platform :ios do
desc "Run regression unit + UI tests on all matrix devices"
lane :regression do
run_tests(
scheme: "MyAppUITests",
devices: ["iPhone 13", "iPhone 14 Pro", "iPad Air"],
os: ["16.6", "17.0", "17.1"]
)
end
desc "Build and distribute to TestFlight internal testers"
lane :beta do
match(type: "development") # ensure certificates
gym(scheme: "MyApp", export_method: "development")
pilot(
distribute_external: false,
groups: ["Regression"]
)
end
end
Run the regression lane locally or in CI:
fastlane ios regression
5. Tooling Comparison Table
Choosing the right tools impacts test maintenance, flakiness, and cost. Below is a comparison of popular iOS regression‑testing solutions as of 2026.
| Tool / Framework | Language | Primary Use | Strengths | Weaknesses | CI Integration | Cost (2026) |
|---|---|---|---|---|---|---|
| XCTest / XCUITest | Swift / Objective‑C | Unit & UI testing | Native, fastest execution, deep Xcode integration | Requires Mac hardware, UI tests can be flaky on simulators | Built‑in with xcodebuild, works with GitHub Actions, Bitrise, Xcode Cloud | Free (part of Xcode) |
| Fastlane | Ruby | Build/test/deploy automation | Orchestrates multiple steps, rich plugin ecosystem | Ruby learning curve, DSL can become dense | Works with any CI that can run shell scripts | Free (open source) |
| Firebase Test Lab | Cloud (Android/iOS) | Device‑farm UI testing | Access to real devices, no hardware maintenance, automatic screenshots | Limited test duration (max 60 min per test), cost per minute | Triggers via gcloud CLI or Firebase console | Pay‑as‑you‑go (~$1/hour per device) |
| Sauce Labs | Cloud | Cross‑platform UI testing | Broad device/OS matrix, video recording, integrates with many frameworks | Higher cost, network latency can affect timing | Supports Jenkins, GitLab CI, CircleCI | Tiered plans; start ~$99/mo |
| Appium (with XCUITest driver) | JavaScript/Java/Python/etc. | Cross‑platform UI testing | Write tests once, run on iOS/Android, supports real devices & simulators | Setup complexity, slower than pure XCUITest, occasional driver lag | Works with any CI that can launch Appium server | Free (open source) + infrastructure |
| SUSA (Autonomous QA) | — | Autonomous exploration + regression script generation | No scripts needed, explores with personas, auto‑generates Appium/Playwright scripts, cross‑session learning | Still emerging for iOS, best as supplement not full replacement | CLI agent, can be invoked in CI pipelines | Free tier; paid plans based on device minutes |
| Xcode Cloud | Apple CI/CD | Build, test, distribute | Tight Xcode integration, automatic device farm (simulators + limited real devices) | Limited to Apple ecosystem, less flexible than generic CI | Native to Xcode Cloud (trigger on PR/merge) | Free tier with limited minutes; paid plans start at $15/mo |
How to Pick
- For teams already invested in Swift and with Mac builders, XCTest/XCUITest + Fastlane gives the lowest overhead.
- If you need real‑device coverage without buying hardware, Firebase Test Lab or Sauce Labs fill the gap.
- When you want to reduce script authoring and gain exploratory insight, run SUSA alongside your automated suite and feed its generated scripts back into Fastlane or Xcode Cloud.
6. Designing Effective Regression Test Suites
A regression suite that tries to execute *everything* becomes slow and brittle. Effective design focuses on risk, change impact, and maintainability.
6.1 Risk‑Based Test Selection
- Identify High‑Risk Areas – modules with recent defects, complex logic, or frequent changes (e.g., payment gateway, authentication).
- Weight by Change Frequency – assign a score based on number of commits in the last 4 weeks.
- Prioritize by User Impact – flows that affect revenue or core retention get higher weight.
- Select Top‑N% – e.g., run the top 20 % of scored test cases on each commit; run the full suite nightly.
6.2 Change‑Impact Analysis
Leverage your source control to compute which files changed in a PR and map them to test cases via a simple traceability matrix (e.g., a JSON file linking source files to test targets).
{
"Sources": {
"LoginViewModel.swift": ["LoginViewModelTests.swift", "LoginUITests.swift"],
"NetworkingLayer.swift": ["NetworkingTests.swift", "ApiClientUITests.swift"]
}
}
A CI step can read the changed files, look up the corresponding test targets, and invoke only those:
# pseudo‑shell
changed=$(git diff --name-only $BASE..$HEAD)
tests=$(jq -r 'to_entries[] | select(.key | IN($changed[])) | .value[]' traceability.json)
xcodebuild test -scheme MyAppUITests -only-testing:$tests -destination 'platform=iOS Simulator,name=iPhone 14,OS=17.0'
6.3 Flaky Test Mitigation
Flaky tests erode confidence in regression results. Common iOS sources include:
- Timing‑dependent animations – use
expectation(for: NSPredicate, evaluatedWith: handler:)instead ofsleep. - Network race conditions – stub network calls with
URLProtocolor a library like MOCKO. - Simulator state – reset simulator between runs (
xcrun simctl erase all).
Implement a retry wrapper in your Fastlane lane:
desc "Run UI tests with up to 2 retries on failure"
lane :flaky_ui do
retry(
attempts: 3,
delay: 10
) do
run_tests(
scheme: "MyAppUITests",
devices: ["iPhone 14 Pro"],
os: ["17.0"]
)
end
end
6.4 Maintaining Test Data
- Use factory‑generated objects (e.g.,
FactoryBot‑style Swift structs) rather than hard‑coded IDs. - For UI tests that rely on server state, spin up a dockerized mock backend or use Network Extension to proxy and record responses.
- Store test data in version‑controlled JSON fixtures; load them via
Bundle.main.url(forResource:)to keep tests deterministic.
6.5 Test Organization
Group tests by feature rather than by test type. Example Xcode test plan:
MyApp.xctestplan
├─ Login
│ ├─ LoginViewModelTests (Unit)
│ └─ LoginUITests (UI)
├─ Payments
│ ├─ PaymentServiceTests (Unit)
│ └─ PaymentFlowUITests (UI)
└─ Core
├─ NetworkingTests (Unit)
└─ AppLaunchUITests (UI)
This layout makes it easy to enable/disable entire feature sets in a test plan based on risk scores.
7. Metrics, Pass/Fail Criteria, and Reporting
Regression testing is only valuable if you can measure its effectiveness and act on the data.
7.1 Key Metrics to Track
| Metric | Definition | Target (example) |
|---|---|---|
| Test Pass Rate | (% of regression tests that pass) | ≥ 98 % |
| Flakiness Rate | (% of tests that show non‑deterministic pass/fail across ≥ 3 runs) | ≤ 2 % |
| Mean Time To Execute (MTTE) | Average wall‑clock time for the full regression suite | ≤ 15 min on CI |
| Defect Leakage | (# of post‑release defects that regression missed) / total post‑release defects | ≤ 5 % |
| Coverage Change | Δ in line/branch coverage between base and new build | ≥ 0 % (no regression) |
| Device/OS Matrix Compliance | % of matrix combos actually executed | 100 % for required combos |
Collect these metrics via your CI system (e.g., GitHub Actions artifacts, Fastlane danger plugin, or a custom script that posts to a monitoring dashboard like Grafana).
7.2 Defining Pass/Fail Criteria
A regression run is considered PASS when:
- All required matrix combos execute without unexpected failures.
- Overall test pass rate meets the threshold (e.g., 98 %).
- Flakiness rate stays below the acceptable limit.
- No new critical defects (severity = S1) are detected in the test logs.
If any of the above fails, the run is FAIL and the merge should be blocked until the issue is resolved.
7.3 Reporting Practices
- JUnit XML output from
xcodebuild testcan be consumed by most CI platforms to render test results. - Attach a summary markdown file to the PR that includes:
- Pass/fail counts per feature.
- List of flaky tests observed.
- Links to device logs and screenshots (if using Firebase Test Lab or SUSA).
- Use danger-swift to post inline comments when a regression test fails, pointing directly to the offending test case.
# Dangerfile.swift
import Danger
let failures = failurereport.tests.filter { !$0.passed }
if !failures.isEmpty {
message("⚠️ \(failures.count) regression test(s) failed:")
for f in failures {
markdown("- `\(f.name)` – see logs: \(f.logUrl)")
}
}
7.4 Continuous Improvement Loop
- Review failed regression tests each sprint.
- Classify each failure: true bug, flaky test, outdated test, or environment issue.
- Act: fix bugs, stabilize flaky tests, delete or update obsolete tests, update CI agents.
- Measure impact on metrics in the next iteration.
8. Common Mistakes Teams Make and How to Avoid Them
Even seasoned iOS teams fall into traps that undermine regression effectiveness. Below are the most frequent pitfalls and concrete remedies.
| Mistake | Why It Happens | Consequence | Fix |
|---|---|---|---|
| Over‑reliance on UI tests | Belief that UI tests catch everything | Slow suites, high flakiness, missed logic bugs | Keep UI tests focused on critical user journeys; supplement with unit/service tests. |
| Ignoring device fragmentation | Testing only on latest simulator | Issues appear on older devices or specific chipsets (e.g., A12 vs A16) | Include at least one older device per generation in the matrix; use real‑device farms for weekly runs. |
| Hard‑coding test data | Convenience during test authoring | Tests break when backend changes or when running in different environments | Use dynamic factories or environment‑specific config files; reset state before each test. |
| Not resetting simulator/device state | Assuming a clean start each time | Leftover caches, login tokens, or notification permissions cause false passes/failures | Call xcrun simctl erase or invoke app.reset() in XCUITest tearDown. |
| Treating regression as a gate only before release | Lack of confidence in CI speed | Bugs accumulate; release becomes risky | Run a lightweight regression on every PR; reserve the full matrix for nightly or pre‑release. |
| Neglecting accessibility checks | Accessibility seen as “nice‑to‑have” | WCAG violations reach users, potential legal risk | Add automated accessibility audits (e.g., XCUIElement's accessibilityValue) and manual spot‑checks. |
| Allowing test duplication | Copy‑pasting similar tests | Maintenance burden, inconsistent updates | Extract common steps into helper functions or Page Object models; use test inheritance. |
| Failing to update the regression matrix | Matrix becomes stale | Missing coverage for new devices/OS versions, false sense of security | Schedule a quarterly matrix review; automate detection of newly released Xcode betas. |
| Using only simulated network | Simulators ignore carrier‑specific behavior | Issues like LTE handoff or captive portal handling go unnoticed | Periodically run a subset of tests on real devices under varied network conditions (using Network Link Conditioner or a hardware shim). |
| Overlooking cross‑session state | Assuming each test starts from a clean slate | Persistent settings (e.g., dark mode, language) cause intermittent failures | Explicitly set app defaults in setUp (UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!)). |
8.1 Example: Fixing a Flaky Network Test
Suppose a UI test that logs in occasionally fails because the mock server delays responding.
Bad approach – adding a static sleep(3).
Better approach – use an expectation that waits for a specific UI element to appear, with a timeout.
func testLoginWithLoadingIndicator() {
let username = app.textFields["Username"]
username.tap()
username.typeText("user@example.com")
let password = app.secureTextFields["Password"]
password.tap()
password.typeText("Passw0rd!")
app.buttons["Log In"].tap()
// Wait for the loading spinner to disappear, max 8 s
let spinner = app.activityIndicators["LoginSpinner"]
let exists = NSPredicate(format: "exists == false")
expectation(for: exists, evaluatedWith: spinner, handler: nil)
waitForExpectations(timeout: 8, handler: nil)
// Verify successful landing
XCTAssertTrue(app.staticTexts["Dashboard"].waitForExistence(timeout: 2))
}
This eliminates the arbitrary sleep and makes the test resilient to variable network latency.
9. CI/CD Integration for iOS Regression
Integrating regression testing into your CI pipeline guarantees that every change is validated against the agreed‑upon matrix before it reaches main.
9.1 Typical Pipeline Stages
- Checkout – fetch source code.
- Dependency Resolution – run
swift package resolveorpod install. - Build – produce
.appand test bundles (xcodebuild build). - Unit Test Stage – run fast unit tests on simulator matrix (parallel).
- UI Test Stage – execute selected UI tests on a subset of devices (real or simulated).
- Autonomous Exploration (Optional) – launch SUSA agent to explore the built app and generate regression scripts.
- Artifact Collection – gather test logs, JUnit XML, screenshots, video recordings.
- Reporting – post summary to PR, update internal dashboard, gate merge based on pass/fail criteria.
- Deploy to TestFlight – if regression passes, promote to internal testers for final validation.
9.2 Example GitHub Actions Workflow (iOS Regression)
name: iOS Regression
on:
pull_request:
branches: [ main ]
push:
branches: [ main ]
jobs:
build-and-test:
runs-on: macos-14-large # 14‑core, 112 GB RAM for parallel simulators
timeout-minutes: 45
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: "15.2"
- name: Cache CocoaPods
uses: actions/cache@v3
with:
path: Pods
key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
restore-keys: |
${{ runner.os }}-pods-
- name: Install dependencies
run: pod install --repo-update
- name: Build app and test targets
run: |
xcodebuild -workspace MyApp.xcworkspace \
-scheme MyAppUITests \
-destination 'platform=iOS Simulator,name=iPhone 14 Pro,OS=17.0' \
build
- name: Run Unit Tests (parallel)
run: |
xcodebuild test -workspace MyApp.xcworkspace \
-scheme MyAppUnitTests \
-destination 'platform=iOS Simulator,name=iPhone 13,OS=16.6' \
-destination 'platform=iOS Simulator,name=iPhone 14 Pro,OS=17.0' \
-destination 'platform=iOS Simulator,name=iPad Air,OS=17.1' \
-parallel-testing-enabled YES \
-resultBundlePath UnitTestResults
- name: Run UI Regression Tests (selected matrix)
run: |
xcodebuild test -workspace MyApp.xcworkspace \
-scheme MyAppUITests \
-only-testing:LoginUITests.testSuccessfulLoginFlow,PaymentsUITests.testPurchaseFlow \
-destination 'platform=iOS Simulator,name=iPhone 13,OS=16.6' \
-destination 'platform=iOS Simulator,name=iPhone 14 Pro,OS=17.0' \
-destination 'platform=iOS Simulator,name=iPad Air,OS=17.1' \
-resultBundlePath UITestResults
- name: Run SUSA Exploration (optional)
if: github.event_name == 'pull_request'
run: |
pip install susatest-agent
susatest explore \
--app MyApp.app \
--device iPhone14Pro \
--ios-version 17.0 \
--personas curious impatient novice \
--output-dir susa-report
- name: Upload Test Results
uses: actions/upload-artifact@v4
with:
name: test-results
path: |
**/TestResults/**/*.xcresult
susa-report/**
- name: Summarize Results
id: summary
run: |
# Simple pass/fail check using xcresulttool (bundled with Xcode)
PASS_COUNT=$(xcrun xcresulttool get --path UnitTestResults.xcresult --format json | jq '.testableSummaries[0].testSummaries[] | select(.testStatus == "Passed") | length')
FAIL_COUNT=$(xcrun xcresulttool get --path UnitTestResults.xcresult --format json | jq '.testableSummaries[0].testSummaries[] | select(.testStatus == "Failed") | length')
echo "unit_pass=$PASS_COUNT" >> $GITHUB_OUTPUT
echo "unit_fail=$FAIL_COUNT" >> $GITHUB_OUTPUT
# similar for UI tests...
- name: Fail job on regression failure
if: steps.summary.outputs.unit_fail != '0' || steps.summary.outputs.ui_fail != '0'
run: |
echo "Regression tests failed – blocking merge"
exit 1
Explanation of Key Choices
- macos-14-large runner provides enough CPU cores to run parallel simulators, cutting UI test time dramatically.
- Caching Pods avoids repeated downloads on each workflow run.
- Parallel testing flag (
-parallel-testing-enabled) speeds up unit test execution. - Only‑testing limits UI tests to the regression‑relevant subset (login and purchase flows) – aligns with the risk‑based selection discussed earlier.
- SUSA step is conditionally executed for PRs; it explores the built app with three personas and stores a report as an artifact. The generated scripts can be later pulled into the repo if the team decides to adopt them.
- Artifact upload preserves
.xcresultbundles for deep debugging (you can open them in Xcode). - Summary step extracts pass/fail** uses
xcresulttoolto count test outcomes and fails the job if any regression test fails.
9.3 Integrating with Xcode Cloud
If your team prefers Apple’s native CI, you can define a test plan that includes only regression‑marked tests and enable parallel device testing.
- In Xcode, open Product → Test Plans, create a plan named
Regression.xctestplan. - Add the test targets you want (e.g.,
MyAppUITests) and under Configuration enable Parallel Testing. - In the Xcode Cloud workflow editor, select that test plan under the Test action.
- Add a post‑test script that runs
susatest exploreon the generated.appartifact (Cloud provides a temporary
Test Your App Autonomously
Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.
Try SUSA Free