Skip to content

Commit 3b2b3a5

Browse files
authored
Merge pull request #62 from browserstack/AXE-231-iframe
feat: support advanced rule execution for iframes
2 parents b004343 + 5820a59 commit 3b2b3a5

File tree

5 files changed

+107
-2
lines changed

5 files changed

+107
-2
lines changed

lib/core/public/load.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
/*global a11yEngine*/
12
import Audit from '../base/audit';
23
import cleanup from './cleanup';
34
import runRules from './run-rules';
45
import respondable from '../utils/respondable';
56
import nodeSerializer from '../utils/node-serializer';
7+
import mergeErrors from '../utils/merge-errors';
68

79
/**
810
* Sets up Rules, Messages and default options for Checks, must be invoked before attempting analysis
@@ -36,6 +38,21 @@ function runCommand(data, keepalive, callback) {
3638
(results, cleanupFn) => {
3739
// Serialize all DqElements
3840
results = nodeSerializer.mapRawResults(results);
41+
42+
//a11y-engine iframe rules error merging logic
43+
const errors = a11yEngine.getErrors();
44+
if (Object.keys(errors).length !== 0) {
45+
if (results[results.length - 1].a11yEngineErrors) {
46+
const error = results.pop();
47+
delete error.a11yEngineErrors;
48+
const mergedErrors = mergeErrors(error, errors);
49+
results.push({ ...mergedErrors, a11yEngineErrors: true });
50+
} else {
51+
results.push({ ...errors, a11yEngineErrors: true });
52+
}
53+
}
54+
a11yEngine.clearErrors();
55+
3956
resolve(results);
4057
// Cleanup AFTER resolve so that selectors can be generated
4158
cleanupFn();

lib/core/public/run-rules.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/*global a11yEngine*/
12
import Context from '../base/context';
23
import teardown from './teardown';
34
import {
@@ -36,7 +37,13 @@ export default function runRules(context, options, resolve, reject) {
3637
performanceTimer.auditStart();
3738
}
3839

39-
if (context.frames.length && options.iframes !== false) {
40+
// If advanced run for iframes is true then setup socket for iframes
41+
if (options.a11yEngineConfig && options.a11yEngineConfig.iframesAdvancedRun) {
42+
a11yEngine.setup(options.a11yEngineConfig);
43+
}
44+
45+
// If run for iframes is true then collect results from iframes
46+
if (context.frames.length && options.iframes === true) {
4047
q.defer((res, rej) => {
4148
collectResultsFromFrames(context, options, 'rules', null, res, rej);
4249
});
@@ -59,6 +66,13 @@ export default function runRules(context, options, resolve, reject) {
5966

6067
// after should only run once, so ensure we are in the top level window
6168
if (context.initiator) {
69+
// Return a11y-engine errors when at top level window
70+
if (results[results.length - 1].a11yEngineErrors) {
71+
const error = results.pop();
72+
delete error.a11yEngineErrors;
73+
a11yEngine.mergeErrors(error);
74+
}
75+
6276
results = audit.after(results, options);
6377

6478
results.forEach(publishMetaData);

lib/core/utils/collect-results-from-frames.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ export default function collectResultsFromFrames(
2222
// elementRefs can't be passed across frame boundaries
2323
options = { ...options, elementRef: false };
2424

25+
// check a11yengine iframe advance run flag
26+
if (
27+
options.a11yEngineConfig &&
28+
options.a11yEngineConfig.iframesAdvancedRun === false
29+
) {
30+
options.a11yEngineConfig.iframesAdvancedRun = true;
31+
}
32+
2533
var q = queue();
2634
var frames = parentContent.frames;
2735

lib/core/utils/merge-errors.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Function to merge errors for a11y-engine.
2+
// Handles errors differently for check_errors and other errors.
3+
// It also adds the target selector to the errors for better identification.
4+
5+
function mergeErrors(mergedErrors, frameErrors, frameSpec) {
6+
for (const [key, value] of Object.entries(frameErrors)) {
7+
if (key === 'check_errors') {
8+
if (!mergedErrors[key]) {
9+
mergedErrors[key] = {};
10+
}
11+
12+
for (const [checkNameKey, checkNameValue] of Object.entries(value)) {
13+
// Add the target if not present. If present then append parents target.
14+
checkNameValue.forEach(checkNameValueError => {
15+
if (!checkNameValueError.target && frameSpec) {
16+
checkNameValueError.target = frameSpec?.selector;
17+
} else if (checkNameValueError.target && frameSpec) {
18+
checkNameValueError.target = [
19+
...frameSpec.selector,
20+
...checkNameValueError.target
21+
];
22+
}
23+
});
24+
if (mergedErrors[key][checkNameKey]) {
25+
mergedErrors[key][checkNameKey].push(...checkNameValue);
26+
} else {
27+
mergedErrors[key][checkNameKey] = Array.isArray(checkNameValue)
28+
? [...checkNameValue]
29+
: [checkNameValue];
30+
}
31+
}
32+
} else {
33+
// Add the target if not present. If present then append parents target.
34+
value.forEach(errorValue => {
35+
if (!errorValue.target && frameSpec) {
36+
errorValue.target = frameSpec?.selector;
37+
} else if (errorValue.target && frameSpec) {
38+
errorValue.target = [...frameSpec.selector, ...errorValue.target];
39+
}
40+
});
41+
if (mergedErrors[key]) {
42+
mergedErrors[key] = [...mergedErrors[key], ...value];
43+
} else {
44+
mergedErrors[key] = value;
45+
}
46+
}
47+
}
48+
49+
return mergedErrors;
50+
}
51+
52+
export default mergeErrors;

lib/core/utils/merge-results.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import nodeSerializer from './node-serializer';
22
import getAllChecks from './get-all-checks';
33
import findBy from './find-by';
4+
import mergeErrors from './merge-errors';
45

56
/**
67
* Adds the owning frame's CSS selector onto each instance of DqElement
@@ -75,13 +76,22 @@ function normalizeResult(result) {
7576
*/
7677
function mergeResults(frameResults, options) {
7778
const mergedResult = [];
79+
// A11yEngine merged errors
80+
let mergedErrors = {};
7881
frameResults.forEach(frameResult => {
7982
const results = normalizeResult(frameResult);
8083
if (!results || !results.length) {
8184
return;
8285
}
8386

8487
const frameSpec = getFrameSpec(frameResult);
88+
// Extract existing errors and merge with new ones
89+
if (results[results.length - 1].a11yEngineErrors) {
90+
const error = results.pop();
91+
delete error.a11yEngineErrors;
92+
mergedErrors = mergeErrors(mergedErrors, error, frameSpec);
93+
}
94+
8595
results.forEach(ruleResult => {
8696
if (ruleResult.nodes && frameSpec) {
8797
pushFrame(ruleResult.nodes, options, frameSpec);
@@ -106,7 +116,11 @@ function mergeResults(frameResults, options) {
106116
});
107117
}
108118
});
109-
return mergedResult;
119+
120+
if (Object.keys(mergedErrors).length === 0) {
121+
return mergedResult;
122+
}
123+
return [...mergedResult, { ...mergedErrors, a11yEngineErrors: true }];
110124
}
111125

112126
function nodeIndexSort(nodeIndexesA = [], nodeIndexesB = []) {

0 commit comments

Comments
 (0)