Skip to content

cy.click() doesn't trigger click event if el to click has "tabindex=0" outside of current viewport #8279

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
tho-masn opened this issue Aug 14, 2020 · 8 comments · Fixed by #14710
Labels
topic: cy.click 🖱 type: regression A bug that didn't appear until a specific Cy version release v3.5.0 🐛 Issue present since 3.5.0

Comments

@tho-masn
Copy link

Hi there,

Current behavior:

I'm using tabindex="0" across a vue app to be able to close forms with the ESC key. This feature introduces the following issue: In the tests the cy.click() calls in some cases are not triggering the actual event. Instead it seems like the click just sets the focus.

Desired behavior:

Click event should get triggered.

Test code to reproduce

I've tried to reproduce it with the cypress-realworld-app. You can check it out here: https://github.com/tho-masn/cypress-realworld-app/pull/1/files

To reproduce, just run the following test file: cypress/tests/ui/bankaccounts.spec.ts
You will see, that the click event doesn't get fired. There should appear a message below the "save" button saying "Submit worked". This issue is introduced by adding tabIndex={0} to the form in BankAccountForm.tsx

There are several options to make this example work again:

  • Remove the tabIndex={0} attribute again
    • Not an option for me, as I need it to close the forms with ESC
  • Add { force: true } to the submit click call in the test.
    • Not an option for me, as I would have to add force: true to basically every click call
  • Don't use a div with a click action handler for the submit button, but a real submit button
    • Not an option for me, as I have click handlers on divs which are not buttons
  • Reduce the height of the div below the save button to 500
    • This one is really weird. The issue only appears, if the page grows

Versions

Cypress: v4.12.1
OS: MacOS 10.15.5

@jennifer-shehane
Copy link
Member

I can recreate this with the branch provided. You can see that during the click - it's highlighting the 'Bank Account' navigation on the left, as if it's focusing there for some reason.

It'd be great to have a simpler example. I've tried to recreate outside of the RWA and haven't been successful.

Doesn't reproduce

<html>
<body>
  <div>
    <a tabindex="0">Link 1</a>
    <div tabindex="0"></div>
  </div>
  <form tabindex="0">
    <input/>
    <div id='save'>Save</div>
    <div id='success'></div>
  </form>
  <script>
    const saveBtn = document.getElementById('save')
    const successMsg = document.getElementById('success')

    saveBtn.addEventListener('click', (e) => {
      successMsg.innerHTML = "Success!"
    })
  </script>
</body>
</html>
it('test', () => {
  cy.visit('index.html')
  cy.get('#save').click()
  cy.get('#success').should('contain', 'Success')
})

@tho-masn
Copy link
Author

tho-masn commented Aug 17, 2020

The issue is only happening, if the clicked element is out of view. I've added a div to your code, which pushes the save button out of the viewport and removed unnecessary tabindexes:

<html>
<body>
  <div>
    <a>Link 1</a>
    <div></div>
  </div>
  <form tabindex="0">
    <input/>
    <div style="height: 2000px"></div>
    <div id='save'>Save</div>
    <div id='success'></div>
  </form>
  <script>
    const saveBtn = document.getElementById('save')
    const successMsg = document.getElementById('success')

    saveBtn.addEventListener('click', (e) => {
      successMsg.innerHTML = "Success!"
    })
  </script>
</body>
</html>

@jennifer-shehane
Copy link
Member

jennifer-shehane commented Aug 17, 2020

Perfect! This issue does not exist in Cypress 3.4.1, so is a regression that was introduced in 3.5.0.

Repro

index.html

<html>
<body>
  <!-- The test passes is you remove tabindex here -->
  <form tabindex="0">
    <div style="height: 2000px"></div>
    <div id="save">Save</div>
    <div id="success"></div>
  </form>
  <script>
    const saveBtn = document.getElementById('save')
    const successMsg = document.getElementById('success')

    saveBtn.addEventListener('click', (e) => {
      successMsg.innerHTML = "Success!"
    })
  </script>
</body>
</html>

spec.js

it('test', () => {
  cy.visit('index.html')
  cy.get('#save').click()
  cy.get('#success').should('contain', 'Success')
})

@jennifer-shehane jennifer-shehane added type: regression A bug that didn't appear until a specific Cy version release v3.5.0 🐛 Issue present since 3.5.0 and removed type: bug labels Aug 17, 2020
@jennifer-shehane jennifer-shehane changed the title cy.click() doesn't trigger click event if "tabindex=0" is used cy.click() doesn't trigger click event if el to click has "tabindex=0" outside of current viewport Aug 17, 2020
@cypress-bot cypress-bot bot added stage: ready for work The issue is reproducible and in scope and removed stage: needs investigating Someone from Cypress needs to look at this labels Aug 17, 2020
@sainthkh
Copy link
Contributor

