cypress-plugin-tab icon indicating copy to clipboard operation
cypress-plugin-tab copied to clipboard

Sometimes the body gets focus which causes indeterminate tests

Open NicholasBoll opened this issue 4 years ago • 0 comments

This issue has plagued me for over a year. Sometimes using cy.tab() or cy.tab({ shift: true }) would fail to transfer focus correctly. Running a bunch of console logs in this plugin and the ally.js package it depends on revealed tabsequence needs to find out what elements support focus.

  if (!supports) {
    supports = _supports();
  }

https://github.com/medialize/ally.js/blob/b8fd3cdbb9464df08708f11cba35b7481ad846d4/src/query/tabsequence.js#L42-L44

It does this by calling .focus() on a bunch of elements to see what elements can receive it.

https://github.com/medialize/ally.js/blob/b8fd3cdbb9464df08708f11cba35b7481ad846d4/src/supports/detect-focus.js#L64-L68

  focus && focus.focus && focus.focus();
  // validate test's result
  return options.validate
    ? options.validate(element, focus, data.document)
    : data.document.activeElement === focus;

In order to not call focus on a bunch of elements every time tabsequence is called, it caches focus tests.

https://github.com/medialize/ally.js/blob/b8fd3cdbb9464df08708f11cba35b7481ad846d4/src/supports/supports.js#L98-L100

  if (supportsCache) {
    return supportsCache;
  }

I'm not sure why the cache is primed sometimes and not others, but my tests only pass when the cache is primed. If the cache is not primed, an element is focused, cy.tab() is called, the focus support functions run and change the focused element (body in my case) and then cy.tab performs the focus. In this case, the sequence is off because body is detected as the activeElement.

If the cache is primed, ally.js does not run focus tests and cy.tab performs like it should.

The solution I found that works is to override cy.visit to prime the cache before any tests have a chance to run:

// cypress/commands.js or cypress/commands.ts
const supports = require('ally.js/supports/supports');

Cypress.Commands.overwrite('visit', (originalFn, url, options = {}) => {
  if (typeof url === 'object') {
    url = options.url;
  }

  return originalFn(url, {
    ...options,
    onBeforeLoad(win) {
      options.onBeforeLoad?.(win);
      supports(); // prime the ally.js supports cache so it doesn't mess with the cypress-plugin-tab
    },
  });
});

This solution only works for tests that load pages using cy.visit. If a Cypress tests clicks a link to navigate, the cache won't be primed. The other method is to add the supports() code to the JS of the page and use if (window.Cypress) { supports() } somewhere.

I'm not sure there's anything this plugin can do to fix this in all cases, but I figured I'd leave a fix in case others run into the same issue.

NicholasBoll avatar Jun 03 '21 19:06 NicholasBoll