Custom Rules
The custom rules feature lets you create new rules to test against and change key behaviors within existing rules.
Custom rules are supported in both XML and Compose Layouts. The implementation of a custom rule itself will vary from XML to Compose, but both will extend the AxeRuleViewHierarchy
class. The below example shows the XML implementation.
Let's create a custom rule that checks for the name property of a view. The result criteria will be as follows:
- Fail: the name is empty.
- Success: the name has some length.
Creating the Rule
- Set up a new class object that inherits from
AxeRuleViewHierarchy
.
You can set up the rule configuration within the initializer to specify the impact, and what standard you're testing against, and add a summary.
class CustomVisibleTextRule : AxeRuleViewHierarchy(
AxeStandard.WCAG_21,
AxeImpact.MINOR.value,
"Ensure AT and non-AT users have the same experience of understanding labels",
false
) {
}
- You'll need to collect the view's properties to test. For our example, we need to access the name property.
override fun collectProps(
axeView: AxeView,
axeProps: AxeProps
) {
super.collectProps(axeView, axeProps)
axeProps[AxeProps.Name.VISIBLE_TEXT] = axeView.text
}
In the above Compose example, you may see error messages on axeProps[ComposeProps.ROLE]. To resolve the error, add the following import statements:
import com.deque.mobile.devtools.wrappers.get
import com.deque.mobile.devtools.wrappers.set
We've exposed a helper function, getRole()
, to grab a role from the Compose view.
- Determine if the rule applies to the view by ensuring the visible text is not null.
override fun isApplicable(axeProps: AxeProps): Boolean {
val visibleText = axeProps[AxeProps.Name.VISIBLE_TEXT] as String?
return visibleText != null && super.isApplicable(axeProps)
}
- Lastly, we'll specify in the
runRule
function what we're testing against to determine whether the rule has succeeded or failed.
override fun runRule(axeProps: AxeProps): RunRuleResult {
val runRuleResult = super.runRule(axeProps)
if (runRuleResult.status.isNotEmpty()) {
return runRuleResult
}
val visibleText = axeProps[AxeProps.Name.VISIBLE_TEXT] as String?
visibleText?.let {
return when (it.isNotEmpty()) {
true -> RunRuleResult(AxeStatus.PASS, "All visible texts are present")
false -> RunRuleResult(AxeStatus.FAIL, "All visible texts are not present")
}
}
return RunRuleResult(AxeStatus.INCOMPLETE, "Need review")
}
Add to axe DevTools
Once the custom rule is ready, add it to the rule list of the initialized axe DevTools object, AxeDevTools
for XML or AxeDevToolsCompose
for Compose. Set configurations before any tests run.
val axe = AxeDevTools() //or AxeDevToolsCompose()
init {
axe.addCustomRule(CustomVisibleTextRule::class.java)
}
Complete Custom Rule Example
class CustomVisibleTextRule: AxeRuleViewHierarchy(
AxeStandard.WCAG_20,
AxeImpact.MINOR.value,
"Ensure AT and non-AT users have the same experience of understanding labels",
false
) {
override fun collectProps(
axeView: AxeView,
axeProps: AxeProps
) {
super.collectProps(axeView, axeProps)
axeProps[AxeProps.Name.VISIBLE_TEXT] = axeView.text
}
override fun isApplicable(axeProps: AxeProps): Boolean {
val visibleText = axeProps[AxeProps.Name.VISIBLE_TEXT] as String?
return visibleText != null && super.isApplicable(axeProps)
}
override fun runRule(axeProps: AxeProps): RunRuleResult {
val runRuleResult = super.runRule(axeProps)
if (runRuleResult.status.isNotEmpty()) {
return runRuleResult
}
val visibleText = axeProps[AxeProps.Name.VISIBLE_TEXT] as String?
visibleText?.let {
return when (it.isNotEmpty()) {
true -> RunRuleResult(AxeStatus.PASS, "All visible texts are present")
false -> RunRuleResult(AxeStatus.FAIL, "All visible texts are not present")
}
}
return RunRuleResult(AxeStatus.INCOMPLETE, "Need review")
}
}