Test Example in Java
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.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.net.MalformedURLException;
import java.net.URL;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class AndroidDebugTest {
private static AndroidDriver driver;
private static final String[] TAGS = {"appium", "qa"};
// ignoreRules is passed directly into each 'mobile: axeScan' call
private static Map<String, Object> scanSettings;
private static final String API_KEY = "<DEQUE_APIKEY>";
private static final String PROJECT_ID = "<DEQUE_PROJECT_ID>";
private static final String APP_PACKAGE = "<YOUR_APP_PACKAGE_NAME>";
// It is very important to make a call to 'mobile: axeStartSession' inside @BeforeAll annotated
// method to set up your session for posting to Axe Developer Hub
@BeforeAll
public static void setUp() throws MalformedURLException {
UiAutomator2Options options = new 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 = new AndroidDriver(new URL("http://localhost:4723/"), options);
scanSettings = new HashMap<>();
// tags and ignoreRules are deprecated and will be removed once we fully transition to Axe Developer Hub
scanSettings.put("ignoreRules", new String[]{"ScreenOrientation"});
scanSettings.put("tags", TAGS);
// Make a one-time call to set up your session for posting to Axe Developer Hub
// This also accepts `axeAccountURL` in case of a private instance
// The `axeSettings` object still accepts `apiKey` with `axeAccountURL`, but is not required
// if you are going to make one time @BeforeAll annotated call to 'mobile: axeStartSession'
Map<String, Object> axeSettings = new HashMap<>();
axeSettings.put("apiKey", API_KEY);
axeSettings.put("projectId", PROJECT_ID);
axeSettings.put("axeUploadResults", true); // default - set to false to keep results local only
driver.executeScript("mobile: axeStartSession", axeSettings);
}
@AfterAll
public static void tearDown() {
if (driver != null) {
// Generate an HTML report from all accumulated scans before quitting
// The report aggregates every 'mobile: axeScan' call made during this session.
try {
Map<String, Object> reportSettings = new HashMap<>();
reportSettings.put("scanName", "My Accessibility Report");
reportSettings.put("axeHtmlReportPath", "build/AxeDevToolsMobileResults");
@SuppressWarnings("unchecked")
Map<String, Object> reportResult = (Map<String, Object>) driver.executeScript(
"mobile: axeGenerateHtmlReportAndSummary", reportSettings);
if (reportResult.containsKey("axeError")) {
System.out.println("Report generation failed: " + reportResult.get("axeError"));
} else {
System.out.println("HTML report saved to: " + reportResult.get("localDirectory"));
}
} catch (Exception e) {
System.out.println("Failed to generate HTML report: " + e.getMessage());
}
driver.quit();
}
}
private void dismissSystemUIError() {
try {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
WebElement systemPopup = wait.until(ExpectedConditions.presenceOfElementLocated(
By.xpath("//*[contains(@text, 'System UI')]")));
if (systemPopup.isDisplayed()) {
WebElement waitButton = wait.until(ExpectedConditions.presenceOfElementLocated(
By.xpath("//*[contains(@text, 'Wait')]")));
waitButton.click();
System.out.println("Dismissed the System UI error popup by clicking 'Wait'.");
}
} catch (Exception e) {
System.out.println("No System UI error popup appeared.");
}
}
private void launchApp() {
try {
driver.terminateApp(APP_PACKAGE);
Thread.sleep(1000); // Give the app time to fully terminate
} catch (Exception e) {
System.out.println("App was not running or failed to terminate: " + e.getMessage());
}
driver.activateApp(APP_PACKAGE);
try {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30));
wait.until(ExpectedConditions.presenceOfElementLocated(
By.xpath("//*[contains(@text, 'Screen Name')]")));
} catch (Exception e) {
dismissSystemUIError();
// Try again after dismissing the error (if not, then fail)
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30));
wait.until(ExpectedConditions.presenceOfElementLocated(
By.xpath("//*[contains(@text, 'Screen Name')]")));
}
}
@BeforeEach
public void 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 Developer Hub.
@Test
@Order(1)
public void test1() {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement element = wait.until(ExpectedConditions.presenceOfElementLocated(
By.xpath("//*[contains(@text, 'Screen Name')]")));
System.out.println(element.getText());
Map<String, Object> appiumScanResult = (Map<String, Object>) driver.executeScript("mobile: axeScan", scanSettings);
List<?> results = (List<?>) appiumScanResult.get("axeRuleResults");
System.out.println("debug: Total results: " + results.size());
}
@Test
@Order(2)
public void test2() {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement element = wait.until(ExpectedConditions.presenceOfElementLocated(
By.xpath("//*[contains(@text, 'Screen Name')]")));
element.click();
driver.findElement(By.xpath("//*[contains(@text, 'announced by a screen')]"));
Map<String, Object> appiumScanResult = (Map<String, Object>) driver.executeScript("mobile: axeScan", scanSettings);
List<?> results = (List<?>) appiumScanResult.get("axeRuleResults");
System.out.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 org.openqa.selenium.support.ui.WebDriverWait;
import java.net.MalformedURLException;
import java.net.URL;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class IOSMapsDebugTest {
private static IOSDriver driver;
private static final String[] TAGS = {"appium", "qa", "ios"};
// ignoreRules is passed directly into each 'mobile: axeScan' call
private static Map<String, Object> scanSettings;
private static final String API_KEY = "<DEQUE_APIKEY>";
private static final String PROJECT_ID = "<DEQUE_PROJECT_ID>";
private static final String BUNDLE_ID = "com.apple.Maps";
// It is very important to make a call to 'mobile: axeStartSession' inside @BeforeAll annotated
// method to set up your session for posting to Axe Developer Hub
@BeforeAll
public static void setUp() throws MalformedURLException {
// Matching the working sample - minimal capabilities
XCUITestOptions options = new 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 = new IOSDriver(new URL("http://127.0.0.1:4723/"), options);
scanSettings = new HashMap<>();
// tags and ignoreRules are deprecated and will be removed once we fully transition to Axe Developer Hub
scanSettings.put("ignoreRules", new String[]{"ScreenOrientation"});
scanSettings.put("tags", TAGS);
// Make a one-time call to set up your session for posting to Axe Developer Hub
// This also accepts `axeAccountURL` in case of a private instance
// The `axeSettings` object still accepts `apiKey` with `axeAccountURL`, but is not required
// if you are going to make one time @BeforeAll annotated call to 'mobile: axeStartSession'
Map<String, Object> axeSettings = new HashMap<>();
axeSettings.put("apiKey", API_KEY);
axeSettings.put("projectId", PROJECT_ID);
axeSettings.put("axeAccountURL", "https://mobile-qa.dequelabs.com");
axeSettings.put("axeUploadResults", true); // default - set to false to keep results local only
driver.executeScript("mobile: axeStartSession", axeSettings);
}
@AfterAll
public static void tearDown() {
if (driver != null) {
// Generate HTML reports before quitting the driver
driver.executeScript("mobile: axeGenerateHtmlReportAndSummary", new HashMap<>());
// Alternative: Generate HTML reports to a custom path
// Map<String, Object> reportSettings = new HashMap<>();
// reportSettings.put("outputPath", "/Users/me/MyReports");
// driver.executeScript("mobile: axeGenerateHtmlReportAndSummary", reportSettings);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
driver.quit();
}
}
private void launchApp() {
driver.activateApp(BUNDLE_ID);
}
@BeforeEach
public void 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 Developer Hub.
@Test
@Order(1)
@Timeout(10)
public void test1_scanMainScreen() {
Map<String, Object> appiumScanResult = (Map<String, Object>) driver.executeScript("mobile: axeScan", scanSettings);
List<?> results = (List<?>) appiumScanResult.get("axeRuleResults");
System.out.println("debug: Total results: " + results.size());
}
@Test
@Order(2)
@Timeout(10)
public void test2_clickSearchAndScan() {
Map<String, Object> appiumScanResult = (Map<String, Object>) driver.executeScript("mobile: axeScan", scanSettings);
List<?> results = (List<?>) appiumScanResult.get("axeRuleResults");
System.out.println("debug: Total results: " + results.size());
}
}
note
Using TestNG? No problem. Our Appium drivers are compatible with this testing framework.
