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.
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
Sign in to the Level Access Platform.
Go your organization and select Manage
From Tools & integrations, select SDKs.
The Mobile section displays a set of mobile SDK cards.
2. Open the Mobile Engine SDK (iOS) detail page
On the Mobile Engine SDK (iOS) card, select Download and install.
The Mobile Engine SDK (iOS) detail displays two sections:
XCFramework
Usage and documentation
3. Download the XCFramework
Under XCFramework, select the Download LevelAccessMobileSdk.xcframework.zip button.
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.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
Open your iOS app project in Xcode.
Drag
LevelAccessMobileSdk.xcframeworkinto the Xcode Project navigator. We recommend placing it under aFrameworks/group at the root of your project.-
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.
2. Confirm the framework is embedded
Select your UI testing target in the project editor and open the General tab.
Confirm that
LevelAccessMobileSdk.xcframeworkappears under Frameworks and Libraries with the Embed & Sign option set.
3. Verify build settings
-
Open the Build Settings for your UI testing target and confirm:
iOS Deployment Target is
17.0or later.Swift Language Version is
5.9or later.
Build the UI testing target. The build should succeed with
LevelAccessMobileSdkavailable 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 |
|---|---|
|
Base URL of your Level Access Platform tenant, including scheme (for example, |
|
API key generated in the Level Access Platform. Used to authenticate scan-session and report requests. |
|
Bundle identifier of the iOS app under test (for example, |
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 asimageLabelPresent,interactiveNamePresent,textContrast,targetSizeMinimum,textHeading, andtextScaling.appleRuleBuilder— Apple-aligned rules includingcontrast,dynamicType,elementDetection,hitRegion,sufficientElementDescription,textClipped, andtrait.
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.
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 |
|---|---|
|
The SDK could not authenticate against |
|
|
|
|
|
|
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:
metadata—vendor,rule,type, and a human-readablemessage.elements— theLevelAccessElementinstances 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 |
|---|---|---|
|
|
Creates a configured SDK instance. Validates the credentials against the platform; throws |
|
|
Builder for the Level Access rule pack. |
|
|
Builder for the Apple-aligned rule pack. |
|
|
Launches the app under test, terminating any existing instance first. |
|
|
Brings the app under test to the foreground. If it is not running, it is launched. |
|
|
Returns |
|
|
Returns |
|
|
Captures the current screen and evaluates the supplied rules. |
|
|
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 |
|---|---|---|
|
|
Uploads results captured for a single screen to the open session. |
|
|
Finalizes the session in the Level Access Platform. A closed session cannot be reopened. |
Rule packs
LevelAccessRule—imageLabelPresent,interactiveNamePresent,interactiveRolePresent,interactiveValuePresent,interactiveStatePresent,orientationPortrait,orientationLandscape,targetSizeMinimum(minimumWidth:minimumHeight:minimumSpacing:),targetSizeEnhanced(minimumWidth:minimumHeight:),textContrast(ratio:),textHeading,textScaling(exclusions:).AppleRule—contrast,dynamicType,elementDetection,hitRegion,sufficientElementDescription,textClipped,trait.
Both builders expose .add(_:), .all(), .reset(), and .build().
Result types
ResultType: success, error, warning, unsupported.
Vendor: levelaccess, apple.
Comments
0 comments
Article is closed for comments.