Appium Example
We're developing an Appium Plugin that's set to launch early 2024. This plugin will enable you to better integrate accessibility testing throughout your Appium tests, no matter the language they are written in!
Using Appium with axe DevTools Today? The below information is provided in context of implementing the axeDevToolsUIKit framework (setup available to view here) and engaging with an embedded floating action button to kick off a scan. The axeDevToolsUIKit framework is decommissioned and will not receive new updates while the plugin is being built.
Supported within:
Before running your tests in Appium, ensure the application under test is set up to run axe DevTools Mobile by embedding the framework into your application. Resource: Setup Guide for Embedding the framework
Next, replace the call to axe?.showA11yFAB()
with axe?.showA11yFAB(customFAB: AutomatedTestFAB())
. This provides an invisible floating action button for tests to engage with that won't interfere with any UI assertions.
Resource: UITesting with axeDevToolsUIKit
Introduction
By embedding the framework into your application, Appium can initiate a scan by engaging with the floating action button by its accessibility identifier: com.deque.axeDevTools.accessibilityFab
. Engage with the floating action button at any point in your tests you'd like to scan for accessibility issues.
Note: Appium support for accessibility testing is limited to UIKit implementations currently. Support for other languages is coming soon.
Examples
Python Client
Resource: Appium Python Client Documentation
Follow the comments marked with TODO
to update your specific configurations to start testing with the script as is. Grab a Deque API Key from your axe Account Settings page.
from appium import webdriver
import os
import requests
from appium.webdriver import WebElement
from appium.webdriver.common.appiumby import AppiumBy
## TODO: Update your configurations
app = "com.dequesystems.axe-devtools-ios-sample-app"
udid = "DFB0EB3F-2C3D-4F62-B684-39F3D12F0DCD"
platformVersion = "16.2"
deviceName = "iPhone 14"
dequeAPIKey = ""
## Class Definitions for parsing the result from the server:
class AxeRuleResult:
def __init__(self, ruleId, ruleSummary, axeViewId, status, impact, props, isVisibleToUser=True):
self.ruleId = ruleId
self.ruleSummary = ruleSummary
self.axeViewId = axeViewId
self.status = status
self.impact = impact
self.props = props
self.isVisibleToUser = isVisibleToUser
class AxeResult:
def __init__(self, axeRuleResults, userName, scanName, tags):
self.axeRuleResults = axeRuleResults
self.userName = userName
self.scanName = scanName
self.tags = tags
class DemoWithAPIRequest:
def __init__(self):
self.driver = None
self.client = requests.Session()
def setup(self):
success = True
desired_caps = {}
desired_caps['platformName'] = 'iOS'
desired_caps['platformVersion'] = platformVersion
desired_caps['deviceName'] = deviceName
desired_caps['udid'] = udid
desired_caps['automationName'] = 'XCUITest'
desired_caps['realDeviceLogger'] = 'idevicesyslog'
desired_caps['app'] = app
## TODO: UPDATE URL for your appium server:
self.driver = webdriver.Remote('http://127.0.0.1:4723', desired_caps)
def runA11y(self):
# Navigate and Click the FAB
fab = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='com.deque.axeDevTools.accessibilityFab')
fab.click()
# Wait for network request to finish and FAB to return.
self.driver.implicitly_wait(10)
# Getting Result Key from the FAB
resultKey = fab.get_attribute("label")
# Fetch Rule Results from API
url = "https://axe-mobile-backend.deque.com/attest/result/axe/"+resultKey
headers = {
"X-Api-Key": dequeAPIKey,
"Content-Type": "application/json"
}
try:
response = self.client.get(url, headers=headers)
if response.status_code == 200:
content = response.json()
# Parse JSON and output the result
axeResult = AxeResult(
axeRuleResults=[AxeRuleResult(**ruleResult) for ruleResult in content['axeRuleResults']],
userName=content['userName'],
scanName=content['scanName'],
tags=content['tags']
)
for rule in axeResult.axeRuleResults:
if rule.status == "FAIL":
print(f"rule: {rule.ruleId}")
else:
print(f"httpCode: {response.status_code}")
except Exception as e:
print(f"error: {str(e)}")
demo = DemoWithAPIRequest()
demo.setup()
demo.runA11y()
Java Client
Resource: Appium Java Client Documentation
build.gradle
plugins {
id 'java'
id 'org.jetbrains.kotlin.jvm' version '1.5.0'
}
group 'org.example'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
maven { url "https://maven.google.com" }
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib"
implementation "junit:junit:4.13.2"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.2"
testImplementation "junit:junit:4.13.1"
implementation "com.github.appium:java-client:8.1.1"
implementation "org.seleniumhq.selenium:selenium-java:4.2.1"
implementation "com.google.code.gson:gson:2.9.0"
implementation "com.squareup.okhttp3:okhttp:4.10.0"
}
Full Example
import com.google.gson.Gson
import io.appium.java_client.AppiumBy
import io.appium.java_client.ios.IOSDriver
import io.appium.java_client.remote.MobileCapabilityType
import io.appium.java_client.remote.MobilePlatform
import okhttp3.OkHttpClient
import okhttp3.Request
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.openqa.selenium.WebDriver
import org.openqa.selenium.remote.DesiredCapabilities
import org.openqa.selenium.support.ui.ExpectedCondition
import org.openqa.selenium.support.ui.WebDriverWait
import java.io.BufferedReader
import java.io.File
import java.net.URL
import java.time.Duration
@RunWith(JUnit4::class)
class DemoWithAPIRequest {
private val client = OkHttpClient()
private lateinit var driver: IOSDriver
val appFilePath = "path/to/your/application.app"
@Before
fun setup() {
val capabilities = DesiredCapabilities()
capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "iPhone 14")
capabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, MobilePlatform.IOS)
capabilities.setCapability(MobileCapabilityType.PLATFORM_VERSION, "16.0")
capabilities.setCapability(MobileCapabilityType.APP, File(appFilePath).absolutePath)
driver = IOSDriver(URL("http://127.0.0.1:4723/wd/hub"), capabilities)
}
@Test
fun runA11y() {
// Navigate and Click the FAB
driver.findElement(
AppiumBy.accessibilityId("com.deque.axeDevTools.accessibilityFab")
).click()
val webDriverWait = WebDriverWait(driver, Duration.ofSeconds(10))
webDriverWait.until(ExpectedCondition { input: WebDriver? ->
input?.findElement(AppiumBy.accessibilityId("com.deque.axeDevTools.accessibilityFab"))
true
})
// Getting Result Key from the FAB
val resultKey: String = driver.findElement(
AppiumBy.accessibilityId("com.deque.axeDevTools.accessibilityFab")
).getAttribute("label")
// Fetch Rule Results from API
val apiKey = "your-api-key-here"
val url = "https://axe-mobile-backend.deque.com/attest/result/axe/$resultKey"
val request = Request.Builder()
.url(url)
.addHeader("X-Api-Key", apiKey)
.addHeader("Content-Type", "application/json")
.build()
try {
val response = client.newCall(request).execute()
if (response.code == 200) {
val content = response.body?.string()
// Parse JSON and output the result
val axeResult = Gson().fromJson(content, AxeResult::class.java)
axeResult.axeRuleResults.forEach {
if (it.status == "FAIL") {
println("rule: ${it.ruleId}")
}
}
} else {
println("httpCode: ${response.code}")
}
} catch (t: Throwable) {
println("error: ${t.message}")
}
}
}
data class AxeRuleResult(
val ruleId: String?,
val ruleSummary: String?,
val axeViewId: String?,
val status: String?, // "PASS", "FAIL", "INCOMPLETE"
val impact: Int?,
val props: HashMap<String, Any?>,
val isVisibileToUser: Boolean = true
)
data class AxeResult(
val axeRuleResults: List<AxeRuleResult>,
val userName: String?,
val scanName: String?,
val tags: List<String>?
)