Giter VIP home page Giter VIP logo

cypress-plugin-tab's People

Contributors

kuceb avatar markgaze avatar nicholasboll avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

cypress-plugin-tab's Issues

Does not work properly if test browser has focus?

Hi!

I created few tests for @valu/focus-trap using this. Unfortunately it seems that it does not work quite right if tests are executed with window focus.

Steps to reproduce:

git clone [email protected]:valu-digital/focus-trap.git
cd focus-trap/
git checkout 82863d8024d1d061de8d5a0a200babb8293d8739
npm ci

Start server in another shell

npm run examples-dev

Open Cypress

npm run cypress

Hit "Run all specs" and observe how bunch of the tests fail.

image

Next open cypress/integration/focus-trap.spec.ts and edit some test name and hit save, live reload should trigger test rerunning and now observe how all tests pass.

image

I'm sometimes able to make tests pass when pressing this re-run button

image

but not always. Not sure why. The live reload is the only way I'm able make them pass consistently.


Any ideas what might be going on?

@valu/focus-trap works by tapping into the focusin event, calls stopImmediatePropagation() on it and programmatically focuses elements it wants with .focus(). The most relevant code here is here

https://github.com/valu-digital/focus-trap/blob/82863d8024d1d061de8d5a0a200babb8293d8739/src/index.ts#L283


Anyway thank you for creating this! This is the only tool I've been able to create any tests for library like this. Cheers!

cy.tab returns `undefined` if keydown was cancelled

This may or may not be an actual issue. I'm not sure the intent.

I'm creating a modal with a focus trap. The way the focus trap works is it will cancel the keydown event if focus will travel outside the trap. cy.tab will return an empty subject in this case even if the focus lands somewhere:

https://github.com/Bkucera/cypress-plugin-tab/blob/ebce91c3d6ff8399e11f1f26a559588e4d1779ce/src/index.js#L122-L135

onCancel is defined as _.noop here:
https://github.com/Bkucera/cypress-plugin-tab/blob/ebce91c3d6ff8399e11f1f26a559588e4d1779ce/src/index.js#L67

From my experiments, I could change the _.noop to be a function that returns the activeElement of the document. This works:

  const simulatedCancel = () => {
    // Perhaps a focus trap cancelled? Return the activeElement
    return cy.now('focus', cy.$$(doc.activeElement));
  };

  return Promise.try(() => {
    return keydown(activeElement, options, simulatedDefault, simulatedCancel);
  }).finally(() => {
    keyup(activeElement, options);
  });

