Skip to content

Commit

Permalink
feat(core): run HTML checks in headless Chrome
Browse files Browse the repository at this point in the history
Replaces NightmareJS (driver for Electron) by Puppeteer (driver for headless Chrome).
Puppeteer is a Node API for headless Chrome, developped and officially supported
by the Chrome DevTools team.

See also: https://github.com/GoogleChrome/puppeteer

Closes #23
  • Loading branch information
rdeltour committed Oct 27, 2017
1 parent 32cdacf commit b150b83
Show file tree
Hide file tree
Showing 7 changed files with 227 additions and 350 deletions.
12 changes: 6 additions & 6 deletions .snyk
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ ignore: {}
patch:
'npm:debug:20170905':
- extract-zip > debug:
patched: '2017-10-18T22:22:29.402Z'
- nightmare > electron > extract-zip > debug:
patched: '2017-10-18T22:22:29.402Z'
patched: '2017-10-27T13:22:41.480Z'
- puppeteer > extract-zip > debug:
patched: '2017-10-27T13:22:41.480Z'
'npm:ms:20170412':
- extract-zip > debug > ms:
patched: '2017-10-18T22:22:29.402Z'
- nightmare > electron > extract-zip > debug > ms:
patched: '2017-10-18T22:22:29.402Z'
patched: '2017-10-27T13:22:41.480Z'
- puppeteer > extract-zip > debug > ms:
patched: '2017-10-27T13:22:41.480Z'
5 changes: 1 addition & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@
"marko": "^4.4.26",
"meow": "^3.7.0",
"multer": "^1.3.0",
"nightmare": "^2.10.0",
"path": "^0.12.7",
"puppeteer": "^0.12.0",
"shortid": "^2.2.8",
"snyk": "^1.42.7",
"tmp": "^0.0.33",
Expand All @@ -85,9 +85,6 @@
"uglify-js": "^3.0.8",
"watch": "^1.0.2"
},
"resolutions": {
"nightmare/electron": "1.7.8"
},
"scripts": {
"start": "node src/cli/cli.js",
"clean": "rimraf dist && mkdir dist",
Expand Down
147 changes: 147 additions & 0 deletions src/checker/checker-chromium.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
'use strict';

const fs = require('fs');
const path = require('path');
const puppeteer = require('puppeteer');
const axe2ace = require('../report/axe2ace.js');
const winston = require('winston');

const PATH_TO_AXE = path.join(path.dirname(require.resolve('axe-core')), 'axe.min.js');
if (!fs.existsSync(PATH_TO_AXE)) {
winston.verbose(PATH_TO_AXE);
throw new Error('Can’t find aXe');
}

const PATH_TO_H5O = path.join(path.dirname(require.resolve('h5o')), 'dist/outliner.min.js');
if (!fs.existsSync(PATH_TO_H5O)) {
winston.verbose(PATH_TO_H5O);
throw new Error('Can’t find h5o');
}

const PATH_TO_AXE_PATCH_GETSELECTOR = path.join(__dirname, '../scripts/axe-patch-getselector.js');
if (!fs.existsSync(PATH_TO_AXE_PATCH_GETSELECTOR)) {
winston.verbose(PATH_TO_AXE_PATCH_GETSELECTOR);
throw new Error('Can’t find axe-patch-getselector script');
}

const PATH_TO_AXE_PATCH_ARIALOOKUPTABLE = path.join(__dirname, '../scripts/axe-patch-arialookuptable.js');
if (!fs.existsSync(PATH_TO_AXE_PATCH_ARIALOOKUPTABLE)) {
winston.verbose(PATH_TO_AXE_PATCH_ARIALOOKUPTABLE);
throw new Error('Can’t find axe-patch-arialookuptable script');
}

const PATH_TO_ACE_AXE = path.join(__dirname, '../scripts/ace-axe.js');
if (!fs.existsSync(PATH_TO_ACE_AXE)) {
winston.verbose(PATH_TO_ACE_AXE);
throw new Error('Can’t find ace-axe script');
}

const PATH_TO_ACE_EXTRACTION = path.join(__dirname, '../scripts/ace-extraction.js');
if (!fs.existsSync(PATH_TO_ACE_EXTRACTION)) {
winston.verbose(PATH_TO_ACE_EXTRACTION);
throw new Error('Can’t find ace-extraction script');
}

async function checkSingle(spineItem, epub, browser) {
winston.info(`- ${spineItem.relpath}`);
try {
const page = await browser.newPage();
await page.goto(spineItem.url);
// page.on('console', msg => console.log(msg.text));

// BEGIN HACK
// Used to differentiate original `script` elements from the one
// added by Puppeteer.
// FIXME remove this hack when GoogleChrome/puppeteer#1179 is fixed
await page.$$eval('script', (scripts) => {
scripts.forEach(script => script.setAttribute('data-ace-orig', ''));
});
// END HACK
await page.addScriptTag({ path: PATH_TO_AXE });
await page.addScriptTag({ path: PATH_TO_AXE_PATCH_GETSELECTOR });
await page.addScriptTag({ path: PATH_TO_AXE_PATCH_ARIALOOKUPTABLE });
await page.addScriptTag({ path: PATH_TO_H5O });
await page.addScriptTag({ path: PATH_TO_ACE_AXE });
await page.addScriptTag({ path: PATH_TO_ACE_EXTRACTION });
// BEGIN HACK
// FIXME remove this hack when GoogleChrome/puppeteer#1179 is fixed
await page.$$eval('script', (scripts) => {
scripts.forEach(script => {
if (script.hasAttribute('data-ace-orig')) {
script.removeAttribute('data-ace-orig');
} else {
script.setAttribute('data-ace', '');
}
});
});
// END HACK


const results = await page.evaluate(() => new Promise((resolve, reject) => {
/* eslint-disable */
window.daisy.ace.run((err, res) => {
if (err) {
return reject(err);
}
return resolve(res);
});
/* eslint-enable */
}));
await page.close();

// Post-process results
results.assertions = (results.axe != null) ? axe2ace.axe2ace(spineItem, results.axe) : [];
delete results.axe;
winston.info(`- ${
(results.assertions == null)
? 'No'
: results.assertions.assertions.length} issues found`);
// Resolve path and locators for extracted data
if (results.data != null) {
Object.getOwnPropertyNames(results.data).forEach((key) => {
if (!Array.isArray(results.data[key])) return;
results.data[key].forEach((item) => {
if (item.src !== undefined) {
if (Array.isArray(item.src)) {
item.src = item.src.map((srcItem) => {
if (srcItem.src !== undefined) {
srcItem.path = path.resolve(path.dirname(spineItem.filepath),
srcItem.src.toString());
srcItem.src = path.relative(epub.basedir, srcItem.path);
}
return srcItem;
});
} else {
item.path = path.resolve(path.dirname(spineItem.filepath), item.src.toString());
item.src = path.relative(epub.basedir, item.path);
}
if (item.cfi !== undefined) {
item.location = `${spineItem.relpath}#epubcfi(${item.cfi})`;
delete item.cfi;
}
}
});
});
}
return results;
} catch (err) {
winston.debug(`Error when running HTML checks: ${err}`);
throw new Error('Failed to check HTML content');
}
}

module.exports.check = async (epub) => {
const browser = await puppeteer.launch();
winston.info('Checking documents...');
return epub.contentDocs.reduce((sequence, spineItem) =>
sequence.then(results =>
checkSingle(spineItem, epub, browser)
.then((result) => {
results.push(result);
return results;
})), Promise.resolve([]))
.then(async (results) => {
await browser.close();
return results;
});
};
129 changes: 0 additions & 129 deletions src/checker/checker-nightmare.js

This file was deleted.

2 changes: 1 addition & 1 deletion src/checker/checker.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

const htmlChecker = require('./checker-nightmare.js');
const htmlChecker = require('./checker-chromium.js');
const epubChecker = require('./checker-epub.js');
const winston = require('winston');

Expand Down
2 changes: 1 addition & 1 deletion src/scripts/ace-extraction.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ ace.getObjects = function() {
}

ace.getScripts = function() {
let scriptElems = document.querySelectorAll('script');
let scriptElems = document.querySelectorAll('script:not([data-ace])');
let scripts = [];
scriptElems.forEach(function(script) {
let scriptObj = {
Expand Down
Loading

0 comments on commit b150b83

Please sign in to comment.