diff --git a/lib/core/utils/get-xpath.js b/lib/core/utils/get-xpath.js index 14298daab0..d79b0f830f 100644 --- a/lib/core/utils/get-xpath.js +++ b/lib/core/utils/get-xpath.js @@ -69,7 +69,7 @@ function getXPathArray(node, path) { function xpathToString(xpathArray) { return xpathArray.reduce((str, elm) => { if (elm.id) { - return `/${elm.str}[@id='${elm.id}']`; + return `//${elm.str}[@id='${elm.id}']`; } else { return str + `/${elm.str}` + (elm.count > 0 ? `[${elm.count}]` : ''); } diff --git a/test/core/public/run-rules.js b/test/core/public/run-rules.js index d04b853a80..394093ee73 100644 --- a/test/core/public/run-rules.js +++ b/test/core/public/run-rules.js @@ -221,8 +221,8 @@ describe('runRules', function () { 'html > body > div:nth-child(2)' ], xpath: [ - "/iframe[@id='context-test']", - "/div[@id='target']" + "//iframe[@id='context-test']", + "//div[@id='target']" ], source: '
', nodeIndexes: [12, 14], @@ -264,8 +264,8 @@ describe('runRules', function () { 'html > body > div:nth-child(1)' ], xpath: [ - "/iframe[@id='context-test']", - "/div[@id='foo']" + "//iframe[@id='context-test']", + "//div[@id='foo']" ], source: '
\n
\n
', @@ -284,8 +284,8 @@ describe('runRules', function () { 'html > body > div:nth-child(1)' ], xpath: [ - "/iframe[@id='context-test']", - "/div[@id='foo']" + "//iframe[@id='context-test']", + "//div[@id='foo']" ], source: '
\n
\n
', @@ -536,7 +536,7 @@ describe('runRules', function () { ancestry: [ 'html > body > div:nth-child(1) > div:nth-child(1)' ], - xpath: ["/div[@id='target']"], + xpath: ["//div[@id='target']"], source: '
Target!
', nodeIndexes: [12], fromFrame: false @@ -578,7 +578,7 @@ describe('runRules', function () { impact: null, node: { selector: ['#target'], - xpath: ["/div[@id='target']"], + xpath: ["//div[@id='target']"], ancestry: [ 'html > body > div:nth-child(1) > div:nth-child(1)' ], @@ -599,7 +599,7 @@ describe('runRules', function () { ancestry: [ 'html > body > div:nth-child(1) > div:nth-child(1)' ], - xpath: ["/div[@id='target']"], + xpath: ["//div[@id='target']"], source: '
Target!
', nodeIndexes: [12], fromFrame: false diff --git a/test/core/public/run.js b/test/core/public/run.js index f3e376045e..19e0b91c23 100644 --- a/test/core/public/run.js +++ b/test/core/public/run.js @@ -4,6 +4,7 @@ describe('axe.run', function () { var fixture = document.getElementById('fixture'); var noop = function () {}; var origRunRules = axe._runRules; + var captureError = axe.testUtils.captureError; beforeEach(function () { axe._load({ @@ -347,12 +348,12 @@ describe('axe.run', function () { { xpath: true }, - function (err, result) { + captureError(function (err, result) { assert.deepEqual(result.violations[0].nodes[0].xpath, [ - "/div[@id='fixture']" + "//div[@id='fixture']" ]); done(); - } + }, done) ); }); @@ -362,13 +363,13 @@ describe('axe.run', function () { { xpath: true }, - function (err, result) { + captureError(function (err, result) { assert.deepEqual( result.violations[0].nodes[0].none[0].relatedNodes[0].xpath, - ["/div[@id='fixture']"] + ["//div[@id='fixture']"] ); done(); - } + }, done) ); }); @@ -379,12 +380,12 @@ describe('axe.run', function () { xpath: true, reporter: 'no-passes' }, - function (err, result) { + captureError(function (err, result) { assert.deepEqual(result.violations[0].nodes[0].xpath, [ - "/div[@id='fixture']" + "//div[@id='fixture']" ]); done(); - } + }, done) ); }); }); @@ -396,10 +397,10 @@ describe('axe.run', function () { { absolutePaths: 0 }, - function (err, result) { + captureError(function (err, result) { assert.deepEqual(result.violations[0].nodes[0].target, ['#fixture']); done(); - } + }, done) ); }); @@ -409,12 +410,12 @@ describe('axe.run', function () { { absolutePaths: 'yes please' }, - function (err, result) { + captureError(function (err, result) { assert.deepEqual(result.violations[0].nodes[0].target, [ 'html > body > #fixture' ]); done(); - } + }, done) ); }); @@ -424,13 +425,13 @@ describe('axe.run', function () { { absolutePaths: true }, - function (err, result) { + captureError(function (err, result) { assert.deepEqual( result.violations[0].nodes[0].none[0].relatedNodes[0].target, ['html > body > #fixture'] ); done(); - } + }, done) ); }); }); diff --git a/test/core/utils/get-xpath.js b/test/core/utils/get-xpath.js index 78f4ec174d..94586b6d66 100644 --- a/test/core/utils/get-xpath.js +++ b/test/core/utils/get-xpath.js @@ -1,46 +1,76 @@ -describe('axe.utils.getXpath', function () { +describe('axe.utils.getXpath', () => { 'use strict'; - var fixture = document.getElementById('fixture'); + const fixture = document.getElementById('fixture'); - afterEach(function () { - fixture.innerHTML = ''; - }); + // @see https://stackoverflow.com/a/14284815/2124254 + function getElementByXPath(path) { + return document.evaluate( + path, + document, + () => 'http://www.w3.org/1998/Math/MathML', + XPathResult.FIRST_ORDERED_NODE_TYPE, + null + ).singleNodeValue; + } - it('should be a function', function () { + it('should be a function', () => { assert.isFunction(axe.utils.getXpath); }); - it('should generate an XPath selector', function () { - var node = document.createElement('div'); + it('should generate an XPath selector', () => { + const node = document.createElement('div'); fixture.appendChild(node); - var sel = axe.utils.getXpath(node); + const sel = axe.utils.getXpath(node); - assert.equal(sel, "/div[@id='fixture']/div"); + assert.equal(sel, "//div[@id='fixture']/div"); + assert.equal(node, getElementByXPath(sel)); }); - it('should handle special characters', function () { - var node = document.createElement('div'); + it('should handle special characters', () => { + const node = document.createElement('div'); node.id = 'monkeys#are.animals\\ok'; fixture.appendChild(node); - assert.equal( - axe.utils.getXpath(node), - "/div[@id='monkeys#are.animals\\ok']" - ); + + const sel = axe.utils.getXpath(node); + + assert.equal(sel, "//div[@id='monkeys#are.animals\\ok']"); + + assert.equal(node, getElementByXPath(sel)); }); - it('should stop on unique ID', function () { - var node = document.createElement('div'); + it('should stop on unique ID', () => { + const node = document.createElement('div'); node.id = 'monkeys'; fixture.appendChild(node); - var sel = axe.utils.getXpath(node); - assert.equal(sel, "/div[@id='monkeys']"); + const sel = axe.utils.getXpath(node); + assert.equal(sel, "//div[@id='monkeys']"); + assert.equal(node, getElementByXPath(sel)); + }); + + it('should use the nearest unique ID', () => { + fixture.innerHTML = ` +
+
+
+
+
+
+
+
+
+ `; + const node = fixture.querySelector('#monkeys > div'); + + const sel = axe.utils.getXpath(node); + assert.equal(sel, "//div[@id='monkeys']/div"); + assert.equal(node, getElementByXPath(sel)); }); - it('should not use ids if they are not unique', function () { - var node = document.createElement('div'); + it('should not use ids if they are not unique', () => { + let node = document.createElement('div'); node.id = 'monkeys'; fixture.appendChild(node); @@ -48,13 +78,14 @@ describe('axe.utils.getXpath', function () { node.id = 'monkeys'; fixture.appendChild(node); - var sel = axe.utils.getXpath(node); + const sel = axe.utils.getXpath(node); - assert.equal(sel, "/div[@id='fixture']/div[2]"); + assert.equal(sel, "//div[@id='fixture']/div[2]"); + assert.equal(node, getElementByXPath(sel)); }); - it('should properly calculate number when siblings are of different type', function () { - var node, target; + it('should properly calculate number when siblings are of different type', () => { + let node, target; node = document.createElement('span'); fixture.appendChild(node); @@ -74,19 +105,22 @@ describe('axe.utils.getXpath', function () { node = document.createElement('span'); fixture.appendChild(node); - var sel = axe.utils.getXpath(target); + const sel = axe.utils.getXpath(target); - assert.equal(sel, "/div[@id='fixture']/div[2]"); + assert.equal(sel, "//div[@id='fixture']/div[2]"); + assert.equal(target, getElementByXPath(sel)); }); - it('should work on the documentElement', function () { - var sel = axe.utils.getXpath(document.documentElement); + it('should work on the documentElement', () => { + const sel = axe.utils.getXpath(document.documentElement); assert.equal(sel, '/html'); + assert.equal(document.documentElement, getElementByXPath(sel)); }); - it('should work on the body', function () { - var sel = axe.utils.getXpath(document.body); + it('should work on the body', () => { + const sel = axe.utils.getXpath(document.body); assert.equal(sel, '/html/body'); + assert.equal(document.body, getElementByXPath(sel)); }); it('should work on namespaced elements', function () { @@ -94,6 +128,7 @@ describe('axe.utils.getXpath', function () { var node = fixture.firstChild; var sel = axe.utils.getXpath(node); - assert.equal(sel, "/div[@id='fixture']/hx:include"); + assert.equal(sel, "//div[@id='fixture']/hx:include"); + // couldn't figure out how to use document.evaluate to select an element with namespace }); }); diff --git a/test/core/utils/merge-results.js b/test/core/utils/merge-results.js index 85c15760fc..f7c758c099 100644 --- a/test/core/utils/merge-results.js +++ b/test/core/utils/merge-results.js @@ -41,7 +41,7 @@ describe('axe.utils.mergeResults', function () { var node = result[0].nodes[0].node; assert.deepEqual(node.selector, ['#target', '#foo']); - assert.deepEqual(node.xpath, ["/iframe[@id='target']", 'html/#foo']); + assert.deepEqual(node.xpath, ["//iframe[@id='target']", 'html/#foo']); assert.deepEqual(node.ancestry, [ 'html > body > div:nth-child(1) > iframe', 'html > div' @@ -76,7 +76,7 @@ describe('axe.utils.mergeResults', function () { var node = result[0].nodes[0].node; assert.deepEqual(node.selector, ['#target', '#foo']); - assert.deepEqual(node.xpath, ["/iframe[@id='target']", 'html/#foo']); + assert.deepEqual(node.xpath, ["//iframe[@id='target']", 'html/#foo']); assert.deepEqual(node.ancestry, [ 'html > body > div:nth-child(1) > iframe', 'html > div'