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:
'',
@@ -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:
'',
@@ -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'