Source code for bok_choy.a11y.a11y_audit

"""
Interface for running accessibility audits on a PageObject.
"""

import os
from abc import abstractmethod, ABCMeta


[docs]class AccessibilityError(Exception): """ The page violates one or more accessibility rules. """
[docs]class A11yAuditConfigError(Exception): """ An error in A11yAuditConfig. """
[docs]class A11yAuditConfig(metaclass=ABCMeta): """ The `A11yAuditConfig` object defines the options available in an accessibility ruleset. """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.rules_file = None
[docs] def set_rules_file(self, path=None): """ Sets `self.rules_file` to the passed file. Args: A filepath where the JavaScript for the ruleset can be found. This is intended to be used in the case of using an extended or modified version of the ruleset. The interface and response format are expected to be unmodified. """ if path: self.rules_file = os.path.abspath(path)
[docs] @abstractmethod def set_rules(self, rules): """ Overrides the default rules to be run. Raises: `NotImplementedError` if this isn't overwritten in the ruleset specific implementation. """ raise NotImplementedError( "The ability to specify rules has not been implemented." )
[docs] @abstractmethod def set_scope(self, include=None, exclude=None): """ Overrides the default scope (part of the DOM) to inspect. Raises: `NotImplementedError` if this isn't overwritten in the ruleset specific implementation. """ raise NotImplementedError( "The ability to specify scope has not been implemented." )
[docs] @abstractmethod def customize_ruleset(self, custom_ruleset_file=None): """ Allows customization of the ruleset. (e.g. adding custom rules, extending the implementation of an existing rule.) Raises: `NotImplementedError` if this isn't overwritten in the ruleset specific implementation. """ raise NotImplementedError( "The ability to customize the ruleset has not been implemented." )
[docs]class A11yAudit(metaclass=ABCMeta): """ Allows auditing of a page for accessibility issues. The ruleset to use can be specified by the environment variable `BOKCHOY_A11Y_RULESET`. Currently, there are two ruleset implemented: `axe_core`: * Ruleset class: AxeCoreAudit * Ruleset config: AxeCoreAuditConfig * This is default ruleset. `google_axs`: * Ruleset class: AxsAudit * Ruleset config: AxsAuditConfig """ def __init__(self, browser, url, config=None, *args, **kwargs): # pylint: disable=keyword-arg-before-vararg """ Sets ruleset to be used. Args: browser: A browser instance url: URL of the page to test config: (optional) A11yAuditConfig or subclass of A11yAuditConfig """ super().__init__(*args, **kwargs) self.url = url self.browser = browser self.config = config or self.default_config def _get_rules_js(self): """ Checks that the rules file for the enabled ruleset exists and returns its contents as string. Raises: `RuntimeError` if the file isn't found. """ if not os.path.isfile(self.config.rules_file): msg = f'Could not find the accessibility tools JS file: {self.config.rules_file}' raise RuntimeError(msg) with open(self.config.rules_file, "r", encoding="utf-8") as rules_file: return rules_file.read()
[docs] def do_audit(self): """ Audit the page for accessibility problems using the enabled ruleset. Returns: A list (one for each browser session) of results returned from the audit. See documentation of `_check_rules` in the enabled ruleset for the format of each result. """ rules_js = self._get_rules_js() audit_results = self._check_rules( self.browser, rules_js, self.config) return audit_results
[docs] def check_for_accessibility_errors(self): """ Run an accessibility audit, parse the results, and raise a single exception if there are violations. Note that an exception is only raised on errors, not on warnings. Returns: None Raises: AccessibilityError """ audit_results = self.do_audit() if audit_results: self.report_errors(audit_results, self.url)
@property @abstractmethod def default_config(self): """ Return an instance of a subclass of A11yAuditConfig. """ raise NotImplementedError("default_config has not been implemented") @staticmethod @abstractmethod def _check_rules(browser, rules_js, config): """ Run an accessibility audit on the page using the implemented ruleset. Args: browser: a browser instance. rules_js: the ruleset JavaScript as a string. config: an AxsAuditConfig instance. Returns: A list of violations. Raises: `NotImplementedError` if this isn't overwritten in the ruleset specific implementation. """ raise NotImplementedError("_check_rules has not been implemented")
[docs] @staticmethod @abstractmethod def report_errors(audit, url): """ Args: audit: results of an accessibility audit. url: the url of the page being audited. Raises: `AccessibilityError` if errors are found in the audit. `NotImplementedError` if this isn't overwritten in the ruleset specific implementation. """ raise NotImplementedError("report_errors has not been implemented")