diff --git a/lib/core/base/context.js b/lib/core/base/context.js index 81ca89ee1..7e7df7f25 100644 --- a/lib/core/base/context.js +++ b/lib/core/base/context.js @@ -118,6 +118,12 @@ function isPageContext({ include }) { function validateContext(context) { if (context.include.length === 0 && context.frames.length === 0) { const env = respondable.isInFrame() ? 'frame' : 'page'; + // [a11y-core]: debug logging for empty-include investigation + (Function("return this")()).console.error('[DEBUG-ISSUE] empty include context', { + env, + t: Math.round(performance.now()), + url: location.href + }); throw new Error('No elements found for include in ' + env + ' Context'); } } diff --git a/lib/core/public/run-rules.js b/lib/core/public/run-rules.js index 68dfd64fe..e73cd6ed6 100644 --- a/lib/core/public/run-rules.js +++ b/lib/core/public/run-rules.js @@ -21,6 +21,12 @@ import log from '../log'; * @param {Function} reject Called when execution failed, receives (err : Error) */ export default function runRules(context, options, resolve, reject) { + // [a11y-core]: debug logging for run lifecycle + (Function("return this")()).console.debug('[DEBUG-ISSUE] axe.runRules start', { + t: Math.round(performance.now()), + running: axe._running, + inFrame: window.top !== window + }); try { context = new Context(context); axe._tree = context.flatTree; diff --git a/lib/core/public/teardown.js b/lib/core/public/teardown.js index efee98fd7..70b149260 100644 --- a/lib/core/public/teardown.js +++ b/lib/core/public/teardown.js @@ -5,6 +5,19 @@ import { resetGlobals } from './run/globals-setup'; * Clean up axe-core tree and caches. `axe.run` will call this function at the end of the run so there's no need to call it yourself afterwards. */ function teardown() { + // [a11y-core]: debug logging for teardown race investigation + if (axe._running) { + (Function("return this")()).console.warn('[DEBUG-ISSUE] axe.teardown() WHILE RUN IN FLIGHT', { + t: Math.round(performance.now()), + inFrame: window.top !== window + }, new Error().stack); + } else { + (Function("return this")()).console.debug('[DEBUG-ISSUE] axe.teardown()', { + t: Math.round(performance.now()), + running: axe._running, + inFrame: window.top !== window + }); + } // Reset MUST to happen before the cache is cleared resetGlobals(); axe._memoizedFns.forEach(fn => fn.clear()); diff --git a/lib/core/utils/collect-results-from-frames.js b/lib/core/utils/collect-results-from-frames.js index 82ff94557..77aef1920 100644 --- a/lib/core/utils/collect-results-from-frames.js +++ b/lib/core/utils/collect-results-from-frames.js @@ -44,7 +44,15 @@ export default function collectResultsFromFrames( return res({ results, frameElement }); } - sendCommandToFrame(frameElement, params, callback, rej); + // [a11y-core]: debug logging for frame rejection path + sendCommandToFrame(frameElement, params, callback, (err) => { + (Function("return this")()).console.error('[DEBUG-ISSUE] frame error — rejecting full run', { + frame: frameElement && (frameElement.id || frameElement.src), + error: err && err.message, + t: Math.round(performance.now()) + }); + rej(err); + }); }); }); diff --git a/lib/core/utils/get-selector.js b/lib/core/utils/get-selector.js index d6d810e18..012edf375 100644 --- a/lib/core/utils/get-selector.js +++ b/lib/core/utils/get-selector.js @@ -353,7 +353,14 @@ function getThreeLeastCommonFeatures(elm, selectorData) { function generateSelector(elm, options, doc) { /*eslint no-loop-func:0*/ // TODO: es-modules_selectorData + // [a11y-core]: debug logging for teardown race investigation if (!axe._selectorData) { + (Function("return this")()).console.error('[DEBUG-ISSUE] _selectorData missing', { + t: Math.round(performance.now()), + running: axe._running, + inFrame: window.top !== window, + url: location.href + }, new Error().stack); throw new Error('Expect axe._selectorData to be set up'); } const { toRoot = false } = options; diff --git a/lib/core/utils/send-command-to-frame.js b/lib/core/utils/send-command-to-frame.js index f6b13fac3..0f3edfe9a 100644 --- a/lib/core/utils/send-command-to-frame.js +++ b/lib/core/utils/send-command-to-frame.js @@ -28,6 +28,12 @@ export default function sendCommandToFrame(node, parameters, resolve, reject) { // This double timeout is important for allowing iframes to respond // DO NOT REMOVE timeout = setTimeout(() => { + // [a11y-core]: debug logging for frame ping failure + (Function("return this")()).console.warn('[DEBUG-ISSUE] frame ping failed (no axe in frame)', { + frame: node && (node.id || node.src || node.name), + debug: parameters.debug, + t: Math.round(performance.now()) + }); if (!parameters.debug) { resolve(null); } else { @@ -48,6 +54,11 @@ function callAxeStart(node, parameters, resolve, reject) { const frameWaitTime = parameters.options?.frameWaitTime ?? 60000; const win = node.contentWindow; const timeout = setTimeout(function collectResultFramesTimeout() { + // [a11y-core]: debug logging for frame timeout + (Function("return this")()).console.error('[DEBUG-ISSUE] frame timed out', { + frame: node && (node.id || node.src || node.name), + t: Math.round(performance.now()) + }); reject(err('Axe in frame timed out', node)); }, frameWaitTime);