testaro 74.2.2 → 74.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/tests/axe.js +45 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testaro",
3
- "version": "74.2.2",
3
+ "version": "74.2.3",
4
4
  "description": "Run 1300 web accessibility tests from 10 tools and get a standardized report",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/tests/axe.js CHANGED
@@ -134,6 +134,45 @@ exports.reporter = async (page, report, actIndex) => {
134
134
  });
135
135
  // If standard results are to be reported and there are any suspicions:
136
136
  if (standard && (totals.rulesViolated || totals.rulesWarned)) {
137
+ // Resolve each suspected element's FULL data-xpath from the live DOM,
138
+ // keyed by its axe target. axe-core truncates node.html (~300 chars,
139
+ // appends '...'), so parsing data-xpath out of node.html yields a
140
+ // truncated XPath for elements with long attribute lists. node.target
141
+ // is a reliable CSS selector; read the injected data-xpath directly off
142
+ // the element instead. Falls back to the node.html parse on any failure.
143
+ const fullXPathByTargetKey = {};
144
+ try {
145
+ const suspectNodes = ['incomplete', 'violations']
146
+ .filter(certainty => nativeResult?.details?.[certainty])
147
+ .flatMap(certainty => nativeResult.details[certainty].flatMap(rule => rule.nodes));
148
+ const targets = suspectNodes.map(node => node.target);
149
+ const resolvedXPaths = await page.evaluate(targetList => targetList.map(target => {
150
+ try {
151
+ // axe target is an array of selectors (nested arrays for frames/
152
+ // shadow roots). Use the deepest plain-string selector for the
153
+ // common, non-framed case; skip otherwise.
154
+ const selector = Array.isArray(target)
155
+ ? (typeof target[target.length - 1] === 'string' ? target[target.length - 1] : null)
156
+ : (typeof target === 'string' ? target : null);
157
+ if (! selector) {
158
+ return null;
159
+ }
160
+ const element = document.querySelector(selector);
161
+ return element ? element.getAttribute('data-xpath') : null;
162
+ }
163
+ catch(error) {
164
+ return null;
165
+ }
166
+ }), targets);
167
+ suspectNodes.forEach((node, index) => {
168
+ if (resolvedXPaths[index]) {
169
+ fullXPathByTargetKey[JSON.stringify(node.target)] = resolvedXPaths[index];
170
+ }
171
+ });
172
+ }
173
+ catch(error) {
174
+ // Leave the map empty; every instance falls back to node.html below.
175
+ }
137
176
  // For each certainty type:
138
177
  ['incomplete', 'violations'].forEach(certainty => {
139
178
  // If there are any suspicions of this type:
@@ -153,8 +192,12 @@ exports.reporter = async (page, report, actIndex) => {
153
192
  + (certainty === 'violations' ? 2 : 0);
154
193
  // Increment the standard total.
155
194
  standardResult.totals[ordinalSeverity]++;
156
- // Get the XPath of the suspected element from its data-xpath attribute.
157
- const xPath = getAttributeXPath(node.html);
195
+ // Get the XPath of the suspected element from its data-xpath
196
+ // attribute. Prefer the full value resolved from the live DOM
197
+ // (above); fall back to parsing it out of axe's node.html,
198
+ // which axe truncates and can corrupt the XPath.
199
+ const xPath = fullXPathByTargetKey[JSON.stringify(node.target)]
200
+ || getAttributeXPath(node.html);
158
201
  const instance = {
159
202
  ruleID: rule.id,
160
203
  what: Array.from(whatSet.values()).join('; '),