Appium Example
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>?
)