Test Example in Python
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 pytest
from appium import webdriver
from appium.options.android import UiAutomator2Options
class TestAndroidAxe:
driver = None
tags = ['appium', 'qa']
# ignoreRules is passed directly into each 'mobile: axeScan' call
scan_settings = {
# tags and ignoreRules are deprecated and will be removed once we fully transition to Axe Developer Hub
'ignoreRules': ['ScreenOrientation'],
'tags': tags
}
api_key = '<DEQUE_APIKEY>'
project_id = '<DEQUE_PROJECT_ID>'
app_package = '<YOUR_APP_PACKAGE_NAME>'
@classmethod
def setup_class(cls):
"""
It is very important to make a call to 'mobile: axeStartSession' inside setup_class
to set up your session for posting to Axe Developer Hub
"""
options = UiAutomator2Options()
options.platform_name = 'Android'
options.device_name = 'Android'
options.app_package = cls.app_package
options.app_activity = '.MainActivity'
options.automation_name = 'AxeUiAutomator2'
options.uiautomator2_server_launch_timeout = 60000
options.uiautomator2_server_install_timeout = 60000
options.adb_exec_timeout = 60000
options.set_capability('ignoreHiddenApiPolicyError', True)
options.set_capability('disableWindowAnimation', True)
options.set_capability('waitForIdle', True)
options.set_capability('commandTimeout', 300)
options.no_reset = False
options.full_reset = False
cls.driver = webdriver.Remote(
command_executor='http://localhost:4723',
options=options
)
# 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 `axe_settings` object still accepts `apiKey` with `axeAccountURL`, but is not required
# if you are going to make one time setup_class call to 'mobile: axeStartSession'
axe_settings = {
'apiKey': cls.api_key,
'projectId': cls.project_id,
'axeUploadResults': True # default - set to False to keep results local only
}
cls.driver.execute_script('mobile: axeStartSession', axe_settings)
@classmethod
def teardown_class(cls):
if cls.driver:
# Generate an HTML report from all accumulated scans before quitting
# The report aggregates every 'mobile: axeScan' call made during this session.
try:
report_settings = {
'scanName': 'My Accessibility Report',
'axeHtmlReportPath': 'build/AxeDevToolsMobileResults'
}
report_result = cls.driver.execute_script(
'mobile: axeGenerateHtmlReportAndSummary', report_settings
)
if 'axeError' in report_result:
print(f"Report generation failed: {report_result['axeError']}")
else:
print(f"HTML report saved to: {report_result['localDirectory']}")
except Exception as e:
print(f'Failed to generate HTML report: {str(e)}')
cls.driver.quit()
def dismiss_system_ui_error(self):
"""Dismiss System UI error popup if it appears"""
try:
system_popup = self.driver.find_element(
by='xpath',
value='//*[contains(@text, "System UI")]'
)
system_popup.is_displayed()
wait_button = self.driver.find_element(
by='xpath',
value='//*[contains(@text, "Wait")]'
)
wait_button.click()
print("Dismissed the System UI error popup by clicking 'Wait'.")
except Exception as e:
print('No System UI error popup appeared.')
def launch_app(self):
"""Launch the app, handling potential System UI errors"""
try:
self.driver.terminate_app(self.app_package)
self.driver.implicitly_wait(1) # Give the app time to fully terminate
except Exception as e:
print(f"App was not running or failed to terminate: {str(e)}")
self.driver.activate_app(self.app_package)
try:
element = self.driver.find_element(
by='xpath',
value='//*[contains(@text, "Screen Name")]'
)
# Wait for element to be displayed (timeout 30 seconds)
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from appium.webdriver.common.appiumby import AppiumBy
WebDriverWait(self.driver, 30).until(
EC.visibility_of(element)
)
except Exception as e:
self.dismiss_system_ui_error()
# Try again after dismissing the error (if not, then fail)
element = self.driver.find_element(
by='xpath',
value='//*[contains(@text, "Screen Name")]'
)
WebDriverWait(self.driver, 30).until(
EC.visibility_of(element)
)
def setup_method(self):
"""Launch app before each test"""
self.launch_app()
def test_test1(self):
"""
Now since your session is authenticated you can keep making 'mobile: axeScan' calls.
The scans will be uploaded to the dashboard and also grouped in Dev Hub.
"""
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
element = self.driver.find_element(
by='xpath',
value='//*[contains(@text, "Screen Name")]'
)
WebDriverWait(self.driver, 10).until(
EC.visibility_of(element)
)
print(element.text)
appium_scan_result = self.driver.execute_script('mobile: axeScan', self.scan_settings)
results = appium_scan_result['axeRuleResults']
print(f'debug: Total results: {len(results)}')
def test_test2(self):
"""Second test with interaction"""
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
element = self.driver.find_element(
by='xpath',
value='//*[contains(@text, "Screen Name")]'
)
WebDriverWait(self.driver, 10).until(
EC.visibility_of(element)
)
element.click()
self.driver.find_element(
by='xpath',
value='//*[contains(@text, "announced by a screen")]'
)
appium_scan_result = self.driver.execute_script('mobile: axeScan', self.scan_settings)
results = appium_scan_result['axeRuleResults']
print(f'debug: Total results: {len(results)}')Full Example with XCUITest
import pytest
from appium import webdriver
from appium.options.ios import XCUITestOptions
class TestIOSMapsAxe:
driver = None
tags = ['appium', 'qa', 'ios']
# ignoreRules is passed directly into each 'mobile: axeScan' call
scan_settings = {
# tags and ignoreRules are deprecated and will be removed once we fully transition to Axe Developer Hub
'ignoreRules': ['ScreenOrientation'],
'tags': tags
}
api_key = '<DEQUE_APIKEY>'
project_id = '<DEQUE_PROJECT_ID>'
bundle_id = 'com.apple.Maps'
@classmethod
def setup_class(cls):
"""
It is very important to make a call to 'mobile: axeStartSession' inside setup_class
to set up your session for posting to Axe Developer Hub
"""
options = XCUITestOptions()
options.platform_name = 'iOS'
options.automation_name = 'AxeXCUITest'
options.udid = '<YOUR_DEVICE_OR_SIMULATOR_UDID>'
options.bundle_id = cls.bundle_id
options.set_capability('wdaLaunchTimeout', 960000) # 16 minutes
# NOT specifying platformVersion - let it auto-detect
cls.driver = webdriver.Remote(
command_executor='http://127.0.0.1:4723',
options=options
)
# 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 `axe_settings` object still accepts `apiKey` with `axeAccountURL`, but is not required
# if you are going to make one time setup_class call to 'mobile: axeStartSession'
axe_settings = {
'apiKey': cls.api_key,
'projectId': cls.project_id,
'axeAccountURL': 'https://mobile-qa.dequelabs.com',
'axeUploadResults': True # default - set to False to keep results local only
}
cls.driver.execute_script('mobile: axeStartSession', axe_settings)
@classmethod
def teardown_class(cls):
if cls.driver:
# Generate HTML reports before quitting the driver
cls.driver.execute_script('mobile: axeGenerateHtmlReportAndSummary', {})
# Alternative: Generate HTML reports to a custom path
# cls.driver.execute_script('mobile: axeGenerateHtmlReportAndSummary', {
# 'outputPath': '/Users/me/MyReports'
# })
cls.driver.implicitly_wait(1)
cls.driver.quit()
def launch_app(self):
"""Launch the Maps app"""
self.driver.activate_app(self.bundle_id)
def setup_method(self):
"""Launch app before each test"""
self.launch_app()
def test_test1_scan_main_screen(self):
"""
Now since your session is authenticated you can keep making 'mobile: axeScan' calls.
The scans will be uploaded to the dashboard and also grouped in Dev Hub.
"""
appium_scan_result = self.driver.execute_script('mobile: axeScan', self.scan_settings)
results = appium_scan_result['axeRuleResults']
print(f'debug: Total results: {len(results)}')
def test_test2_click_search_and_scan(self):
"""Second test - click search and scan"""
appium_scan_result = self.driver.execute_script('mobile: axeScan', self.scan_settings)
results = appium_scan_result['axeRuleResults']
print(f'debug: Total results: {len(results)}')
