Test Example in Kotlin
Not for use with personal data
Be sure to check out the full Appium setup guide with axe DevTools Mobile if you're just getting started, or more examples of axe DevTools Mobile for Appium in other languages.
Full Example with UIAutomator2
import io.appium.java_client.android.AndroidDriver
import io.appium.java_client.android.options.UiAutomator2Options
import org.junit.jupiter.api.*
import org.openqa.selenium.By
import org.openqa.selenium.support.ui.ExpectedConditions
import org.openqa.selenium.support.ui.WebDriverWait
import java.net.URL
import java.time.Duration
@TestMethodOrder(MethodOrderer.OrderAnnotation::class)
class AndroidDebugTest {
companion object {
private lateinit var driver: AndroidDriver
// The axeSettings object still accepts apiKey and axeServiceUrl but is not required
// if you are going to make one time @BeforeAll annotated call to 'mobile: axeStartSession'
private lateinit var axeSettings: Map<String, Any>
private const val API_KEY = "<YOUR_API_KEY>"
private const val PROJECT_ID = "<YOUR_DEV_HUB_PROJECT_ID>"
private const val APP_PACKAGE = "<YOUR_APP_PACKAGE_NAME>"
// It is very important to make a call to 'mobile: axeStartSession' inside @BeforeAll annotated
// method to setup your session for posting to Dev Hub.
@JvmStatic
@BeforeAll
fun setUp() {
axeSettings = mapOf(
"tags" to listOf("appium", "qa"),
"uploadToDashboard" to true,
"ignoreRules" to listOf("ScreenOrientation")
)
val options = UiAutomator2Options()
.setPlatformName("Android")
.setDeviceName("Android")
.setAppPackage(APP_PACKAGE)
.setAppActivity(".MainActivity")
.setAutomationName("AxeUiAutomator2")
.setUiautomator2ServerLaunchTimeout(Duration.ofMillis(60000))
.setUiautomator2ServerInstallTimeout(Duration.ofMillis(60000))
.setAdbExecTimeout(Duration.ofMillis(60000))
.setIgnoreHiddenApiPolicyError(true)
.setDisableWindowAnimation(true)
.setCapability("waitForIdle", true)
.setNewCommandTimeout(Duration.ofSeconds(300))
.setNoReset(false)
.setFullReset(false)
driver = AndroidDriver(URL("http://localhost:4723/"), options)
// Make one time call to setup your session for posting to Dev Hub.
// This also accepts axeServiceUrl in case of private instance.
val axeAuthSettings = mapOf(
"apiKey" to API_KEY,
"projectId" to PROJECT_ID
)
driver.executeScript("mobile: axeStartSession", axeAuthSettings)
}
@JvmStatic
@AfterAll
fun tearDown() {
if (::driver.isInitialized) {
driver.quit()
}
}
}
private fun dismissSystemUIError() {
try {
val wait = WebDriverWait(driver, Duration.ofSeconds(5))
val systemPopup = wait.until(
ExpectedConditions.presenceOfElementLocated(
By.xpath("//*[contains(@text, 'System UI')]")
)
)
if (systemPopup.isDisplayed) {
val waitButton = wait.until(
ExpectedConditions.presenceOfElementLocated(
By.xpath("//*[contains(@text, 'Wait')]")
)
)
waitButton.click()
println("Dismissed the System UI error popup by clicking 'Wait'.")
}
} catch (e: Exception) {
println("No System UI error popup appeared.")
}
}
private fun launchApp() {
try {
driver.terminateApp(APP_PACKAGE)
Thread.sleep(1000) // Give the app time to fully terminate
} catch (e: Exception) {
println("App was not running or failed to terminate: ${e.message}")
}
driver.activateApp(APP_PACKAGE)
try {
val wait = WebDriverWait(driver, Duration.ofSeconds(30))
wait.until(
ExpectedConditions.presenceOfElementLocated(
By.xpath("//*[contains(@text, 'Screen Name')]")
)
)
} catch (e: Exception) {
dismissSystemUIError()
// Try again after dismissing the error (if not, then fail)
val wait = WebDriverWait(driver, Duration.ofSeconds(30))
wait.until(
ExpectedConditions.presenceOfElementLocated(
By.xpath("//*[contains(@text, 'Screen Name')]")
)
)
}
}
@BeforeEach
fun beforeEach() {
launchApp()
}
// Now since your session is authenticated you can keep making 'mobile: axeScan' call
// like below in test1 and test2. The scans will be uploaded to the dashboard and also grouped in Dev Hub.
@Test
@Order(1)
fun test1() {
val wait = WebDriverWait(driver, Duration.ofSeconds(10))
val element = wait.until(
ExpectedConditions.presenceOfElementLocated(
By.xpath("//*[contains(@text, 'Screen Name')]")
)
)
println(element.text)
val appiumScanResult = driver.executeScript("mobile: axeScan", axeSettings) as Map<*, *>
val results = appiumScanResult["axeRuleResults"] as List<*>
println("debug: Total results: ${results.size}")
}
@Test
@Order(2)
fun test2() {
val wait = WebDriverWait(driver, Duration.ofSeconds(10))
val element = wait.until(
ExpectedConditions.presenceOfElementLocated(
By.xpath("//*[contains(@text, 'Screen Name')]")
)
)
element.click()
driver.findElement(By.xpath("//*[contains(@text, 'announced by a screen')]"))
val appiumScanResult = driver.executeScript("mobile: axeScan", axeSettings) as Map<*, *>
val results = appiumScanResult["axeRuleResults"] as List<*>
println("debug: Total results: ${results.size}")
}
}
Full Example with XCUITest
import io.appium.java_client.ios.IOSDriver
import io.appium.java_client.ios.options.XCUITestOptions
import org.junit.jupiter.api.*
import java.net.URL
import java.time.Duration
@TestMethodOrder(MethodOrderer.OrderAnnotation::class)
class IOSMapsDebugTest {
companion object {
private lateinit var driver: IOSDriver
// The axeSettings object still accepts apiKey and axeServiceUrl but is not required
// if you are going to make one time @BeforeAll annotated call to 'mobile: axeStartSession'
private lateinit var axeSettings: Map<String, Any>
private const val API_KEY = "<YOUR_API_KEY>"
private const val PROJECT_ID = "<YOUR_DEV_HUB_PROJECT_ID>"
private const val BUNDLE_ID = "com.apple.Maps"
// It is very important to make a call to 'mobile: axeStartSession' inside @BeforeAll annotated
// method to setup your session for posting to Dev Hub.
@JvmStatic
@BeforeAll
fun setUp() {
axeSettings = mapOf(
"tags" to listOf("appium", "qa", "ios"),
"uploadToDashboard" to true,
"ignoreRules" to listOf("ScreenOrientation")
)
// Matching the working sample - minimal capabilities
val options = XCUITestOptions()
.setPlatformName("iOS")
.setAutomationName("AxeXCUITest")
.setUdid("<YOUR_DEVICE_OR_SIMULATOR_UDID>")
.setBundleId(BUNDLE_ID)
.setWdaLaunchTimeout(Duration.ofMillis(960000)) // 16 minutes like the working sample
// NOT specifying platformVersion - let it auto-detect
driver = IOSDriver(URL("http://127.0.0.1:4723/"), options)
// Make one time call to setup your session for posting to Dev Hub.
// This also accepts axeServiceUrl in case of private instance.
val axeAuthSettings = mapOf(
"apiKey" to API_KEY,
"projectId" to PROJECT_ID,
"axeServiceUrl" to "https://mobile-qa.dequelabs.com"
)
driver.executeScript("mobile: axeStartSession", axeAuthSettings)
}
@JvmStatic
@AfterAll
fun tearDown() {
if (::driver.isInitialized) {
Thread.sleep(1000)
driver.quit()
}
}
}
private fun launchApp() {
driver.activateApp(BUNDLE_ID)
}
@BeforeEach
fun beforeEach() {
launchApp()
}
// Now since your session is authenticated you can keep making 'mobile: axeScan' call
// like below in test1 and test2. The scans will be uploaded to the dashboard and also grouped in Dev Hub.
@Test
@Order(1)
@Timeout(10)
fun test1ScanMainScreen() {
val appiumScanResult = driver.executeScript("mobile: axeScan", axeSettings) as Map<*, *>
val results = appiumScanResult["axeRuleResults"] as List<*>
println("debug: Total results: ${results.size}")
}
@Test
@Order(2)
@Timeout(10)
fun test2ClickSearchAndScan() {
val appiumScanResult = driver.executeScript("mobile: axeScan", axeSettings) as Map<*, *>
val results = appiumScanResult["axeRuleResults"] as List<*>
println("debug: Total results: ${results.size}")
}
}
