Skip to main content

Mobile engine SDK for iOS

Automate accessibility testing for your iOS applications with the Mobile Engine SDK. Integrate the SDK into your XCUITest workflow to scan the current screen of your application and submit accessibility results directly to the Level Access Platform. The Mobile Engine SDK includes a comprehensive rule library for native iOS and SwiftUI, helping you identify accessibility issues early in development and reduce manual testing before release.

Who can use this feature? 

  • Organization administrators (SDK admin, SDK user) 
  • Available for Accelerate and Enterprise. 

Minimum supported target is iOS 17.0.

Please note: If you are migrating from Java for Mobile (the previous mobile testing SDK based on Appium) to the Mobile Engine SDK, establish a new accessibility baseline using results from the Mobile Engine SDK. Because the Mobile Engine SDK uses a different accessibility rule engine, results are not directly comparable with Java for Mobile. Historical trends, existing baselines, and pass/fail thresholds should be recreated using scans generated by the Mobile Engine SDK.

On this page:

Prerequisites

Before installing the SDK, make sure the following are in place:

  • Xcode 16 or later: Download Xcode

  • iOS 17.0 or later as your minimum deployment target

  • Swift 5.9 or later

  • A Level Access Platform tenant on Accelerate or Enterprise

  • An iOS digital asset and an API key generated in the Level Access Platform 

  • An existing iOS app project to test, with a UI testing target (XCUITest)

You must be an organization administrator in the Level Access Platform to download the SDK and generate the credentials below.

Get started

Gather the following from the Level Access Platform:

  • Host: The base URL of your Level Access Platform tenant, not including scheme (for example, tenant.levelaccess.com).

  • API key: Generate one in the Level Access Platform.

  • App identifier: The bundle identifier of the iOS app you are testing (for example, com.example.myapp).

  • Digital asset ID: The ID of the iOS digital asset that scan results will be associated with.

  • Scan tag ID: The ID of the scan tag you want to attach to your scans.

Refer to Find API parameters in the platform for how to locate the digital asset ID, scan tag ID, and API key in the platform.

Download the SDK from the Level Access Platform

1. Open the SDKs page

  1. Sign in to the Level Access Platform.

  2. Go your organization and select Manage

  3. From Tools & integrations, select SDKs.

The Mobile section displays a set of mobile SDK cards.

Screenshot of the SDKs page in the platform, displaying the web and mobile sections with downloadable options.

2. Open the Mobile Engine SDK (iOS) detail page

  1. On the Mobile Engine SDK (iOS) card, select Download and install.

The Mobile Engine SDK (iOS) detail displays two sections:

  • XCFramework

  • Usage and documentation

Screenshot of the SDKs page with instructions and code examples for SDK configuration and installation.

3. Download the XCFramework

  1. Under XCFramework, select the Download LevelAccessMobileSdk.xcframework.zip button.

  2. Your browser downloads LevelAccessMobileSdk.xcframework.zip. The Level Access Platform serves the XCFramework from the Level Access artifact repository using your tenant's entitlement token — no additional authentication is required from your side.

  3. Locate the downloaded archive and unzip it. You should see a folder named LevelAccessMobileSdk.xcframework.

Add the SDK to your Xcode project

1. Add the XCFramework to your project

  1. Open your iOS app project in Xcode.

  2. Drag LevelAccessMobileSdk.xcframework into the Xcode Project navigator. We recommend placing it under a Frameworks/ group at the root of your project.

  3. In the Add Files dialog:

    • Select Copy items if needed.

    • Under Add to targets, check only your UI testing target (the XCUITest bundle). The XCFramework does not need to be linked to your main app target.

Project destination folder. displays action, groups, and targets for the SDK folders.

2. Confirm the framework is embedded

  1. Select your UI testing target in the project editor and open the General tab.

  2. Confirm that LevelAccessMobileSdk.xcframework appears under Frameworks and Libraries with the Embed & Sign option set.

Framework page, displays the project pane, and multiple tabs for project personalization.

3. Verify build settings

  • Open the Build Settings for your UI testing target and confirm:

    • iOS Deployment Target is 17.0 or later.

    • Swift Language Version is 5.9 or later.

  • Build the UI testing target. The build should succeed with LevelAccessMobileSdk available as an importable module.

Configure the SDK

In your UI testing target, import the SDK and create a MobileSdk instance with a MobileSdkConfig.

import XCTest
import LevelAccessMobileSdk
@MainActor
final class AccessibilityTests: XCTestCase {
    private let host = "platform.levelaccess.com"
    private let apiKey = "<YOUR-API-KEY>"
    private let appIdentifier = "com.example.myapp"
    private var mobileSdk: MobileSdk!
    private var rules: [Rule] = []
    override func setUp() async throws {
        try await super.setUp()
        mobileSdk = try await MobileSdk.create(
            config: MobileSdkConfig(
                host: host,
                apiKey: apiKey,
                appIdentifier: appIdentifier
            )
        )
        // Configure the rules to run during the scan.
        rules =
            mobileSdk.levelAccessRuleBuilder.all().build() +
            mobileSdk.appleRuleBuilder.all().build()
        // Launch the app under test.
        mobileSdk.launchApp()
    }
}