I see that newElm.focus() was actually commented out... It made me think, why is tabsequence even needed? Can we just always send the correct key events and just always return the new doc.activeElement whatever that might be? This would allow support for tabIndex=-1 which is currently not supported (because the element doesn't exist in the tabsequence), but works in the browser regardless.

How to use with typescript?

How can I use it with TS?

I see that the types are been exported, but I don't know how to add it in cypress to detect in the lint.

Thanks!

Feature Request: .tabUntil

Would love the ability to say "hit tab until you match" to ensure things are in a flow.

cy.visit(url)
  .get('#carousel-xyz')
  .find('[data-item=0]')
  .focus()
  .tabUntil('.right-arrow');

Thanks!

First tab selects wrong element

I tried the cypress-plugin-tab, I have some differences in order at manual testing, if I have divs in body, which have different tabindices, e.g. this one:

<html>
  <head>
  </head>
  <body>
    <div tabindex="10">1</div>
    <div tabindex="5">2</div>
    <div tabindex="3">3</div>
  </body>
</html>

When using the plugin, the first cy.get("body").tab() selects the div with "10" tabindex.
But at manual testing the "3" tabindex won.

Is it a bug of the plugin, or I did something wrong? How can I fix that?
It seems to me alphabetical order instead of numerical order.

cy.tab() doesn't accept tabindex="-1" elements as focusable

Sadly I don't have a super clean example of this in a Plunkr or similar but I'm having an issue tabbing out of elements that are focusable through an included [tabindex="-1"].

On navigation to some of our pages, we move the default focus to the titles as a way to skip our logo by default via a -1 tabindex and a focus shift on initialize. Cypress correctly passes these elements as having focus (which is verified via manual testing as well) but trying to tab() out of those elements results in an error from the plugin.

sample error

cy.get('#passwordResetTitle')
   .should('have.focus')
   .tab();

Let me know what other info would be helpful here! Or if I'm using the plugin incorrectly to test these interactions, also let me know that.

Edit:
Using cy.focused().tab(); also results in the same behavior. Seems like Cypress sees the elements as focusable, but the plugin differs from what it considers focusable.

cy.tab executes too quickly

I ran into an edge case where cy.tab() happens too fast. You can actually witness this while watching the GUI. If you cy.tab().tab(), you can't see focus changing. If I added a cy.wait(), I could see focus changing.

I'm building an accessible modal for github.com/Workday/canvas-kit: Workday/canvas-kit#59. Our Accessibility Specialists want the header to receive focus when the modal opens if there is no close icon (a config option for modals). That header should not be focusable again (removes the tabIndex on blur). cy.tab executes so fast that the DOM doesn't have time to reflect this change (since there is no wait between tabs). The blur fires and the tabIndex attribute is removed, but the DOM isn't updated by the time the next Cypress command runs.

Adding a wait for a single frame (using requestAnimationFrame) solves this issue where focus or blur events are allowed to execute and the DOM has been updated before continuing. This change also allows the focus ring to visibly change while the test is running.

Some other commands either wait 50ms or use https://github.com/cypress-io/cypress/blob/develop/packages/driver/src/cy/actionability.coffee

I can override tab in my support file to force a wait to fix this, but perhaps this will cause issues for other people.

Cannot read property '0' of null

It seems that some recent browser changes have started causing issues in platform.js which is a dependency of a11y.js used here. More details can be seen on the platform.js issue here: bestiejs/platform.js#196. It seems a11y.js hasn't been touched for 5 years and platform.js for 2. So I doubt they will get patched anytime soon. Given this package only uses 1 method from a11y.js it seems like we could probably just include that functionality internally, and removed the need for any dependencies?

Does not work with cy.clock()

Hi!

I adding

beforeEach(() =>  {
    cy.cloโ€โ€ck(new Date(2021, 2, 30, 12))
})

to the first of the spec and cy.tab() does not work.

Screenshot from 2021-03-31 10-45-40

Screenshot from 2021-03-31 10-38-52

Allowing passing `tabsequence` strategy

When working with Ionic apps that heavily use Shadow DOM, it looks like we'll need to be able to pass strategy: 'strict' to this plugin.

image

I could try submitting a PR. A new option strategy?

cy.get('body').tab({ strategy: 'strict' });

For my app, I think I'd then use overwrite to always set this as my default.

import 'cypress-plugin-tab';

Cypress.commands.overwrite('tab', (originalFn, options) => {
  // allow overriding `strategy` per query, but default to 'strict' if undefined
  return originalFn({ strategy: 'strict', ...options });
});

Thoughts?

types not specified in package.json

The typescript type definition for the index.d.ts is not specified in package.json as "types": "src/index.d.ts" and so is inaccessible to typescript. The fix is to release the exact same code with a "types" field in package.json

multiple .tab() required to fire/register event

I am using this plugin and it requires me to call .tab() twice before an event is registered/fired

examples:

cy.get('body').tab(); // does not register/fire event

cy.get('body').tab().tab(); // fires an event

is this a known issue ? as i am trying to replicate a users path through a system and need event to fire on one tab().

Cypress v4.x Support

Cypress has updated to version 4.x.x, which will require this plugin to update its dependencies in order to maintain compatibility with the current version. Thank you!

Property tab does not exist on type Chainable JQuery HTMLBodyElement

Hi there, thanks for this plugin! I'm trying to get it working for the first time and having a little trouble. I've installed the plugin, added require('cypress-plugin-tab') to cypress/support/index.js, but when I go to use the tab() method I receive the following error.

Property 'tab' does not exist on type 'Chainable<JQuery<HTMLBodyElement>>'.ts(2339)

Screen Shot 2020-02-21 at 10 02 50 AM

I'm using this withing a lerna monorepo, in case it helps diagnose. I have a branch you can checkout to run it locally if you like, it lives here https://github.com/seanforyou23/patternfly-react/tree/cypress-tab-support. You can run this locally with the following:

git clone [email protected]:seanforyou23/patternfly-react.git && cd patternfly-react/
yarn && yarn build:pf4 && yarn start:demo-app
# then, while "yarn start:demo-app" is running, in another console run the following
yarn start:cypress

I'm trying to use it for a simple case with button, you can see where I attempt here seanforyou23/patternfly-react@af77075#diff-ad8dcb99e87958a35e5815311202b43dR21

Please let me know if you spot anything I'm doing wrong, looking forward to using this plugin. Thanks!!!

just a question

Hello,

I have a question if your plugin can help me to do the following
Let's say I have a page with an icon when I click on it, it generates a PDF report in a different tab. Then I want to navigate to that tab and run cy.screenshot(). There will be no web element available on that tab because It's just an Adobe PDF browser container. Please, let me know

Thanks

Jeff

Peer dependency vulnerability

Running npm audit results in the following dependency vulnerability:
Screen Shot 2020-11-30 at 4 00 24 PM

Is anyone able to give an update on whether this is being looked into? This is non-breaking for me, and I understand that 3rd party packages might not have releases to remove such warnings, but this is creating a lot of noise for my project.

Any insight would be much appreciated!

cy.tab doesn't properly log

I noticed while using this plugin that a Cypress Command log only happened in certain instances. It only creates Command Log entries when the tab event isn't cancelled. In my case, I'm using a focus trap and the library often cancels the event.

Logging happens implicitly here:https://github.com/Bkucera/cypress-plugin-tab/blob/8e74c21b084fe83fc8c036a879694be0f83481b5/src/index.js#L60 which runs https://github.com/cypress-io/cypress/blob/6ed8d31cf045acd486757474934ef84f5f96cb74/packages/driver/src/cy/commands/actions/focus.coffee#L11 which does contain a Cypress.log.

Playing around a bit, I commented out the cy.now('focus', cy.$$(newEl)) and instead did:

return cy.$$(newElm).focus();

With that change, all logging goes away. To bring it back (always), we can add explicit logging:

const log = Cypress.log({
  $el: cy.$$(subject || win.document.activeElement),
  consoleProps: () => {
    'Applied To': Array.from(subject), // convert jQuery nodeList to normal array for logging
    // additional props can go here
  }
})

// get a "before" snapshot
log.snapshot('before', { next: 'after' });

// this replaces the return of this command:
  return new Promise((resolve) => {
    doc.defaultView.requestAnimationFrame(resolve)
  }).then(() => {
  // return Promise.try(() => {
    return keydown(activeElement, options, simulatedDefault, () => cy.$$(doc.activeElement))
  }).then((el) => {
    // Set the new element and finalize the snapshot
    log.set('$el', el).snapshot();
    return el;
  }).finally(() => {
    keyup(activeElement, options)
    log.end()
  })

I also added the cy.$$(doc.activeElement) because a non-jQuery-wrapped subject was being returned which cause problems in certain cases.

Cypress does some magic and I can't remember if a log automatically happens on error or not. I don't have a log.snapshot() on failure explicitly here, but there is one on success. There will be a Cypress DOM snapshot before and after the tab key was pressed, which should show any DOM diffs of any focus or blur handlers applied to the elements. Also the before snapshot will have the element that previously had focus and the after snapshot will have the element that focus went to.

I could make a PR with this change, for now I'm wrapping for this logging.

Right now I'm using the following to add this functionality:

Cypress.Commands.overwrite('tab', (originalFn, subject) => {
  const prevSubject = cy.$$(subject || cy.state('window').document.activeElement);

  const log = Cypress.log({
    $el: prevSubject,
    consoleProps() {
      return {
        'Applied To': prevSubject.toArray()[0],
      };
    },
  });

  log.snapshot('before', {next: 'after'});

  return Cypress.Promise.try(() => originalFn(subject))
    .then(value => {
      log.set('$el', value).snapshot();
      return value;
    })
    .finally(() => {
      log.end();
    });
});

nextItemFromIndex suffers off-by-one errors

I found this issue hitting some edge cases in our CI (I assume timing related). On debugging I found an issue where the nextItemFromIndex function doesn't return the correct element if it comes to the end of a sequence (off by one error).

https://github.com/Bkucera/cypress-plugin-tab/blob/master/src/index.js#L76-L94

const seq = [1, 2, 3, 4];

const nextItem = nextItemFromIndex(3, seq, false);
console.log(nextItem); // undefined

const previousItem = nextItemFromIndex(0, seq, true);
console.log(nextItem); // undefined

What happens is 3 is not the length of the array, so the if (i === seq.length) is not true. Even if it was, the logic is still wrong, it would have to set i = -1 instead, because i is getting incremented.

The correct implementation should be:

const nextItemFromIndex = (i, seq, reverse) => {
  let nextIndex;
  if (reverse) {
    nextIndex = i - 1;
    if (nextIndex < 0) {
      nextIndex = seq.length - 1;
    }
  } else {
    nextIndex = i + 1;
    if (nextIndex === seq.length) {
      nextIndex = 0;
    }
  }
  return seq[nextIndex];
}

Or a bit more succinct:

const nextItemFromIndex = (i, seq, reverse) => {
  if (reverse) {
    const nextIndex = i === 0 ? seq.length - 1 : i - 1;
    return seq[nextIndex];
  } else {
    const nextIndex = i === seq.length - 1 ? 0 : i + 1;
    return seq[nextIndex];
  }
}

Please support `event.preventDefault()`

I'm writing Cypress tests for my React Time Input Polyfill at the moment.

https://dan503.github.io/react-time-input-polyfill/

Part of the functionality is that when you press the [Tab] key, it goes to the next segment rather than the next focus-able element.

In order to do that, I need to prevent the default tab functionality from firing using event.preventDefault().

This plugin doesn't seem to support the event.preventDefault() use case though. ๐Ÿ˜Ÿ

Sometimes the body gets focus which causes indeterminate tests

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.

If a `keydown` handler changes focus, `cy.tab` will chose the wrong element

We have a focus redirect JS function for managing focus of a non-modal dialog. According to WCAG focus requirements, a non-modal dialog should be in the focus order after a triggering button:

https://www.w3.org/TR/UNDERSTANDING-WCAG20/navigation-mechanisms-focus-order.html

A Web page implements modeless dialogs via scripting. When the trigger button is activated, a dialog opens. The interactive elements in the dialog are inserted in the focus order immediately after the button. When the dialog is open, the focus order goes from the button to the elements of the dialog, then to the interactive element following the button. When the dialog is closed, the focus order goes from the button to the following element.

Since we "portal" our dialog, we manage focus manually. When we're on the last focusable element in a dialog, a keydown handler will close the dialog and shift focus back to the triggering button. When the keydown event is completed, the browser will perform the default action of advancing focus. This plugin uses the previous el to determine the next focusable element before the keydown event is processed. This means this plugin doesn't account for focus changes inside a keydown which is a valid thing to do.

If instead doc.activeElement was used to determine index, this use-case would work.

But this breaks the documented API where focus() is not necessary to tab.

Example, in the tests:

cy.get('a:first').tab()

This example allows you to skip focusing on the a:first element and tab from it anyway, which is not how users really interact with a page. With this change, the test would have to be changed to:

cy.get('a:first').focus().tab()

@bkucera What do you think? My proposal is more realistic to how browsers work, but would be a breaking change according to the documented API.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.