diff --git a/lib/core/public/load.js b/lib/core/public/load.js index 124c849b1..e80d3d8f6 100644 --- a/lib/core/public/load.js +++ b/lib/core/public/load.js @@ -1,8 +1,10 @@ +/*global a11yEngine*/ import Audit from '../base/audit'; import cleanup from './cleanup'; import runRules from './run-rules'; import respondable from '../utils/respondable'; import nodeSerializer from '../utils/node-serializer'; +import mergeErrors from '../utils/merge-errors'; /** * Sets up Rules, Messages and default options for Checks, must be invoked before attempting analysis @@ -36,6 +38,21 @@ function runCommand(data, keepalive, callback) { (results, cleanupFn) => { // Serialize all DqElements results = nodeSerializer.mapRawResults(results); + + //a11y-engine iframe rules error merging logic + const errors = a11yEngine.getErrors(); + if (Object.keys(errors).length !== 0) { + if (results[results.length - 1].a11yEngineErrors) { + const error = results.pop(); + delete error.a11yEngineErrors; + const mergedErrors = mergeErrors(error, errors); + results.push({ ...mergedErrors, a11yEngineErrors: true }); + } else { + results.push({ ...errors, a11yEngineErrors: true }); + } + } + a11yEngine.clearErrors(); + resolve(results); // Cleanup AFTER resolve so that selectors can be generated cleanupFn(); diff --git a/lib/core/public/run-rules.js b/lib/core/public/run-rules.js index 5eb26d7c9..7ae0b92b9 100644 --- a/lib/core/public/run-rules.js +++ b/lib/core/public/run-rules.js @@ -1,3 +1,4 @@ +/*global a11yEngine*/ import Context from '../base/context'; import teardown from './teardown'; import { @@ -36,7 +37,13 @@ export default function runRules(context, options, resolve, reject) { performanceTimer.auditStart(); } - if (context.frames.length && options.iframes !== false) { + // If advanced run for iframes is true then setup socket for iframes + if (options.a11yEngineConfig && options.a11yEngineConfig.iframesAdvancedRun) { + a11yEngine.setup(options.a11yEngineConfig); + } + + // If run for iframes is true then collect results from iframes + if (context.frames.length && options.iframes === true) { q.defer((res, rej) => { collectResultsFromFrames(context, options, 'rules', null, res, rej); }); @@ -59,6 +66,13 @@ export default function runRules(context, options, resolve, reject) { // after should only run once, so ensure we are in the top level window if (context.initiator) { + // Return a11y-engine errors when at top level window + if (results[results.length - 1].a11yEngineErrors) { + const error = results.pop(); + delete error.a11yEngineErrors; + a11yEngine.mergeErrors(error); + } + results = audit.after(results, options); results.forEach(publishMetaData); diff --git a/lib/core/utils/collect-results-from-frames.js b/lib/core/utils/collect-results-from-frames.js index be17f8ccf..e19e356a8 100644 --- a/lib/core/utils/collect-results-from-frames.js +++ b/lib/core/utils/collect-results-from-frames.js @@ -22,6 +22,14 @@ export default function collectResultsFromFrames( // elementRefs can't be passed across frame boundaries options = { ...options, elementRef: false }; + // check a11yengine iframe advance run flag + if ( + options.a11yEngineConfig && + options.a11yEngineConfig.iframesAdvancedRun === false + ) { + options.a11yEngineConfig.iframesAdvancedRun = true; + } + var q = queue(); var frames = parentContent.frames; diff --git a/lib/core/utils/merge-errors.js b/lib/core/utils/merge-errors.js new file mode 100644 index 000000000..7fffe01eb --- /dev/null +++ b/lib/core/utils/merge-errors.js @@ -0,0 +1,52 @@ +// Function to merge errors for a11y-engine. +// Handles errors differently for check_errors and other errors. +// It also adds the target selector to the errors for better identification. + +function mergeErrors(mergedErrors, frameErrors, frameSpec) { + for (const [key, value] of Object.entries(frameErrors)) { + if (key === 'check_errors') { + if (!mergedErrors[key]) { + mergedErrors[key] = {}; + } + + for (const [checkNameKey, checkNameValue] of Object.entries(value)) { + // Add the target if not present. If present then append parents target. + checkNameValue.forEach(checkNameValueError => { + if (!checkNameValueError.target && frameSpec) { + checkNameValueError.target = frameSpec?.selector; + } else if (checkNameValueError.target && frameSpec) { + checkNameValueError.target = [ + ...frameSpec.selector, + ...checkNameValueError.target + ]; + } + }); + if (mergedErrors[key][checkNameKey]) { + mergedErrors[key][checkNameKey].push(...checkNameValue); + } else { + mergedErrors[key][checkNameKey] = Array.isArray(checkNameValue) + ? [...checkNameValue] + : [checkNameValue]; + } + } + } else { + // Add the target if not present. If present then append parents target. + value.forEach(errorValue => { + if (!errorValue.target && frameSpec) { + errorValue.target = frameSpec?.selector; + } else if (errorValue.target && frameSpec) { + errorValue.target = [...frameSpec.selector, ...errorValue.target]; + } + }); + if (mergedErrors[key]) { + mergedErrors[key] = [...mergedErrors[key], ...value]; + } else { + mergedErrors[key] = value; + } + } + } + + return mergedErrors; +} + +export default mergeErrors; diff --git a/lib/core/utils/merge-results.js b/lib/core/utils/merge-results.js index e5c9547ad..0e6277132 100644 --- a/lib/core/utils/merge-results.js +++ b/lib/core/utils/merge-results.js @@ -1,6 +1,7 @@ import nodeSerializer from './node-serializer'; import getAllChecks from './get-all-checks'; import findBy from './find-by'; +import mergeErrors from './merge-errors'; /** * Adds the owning frame's CSS selector onto each instance of DqElement @@ -75,6 +76,8 @@ function normalizeResult(result) { */ function mergeResults(frameResults, options) { const mergedResult = []; + // A11yEngine merged errors + let mergedErrors = {}; frameResults.forEach(frameResult => { const results = normalizeResult(frameResult); if (!results || !results.length) { @@ -82,6 +85,13 @@ function mergeResults(frameResults, options) { } const frameSpec = getFrameSpec(frameResult); + // Extract existing errors and merge with new ones + if (results[results.length - 1].a11yEngineErrors) { + const error = results.pop(); + delete error.a11yEngineErrors; + mergedErrors = mergeErrors(mergedErrors, error, frameSpec); + } + results.forEach(ruleResult => { if (ruleResult.nodes && frameSpec) { pushFrame(ruleResult.nodes, options, frameSpec); @@ -106,7 +116,11 @@ function mergeResults(frameResults, options) { }); } }); - return mergedResult; + + if (Object.keys(mergedErrors).length === 0) { + return mergedResult; + } + return [...mergedResult, { ...mergedErrors, a11yEngineErrors: true }]; } function nodeIndexSort(nodeIndexesA = [], nodeIndexesB = []) {