@@ -4,7 +4,16 @@ if (!options || !options.selector || typeof options.selector !== 'string') {
 	);
 }
 
-let elms = axe.utils.querySelectorAll(virtualNode, options.selector);
+// only look at the first node and it's related nodes
+const key = 'page-no-duplicate;' + options.selector;
+if (axe._cache.get(key)) {
+	return 'ignored';
+}
+axe._cache.set(key, true);
+
+let elms = axe.utils
+	.querySelectorAll(axe._tree[0], options.selector)
+	.filter(elm => elm !== virtualNode);
 
 // Filter elements that, within certain contexts, don't map their role.
 // e.g. a <footer> inside a <main> is not a banner, but in the <body> context it is
if (!options || !options.selector || typeof options.selector !== 'string') {
	throw new TypeError(
		'visible-in-page requires options.selector to be a string'
	);
}

let elms = axe.utils.querySelectorAll(virtualNode, options.selector);

// Filter elements that, within certain contexts, don't map their role.
// e.g. a <footer> inside a <main> is not a banner, but in the <body> context it is
if (typeof options.nativeScopeFilter === 'string') {
	elms = elms.filter(elm => {
		return (
			elm.actualNode.hasAttribute('role') ||
			!axe.commons.dom.findUpVirtual(elm, options.nativeScopeFilter)
		);
	});
}

this.relatedNodes(elms.map(elm => elm.actualNode));

return elms.length <= 1;

Hmm, not too keen on having strings as the return value of checks. I think we should use data() for this.