diff --git a/packages/ace-axe-runner-puppeteer/package.json b/packages/ace-axe-runner-puppeteer/package.json new file mode 100644 index 00000000..6aca4a8f --- /dev/null +++ b/packages/ace-axe-runner-puppeteer/package.json @@ -0,0 +1,27 @@ +{ + "name": "@daisy/ace-axe-runner-puppeteer", + "version": "1.0.0", + "description": "Abstract Axe runner for Ace", + "author": { + "name": "DAISY developers", + "organization": "DAISY Consortium", + "url": "http://www.daisy.org/" + }, + "repository": { + "type": "git", + "url": "https://github.com/daisy/ace", + "directory": "packages/ace-axe-runner-puppeteer" + }, + "bugs": { + "url": "https://github.com/daisy/ace/issues" + }, + "license": "MIT", + "main": "lib/index.js", + "dependencies": { + "@daisy/puppeteer-utils": "^1.0.0", + "puppeteer": "^1.0.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/ace-axe-runner-puppeteer/src/index.js b/packages/ace-axe-runner-puppeteer/src/index.js new file mode 100644 index 00000000..1fa1f2ad --- /dev/null +++ b/packages/ace-axe-runner-puppeteer/src/index.js @@ -0,0 +1,42 @@ +'use strict'; + +const os = require('os'); +const puppeteer = require('puppeteer'); +const utils = require('@daisy/puppeteer-utils'); + +let _browser = undefined; + +module.exports = { + concurrency: 4, + launch: async function() { + const args = []; + if (os.platform() !== 'win32' && os.platform() !== 'darwin') { + args.push('--no-sandbox') + } + _browser = await puppeteer.launch({ args }); + }, + close: async function() { + await _browser.close(); + }, + run: async function(url, scripts, scriptContents, basedir) { + + const page = await _browser.newPage(); + await page.goto(url); + + await utils.addScriptContents(scriptContents, page); + await utils.addScripts(scripts, page); + + 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(); + return results; + } +}; \ No newline at end of file diff --git a/packages/ace-cli/package.json b/packages/ace-cli/package.json index 07798a68..ec788bf7 100644 --- a/packages/ace-cli/package.json +++ b/packages/ace-cli/package.json @@ -19,6 +19,7 @@ "main": "lib/index.js", "bin": "bin/ace.js", "dependencies": { + "@daisy/ace-axe-runner-puppeteer": "^1.0.0", "@daisy/ace-config": "^1.0.0", "@daisy/ace-core": "^1.0.2", "@daisy/ace-logger": "^1.0.1", diff --git a/packages/ace-cli/src/index.js b/packages/ace-cli/src/index.js index efbc3053..4a5949f1 100755 --- a/packages/ace-cli/src/index.js +++ b/packages/ace-cli/src/index.js @@ -5,6 +5,8 @@ const meow = require('meow'); const path = require('path'); const winston = require('winston'); +const axeRunner = require('@daisy/ace-axe-runner-puppeteer'); + const logger = require('@daisy/ace-logger'); const ace = require('@daisy/ace-core'); @@ -105,7 +107,7 @@ ${overrides.map(file => ` - ${file}`).join('\n')} silent: cli.flags.silent, jobId: '', lang: cli.flags.lang, - }) + }, axeRunner) .then((jobData) => { var reportJson = jobData[1]; // if there were violations from the validation process, return 2 diff --git a/packages/ace-core/package.json b/packages/ace-core/package.json index 1c7d4f2b..5c5649fb 100644 --- a/packages/ace-core/package.json +++ b/packages/ace-core/package.json @@ -24,12 +24,10 @@ "@daisy/ace-report": "^1.0.1", "@daisy/ace-report-axe": "^1.0.1", "@daisy/epub-utils": "^1.0.2", - "@daisy/puppeteer-utils": "^1.0.0", "axe-core": "^3.2.2", "file-url": "^2.0.2", "h5o": "^0.11.3", "p-map": "^1.2.0", - "puppeteer": "^1.0.0", "tmp": "^0.0.33", "winston": "^2.4.0" }, diff --git a/packages/ace-core/src/checker/checker-chromium.js b/packages/ace-core/src/checker/checker-chromium.js index ab912a23..c68a3961 100644 --- a/packages/ace-core/src/checker/checker-chromium.js +++ b/packages/ace-core/src/checker/checker-chromium.js @@ -4,13 +4,10 @@ const fileUrl = require('file-url'); const fs = require('fs-extra'); const path = require('path'); const pMap = require('p-map'); -const puppeteer = require('puppeteer'); -const os = require('os'); const tmp = require('tmp'); const winston = require('winston'); const axe2ace = require('@daisy/ace-report-axe'); -const utils = require('@daisy/puppeteer-utils'); const { getRawResourcesForCurrentLanguage } = require('../l10n/localize').localizer; @@ -25,7 +22,7 @@ const scripts = [ require.resolve('../scripts/ace-extraction.js'), ]; -async function checkSingle(spineItem, epub, browser, lang) { +async function checkSingle(spineItem, epub, lang, axeRunner) { winston.verbose(`- Processing ${spineItem.relpath}`); try { let url = spineItem.url; @@ -41,10 +38,8 @@ async function checkSingle(spineItem, epub, browser, lang) { url = fileUrl(tmpFile); winston.debug(`checking copied file at ${url}`) } - - const page = await browser.newPage(); - await page.goto(url); + const scriptContents = []; let localePath = ""; try { winston.info(`- Axe locale: [${lang}]`); @@ -57,7 +52,7 @@ async function checkSingle(spineItem, epub, browser, lang) { if (fs.existsSync(localePath)) { const localeStr = fs.readFileSync(localePath, { encoding: "utf8" }); const localeScript = `window.__axeLocale__=${localeStr};`; - await utils.addScriptContents([localeScript], page); + scriptContents.push(localeScript); } else { winston.info(`- Axe locale missing? [${lang}] => ${localePath}`); } @@ -76,33 +71,21 @@ async function checkSingle(spineItem, epub, browser, lang) { } } }); - await utils.addScriptContents([localizedScript], page); - + scriptContents.push(localizedScript); + } catch (err) { console.log(err); winston.verbose(err); winston.info(`- Axe locale problem? [${lang}] => ${localePath}`); } - await utils.addScripts(scripts, page); - - 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(); + const results = await axeRunner.run(url, scripts, scriptContents, epub.basedir); // Post-process results results.assertions = (results.axe != null) ? axe2ace.axe2ace(spineItem, results.axe, lang) : []; delete results.axe; winston.info(`- ${spineItem.relpath}: ${ - (results.assertions && results.assertions.assertions.length > 0) + (results.assertions && results.assertions.assertions && results.assertions.assertions.length > 0) ? results.assertions.assertions.length : 'No'} issues found`); // Resolve path and locators for extracted data @@ -139,16 +122,16 @@ async function checkSingle(spineItem, epub, browser, lang) { } } -module.exports.check = async (epub, lang) => { - const args = []; - if (os.platform() !== 'win32' && os.platform() !== 'darwin') { - args.push('--no-sandbox') - } - const browser = await puppeteer.launch({ args }); +module.exports.check = async (epub, lang, axeRunner) => { + await axeRunner.launch(); winston.info('Checking documents...'); - return pMap(epub.contentDocs, doc => checkSingle(doc, epub, browser, lang), { concurrency: 4 }) + return pMap(epub.contentDocs, doc => checkSingle(doc, epub, lang, axeRunner), { concurrency: axeRunner.concurrency }) .then(async (results) => { - await browser.close(); + await axeRunner.close(); return results; + }).catch(async (err) => { + winston.info(`Error HTML check: ${err}`); + await axeRunner.close(); + return []; }); }; diff --git a/packages/ace-core/src/checker/checker.js b/packages/ace-core/src/checker/checker.js index ce297081..3b81ec31 100644 --- a/packages/ace-core/src/checker/checker.js +++ b/packages/ace-core/src/checker/checker.js @@ -8,23 +8,41 @@ function consolidate(results, report) { winston.info('Consolidating results...'); // Integrate checker results to the report results.forEach((res) => { - report.addAssertions(res.assertions); - report.addProperties(res.properties); - report.addData(res.data); + if (res.assertions) { + report.addAssertions(res.assertions); + } + if (res.properties) { + report.addProperties(res.properties); + } + if (res.data) { + report.addData(res.data); + } }); // Get a flat array of all the headings in the documents const headings = [] - .concat(...results.map(docResult => docResult.outlines.headings)) + .concat(...results.map(docResult => { + if (docResult.outlines && docResult.outlines.headings) { + return docResult.outlines.headings; + } + return undefined; + })) .filter(e => e !== undefined); report.addHeadings(headings); // Aggregated array of the HTML outlines - const htmlOutlines = results.map(docResult => docResult.outlines.html); + const htmlOutlines = [] + .concat(results.map(docResult => { + if (docResult.outlines && docResult.outlines.html) { + return docResult.outlines.html; + } + return undefined; + })) + .filter(e => e !== undefined); report.addHTMLOutlines(htmlOutlines); return report; } -module.exports.check = function check(epub, report,lang) { +module.exports.check = function check(epub, report, lang, axeRunner) { return epubChecker.check(epub, report) - .then(() => htmlChecker.check(epub, lang)) + .then(() => htmlChecker.check(epub, lang, axeRunner)) .then(results => consolidate(results, report)); }; diff --git a/packages/ace-core/src/core/ace.js b/packages/ace-core/src/core/ace.js index 983db885..de02e902 100644 --- a/packages/ace-core/src/core/ace.js +++ b/packages/ace-core/src/core/ace.js @@ -16,7 +16,8 @@ const logger = require('@daisy/ace-logger'); tmp.setGracefulCleanup(); -module.exports = function ace(epubPath, options) { +module.exports = function ace(epubPath, options, axeRunner) { + if (options.lang) { setCurrentLanguage(options.lang); } @@ -69,7 +70,7 @@ module.exports = function ace(epubPath, options) { // initialize the report .then(() => new Report(epub, options.outdir, options.lang)) // Check each Content Doc - .then(report => checker.check(epub, report, options.lang)) + .then(report => checker.check(epub, report, options.lang, axeRunner)) // Process the Results .then((report) => { if (options.outdir === undefined) { diff --git a/packages/ace-http/package.json b/packages/ace-http/package.json index e1a04703..c8cf954e 100644 --- a/packages/ace-http/package.json +++ b/packages/ace-http/package.json @@ -19,6 +19,7 @@ "main": "lib/index.js", "bin": "bin/ace-http.js", "dependencies": { + "@daisy/ace-axe-runner-puppeteer": "^1.0.0", "@daisy/ace-core": "^1.0.2", "@daisy/ace-logger": "^1.0.1", "@daisy/ace-meta": "^1.0.3", diff --git a/packages/ace-http/src/index.js b/packages/ace-http/src/index.js index cf4aa158..d2b410c2 100644 --- a/packages/ace-http/src/index.js +++ b/packages/ace-http/src/index.js @@ -12,6 +12,8 @@ const meow = require('meow'); const ace = require('@daisy/ace-core'); const logger = require('@daisy/ace-logger'); +const axeRunner = require('@daisy/ace-axe-runner-puppeteer'); + const pkg = require('@daisy/ace-meta/package'); const UPLOADS = tmp.dirSync({ unsafeCleanup: true }).name; @@ -159,7 +161,7 @@ function newJob(jobdata) { joblist.push(jobdata); // execute the job with Ace - ace(jobdata.internal.epubPath, {'jobid': jobdata.internal.id, 'outdir': jobdata.internal.outputDir, 'lang': jobdata.internal.lang}) + ace(jobdata.internal.epubPath, {'jobid': jobdata.internal.id, 'outdir': jobdata.internal.outputDir, 'lang': jobdata.internal.lang}, axeRunner) .then((jobData) => { var jobId = jobData[0]; var idx = joblist.findIndex(job => job.internal.id === jobId); diff --git a/tests/runAceJS.js b/tests/runAceJS.js index 8625cee1..c081de49 100644 --- a/tests/runAceJS.js +++ b/tests/runAceJS.js @@ -3,6 +3,8 @@ const ace = require('@daisy/ace-core'); const logger = require('@daisy/ace-logger'); +const axeRunner = require('@daisy/ace-axe-runner-puppeteer'); + function runAce(epub, { cwd = process.cwd(), outdir, @@ -19,7 +21,7 @@ function runAce(epub, { verbose, silent, lang, - }); + }, axeRunner); } module.exports = runAce;