Setup for XML Layouts

Link to Setup for XML Layouts copied to clipboard

This page highlights the steps for setting up the library to scan XML layouts for accessibility issues. For steps to pull in the library, please see Getting Started. For scanning Compose layouts, please see Setup for Compose Layouts.

Manual Testing

A person can initiate a scan with manual testing while using the app in a testing flow. A testing team can use axe DevTools to scan for accessibility issues with manual testing enabled in QA builds. It's also helpful while developing to scan the screen and check in on your progress to ensure it is accessible.

Initialize the Library

Choose one of the following ways to authenticate with our library. You will need to add this to each Activity that you would like to run scans on.

Connect for a different user

If you are creating a build for another user to test with, calling showA11yFAB without connect will show the axe login floating action button allowing them to authenticate with their user:

val axe = AxeDevTools()
    
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    
    axe.showA11yFAB(this)
}

Learn more about the login feature. That's it! Upon using this build, another user will be able to use the axe floating action button to log in to the framework and start initiating their own scans. Note: You may want to send them the documentation for the login feature if it's their first time with the library.

Connect with API key:

Generate an API key at axe.deque.com.

val axe = AxeDevTools()
    
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    axe.connect(
        API_KEY,
        ConnectionConfig.DEFAULT_CONNECTION_CONFIG
    )
    axe.showA11yFAB(this)
}

Connect with username and password:

val axe = AxeDevTools()
    
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    axe.connect(
        "username",
        "password",
        ConnectionConfig.DEFAULT_CONNECTION_CONFIG
    )
    axe.showA11yFAB(this)
}

Load the Floating Action Button (FAB)

important

Access the axe object within any activity you wish to test and register that activity for testing with axe.showA11yFAB(this).

From here, you can now run your application to access the floating action button. The button can be moved around the screen to not obstruct development. When you're ready for a scan, tap the button and a scan of the current screen will be sent to axe DevTools Mobile Dashboard where you can view the results. Note: Altering the screen state prior to the floating action button finishing its scan may result in a crash.

Automated Testing

Before running Espresso tests, initialize the main entry for the AxeDevTools library.

tip

Looking for Appium Setup? Prepare your application for manual testing using the floating action button then check out the Appium setup guide.

Initialize the Library

In the test class init, connect to the library with one of the following:

Connect with API key:

Generate an API key at axe.deque.com.

private val axe = AxeDevTools()

init {
     axe.connect(
        "APIKEY",
        ConnectionConfig.DEFAULT_CONNECTION_CONFIG
    )
}

Connect with username and password:

private val axe = AxeDevTools()

init {
    axe.connect(
        "username",
        "password",
        ConnectionConfig.DEFAULT_CONNECTION_CONFIG
    )
}

Run a Scan

When you're ready to run a scan from your tests, you'll want to run axe.scan(...). We've included a function below that highlights some additional functionality you may find helpful.

Read about these features and more on the Android Features page. This function is broken down below the snippet.

private fun a11yScan() {
    rule.scenario.onActivity { activity ->
        //1. Scan and receive the ScanResultHandler locally
        val scanResultHandler = axe.scan(activity)

        //2. Upload it to the dashboard
        scanResultHandler?.uploadToDashboard()

        //3. Using the results in your test suite
        val result: AxeResult? = scanResultHandler?.getSerializedResult()
        result?.axeRuleResults?.forEach { result ->
             if(result.status == AxeStatus.PASS) {
        
             }
             else if(result.status == AxeStatus.FAIL) {
        
             }
             else if(result.status == AxeStatus.INCOMPLETE) {
    
             }
         }

        //4. Save the result JSON to a local file for later use
        scanResultHandler?.saveResultToLocalStorage("your_file_prefix")
    }
}

Code snippet breakdown:

  1. Call axe.scan(activity) anytime the UI should be scanned. The scan function can be called multiple times within the same test run. The current activity will be scanned for accessibility issues, and a scan result handler will be returned.
  2. From the result handler, call uploadToDashboard() to send the most recent scan to the dashboard. The scan will be published from the authenticated account used in the init function.
  3. From the result handler, call getSerializedResult() to use results from the library locally. For example, you may choose to fail the test when failures are found in the accessibility scan results. This will not upload your results to the server. Uploading your results is not required to get the results locally.
  4. From the result handler, call saveResultToLocalStorage() to save the results as JSON in a local file within your test device. To access the saved file, refer to the feature documentation for Saving Results Locally.

Scan a Dialog

Released with version 3.1.0, axe DevTools for Android now supports scanning dialogs. From your dialogs Dialog.show() method, pass the returned dialog as a parameter to the scan method as well as a callback to handle the scan's result. The callback returns a ScanResultHandler by default so that you may handle the result as you see fit. See the following example:

Kotlin Example:

val dialog = AlertDialog.Builder(requireContext())
    .setTitle("Title")
    .setMessage("Message")
    .show()

MainScope().launch {
    delay(5000)
    axe.scan(dialog) { handler ->
         handler?.uploadToDashboard()
    }
}

When scanning a dialog the scan itself will take place immediately after the dialog has been inflated in both manual and automated testing. Note that a scan will only take place if the following are true:

  • The user is authenticated
  • The floating action button is displayed behind the dialog or espresso tests are running
  • The dialog is inflated before the timeout expires

Deinitialize

Once the test has been completed, call tearDown() on the AxeDevTools object you have created to clear the state of previous tests. We recommend putting this in your testing file's tearDown function, but it can also be called within a test if needed.

@After
fun tearDown() {
    axe.tearDown()
}

Example Espresso Test Class for XML

class ExampleInstrumentedTestWithAccessibility : Utils {

    @Rule
    @JvmField
    var rule: ActivityScenarioRule<MainActivity> = ActivityScenarioRule(MainActivity::class.java)

    private val axe = AxeDevTools()

    init {
        //Connect using an API Key
        axe.connect(
            "APIKey",
            ConnectionConfig.DEFAULT_CONNECTION_CONFIG
        )
    }

    @Before
    fun setup() {
        axe.tagScanAs(setOf("ScanningApp"))
        axe.setTestingConfig(AxeDevToolsEspressoConfig(IdlingRegistry.getInstance()))
    }

    @Test
    fun exampleTest() {
        onView(withText("Active View Name")).perform(click())
        onView(withText("Actionable Button")).perform(click())
        onView(withContentDescription("Share Data")).perform(click())
    }

    @After
    fun runAccessibilityScan() {
        rule.scenario.onActivity {
            val scan = axe.scan(it)
            scan?.uploadToDashboard()
            axe.tearDown()
        }
    }
}

Scan Dialog Espresso Example

class ExampleInstrumentedTestWithAccessibility : Utils {
    private lateinit var countingResource: CountingIdlingResource

    @Before
    fun setTestingConfig() {
        countingResource = CountingIdlingResource("DialogScan")
        IdlingRegistry.getInstance().register(countingResource)
        
        axe.setTestingConfig(AxeDevToolsEspressoConfig(IdlingRegistry.getInstance()))
    }

    @Test
    fun dialogScan() {
        countingResource.increment()

        rule.scenario.onActivity { activity ->
            val dialog = AlertDialog.Builder(activity)
                .setTitle("Title")
                .setMessage("Message")
                .show()

            axe.scan(dialog) { handler ->
                val result = handler?.uploadToDashboard()
                countingResource.decrement()
            }
        }

        while(!countingResource.isIdleNow) { Thread.sleep(100) }
    }

    @After
    fun tearDown() {
        IdlingRegistry.getInstance().unregister(countingResource)
        axe.tearDown()
    }
}