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)