cypress-io / cypress

Fast, easy and reliable testing for anything that runs in a browser.
https://cypress.io
MIT License
46.69k stars 3.16k forks source link

Cypress fails to uncheck all checked checkboxes #26208

Closed mhssmnn closed 3 days ago

mhssmnn commented 1 year ago

Current behavior

Currently running the following command only unchecks some of the checkboxes:

cy.get('input[type="checkbox"]:checked').uncheck();

Screen Shot 2023-03-24 at 2 34 58 PM

Desired behavior

Cypress should uncheck all checkboxes, as it did in at least v10.2

Test code to reproduce

Here is the failing code:

cy.get('input[type="checkbox"]:checked').uncheck();
cy.get('input[type="checkbox"]:checked').should('not.exist');

And a test repo:

https://github.com/mhssmnn/cypress-issue-checkboxes-repro

Cypress Version

12.7.0

Node version

v18.11.0

Operating System

macOS 12.3.1

Debug Logs

cypress:server:reporter got mocha event 'fail' with args: [ { _testConfig: { testConfigList: [], unverifiedTestConfig: {}, applied: 'complete' }, id: 'r3', order: 1, title: 'unchecks all the checked check boxes so no checks are left', err: { message: 'Timed out retrying after 4200ms: `cy.uncheck()` failed because the page updated while this command was executing. Cypress tried to locate elements based on this query:\n' + '\n' + '> cy.get(input[type="checkbox"]:checked)\n' + '\n' + 'We initially found matching element(s), but while waiting for them to become actionable, they disappeared from the page. Common situations why this happens:\n' + '  - Your JS framework re-rendered asynchronously\n' + '  - Your app code reacted to an event firing and removed the element\n' + '\n' + 'You can typically solve this by breaking up a chain. For example, rewrite:\n' + '\n' + "> `cy.get('button').click().click()`\n" + '\n' + 'to\n' + '\n' + "> `cy.get('button').as('btn').click()`\n" + "> `cy.get('@btn').click()`\n" + '\n' + 'https://on.cypress.io/element-has-detached-from-dom', name: 'CypressError', stack: 'CypressError: Timed out retrying after 4200ms: `cy.uncheck()` failed because the page updated while this command was executing. Cypress tried to locate elements based on this query:\n' + '\n' + '> cy.get(input[type="checkbox"]:checked)\n' + '\n' + 'We initially found matching element(s), but while waiting for them to become actionable, they disappeared from the page. Common situations why this happens:\n' + '  - Your JS framework re-rendered asynchronously\n' + '  - Your app code reacted to an event firing and removed the element\n' + '\n' + 'You can typically solve this by breaking up a chain. For example, rewrite:\n' + '\n' + "> `cy.get('button').click().click()`\n" + '\n' + 'to\n' + '\n' + "> `cy.get('button').as('btn').click()`\n" + "> `cy.get('@btn').click()`\n" + '\n' + 'https://on.cypress.io/element-has-detached-from-dom\n' + '    at retryActionability (http://localhost:8080/__cypress/runner/cypress_runner.js:130623:82)\n' + '    at tryCatcher (http://localhost:8080/__cypress/runner/cypress_runner.js:8914:23)\n' + '    at Promise.attempt.Promise.try (http://localhost:8080/__cypress/runner/cypress_runner.js:6188:29)\n' + '    at whenStable (http://localhost:8080/__cypress/runner/cypress_runner.js:146732:65)\n' + '    at <unknown> (http://localhost:8080/__cypress/runner/cypress_runner.js:146173:14)\n' + '    at tryCatcher (http://localhost:8080/__cypress/runner/cypress_runner.js:8914:23)\n' + '    at Promise._settlePromiseFromHandler (http://localhost:8080/__cypress/runner/cypress_runner.js:6849:31)\n' + '    at Promise._settlePromise (http://localhost:8080/__cypress/runner/cypress_runner.js:6906:18)\n' + '    at Promise._settlePromise0 (http://localhost:8080/__cypress/runner/cypress_runner.js:6951:10)\n' + '    at Promise._settlePromises (http://localhost:8080/__cypress/runner/cypress_runner.js:7031:18)\n' + '    at Promise._fulfill (http://localhost:8080/__cypress/runner/cypress_runner.js:6975:18)\n' + '    at <unknown> (http://localhost:8080/__cypress/runner/cypress_runner.js:8589:46)\n' + 'From Your Spec Code:\n' + '    at Context.eval (webpack:///./cypress/e2e/spec.cy.js:4:45)', parsedStack: [Array], codeFrame: [Object] }, state: 'failed', pending: false, body: '() => {\n' + "    cy.visit('/page.html');\n" + `    cy.get('input[type="checkbox"]:checked').uncheck();\n` + `    cy.get('input[type="checkbox"]:checked').should('not.exist');\n` + '  }', type: 'test', duration: 7600, wallClockStartedAt: '2023-03-24T01:43:43.038Z', timings: { lifecycle: 197, test: [Object] }, file: null, invocationDetails: { function: 'Suite.eval', fileUrl: 'http://localhost:8080/__cypress/tests?p=cypress/e2e/spec.cy.js', originalFile: 'webpack:///./cypress/e2e/spec.cy.js', relativeFile: 'cypress/e2e/spec.cy.js', absoluteFile: '/Users/markhaussmann/Projects/cypress-issue-checkboxes-repro/cypress/e2e/spec.cy.js', line: 2, column: 2, whitespace: '    ', stack: 'Error\n' + '    at Suite.eval (http://localhost:8080/__cypress/tests?p=cypress/e2e/spec.cy.js:100:3)\n' + '    at ./cypress/e2e/spec.cy.js (http://localhost:8080/__cypress/tests?p=cypress/e2e/spec.cy.js:99:1)\n' + '    at __webpack_require__ (http://localhost:8080/__cypress/tests?p=cypress/e2e/spec.cy.js:20:30)\n' + '    at 0 (http://localhost:8080/__cypress/tests?p=cypress/e2e/spec.cy.js:116:18)\n' + '    at __webpack_require__ (http://localhost:8080/__cypress/tests?p=cypress/e2e/spec.cy.js:20:30)\n' + '    at eval (http://localhost:8080/__cypress/tests?p=cypress/e2e/spec.cy.js:84:18)\n' + '    at eval (http://localhost:8080/__cypress/tests?p=cypress/e2e/spec.cy.js:87:10)\n' + '    at eval (<anonymous>)' }, currentRetry: 0, retries: 0, _slow: 10000 } ] +8s
    1) unchecks all the checked check boxes so no checks are left
  cypress:server:project onMocha test end +16ms
  cypress:server:reporter got mocha event 'test end' with args: [ { _testConfig: { testConfigList: [], unverifiedTestConfig: {}, applied: 'complete' }, id: 'r3', order: 1, title: 'unchecks all the checked check boxes so no checks are left', err: { message: 'Timed out retrying after 4200ms: `cy.uncheck()` failed because the page updated while this command was executing. Cypress tried to locate elements based on this query:\n' + '\n' + '> cy.get(input[type="checkbox"]:checked)\n' + '\n' + 'We initially found matching element(s), but while waiting for them to become actionable, they disappeared from the page. Common situations why this happens:\n' + '  - Your JS framework re-rendered asynchronously\n' + '  - Your app code reacted to an event firing and removed the element\n' + '\n' + 'You can typically solve this by breaking up a chain. For example, rewrite:\n' + '\n' + "> `cy.get('button').click().click()`\n" + '\n' + 'to\n' + '\n' + "> `cy.get('button').as('btn').click()`\n" + "> `cy.get('@btn').click()`\n" + '\n' + 'https://on.cypress.io/element-has-detached-from-dom', name: 'CypressError', stack: 'CypressError: Timed out retrying after 4200ms: `cy.uncheck()` failed because the page updated while this command was executing. Cypress tried to locate elements based on this query:\n' + '\n' + '> cy.get(input[type="checkbox"]:checked)\n' + '\n' + 'We initially found matching element(s), but while waiting for them to become actionable, they disappeared from the page. Common situations why this happens:\n' + '  - Your JS framework re-rendered asynchronously\n' + '  - Your app code reacted to an event firing and removed the element\n' + '\n' + 'You can typically solve this by breaking up a chain. For example, rewrite:\n' + '\n' + "> `cy.get('button').click().click()`\n" + '\n' + 'to\n' + '\n' + "> `cy.get('button').as('btn').click()`\n" + "> `cy.get('@btn').click()`\n" + '\n' + 'https://on.cypress.io/element-has-detached-from-dom\n' + '    at retryActionability (http://localhost:8080/__cypress/runner/cypress_runner.js:130623:82)\n' + '    at tryCatcher (http://localhost:8080/__cypress/runner/cypress_runner.js:8914:23)\n' + '    at Promise.attempt.Promise.try (http://localhost:8080/__cypress/runner/cypress_runner.js:6188:29)\n' + '    at whenStable (http://localhost:8080/__cypress/runner/cypress_runner.js:146732:65)\n' + '    at <unknown> (http://localhost:8080/__cypress/runner/cypress_runner.js:146173:14)\n' + '    at tryCatcher (http://localhost:8080/__cypress/runner/cypress_runner.js:8914:23)\n' + '    at Promise._settlePromiseFromHandler (http://localhost:8080/__cypress/runner/cypress_runner.js:6849:31)\n' + '    at Promise._settlePromise (http://localhost:8080/__cypress/runner/cypress_runner.js:6906:18)\n' + '    at Promise._settlePromise0 (http://localhost:8080/__cypress/runner/cypress_runner.js:6951:10)\n' + '    at Promise._settlePromises (http://localhost:8080/__cypress/runner/cypress_runner.js:7031:18)\n' + '    at Promise._fulfill (http://localhost:8080/__cypress/runner/cypress_runner.js:6975:18)\n' + '    at <unknown> (http://localhost:8080/__cypress/runner/cypress_runner.js:8589:46)\n' + 'From Your Spec Code:\n' + '    at Context.eval (webpack:///./cypress/e2e/spec.cy.js:4:45)', parsedStack: [Array], codeFrame: [Object] }, state: 'failed', pending: false, body: '() => {\n' + "    cy.visit('/page.html');\n" + `    cy.get('input[type="checkbox"]:checked').uncheck();\n` + `    cy.get('input[type="checkbox"]:checked').should('not.exist');\n` + '  }', type: 'test', duration: 7600, wallClockStartedAt: '2023-03-24T01:43:43.038Z', timings: { lifecycle: 197, test: [Object] }, file: null, invocationDetails: { function: 'Suite.eval', fileUrl: 'http://localhost:8080/__cypress/tests?p=cypress/e2e/spec.cy.js', originalFile: 'webpack:///./cypress/e2e/spec.cy.js', relativeFile: 'cypress/e2e/spec.cy.js', absoluteFile: '/Users/markhaussmann/Projects/cypress-issue-checkboxes-repro/cypress/e2e/spec.cy.js', line: 2, column: 2, whitespace: '    ', stack: 'Error\n' + '    at Suite.eval (http://localhost:8080/__cypress/tests?p=cypress/e2e/spec.cy.js:100:3)\n' + '    at ./cypress/e2e/spec.cy.js (http://localhost:8080/__cypress/tests?p=cypress/e2e/spec.cy.js:99:1)\n' + '    at __webpack_require__ (http://localhost:8080/__cypress/tests?p=cypress/e2e/spec.cy.js:20:30)\n' + '    at 0 (http://localhost:8080/__cypress/tests?p=cypress/e2e/spec.cy.js:116:18)\n' + '    at __webpack_require__ (http://localhost:8080/__cypress/tests?p=cypress/e2e/spec.cy.js:20:30)\n' + '    at eval (http://localhost:8080/__cypress/tests?p=cypress/e2e/spec.cy.js:84:18)\n' + '    at eval (http://localhost:8080/__cypress/tests?p=cypress/e2e/spec.cy.js:87:10)\n' + '    at eval (<anonymous>)' }, final: true, currentRetry: 0, retries: 0, _slow: 10000 } ] +17ms

  cypress:server:project onMocha suite end +289ms
  cypress:server:reporter got mocha event 'suite end' with args: [ { id: 'r2', title: 'issue reproduction', root: false, pending: false, type: 'suite', file: null, invocationDetails: { function: './cypress/e2e/spec.cy.js', fileUrl: 'http://localhost:8080/__cypress/tests?p=cypress/e2e/spec.cy.js', originalFile: 'webpack:///./cypress/e2e/spec.cy.js', relativeFile: 'cypress/e2e/spec.cy.js', absoluteFile: '/Users/markhaussmann/Projects/cypress-issue-checkboxes-repro/cypress/e2e/spec.cy.js', line: 1, column: 0, whitespace: '    ', stack: 'Error\n' + '    at ./cypress/e2e/spec.cy.js (http://localhost:8080/__cypress/tests?p=cypress/e2e/spec.cy.js:99:1)\n' + '    at __webpack_require__ (http://localhost:8080/__cypress/tests?p=cypress/e2e/spec.cy.js:20:30)\n' + '    at 0 (http://localhost:8080/__cypress/tests?p=cypress/e2e/spec.cy.js:116:18)\n' + '    at __webpack_require__ (http://localhost:8080/__cypress/tests?p=cypress/e2e/spec.cy.js:20:30)\n' + '    at eval (http://localhost:8080/__cypress/tests?p=cypress/e2e/spec.cy.js:84:18)\n' + '    at eval (http://localhost:8080/__cypress/tests?p=cypress/e2e/spec.cy.js:87:10)\n' + '    at eval (<anonymous>)' }, retries: -1, _slow: 10000 } ] +288ms
  cypress:server:project onMocha test:after:run +8ms
  cypress:server:reporter got mocha event 'test:after:run' with args: [ { _testConfig: { testConfigList: [], unverifiedTestConfig: {}, applied: 'complete' }, id: 'r3', order: 1, title: 'unchecks all the checked check boxes so no checks are left', err: { message: 'Timed out retrying after 4200ms: `cy.uncheck()` failed because the page updated while this command was executing. Cypress tried to locate elements based on this query:\n' + '\n' + '> cy.get(input[type="checkbox"]:checked)\n' + '\n' + 'We initially found matching element(s), but while waiting for them to become actionable, they disappeared from the page. Common situations why this happens:\n' + '  - Your JS framework re-rendered asynchronously\n' + '  - Your app code reacted to an event firing and removed the element\n' + '\n' + 'You can typically solve this by breaking up a chain. For example, rewrite:\n' + '\n' + "> `cy.get('button').click().click()`\n" + '\n' + 'to\n' + '\n' + "> `cy.get('button').as('btn').click()`\n" + "> `cy.get('@btn').click()`\n" + '\n' + 'https://on.cypress.io/element-has-detached-from-dom', name: 'CypressError', stack: 'CypressError: Timed out retrying after 4200ms: `cy.uncheck()` failed because the page updated while this command was executing. Cypress tried to locate elements based on this query:\n' + '\n' + '> cy.get(input[type="checkbox"]:checked)\n' + '\n' + 'We initially found matching element(s), but while waiting for them to become actionable, they disappeared from the page. Common situations why this happens:\n' + '  - Your JS framework re-rendered asynchronously\n' + '  - Your app code reacted to an event firing and removed the element\n' + '\n' + 'You can typically solve this by breaking up a chain. For example, rewrite:\n' + '\n' + "> `cy.get('button').click().click()`\n" + '\n' + 'to\n' + '\n' + "> `cy.get('button').as('btn').click()`\n" + "> `cy.get('@btn').click()`\n" + '\n' + 'https://on.cypress.io/element-has-detached-from-dom\n' + '    at retryActionability (http://localhost:8080/__cypress/runner/cypress_runner.js:130623:82)\n' + '    at tryCatcher (http://localhost:8080/__cypress/runner/cypress_runner.js:8914:23)\n' + '    at Promise.attempt.Promise.try (http://localhost:8080/__cypress/runner/cypress_runner.js:6188:29)\n' + '    at whenStable (http://localhost:8080/__cypress/runner/cypress_runner.js:146732:65)\n' + '    at <unknown> (http://localhost:8080/__cypress/runner/cypress_runner.js:146173:14)\n' + '    at tryCatcher (http://localhost:8080/__cypress/runner/cypress_runner.js:8914:23)\n' + '    at Promise._settlePromiseFromHandler (http://localhost:8080/__cypress/runner/cypress_runner.js:6849:31)\n' + '    at Promise._settlePromise (http://localhost:8080/__cypress/runner/cypress_runner.js:6906:18)\n' + '    at Promise._settlePromise0 (http://localhost:8080/__cypress/runner/cypress_runner.js:6951:10)\n' + '    at Promise._settlePromises (http://localhost:8080/__cypress/runner/cypress_runner.js:7031:18)\n' + '    at Promise._fulfill (http://localhost:8080/__cypress/runner/cypress_runner.js:6975:18)\n' + '    at <unknown> (http://localhost:8080/__cypress/runner/cypress_runner.js:8589:46)\n' + 'From Your Spec Code:\n' + '    at Context.eval (webpack:///./cypress/e2e/spec.cy.js:4:45)', parsedStack: [Array], codeFrame: [Object] }, state: 'failed', pending: false, body: '() => {\n' + "    cy.visit('/page.html');\n" + `    cy.get('input[type="checkbox"]:checked').uncheck();\n` + `    cy.get('input[type="checkbox"]:checked').should('not.exist');\n` + '  }', type: 'test', duration: 7600, wallClockStartedAt: '2023-03-24T01:43:43.038Z', wallClockDuration: 7627, timings: { lifecycle: 197, test: [Object] }, file: null, invocationDetails: { function: 'Suite.eval', fileUrl: 'http://localhost:8080/__cypress/tests?p=cypress/e2e/spec.cy.js', originalFile: 'webpack:///./cypress/e2e/spec.cy.js', relativeFile: 'cypress/e2e/spec.cy.js', absoluteFile: '/Users/markhaussmann/Projects/cypress-issue-checkboxes-repro/cypress/e2e/spec.cy.js', line: 2, column: 2, whitespace: '    ', stack: 'Error\n' + '    at Suite.eval (http://localhost:8080/__cypress/tests?p=cypress/e2e/spec.cy.js:100:3)\n' + '    at ./cypress/e2e/spec.cy.js (http://localhost:8080/__cypress/tests?p=cypress/e2e/spec.cy.js:99:1)\n' + '    at __webpack_require__ (http://localhost:8080/__cypress/tests?p=cypress/e2e/spec.cy.js:20:30)\n' + '    at 0 (http://localhost:8080/__cypress/tests?p=cypress/e2e/spec.cy.js:116:18)\n' + '    at __webpack_require__ (http://localhost:8080/__cypress/tests?p=cypress/e2e/spec.cy.js:20:30)\n' + '    at eval (http://localhost:8080/__cypress/tests?p=cypress/e2e/spec.cy.js:84:18)\n' + '    at eval (http://localhost:8080/__cypress/tests?p=cypress/e2e/spec.cy.js:87:10)\n' + '    at eval (<anonymous>)' }, final: true, currentRetry: 0, retries: 0, _slow: 10000 } ] +8ms

  0 passing (8s)
  1 failing

  1) issue reproduction
       unchecks all the checked check boxes so no checks are left:
     CypressError: Timed out retrying after 4200ms: `cy.uncheck()` failed because the page updated while this command was executing. Cypress tried to locate elements based on this query:

