Custom Rules

Link to Custom Rules copied to clipboard

The custom rules feature lets you create new rules to test against and change key behaviors within existing rules.

note

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

  1. 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
) {
    
}
  1. 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
}
note

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.

  1. 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)
}
  1. 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")
    }
}