MobileSdk.create(config:) validates the supplied credentials against the platform and throws MobileSdkError.authenticationFailed if host or apiKey is invalid.

Configuration parameters

Parameter

Description

host

Base URL of your Level Access Platform tenant, including scheme (for example, tenant.levelaccess.com).

apiKey

API key generated in the Level Access Platform. Used to authenticate scan-session and report requests.

appIdentifier

Bundle identifier of the iOS app under test (for example, com.example.myapp).

Choose your rules

The SDK exposes two rule sets that can be combined:

  • levelAccessRuleBuilder — the Level Access rule pack, covering Level Access best-practice checks such as imageLabelPresent, interactiveNamePresent, textContrast, targetSizeMinimum, textHeading, and textScaling.

  • appleRuleBuilder — Apple-aligned rules including contrast, dynamicType, elementDetection, hitRegion, sufficientElementDescription, textClipped, and trait.

Use .all() to enable every rule in a pack, or .add(_:) to enable specific rules.

// All rules from both packs
let rules =
    mobileSdk.levelAccessRuleBuilder.all().build() +
    mobileSdk.appleRuleBuilder.all().build()
// Or a curated subset
let rules =
    mobileSdk.levelAccessRuleBuilder
        .add(.imageLabelPresent)
        .add(.interactiveNamePresent)
        .add(.textContrast(ratio: 4.5))
        .build() +
    mobileSdk.appleRuleBuilder
        .add(.contrast)
        .add(.dynamicType)
        .build()

Run an accessibility scan

Drive your app to the screen you want to evaluate, then call scan(rules:) to capture the screen and run the configured rules.

@MainActor
func test_scan_settingsScreen() throws {
    // Drive the app to the screen you want to scan
    let app = XCUIApplication()
    app.tabBars.buttons["Settings"].tap()
    // Run the scan
    let results = try mobileSdk.scan(rules: rules)
    XCTAssertFalse(results.isEmpty)
}

scan(rules:) returns an array of Result objects. Each Result represents one rule evaluation against the current screen.

Submit results to the Level Access Platform

To send scan results to the Level Access Platform, open a scan session, report results to it, and close it when you are done. A single scan session can carry results from multiple screens, so you can drive your app through a flow and report each screen as you go.

@MainActor
func test_scan_andSubmitToPlatform() async throws {
    let session = try await mobileSdk.createScanSession(
        digitalAssetId: "<YOUR-DIGITAL-ASSET-ID>",
        sessionName: "Smoke run — Settings flow",
        scanTagId: "<YOUR-SCAN-TAG-ID>"
    )
    // Screen 1
    let homeResults = try mobileSdk.scan(rules: rules)
    try await session.report(screenName: "Home", results: homeResults)
    // Navigate, then Screen 2
    XCUIApplication().tabBars.buttons["Settings"].tap()
    let settingsResults = try mobileSdk.scan(rules: rules)
    try await session.report(screenName: "Settings", results: settingsResults)
    try await session.close()
}

After the session is closed, the scan appears on the Scans page of the corresponding digital asset in the Level Access Platform.

Mobile app scans page displaying a list of scan sessions by date, scan title, device and operating system, scan type, score and actions.

Errors

Operations that talk to the platform can throw a MobileSdkError. Handle them in your test or let them fail the test naturally:

Case

When it is thrown

.authenticationFailed(String)

The SDK could not authenticate against host with the supplied apiKey. Returned from MobileSdk.create(config:).

.sessionCreationFailed(String)

createScanSession could not open a session. Verify the digital asset ID and scan tag ID.

.scanReportFailed(String)

report(screenName:results:) could not upload results.

.sessionCloseFailed(String)

close() could not finalize the session.

Inspect results and elements

The scan(rules:) method returns a list of Result values. A result can be one of:

  • success — the rule passed for the current screen.

  • warning — a possible issue was detected.

  • error — a definite issue was detected.

  • unsupported — the rule does not apply to the current screen.

unsupported results are silently filtered out by ScanSession.report(screenName:results:) before upload. Only warning and error results carry source elements; other result types return an empty elements array.

Each Result exposes:

  • metadatavendor, rule, type, and a human-readable message.

  • elements — the LevelAccessElement instances the result applies to.

  • snapshots — the captured screenshots and view-hierarchy snapshots.

  • getAnnotatedScreenshots() — screenshots with bounding boxes drawn around the offending elements, suitable for attachment to a test failure.

Attach screenshots to test failures

