Automate accessibility testing for your Android applications with the Mobile Engine SDK. Integrate the SDK into your instrumented tests 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 Android and Jetpack Compose, helping you identify accessibility issues early in development and reduce manual testing before release.
Who can use this feature?
Organization administrators
Available for Accelerate and Enterprise.
Minimum supported target is Android 9.0 (API level 28).
On this page:
Prerequisites
Before installing the SDK, make sure the following are in place:
Android Studio 2024.1 (Koala) or later: Download Android Studio
Android 9.0 (API level 28) or later as your minimum SDK version
Kotlin 2.0 or later
Gradle 8.5 or later with the Kotlin DSL (
build.gradle.kts,settings.gradle.kts)A Level Access Platform tenant on Accelerate or Enterprise
An Android digital asset and an API key generated in the Level Access Platform.
An existing Android app project to test, with an instrumented test target (
androidTest)
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, including scheme (for example,
https://platform.levelaccess.com).API key — generate one in the Level Access Platform.
App identifier — the package name of the Android app you are testing (for example,
com.example.myapp).Digital asset ID — the ID of the Android 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 UI.
Download the SDK from the Level Access Platform
1. Open the SDKs page
Sign in to the Level Access Platform.
Go to your organization and select Manage.
Select to Tools & integrations and click SDKs.
The Mobile section displays a set of mobile SDK cards.
2. Open the Mobile Engine SDK (Android) detail page
On the Mobile Engine SDK (Android) card, select Download and install.
The Mobile Engine SDK (Android) detail page opens, with instructions for Using Gradle and Usage documentation.
Add the SDK to your Android Studio project
1. Configure the artifact repository
Open your Android app project in Android Studio.
Open
settings.gradle.ktsat the root of your project.Add the Level Access Cloudsmith Gradle repository to the
dependencyResolutionManagementblock. You will need a Cloudsmith token provided by your Level Access organization administrator.
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven {
url = uri("https://dl.levelaccess.net/<YOUR-CLOUDSMITH-TOKEN>/mobile-solutions-snapshots/maven/")
}
}
}2. Add the SDK dependency
Open
app/build.gradle.kts(your app module's Gradle file).Add the Level Access ME SDK as an
androidTestImplementationdependency in thedependenciesblock, alongside the AndroidX test dependencies:
dependencies {
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.3.0")
androidTestImplementation("androidx.test.ext:junit:1.3.0")
androidTestImplementation("androidx.test:runner:1.7.0")
androidTestImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2")
androidTestImplementation("lib.levelaccess.mobile.sdk.client")
}Please note: Check with your Level Access representative for the latest available version.
Go to File
Click Sync Project with Gradle Files.
Configure the SDK
In your androidTest target, import the SDK and create a MobileSdk instance with a MobileSdkConfig.
package com.levelaccess.mobile
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.levelaccess.mobilesdk.MobileSdk
import com.levelaccess.mobilesdk.MobileSdkConfig
import com.levelaccess.mobilesdk.ScanSession
import com.levelaccess.mobilesdk.rule.Rule
import com.levelaccess.mobilesdk.rule.ruleset.GoogleRuleSet
import io.kotest.matchers.collections.haveSize
import io.kotest.matchers.shouldNot
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class AccessibilityTests {
private val host = "https://platform.levelaccess.com"
private val apiKey = "<YOUR-API-KEY>"
private val appIdentifier = "com.example.myapp"
private lateinit var mobileSdk: MobileSdk
private lateinit var rules: List<Rule>
@Before
fun before() {
mobileSdk = runBlocking {
MobileSdk.create(
MobileSdkConfig(
host = host,
apiKey = apiKey,
appIdentifier = appIdentifier,
),
)
}
// Configure the rules to run during the scan.
rules =
mobileSdk.levelAccessRules { all() } + mobileSdk.googleRuleSet { withVersion(GoogleRuleSet.VERSION_4_0) }
// Launch the app under test.
mobileSdk.launchApp()
}
}MobileSdk.create(config) validates the supplied credentials against the platform and throws MobileSdkError.AUTHENTICATION_FAILED 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. |
|
Package name of the Android app under test (for example, |
Choose your rules
The SDK exposes two rule sets that can be combined:
levelAccessRuleSet— the Level Access rule pack, covering Level Access best-practice checks such asNON_TEXT_CONTENT_LABEL_PRESENT,INTERACTIVE_NAME_PRESENT,TARGET_SIZE_MINIMUM,TEXT_SCALING, and others.googleRuleSet— Google-aligned rules includingcontrast,TOUCH_TARGET_SIZE,SPEAKABLE_TEXT_PRESENT,TEXT_CONTRAST, and others.
Use .all() to enable every rule in a pack, or .add(_:) to enable specific rules.
// All rules from both packs
val rules =
mobileSdk.levelAccessRules { all() } +
mobileSdk.googleRules { all() }
// Or a curated subset
val rules =
mobileSdk.levelAccessRuleSet
.add(LevelAccessRules.NON_TEXT_CONTENT_LABEL_PRESENT)
.add(LevelAccessRules.INTERACTIVE_NAME_PRESENT)
.build() +
mobileSdk.googleRuleSet
.add(GoogleRules.TEXT_CONTRAST)
.add(GoogleRules.TOUCH_TARGET_SIZE)
.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.
@Test
fun test_scan_settingsScreen() {
// Drive the app to the screen you want to scan
val intent = context.packageManager.getLaunchIntentForPackage(appIdentifier)
context.startActivity(intent)
device.findObject(By.text("Settings")).click()
// Run the scan
val results = mobileSdk.scan(rules)
results shouldNot haveSize(0)
}scan(rules) returns a list 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.
@Test
fun test_scan_andSubmitToPlatform() {
val scanSession: ScanSession = runBlocking {
mobileSdk.createScanSession(
digitalAssetId = "<YOUR-DIGITAL-ASSET-ID>",
sessionName = "Smoke run — Settings flow",
scanTagId = "<YOUR-SCAN-TAG-ID>",
)
}
// Screen 1
val homeResults = mobileSdk.scan(rules)
runBlocking { scanSession.report("Home", homeResults) }
// Navigate, then Screen 2
device.findObject(By.text("Settings")).click()
val settingsResults = mobileSdk.scan(rules)
runBlocking { scanSession.report("Settings", settingsResults) }
scanSession.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 MobileSdkErrorCode. Handle them in your test or let them fail the test naturally:
Case |
When it is thrown |
|---|---|
|
The SDK could not authenticate against |
|
The SDK was used in an unexpected order (for example, reporting after |
|
|
|
|
|
|
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.snapshots contain a screenshot with bounding boxes drawn around the offending elements, suitable for attachment to a test failure.
Attach screenshots to test failures
@Test
fun testAccessibility() {
val results = mobileSdk.scan(rules)
results
.filter { it.metadata.type == ResultType.ERROR }
.forEach { result ->
result.snapshots.forEach { snapshot ->
snapshot.screenshot?.let { screenshot ->
val file = File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
screenshot.fileName
)
file.outputStream().use { stream ->
screenshot.bitmap?.compress(Bitmap.CompressFormat.PNG, 100, stream)
}
}
}
throw AssertionError("${result.metadata.rule}: ${result.metadata.message}")
}
}Find the bounds of a flagged element
val results = mobileSdk.scan(rules)
results.forEach { result ->
val element = result.elements.firstOrNull()
if (element != null) {
val bounds = element.bounds
val role = element.roleDescription ?: element.className
val label = element.contentDescription
// ...
} else {
// No elements available (success, unsupported, or no source element).
}
}Example: end-to-end test case
The following instrumented test class is a complete starting point. Replace the placeholder credentials with values from your tenant.
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.levelaccess.mobilesdk.MobileSdk
import com.levelaccess.mobilesdk.MobileSdkConfig
import com.levelaccess.mobilesdk.ScanSession
import com.levelaccess.mobilesdk.rule.Rule
import com.levelaccess.mobilesdk.rule.ruleset.GoogleRuleSet
import io.kotest.matchers.collections.haveSize
import io.kotest.matchers.shouldNot
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class AccessibilityTests {
private val host = "https://platform.levelaccess.com"
private val apiKey = "<YOUR-API-KEY>"
private val appIdentifier = "com.example.myapp"
private val digitalAssetId = "<YOUR-DIGITAL-ASSET-ID>"
private val scanTagId = "<YOUR-SCAN-TAG-ID>"
private lateinit var mobileSdk: MobileSdk
private lateinit var rules: List<Rule>
@Before
fun before() {
mobileSdk = runBlocking {
MobileSdk.create(
MobileSdkConfig(
host = host,
apiKey = apiKey,
appIdentifier = appIdentifier,
),
)
}
rules =
mobileSdk.levelAccessRules { all() } + mobileSdk.googleRuleSet { withVersion(GoogleRuleSet.VERSION_4_0) }
mobileSdk.launchApp()
}
@Test
fun test_scan_shouldReturnResults_whenScanningWithAllRules() {
val results = mobileSdk.scan(rules)
results shouldNot haveSize(0)
}
@Test
fun test_scan_shouldSucceed_whenScanningAndReportingResults() {
val scanSession: ScanSession = runBlocking {
mobileSdk.createScanSession(
digitalAssetId = digitalAssetId,
sessionName = "Example session",
scanTagId = scanTagId,
)
}
val results = mobileSdk.scan(rules)
runBlocking { session.report("Home", results) }
runBlocking { 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 |
|
|
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
data class MobileSdkConfig( val host: String, val apiKey: String, val 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
LevelAccessRules—NON_TEXT_CONTENT_LABEL_PRESENT,INTERACTIVE_NAME_PRESENT,INTERACTIVE_ROLE_PRESENT,INTERACTIVE_VALUE_PRESENT,INTERACTIVE_STATE_PRESENT,ORIENTATION_PORTRAIT,ORIENTATION_LANDSCAPE,TARGET_SIZE_MINIMUM(minimumWidth, minimumHeight, minimumSpacing),TARGET_SIZE_ENHANCED(minimumWidth, minimumHeight),TEXT_SCALING(exclusions).GoogleRules—CLASS_NAME,CLICKABLE_SPAN,DUPLICATE_CLICKABLE_BOUNDS,DUPLICATE_SPEAKABLE_TEXT,EDITABLE_CONTENT_DESCRIPTION,IMAGE_CONTRAST,LINK_PURPOSE_UNCLEAR,REDUNDANT_DESCRIPTION,SPEAKABLE_TEXT_PRESENT,TEXT_CONTRAST,TEXT_SIZE,TOUCH_TARGET_SIZE,TRAVERSAL_ORDER,UNEXPOSED_TEXT.
Both builders expose .add(rule), .all(), .reset(), and .build().
Result types
ResultType: SUCCESS, ERROR, WARNING, UNSUPPORTED.
Vendor: LEVEL_ACCESS, GOOGLE
Comments
0 comments
Article is closed for comments.