dequelabs / axe-webdriverjs

Provides a chainable axe API for Selenium's WebDriverJS and automatically injects into all frames.
Mozilla Public License 2.0
130 stars 46 forks source link

Inject aXe-core via direct evaluation if script tag fails (Chrome and Firefox CSP workaround) #29

Closed toolness closed 7 years ago

toolness commented 7 years ago

So I did some tinkering on the problem I described in #28 and discovered that what Chrome Webdriver really doesn't like is injecting inline <script> tags on CSP-enabled pages that prohibit such things--but webdriver's own executeScript/executeAsyncScript work just fine. Chrome allows calling eval() in such contexts even if the page's CSP prohibits it, but Firefox doesn't.

So this implements some fallback logic whereby, at the time that axe is needed, if it doesn't exist due to CSP, we just evaluate the aXe source code inline.

(Note: I originally tried modifying the script in inject.js to just evaluate aXe's source code directly rather than wrapping it in a <script>, but this only worked on Chrome--it broke all tests on Firefox. You can see this attempt in aa550adc5617ef327ff585f95ccfe4459fbc026e.)

This PR does currently have some limitations, though:

Manual testing

Run this script from the root of the repo:

var AxeBuilder = require('./lib');
var WebDriver = require('selenium-webdriver');

var driver = new WebDriver.Builder()
  .forBrowser('firefox')
  .build();

driver
  .get('https://github.com/dequelabs/axe-webdriverjs')
  .then(function () {
    AxeBuilder(driver)
      .analyze(function (results) {
        console.log(results);
      });
  });

Due to the fact that different browsers behave differently when a site is CSP'd, it might make sense to actually run these CSP-related tests on multiple browsers. So far I've tested on Firefox and Chrome and they seem to work with this fallback approach.

CLAassistant commented 7 years ago

CLA assistant check
All committers have signed the CLA.

marcysutton commented 7 years ago

Sadly this doesn't solve it for me–I get the same problem on sites with a restrictive CSP. @dylanb thought maybe we could "disable the content security policy in Webdriver". I'll have to look into that some more to see if it's something we can do.

dylanb commented 7 years ago

I was able to get this to work properly by changing this line

https://github.com/dequelabs/axe-webdriverjs/blob/master/lib/inject.js#L45

to

var script = axeSource + ';axe.configure({branding:{application:"webdriverjs"}});';

Which should also work for iframes - @toolness can you test this change?

marcysutton commented 7 years ago

@dylanb I assume you mean changing the entirety of lines 45-51 in inject.js?

var script = '(function () {' +
        'if (typeof axe === "object" && axe.version) { return; }' +
        'var s = document.createElement("script");' +
        // stringify so that quotes are properly escaped
        's.innerHTML = ' + JSON.stringify(axeSource + ';' + 'axe.configure({branding:{application:"webdriverjs"}});') + ';' +
        'document.body.appendChild(s);' +
        '}());';

With this code, eliminating the script tag creation and appendChild call?

var script = axeSource + ';axe.configure({branding:{application:"webdriverjs"}});';

Edit: It does work after a while if you base it on master instead of this branch, but our tests fail. I'll keep playing with it.

marcysutton commented 7 years ago

Limited CSP should be addressed with #34, so I'm going to close this PR. Thanks for your contribution!