> cy.get(input[type="checkbox"]:checked)

We initially found matching element(s), but while waiting for them to become actionable, they disappeared from the page. Common situations why this happens:
  - Your JS framework re-rendered asynchronously
  - Your app code reacted to an event firing and removed the element

You can typically solve this by breaking up a chain. For example, rewrite:

> `cy.get('button').click().click()`

to

> `cy.get('button').as('btn').click()`
> `cy.get('@btn').click()`

https://on.cypress.io/element-has-detached-from-dom
      at retryActionability (http://localhost:8080/__cypress/runner/cypress_runner.js:130623:82)
      at tryCatcher (http://localhost:8080/__cypress/runner/cypress_runner.js:8914:23)
      at Promise.attempt.Promise.try (http://localhost:8080/__cypress/runner/cypress_runner.js:6188:29)
      at whenStable (http://localhost:8080/__cypress/runner/cypress_runner.js:146732:65)
      at <unknown> (http://localhost:8080/__cypress/runner/cypress_runner.js:146173:14)
      at tryCatcher (http://localhost:8080/__cypress/runner/cypress_runner.js:8914:23)
      at Promise._settlePromiseFromHandler (http://localhost:8080/__cypress/runner/cypress_runner.js:6849:31)
      at Promise._settlePromise (http://localhost:8080/__cypress/runner/cypress_runner.js:6906:18)
      at Promise._settlePromise0 (http://localhost:8080/__cypress/runner/cypress_runner.js:6951:10)
      at Promise._settlePromises (http://localhost:8080/__cypress/runner/cypress_runner.js:7031:18)
      at Promise._fulfill (http://localhost:8080/__cypress/runner/cypress_runner.js:6975:18)
      at <unknown> (http://localhost:8080/__cypress/runner/cypress_runner.js:8589:46)
  From Your Spec Code:
      at Context.eval (webpack:///./cypress/e2e/spec.cy.js:4:45)

Other

No response

lmiller1990 commented 1 year ago

I reproduced with the repository provided. Definitely a bug, we should fix this.

Would you be interested in making a PR by any chance?

mhssmnn commented 1 year ago

I'd love to, but I wouldn't know where to start. 🤷

lmiller1990 commented 1 year ago

If you'd like to give it a try

You can let me know if you need more help/tips - I don't know exactly how to fix this off the top of my head, but that should point you in the right direction.

mhssmnn commented 1 year ago

I've created a PR with the failing test so far.

After some investigation, I think the issue stems from changes made in https://github.com/cypress-io/cypress/pull/24628.

Specifically the changes that requery the DOM:

https://github.com/cypress-io/cypress/blob/9ae911f396fa6cac0a1464537d1492d68cbb1898/packages/driver/cypress/e2e/commands/actions/check.cy.js#L101-L120

So if :checked is used in a selector, when the DOM is re-queried after a un/check event the selector will be dealing with a subset of the original elements. That will render the index of this line incorrect:

https://github.com/cypress-io/cypress/blob/9ae911f396fa6cac0a1464537d1492d68cbb1898/packages/driver/src/cy/commands/actions/check.ts#L140

lmiller1990 commented 1 year ago

Ahh https://github.com/cypress-io/cypress/pull/24628 is a likely candidate for a breaking change, it rejiggered the internals quite a bit. Great detective work.

cypress-app-bot commented 11 months ago

This issue has not had any activity in 180 days. Cypress evolves quickly and the reported behavior should be tested on the latest version of Cypress to verify the behavior is still occurring. It will be closed in 14 days if no updates are provided.

AviMol commented 6 months ago

I'm with cy13 and still facing this issue

cypress-app-bot commented 2 weeks ago

This issue has not had any activity in 180 days. Cypress evolves quickly and the reported behavior should be tested on the latest version of Cypress to verify the behavior is still occurring. It will be closed in 14 days if no updates are provided.

cypress-app-bot commented 3 days ago

This issue has been closed due to inactivity.