diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 05491b212dd2..675e194e3ec6 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -5,6 +5,7 @@ _Released 2/27/2024 (PENDING)_ **Bugfixes:** +- Fixed an issue where `.click()` commands on children of disabled elements would still produce "click" events -- even without `{ force: true }`. Fixes [#28788](https://github.com/cypress-io/cypress/issues/28788). - Changed RequestBody type to allow for boolean and null literals to be passed as body values. [#28789](https://github.com/cypress-io/cypress/issues/28789) ## 13.6.6 diff --git a/packages/driver/cypress/e2e/commands/actions/click.cy.js b/packages/driver/cypress/e2e/commands/actions/click.cy.js index 98650cb31341..bc679e8c6b9f 100644 --- a/packages/driver/cypress/e2e/commands/actions/click.cy.js +++ b/packages/driver/cypress/e2e/commands/actions/click.cy.js @@ -546,6 +546,30 @@ describe('src/cy/commands/actions/click', () => { cy.getAll('span2', 'focus click mousedown').each(shouldNotBeCalled) }) + // https://github.com/cypress-io/cypress/issues/28788 + it('no click when element is disabled', () => { + const btn = cy.$$('button:first') + const span = $('foooo') + + attachFocusListeners({ btn, span }) + attachMouseClickListeners({ btn, span }) + attachMouseHoverListeners({ btn, span }) + + btn.html('') + btn.attr('disabled', true) + btn.append(span) + + cy.get('button:first span').click() + + if (Cypress.browser.name === 'chrome') { + cy.getAll('btn', 'mouseenter mousedown mouseup').each(shouldBeCalled) + } + + cy.getAll('btn', 'focus click').each(shouldNotBeCalled) + cy.getAll('span', 'mouseenter mousedown mouseup').each(shouldBeCalled) + cy.getAll('span', 'focus click').each(shouldNotBeCalled) + }) + it('no click when new element at coords is not ancestor', () => { const btn = cy.$$('button:first') const span1 = $('foooo') diff --git a/packages/driver/src/cy/mouse.ts b/packages/driver/src/cy/mouse.ts index 2a34656ac7ff..c87e4b110a4b 100644 --- a/packages/driver/src/cy/mouse.ts +++ b/packages/driver/src/cy/mouse.ts @@ -567,6 +567,21 @@ export const create = (state: StateFunc, keyboard: Keyboard, focused: IFocused, return { skipClickEventReason: 'element was detached' } } + // Only send click event if element is not disabled. + // First find an parent element that can actually be disabled + const findParentThatCanBeDisabled = (el: HTMLElement): HTMLElement | null => { + const elementsThatCanBeDisabled = ['button', 'input', 'select', 'textarea', 'optgroup', 'option', 'fieldset'] + + return elementsThatCanBeDisabled.includes($elements.getTagName(el)) ? el : null + } + + const parentThatCanBeDisabled = $elements.findParent(mouseUpPhase.targetEl, findParentThatCanBeDisabled) || $elements.findParent(mouseDownPhase.targetEl, findParentThatCanBeDisabled) + + // Then check if parent is indeed disabled + if (parentThatCanBeDisabled !== null && $elements.isDisabled($(parentThatCanBeDisabled))) { + return { skipClickEventReason: 'element was disabled' } + } + const commonAncestor = mouseUpPhase.targetEl && mouseDownPhase.targetEl && $elements.getFirstCommonAncestor(mouseUpPhase.targetEl, mouseDownPhase.targetEl)