@MainActor
func testAccessibility() throws {
    let results = try mobileSdk.scan(rules: rules)
    for result in results where result.metadata.type == .error {
        for screenshot in result.getAnnotatedScreenshots() {
            let attachment = XCTAttachment(image: screenshot.image)
            attachment.name = screenshot.fileName
            attachment.lifetime = .keepAlways
            add(attachment)
        }
        XCTFail("\(result.metadata.rule): \(result.metadata.message)")
    }
}

Find the bounds of a flagged element

let results = try mobileSdk.scan(rules: rules)
for result in results {
    if let element = result.elements.first {
        let bounds = element.bounds
        let role = element.role
        let label = element.label
        // ...
    } else {
        // No elements available (success, unsupported, or no source element).
    }
}

Example: end-to-end test case

The following XCUITest class is a complete starting point. Replace the placeholder credentials with values from your tenant.

import XCTest
import LevelAccessMobileSdk
@MainActor
final class AccessibilityTests: XCTestCase {
    private let host = "tenant.levelaccess.com"
    private let apiKey = "<YOUR-API-KEY>"
    private let appIdentifier = "com.example.myapp"
    private let digitalAssetId = "<YOUR-DIGITAL-ASSET-ID>"
    private let scanTagId = "<YOUR-SCAN-TAG-ID>"
    private var mobileSdk: MobileSdk!
    private var rules: [Rule] = []
    override func setUp() async throws {
        try await super.setUp()
        mobileSdk = try await MobileSdk.create(
            config: MobileSdkConfig(
                host: host,
                apiKey: apiKey,
                appIdentifier: appIdentifier
            )
        )
        rules =
            mobileSdk.levelAccessRuleBuilder.all().build() +
            mobileSdk.appleRuleBuilder.all().build()
        mobileSdk.launchApp()
    }
    func test_scan_shouldReturnResults_whenScanningWithAllRules() throws {
        let results = try mobileSdk.scan(rules: rules)
        XCTAssertFalse(results.isEmpty)
    }
    func test_scan_shouldSucceed_whenScanningAndReportingResults() async throws {
        let session = try await mobileSdk.createScanSession(
            digitalAssetId: digitalAssetId,
            sessionName: "Example session",
            scanTagId: scanTagId
        )
        let results = try mobileSdk.scan(rules: rules)
        try await session.report(screenName: "Home", results: results)
        try await session.close()
    }
}

Reference

MobileSdk

Entry point for creating the SDK, controlling the app under test, scanning, and opening scan sessions.

Member

Signature

Description

create

static func create(config: MobileSdkConfig) async throws -> MobileSdk

Creates a configured SDK instance. Validates the credentials against the platform; throws MobileSdkError.authenticationFailed on failure.

levelAccessRuleBuilder

LevelAccessRuleBuilder

Builder for the Level Access rule pack.

appleRuleBuilder

AppleRuleBuilder

Builder for the Apple-aligned rule pack.

launchApp()

func launchApp()

Launches the app under test, terminating any existing instance first.

activateApp()

func activateApp()

Brings the app under test to the foreground. If it is not running, it is launched.

isAppRunning()

func isAppRunning() -> Bool

Returns true if the app under test is running in the foreground or background.

isAppInForeground()

func isAppInForeground() -> Bool

Returns true if the app under test is currently active in the foreground.

scan(rules:)

func scan(rules: [Rule]) throws -> [Result]

Captures the current screen and evaluates the supplied rules.

createScanSession(...)

func createScanSession(digitalAssetId: String, sessionName: String, scanTagId: String) async throws -> ScanSession

Opens a new scan session on the Level Access Platform.

MobileSdkConfig

public struct MobileSdkConfig {
    public let host: String
    public let apiKey: String
    public let appIdentifier: String
    public init(host: String, apiKey: String, appIdentifier: String)
}

ScanSession

Member

 

Signature

 

Description

 

report

func report(screenName: String, results: [Result]) async throws

Uploads results captured for a single screen to the open session. unsupported results are filtered out; screenshots are scaled to fit within a 2000×2000 pixels bounding box before upload.

close

func close() async throws

Finalizes the session in the Level Access Platform. A closed session cannot be reopened.

Rule packs

  • LevelAccessRuleimageLabelPresent, interactiveNamePresent, interactiveRolePresent, interactiveValuePresent, interactiveStatePresent, orientationPortrait, orientationLandscape, targetSizeMinimum(minimumWidth:minimumHeight:minimumSpacing:), targetSizeEnhanced(minimumWidth:minimumHeight:), textContrast(ratio:), textHeading, textScaling(exclusions:).

  • AppleRulecontrast, dynamicType, elementDetection, hitRegion, sufficientElementDescription, textClipped, trait.

Both builders expose .add(_:), .all(), .reset(), and .build().

Result types

ResultType: success, error, warning, unsupported.

Vendor: levelaccess, apple.

Powered by Zendesk