Skip to content
This repository has been archived by the owner on Jan 19, 2024. It is now read-only.

Commit

Permalink
allow A11yAudits to run on browsers other than phantomjs
Browse files Browse the repository at this point in the history
  • Loading branch information
Christine Lytwynec committed Oct 5, 2015
1 parent 53eee41 commit 07a8ce2
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 311 deletions.
100 changes: 15 additions & 85 deletions bok_choy/a11y/a11y_audit.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
"""
Interface for running accessibility audits on a PageObject.
"""
import json
import os
import requests

from abc import abstractmethod, abstractproperty, ABCMeta
from textwrap import dedent


class AccessibilityError(Exception):
Expand Down Expand Up @@ -95,7 +91,7 @@ def __init__(self, browser, url, config=None, *args, **kwargs):
Sets ruleset to be used.
Args:
browser: A phantomjs browser instance
browser: A browser instance
url: URL of the page to test
config: (optional) A11yAuditConfig or subclass of A11yAuditConfig
"""
Expand All @@ -104,37 +100,10 @@ def __init__(self, browser, url, config=None, *args, **kwargs):
self.browser = browser
self.config = config or self.default_config

def _phantomjs_setup(self):
"""
Verifies that phantomjs is being used and returns the ghostdriver URL
and session information.
Returns: (ghostdriver_url, sessions) where sessions is a list of
session ids.
Raises: `NotImplementedError` if not using phantomjs.
def _get_rules_js(self):
"""
if self.browser.name != 'phantomjs':
msg = (
'Accessibility auditing is only supported with PhantomJS as'
' the browser.'
)
raise NotImplementedError(msg)

# The ghostdriver URL will be something like this:
# 'http://localhost:33225/wd/hub'
ghostdriver_url = self.browser.service.service_url

# Get the session_id from ghostdriver so that we can inject JS into
# the page.
resp = requests.get('{}/sessions'.format(ghostdriver_url))
sessions = resp.json()

return ghostdriver_url, sessions

def _verify_rules_file_exists(self):
"""
Checks that the rules file for the enabled ruleset exists.
Checks that the rules file for the enabled ruleset exists
and returns its contents as string.
Raises: `RuntimeError` if the file isn't found.
"""
Expand All @@ -143,62 +112,23 @@ def _verify_rules_file_exists(self):
self.config.rules_file)
raise RuntimeError(msg)

else:
with open(self.config.rules_file, "r") as rules_file:
return rules_file.read()

def do_audit(self):
"""
Audit the page for accessibility problems using the enabled ruleset.
Since this needs to inject JavaScript into the browser page, the only
known way to do this is to use PhantomJS as your browser.
Raises:
* NotImplementedError if you are not using PhantomJS
* RuntimeError if there was a problem with the injected JS or
getting the report
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.
`None` if no results are returned.
"""
ghostdriver_url, sessions = self._phantomjs_setup()
self._verify_rules_file_exists()

# report is the list that is returned, with one item for each
# browser session.
report = []
for session in sessions.get('value'):
session_id = session.get('id')

# First make sure you can successfully inject the JS on the page
script = dedent("""
return this.injectJs("{file}");
""".format(file=self.config.rules_file))

payload = {"script": script, "args": []}
resp = requests.post('{}/session/{}/phantom/execute'.format(
ghostdriver_url, session_id), data=json.dumps(payload))

result = resp.json().get('value')

if result is False:
msg = '{msg} \nScript:{script} \nResponse:{response}'.format(
msg=(
'Failure injecting the Accessibility Audit JS '
'on the page.'
),
script=script,
response=resp.text)
raise RuntimeError(msg)

audit_results = self._check_rules(
ghostdriver_url, session_id, self.config)

if audit_results:
report.append(audit_results)

return report or None
rules_js = self._get_rules_js()
audit_results = self._check_rules(
self.browser, rules_js, self.config)
return audit_results

def check_for_accessibility_errors(self):
"""
Expand Down Expand Up @@ -228,14 +158,14 @@ def default_config(self):

@staticmethod
@abstractmethod
def _check_rules(ghostdriver_url, session_id, config):
def _check_rules(browser, rules_js, config):
"""
Run an accessibility audit on the page using the implemented ruleset.
Args:
ghostdriver_url: url of ghostdriver.
session_id: a session id to test.
browser: a browser instance.
rules_js: the ruleset JavaScript as a string.
config: an AxsAuditConfig instance.
Returns:
Expand Down
69 changes: 28 additions & 41 deletions bok_choy/a11y/axe_core_ruleset.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
"""
import json
import os
import requests

from textwrap import dedent, fill

Expand Down Expand Up @@ -154,9 +153,6 @@ class AxeCoreAudit(A11yAudit):
"""
Use Deque Labs' axe-core engine to audit a page for accessibility issues.
Since this needs to inject JavaScript into the browser page, the only
known way to do this is to use PhantomJS as your browser.
Related documentation:
https://github.com/dequelabs/axe-core/blob/master/doc/API.md
Expand All @@ -165,18 +161,18 @@ class AxeCoreAudit(A11yAudit):
@property
def default_config(self):
"""
Returns an instance of a subclass of AxeCoreAuditConfig.
Returns an instance of AxeCoreAuditConfig.
"""
return AxeCoreAuditConfig()

@staticmethod
def _check_rules(ghostdriver_url, session_id, config):
def _check_rules(browser, rules_js, config):
"""
Run an accessibility audit on the page using the axe-core ruleset.
Args:
ghostdriver_url: url of ghostdriver.
session_id: a session id to test.
browser: a browser instance.
rules_js: the ruleset JavaScript as a string.
config: an AxsAuditConfig instance.
Returns:
Expand All @@ -189,31 +185,25 @@ def _check_rules(ghostdriver_url, session_id, config):
__Caution__: You probably don't really want to call this method
directly! It will be used by `AxeCoreAudit.do_audit`.
"""
script_url = '{}/session/{}/phantom/execute'.format(
ghostdriver_url, session_id)

audit_run_script = dedent("""
return this.evaluate(function(){{
var updatedResults = function(r) {{
window.a11yAuditResults = JSON.stringify(r);
window.console.log(window.a11yAuditResults);
}}
axe.a11yCheck({context}, {options}, updatedResults);
}});
""".format(context=config.context, options=config.rules))
{rules_js}
var updatedResults = function(r) {{
window.a11yAuditResults = JSON.stringify(r);
window.console.log(window.a11yAuditResults);
}}
axe.a11yCheck({context}, {options}, updatedResults);
""").format(
rules_js=rules_js,
context=config.context,
options=config.rules
)

audit_results_script = dedent("""
return this.evaluate(function(){{
window.console.log(window.a11yAuditResults);
return window.a11yAuditResults;
}});
window.console.log(window.a11yAuditResults);
return window.a11yAuditResults;
""")

requests.post(
script_url,
data=json.dumps({"script": audit_run_script, "args": []}),
)
browser.execute_script(audit_run_script)

def audit_results_check_func():
"""
Expand All @@ -224,13 +214,11 @@ def audit_results_check_func():
(True, results) if the results are available.
(False, None) if the results aren't available.
"""
resp = requests.post(
script_url,
data=json.dumps({"script": audit_results_script, "args": []})
)

unicode_results = browser.execute_script(audit_results_script)

try:
results = json.loads(resp.json().get('value'))
results = json.loads(unicode_results)
except (TypeError, ValueError):
results = None

Expand All @@ -251,23 +239,22 @@ def audit_results_check_func():
return audit_results

@staticmethod
def get_errors(audit):
def get_errors(audit_results):
"""
Args:
audit: results of `AxeCoreAudit.do_audit()`.
audit_results: results of `AxeCoreAudit.do_audit()`.
Returns:
A dictionary with keys "errors" and "total".
"""
errors = {"errors": [], "total": 0}
for session_result in audit:
if session_result:
errors["errors"].extend(session_result)
for i in session_result:
for _node in i["nodes"]:
errors["total"] += 1
if audit_results:
errors["errors"].extend(audit_results)
for i in audit_results:
for _node in i["nodes"]:
errors["total"] += 1
return errors

@staticmethod
Expand Down
Loading

0 comments on commit 07a8ce2

Please sign in to comment.