Skip to main content

Mobile engine SDK for Android

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).

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:

  • 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

  1. Sign in to the Level Access Platform.

  2. Go to your organization and select Manage.

  3. Select to Tools & integrations and click 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 (Android) detail page

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

  2. The Mobile Engine SDK (Android) detail page opens, with instructions for Using Gradle and Usage documentation.

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

Add the SDK to your Android Studio project

1. Configure the artifact repository

  1. Open your Android app project in Android Studio.

  2. Open settings.gradle.kts at the root of your project.

  3. Add the Level Access Cloudsmith Gradle repository to the dependencyResolutionManagement block. 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

  1. Open app/build.gradle.kts (your app module's Gradle file).

  2. Add the Level Access ME SDK as an androidTestImplementation dependency in the dependencies block, 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.

  1. Go to File

  2. 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

host

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

apiKey

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

appIdentifier

Package name of the Android app under test (for example, com.example.myapp).

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 as NON_TEXT_CONTENT_LABEL_PRESENT, INTERACTIVE_NAME_PRESENT, TARGET_SIZE_MINIMUM, TEXT_SCALING, and others.

  • googleRuleSet — Google-aligned rules including contrast, 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

AUTHENTICATION_FAILED

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

ILLEGAL_STATE

The SDK was used in an unexpected order (for example, reporting after close()).

SESSION_CREATION_FAILED

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

SCAN_REPORT_FAILED

report(screenName, results) could not upload results.

SESSION_CLOSE_FAILED

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.

    • 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

create

suspend fun create(config: MobileSdkConfig): MobileSdk

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

launchApp()

fun launchApp()

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

activateApp()

fun activateApp()

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

isAppRunning()

fun isAppRunning(): Boolean

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

isAppInForeground()

fun isAppInForeground(): Boolean

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

scan(rules)

fun scan(rules: List<Rule>): List<Result>

Captures the current screen and evaluates the supplied rules.

createScanSession(...)

suspend fun createScanSession(digitalAssetId: String, sessionName: String, scanTagId: String): ScanSession

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

 

report

suspend fun report(screenName: String, results: List<Result>)

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-pixel bounding box before upload.

close

suspend fun close()

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

Rule packs

  • LevelAccessRulesNON_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).

  • GoogleRulesCLASS_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

Powered by Zendesk