Interesting note. This works fine in Firefox.

@sainthkh
Copy link
Contributor

This happens because of the code below:

click (fromElViewport, forceEl, pointerEvtOptionsExtend = {}, mouseEvtOptionsExtend = {}) {
debug('mouse.click', { fromElViewport, forceEl })
const mouseDownPhase = mouse.down(fromElViewport, forceEl, pointerEvtOptionsExtend, mouseEvtOptionsExtend)
const skipMouseupEvent = mouseDownPhase.events.pointerdown.skipped || mouseDownPhase.events.pointerdown.preventedDefault
const mouseUpPhase = mouse.up(fromElViewport, forceEl, skipMouseupEvent, pointerEvtOptionsExtend, mouseEvtOptionsExtend)
const getElementToClick = () => {
// Never skip the click event when force:true
if (forceEl) {
return { elToClick: forceEl }
}
// Only send click event if mousedown element is not detached.
if ($elements.isDetachedEl(mouseDownPhase.targetEl) || $elements.isDetached(mouseUpPhase.targetEl)) {
return { skipClickEventReason: 'element was detached' }
}
const commonAncestor = mouseUpPhase.targetEl &&
mouseDownPhase.targetEl &&
$elements.getFirstCommonAncestor(mouseUpPhase.targetEl, mouseDownPhase.targetEl)
return { elToClick: commonAncestor }
}
const { skipClickEventReason, elToClick } = getElementToClick()
const mouseClickEvents = mouse._mouseClickEvents(fromElViewport, elToClick, forceEl, skipClickEventReason, mouseEvtOptionsExtend)
return _.extend({}, mouseDownPhase.events, mouseUpPhase.events, mouseClickEvents)
},

mouseDownPhase.targetEl and mouseUpPhase.targetEl are different. Because of that, click event isn't sent to the button.

I couldn't verify whether it's the problem of Cypress or Chrome.

@tgreen7
Copy link
Contributor

tgreen7 commented Jan 22, 2021

down (fromElViewport, forceEl, pointerEvtOptionsExtend = {}, mouseEvtOptionsExtend = {}) {
const $previouslyFocused = focused.getFocused()
const mouseDownPhase = mouse._downEvents(fromElViewport, forceEl, pointerEvtOptionsExtend, mouseEvtOptionsExtend)
// el we just send pointerdown
const el = mouseDownPhase.targetEl
if (mouseDownPhase.events.pointerdown.preventedDefault || mouseDownPhase.events.mousedown.preventedDefault || !$elements.isAttachedEl(el)) {
return mouseDownPhase
}
//# retrieve the first focusable $el in our parent chain
const $elToFocus = $elements.getFirstFocusableEl($(el))
debug('elToFocus:', $elToFocus[0])
if (focused.needsFocus($elToFocus, $previouslyFocused)) {
debug('el needs focus')
if ($dom.isWindow($elToFocus)) {
// if the first focusable element from the click
// is the window, then we can skip the focus event
// since the user has clicked a non-focusable element
const $focused = focused.getFocused()
if ($focused) {
focused.fireBlur($focused.get(0))
}
} else {
// the user clicked inside a focusable element
focused.fireFocus($elToFocus.get(0))
}
}

if the code on line 482 is changed to
focused.fireFocus($elToFocus.get(0), { preventScroll: true })

and then those options are passed through in focused.js

$elements.callNativeMethod(el, 'focus', opts)

That scroll is stopped and @jennifer-shehane repro is fixed

@cypress-bot cypress-bot bot added stage: work in progress stage: needs review The PR code is done & tested, needs review and removed stage: ready for work The issue is reproducible and in scope stage: work in progress labels Jan 22, 2021
@cypress-bot cypress-bot bot added stage: work in progress and removed stage: needs review The PR code is done & tested, needs review labels Feb 15, 2021
@cypress-bot
Copy link
Contributor

cypress-bot bot commented Feb 23, 2021

The code for this is done in cypress-io/cypress#14710, but has yet to be released.
We'll update this issue and reference the changelog when it's released.

@bahmutov
Copy link
Contributor

bahmutov commented Apr 7, 2021

Released in v6.7.0

@cypress-io cypress-io locked as resolved and limited conversation to collaborators May 5, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
topic: cy.click 🖱 type: regression A bug that didn't appear until a specific Cy version release v3.5.0 🐛 Issue present since 3.5.0
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants