Continued from Part 1

While I began writing my package as a direct, one-to-one translation of the Java package, my package turned out to look very different.

Java and Python are, after all, very different languages. I found some methods to be unnecessary in python, and chose to not use the same design pattern as axe-selenium-java.

The Java package, written by Deque Labs, uses the Builder design pattern.

I wrote the methods used by the Builder class to be methods of the Axe class instead.

getContents() and inject()

As I mentioned in my previous post, I decided that get_contents() was an unnecessary method for the python package.

It appears that reading JavaScript from a file requires parsing in Java, as well as a try/catch block to throw a runtime exception if an error occurs.

The next method I implemented was the inject() method, to inject the axe-core script into the page.

I was able to translate the getContents() and inject() methods into a single line of python code:
def inject(self, selenium, script_url):

Java evidently requires a special class called JavascriptExecutor in order to run the JavaScript against the webdriver instance:
public static void inject(final WebDriver driver, final URL scriptUrl) {
final String script = getContents(scriptUrl);
final ArrayList parents = new ArrayList();

injectIntoFrames(driver, script, parents);

JavascriptExecutor js = (JavascriptExecutor) driver;

Recursively Inject into Frames

The next method in the axe-selenium-java class is injectIntoFrames(), which is a recursive method.

injectIntoFrames() injects the script into the top-level document, as well as any iframes or nested iframes.

Recursive algorithms are a little more difficult to both write and comprehend, and deciphering someone else's recursive function can be very time-consuming.

As such, I will break down this method in another post, Analyzing and Understanding Recursive Functions.

My mentor and I decided that, at least for the time being, we will not be implementing this method, and instead focus on developing an MVP version of this package.

Reporting the Results

The next method in the Axe class is the report() method, which creates human-friendly output of the test results.

This result will appear in the pytest traceback, as well in the HTML report, when using pytest-html:

AssertionError: Found 1 accessibility violation:
1) id attribute value must be unique:

  #masthead > [role="navigation"] > a:nth-child(1) > img[src$="mozillians-logo.png"]

  Fix any of the following:
    Document has multiple elements with the same id attribute: mozillians-logo

    Impact: moderate

This is a very basic results report, and definitely a rough draft version. One of the stretch goals of my internship is to implement reporting that creates an HTML report (that doesn't scorch the eyeballs).

Though I am not a designer, I do place a high importance on aesthetics, and I'm looking forward to designing more sophisticated reports.

get_rules() and write_results()

I also decided to implement two additional methods in my aXe integration package.

The first is the get_rules() method. This function simply retrieves a list of accessibility rules from aXe, using axe.getRules().

As of now, this method does not accept parameters, but I plan to modify it to mirror the API method.

The getRules() method accepts an array of tag names, e.g. ['wcag2a', 'section508'], and will return a set of rules that includes those tags.
def get_rules(self, selenium):
"""Return array of accessibility rules."""
response = selenium.execute_script('return axe.getRules();')
return response

The second method I added is the write_results() method, which writes the results to a file.

This method accepts two parameters: a filename, and a JSON object to be written to the file.

To make the data more readable to the human eye, I used the json python library to format or "pretty-print" the data.
def write_results(self, name, output):
file = open(name, 'w+')
file.write(json.dumps(output, indent=4))

Below is my test function for the write_results() method.

It probably isn't preferable for long URLs, but it does create meaningful and timestamped file names.
def test_write_results(base_url, selenium, axe):
"""Write JSON results to file."""
# get string of current python version
version = 'v' + str(sys.version_info[0]) + '_' + \
str(sys.version_info[1]) + '_' + str(sys.version_info[2])
# strip protocol and dots
filename = re.sub('(http:\/\/|https:\/\/|\.php|\.asp|\.html)', '', base_url)
# replace slashes with underscores
filename = re.sub('(\/|\.)', '_', filename)
# create filename "examplecom-datetime-python-version.json"
filename += '-' + time.strftime('%m-%d-%y-%X') + '-' + version + '.json'

data = axe.execute(selenium)
axe.write_results(filename, data)
# check that file exists and is not empty
assert os.path.exists(filename) and os.path.getsize(filename) > 0, \
'Output file not found.'

An example output filename from using Python 2.7.10:


Moving forward, my focus is to clean up the package to maximize stability, usability, and to provide well-written documentation.