From 0e0743aba79989b6bc9de7ff6e7a6dad9662409d Mon Sep 17 00:00:00 2001 From: Blue F Date: Tue, 27 Sep 2022 12:20:26 -0700 Subject: [PATCH 01/39] feat: Error if command used inside .should() callback (#23734) * feat: Error if command used inside .should() callback * Update packages/driver/cypress/e2e/commands/assertions.cy.js Co-authored-by: Zach Bloomquist * Match error message and test Co-authored-by: Zach Bloomquist --- .../cypress/e2e/commands/assertions.cy.js | 13 +++++ packages/driver/src/cy/commands/asserting.ts | 13 +++++ packages/driver/src/cypress/error_messages.ts | 54 +++++++++++-------- 3 files changed, 58 insertions(+), 22 deletions(-) diff --git a/packages/driver/cypress/e2e/commands/assertions.cy.js b/packages/driver/cypress/e2e/commands/assertions.cy.js index d09ed0d52bbb..ec834d144d7a 100644 --- a/packages/driver/cypress/e2e/commands/assertions.cy.js +++ b/packages/driver/cypress/e2e/commands/assertions.cy.js @@ -325,6 +325,19 @@ describe('src/cy/commands/assertions', () => { }) }) + // https://github.com/cypress-io/cypress/issues/22587 + it('does not allow cypress commands inside the callback', (done) => { + cy.on('fail', (err) => { + expect(err.message).to.eq('`cy.should()` failed because you invoked a command inside the callback. `cy.should()` retries the inner function, which would result in commands being added to the queue multiple times. Use `cy.then()` instead of `cy.should()`, or move any commands outside the callback function.\n\nThe command invoked was:\n\n > `cy.log()`') + + done() + }) + + cy.window().should((win) => { + cy.log(win) + }) + }) + context('remote jQuery instances', () => { beforeEach(function () { this.remoteWindow = cy.state('window') diff --git a/packages/driver/src/cy/commands/asserting.ts b/packages/driver/src/cy/commands/asserting.ts index cdb37022ec31..8d4bc568c283 100644 --- a/packages/driver/src/cy/commands/asserting.ts +++ b/packages/driver/src/cy/commands/asserting.ts @@ -34,12 +34,25 @@ export default function (Commands, Cypress, cy, state) { const shouldFnWithCallback = function (subject, fn) { state('current')?.set('followedByShouldCallback', true) + const commandEnqueued = (obj) => { + $errUtils.throwErrByPath( + 'should.command_inside_should', { + args: { action: obj.name }, + }, + ) + } + return Promise .try(() => { const remoteSubject = cy.getRemotejQueryInstance(subject) + Cypress.once('command:enqueued', commandEnqueued) + return fn.call(this, remoteSubject ? remoteSubject : subject) }) + .finally(() => { + Cypress.removeListener('command:enqueued', commandEnqueued) + }) .tap(() => { state('current')?.set('followedByShouldCallback', false) }) diff --git a/packages/driver/src/cypress/error_messages.ts b/packages/driver/src/cypress/error_messages.ts index 6bdef06c4642..c56ba1c2bf5a 100644 --- a/packages/driver/src/cypress/error_messages.ts +++ b/packages/driver/src/cypress/error_messages.ts @@ -132,28 +132,6 @@ export default { }, }, - selectFile: { - docsUrl: 'https://on.cypress.io/selectfile', - invalid_action: { - message: `${cmd('selectFile')} \`action\` can only be \`select\` or \`drag-drop\`. You passed: \`{{action}}\`.`, - }, - invalid_array_file_reference: { - message: `${cmd('selectFile')} must be passed an array of Buffers or objects with non-null \`contents\`. At files[{{index}}] you passed: \`{{file}}\`.`, - }, - invalid_single_file_reference: { - message: `${cmd('selectFile')} must be passed a Buffer or an object with a non-null \`contents\` property as its 1st argument. You passed: \`{{file}}\`.`, - }, - multiple_elements: { - message: `${cmd('selectFile')} can only be called on a single element. Your subject contained {{num}} elements.`, - }, - not_file_input: { - message: `${cmd('selectFile')} can only be called on an \`\` or a \`