Source code for bok_choy.a11y.axs_ruleset
"""
Interface for using the google accessibility ruleset.
See: https://github.com/GoogleChrome/accessibility-developer-tools
"""
import logging
import os
from collections import namedtuple
from textwrap import dedent
from .a11y_audit import A11yAudit, A11yAuditConfig, AccessibilityError
log = logging.getLogger(__name__)
AuditResults = namedtuple('AuditResults', 'errors, warnings')
CUR_DIR = os.path.dirname(os.path.abspath(__file__))
[docs]class AxsAuditConfig(A11yAuditConfig):
"""
The `AxsAuditConfig` object defines the options available when
running an `AxsAudit`.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.rules_file = None
self.scope, self.rules_to_run, self.rules_to_ignore = None, None, None
self.rules_file = os.path.join(
os.path.split(CUR_DIR)[0],
'vendor/google/axs_testing.js'
)
self.set_rules({})
self.set_scope()
[docs] def set_rules(self, rules):
"""
Sets the rules to be run or ignored for the audit.
Args:
rules: a dictionary of the format `{"ignore": [], "apply": []}`.
See https://github.com/GoogleChrome/accessibility-developer-tools/tree/master/src/audits
Passing `{"apply": []}` or `{}` means to check for all available rules.
Passing `{"apply": None}` means that no audit should be done for this page.
Passing `{"ignore": []}` means to run all otherwise enabled rules.
Any rules in the "ignore" list will be ignored even if they were also
specified in the "apply".
Examples:
To check only `badAriaAttributeValue`::
page.a11y_audit.config.set_rules({
"apply": ['badAriaAttributeValue']
})
To check all rules except `badAriaAttributeValue`::
page.a11y_audit.config.set_rules({
"ignore": ['badAriaAttributeValue'],
})
"""
self.rules_to_ignore = rules.get("ignore", [])
self.rules_to_run = rules.get("apply", [])
[docs] def set_scope(self, include=None, exclude=None):
"""
Sets `scope`, the "start point" for the audit.
Args:
include: A list of css selectors specifying the elements that
contain the portion of the page that should be audited.
Defaults to auditing the entire document.
exclude: This arg is not implemented in this ruleset.
Examples:
To check only the `div` with id `foo`::
page.a11y_audit.config.set_scope(["div#foo"])
To reset the scope to check the whole document::
page.a11y_audit.config.set_scope()
"""
if include:
self.scope = f"document.querySelector(\"{', '.join(include)}\")"
else:
self.scope = "null"
if exclude is not None:
raise NotImplementedError(
"The argument `exclude` has not been implemented in "
"AxsAuditConfig.set_scope method."
)
[docs] def customize_ruleset(self, custom_ruleset_file=None):
"""
This has not been implemented for the google_axs ruleset.
Raises:
`NotImplementedError`
"""
raise NotImplementedError(
"The ability to customize the ruleset has not been implemented."
)
[docs]class AxsAudit(A11yAudit):
"""
Use Google's Accessibility Developer Tools to audit a
page for accessibility problems.
See https://github.com/GoogleChrome/accessibility-developer-tools
"""
@property
def default_config(self):
"""
Returns an instance of AxsAuditConfig.
"""
return AxsAuditConfig()
@staticmethod
def _check_rules(browser, rules_js, config):
"""
Check the page for violations of the configured rules. By default,
all rules in the ruleset will be checked.
Args:
browser: a browser instance.
rules_js: the ruleset JavaScript as a string.
config: an AxsAuditConfig instance.
Returns:
A namedtuple with 'errors' and 'warnings' fields whose values are
the errors and warnings returned from the audit.
None if config has rules_to_run set to None.
__Caution__: You probably don't really want to call this method
directly! It will be used by `A11yAudit.do_audit` if using this ruleset.
"""
if config.rules_to_run is None:
msg = 'No accessibility rules were specified to check.'
log.warning(msg)
return None
# This line will only be included in the script if rules to check on
# this page are specified, as the default behavior of the js is to
# run all rules.
rules = config.rules_to_run
if rules:
rules_config = f"auditConfig.auditRulesToRun = {rules};"
else:
rules_config = ""
ignored_rules = config.rules_to_ignore
if ignored_rules:
rules_config += f"\nauditConfig.auditRulesToIgnore = {ignored_rules};"
script = dedent(f"""
{rules_js}
var auditConfig = new axs.AuditConfiguration();
{rules_config}
auditConfig.scope = {config.scope};
var run_results = axs.Audit.run(auditConfig);
var audit_results = axs.Audit.auditResults(run_results)
return audit_results;
""")
result = browser.execute_script(script)
# audit_results is report of accessibility errors for that session
audit_results = AuditResults(
errors=result.get('errors_'),
warnings=result.get('warnings_')
)
return audit_results
[docs] @staticmethod
def get_errors(audit_results):
"""
Args:
audit_results: results of `AxsAudit.do_audit()`.
Returns: a list of errors.
"""
errors = []
if audit_results:
if audit_results.errors:
errors.extend(audit_results.errors)
return errors
[docs] @staticmethod
def report_errors(audit, url):
"""
Args:
audit: results of `AxsAudit.do_audit()`.
url: the url of the page being audited.
Raises: `AccessibilityError`
"""
errors = AxsAudit.get_errors(audit)
if errors:
msg = f"URL '{url}' has {len(errors)} errors:\n{', '.join(errors)}"
raise AccessibilityError(msg)