cypress-io / cypress

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

Calling cy commands and assertions from within Cypress event listeners does not work #6316

Open CSchulz opened 4 years ago

CSchulz commented 4 years ago

Current behavior:

I want to log all unexpected application errors by using the uncaught:exception and calling a task. But it seems that a task always needs a return value.

Uncaught Uncaught CypressError: Cypress detected that you returned a promise from a command while also invoking one or more cy commands in that promise.

The command that returned the promise was:

  > cy.get()

The cy command you invoked inside the promise was:

  > cy.task()

Because Cypress commands are already promise-like, you don't need to wrap them or return your own promise.

Cypress will resolve your command with whatever the final Cypress command yields.

The reason this is an error instead of a warning is because Cypress internally queues commands serially whereas Promises execute as soon as they are invoked. Attempting to reconcile this would prevent Cypress from ever resolving.

Returning null results into another weird behavior:

CypressError: You attempted to make a chai-jQuery assertion on an object that is neither a DOM object or a jQuery object.

The chai-jQuery assertion you used was:

  > class

The invalid subject you asserted on was:

  > null

To use chai-jQuery assertions your subject must be valid.

This can sometimes happen if a previous assertion changed the subject.

It seems that cypress tries to use the subject from the called task even if the task was called by uncaught:exception event.

Desired behavior:

I would expect the subject returned by a task called from the uncaught:exception event will be ignored.

Test code to reproduce

support.js

  Cypress.on('uncaught:exception', (error) => {
    cy.task('uncaughtException', {name: error.name, message: error.message, stack: error.stack}, {log: false});
  });

plugins.js

  on('task', {
      uncaughtException: function (error) {
        logger.error(`Uncaught exception ${error.name}`, error.stack);
        return null;
      },
    }
  )

Versions

3.8.2

jennifer-shehane commented 4 years ago

I simplified this example down

index.html

<!DOCTYPE html>
<html>
  <body>
    <script>
      throw new Error('app error')
    </script>
  </body>
</html>

spec.js

Cypress.on('uncaught:exception', () => {
  cy.task('uncaughtException', 'foo')
})

it('throws', () => {
  cy.visit('error.html')
})

plugins/index.js

module.exports = (on, config) => {
  on('task', {
    uncaughtException () {
      console.log('foo')
      return null
    },
  })
}

It prints the task to the Command Log, but doesn't actually seem to run the code within the task.

Screen Shot 2020-02-04 at 11 19 36 PM
CSchulz commented 4 years ago

@jennifer-shehane Thanks for your efforts. You should see the 'foo' output on the command line.

jennifer-shehane commented 4 years ago

This behavior actually applies to any cy command within an uncaught:exception handler. I just do not think that we actually support running cypress commands within this handler.

gustawx commented 4 years ago

@jennifer-shehane this feature would be very useful. For me especially calling cy.task() inside of a listener. I face also a very similar issue, but within the Cypress.overwrite(...) My case is that i save a log file from test execution. For that i use winston I wanted to log request headers and other stuff by overwriting request() but unfortunately my log("some info") inside of Cypress.overwrite(...) is just ignored. No error nor message. Do you have some thoughts about that?

Do yo think fix for this issue will solve also my problem with logging?

dcmariotti commented 4 years ago

Couldn't add context to report because cy.task() does not work inside Cypress.on('test:after:run'

version 4.0.2

dave-newson commented 4 years ago

I'm bumping into this issue with trying to call a cy.task inside a Cypress.on('window:load') event, as I'd like to collect some additional page data into a report with every page that's loaded.

The condition for the error is here: https://github.com/cypress-io/cypress/blob/develop/packages/driver/src/cypress/cy.js#L365

I've found that if I side-step this condition so the error isn't thrown, I can run my cy.task and nothing seems to go wrong with Cypress, so it feels like this is a sledgehammer solution?

Can anyone give any clarity over why this check exists?

Here's all the history I could dig up: https://github.com/cypress-io/cypress/commit/bf0353562ca081fedf96ef30f86e053304abb33a https://github.com/cypress-io/cypress/commit/cd8a96d99500f29b4814bd0e8a56aee1a6d504f1

Edit: Work-around, for my use case

I've worked around this using a similar mechanism to the code coverage driver: https://github.com/cypress-io/code-coverage/blob/master/support.js#L144

During my window:load listener I'm now saving the entries to a window.__my_handy_var__, and then in after I'm able to call cy.task and send that data to the back-end task.

Prabhs2 commented 3 years ago

Why can't we pass a function that holds return cy.get("elementpath") to the methods defined in commands.js?

I have method defined in a testPO.js getTextbox(){ return cy.get("[input='text']") }

and a function under commands.js Cypress.Commands.add("enterText", (element, text_to_enter) => { //code to enter the text })

I am calling cy.enterText(getTextbox(), "test") method in test2.js file, this is throwing as error saying

You attempted to make a chai-jQuery assertion on an object that is neither a DOM object or a jQuery object.

The chai-jQuery assertion you used was:

visible

The invalid subject you asserted on was:

undefined

To use chai-jQuery assertions your subject must be valid.

This can sometimes happen if a previous assertion changed the subject.

jbarriojim commented 3 years ago

It will be awesome to have this. I need to search for a file in a folder. I cannot use fs in Cypress.on("test:after:run") and I did on a cy.task.

xeger commented 3 years ago

I don't need to be able to run Cypress commands inside an uncaught:exception; however, it would be lovely if Cypress would warn me what's happening, by forbidding me from running commands and failing the test with an explicit, clear message explaining that I can't do that.

I value the safeguards that Cypress has, which fail tests when I do an invalid thing. (In fact, it is one of these very safeguards that's triggering in the issue -- it's just not the safeguard that most accurately describes what I did wrong!)

My use case is:

  1. cy.visit() the app
  2. the app throws an uncaught exception
  3. Cypress delegates to my handler, which cy.log's the ignored app exception
  4. Cypress complains I ran a log in the middle of visit which has returned a promise

Of course, I don't control the implementation of cy.visit() or the fact that it returns a promise, so the root cause is very hard to ascertain!

If Cypress had a 2nd level safeguard that prevents me from running commands during an on that doesn't support them, it would have saved me 3 hours of debugging time. This is probably a much easier goal than fully supporting command execution in an event handler.

jennifer-shehane commented 3 years ago

We could likely throw an error in this situation, to at least help people to understand that calling Cypress commands is not possible from within Cypress event listeners.

You could possibly work around this by calling the original command in the event listener in cy.now(). This leads to some confusing logging and I wouldn't necessary recommend it, but it does seem to possibly run the command in some circumstances.

Cypress.on('uncaught:exception', () => {
  cy.now('task', 'nameOfTask')

  return false
})
rilham97 commented 1 year ago

Hi @jennifer-shehane I got same issue, and I tried to use cy.now but I got blocker if the task requires input parameter how we handle it? e.g cy.task('nameOfTask', 'params')

sneko commented 1 year ago

@jennifer-shehane I see that people liked your workaround, but could you please share the same to log? Fighting with this but no success.

Thank you,

KleaTech commented 10 months ago

Is there any updates or plans on this? The cy.now workaround no longer works.