Skip to content
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

Is it possible to handle errors during .get? We need to put a retry process in place. #395

Closed
cristopher-rodrigues opened this issue Jan 26, 2017 · 22 comments

Comments

@cristopher-rodrigues
Copy link

[QUESTION]

Is possible handler .get error? Because I need retry process case element not exists in DOM.

@cristopher-rodrigues cristopher-rodrigues changed the title How to handler .get error Is it possible to handle errors during .get? We need to put a retry process in place. Jan 26, 2017
@brian-mann
Copy link
Member

brian-mann commented Jan 26, 2017

The problem here is likely your approach.

There is not and will never be a way to catch or recover from errors in Cypress. The moment error handling is introduced would create a scenario where it becomes logically impossible to consistently reproduce a test case.

It's like trying to write a test that tests whether a process may crash. The problem is that you have no idea if or when it would crash. So to write a test you'd basically have to construct arbitrary time requirements. If the process does not crash in 10 seconds, or if the process does not crash in 10 days. Else you'd be waiting potentially until the heat death of the universe because in fact the process may never crash.

Testing in Cypress is the same way. You cannot recover from errors because you the programmer must tell us what and when you expect state to be reached in your application. If you created two flows like - do this IF this thing exists, else do something else if this thing does NOT exist - it's impossible for a robot to understand when it should or not should give up trying.

I might be way off on my bearings for your question, so let's approach it more pragmatically:

By default Cypress assumes whenever you cy.get an element - for that element to exist. It will wait around until it does exist or it will time out. If it times, the test fails.

If you want Cypress to wait until the element DOES NOT EXIST, you simply add that as an assertion.

cy.get("button").should("not.exist")

Now we know to retry until the element does not exist, or we time out and the test errors.

However if what you're asking is - how do I tell Cypress to do something different IF THE ELEMENT DOES NOT EXIST - then that's the whole problem. How does Cypress know when or when not the element should exist? Should it wait for an arbitrary amount of time? If so how much? It's logically impossible to dictate fallback strategies because it cannot be known when something will happen, it can only be known when it has already happened.

You could achieve this yourself but if you do this, your tests will not consistently pass or fail if you are using a modern JS framework - because there is no guarantee that what you're querying for is about to exist.

// we DO NOT RECOMMEND doing this
cy.get("body").then(($body) => {
  // synchronously query for element
  if ($body.find("element").length) {
    // do something
   } else {
    // do something else
   }
})

If what I've written is way off, please provide some code to further explain what you're trying to do.

@robert-king
Copy link

robert-king commented Jan 17, 2019

my use case is this beforeEach:

cy.visit('my-profile-page')

if (not at my profile page) {
  cy.visit('/cypress-quick-login')
  cy.visit('my-profile-page')
}

Just prevents having to login each time, i just assume you're usually logged in, but sometimes a test will fail if not logged in. (Note, I'm using firebase and found it difficult implementing a cypress function for login)

@robert-king
Copy link

I guess a better solution would be to only visiti cypress-quick-login before all my tests are run if i haven't logged in in a while.

@waitsangbv
Copy link

There are loads of scenario in which a system under test could legitimately behave differently at a given time. It could be due to data variations, system readiness, preset conditions etc. So in web testing elementA or elementB may show at a given point in the web navigation, both scenario are valid. A good test script should be able to handle this. If cypress cannot handle this kind of if (get elementA) else (get elementB) scenario, it is a major drawback.

@ITsolution-git
Copy link

Is there any update on this feature? To handle if element does not exist?

@ankitharendra
Copy link

The problem here is likely your approach.

There is not and will never be a way to catch or recover from errors in Cypress. The moment error handling is introduced would create a scenario where it becomes logically impossible to consistently reproduce a test case.

It's like trying to write a test that tests whether a process may crash. The problem is that you have no idea if or when it would crash. So to write a test you'd basically have to construct arbitrary time requirements. If the process does not crash in 10 seconds, or if the process does not crash in 10 days. Else you'd be waiting potentially until the heat death of the universe because in fact the process may never crash.

Testing in Cypress is the same way. You cannot recover from errors because you the programmer must tell us what and when you expect state to be reached in your application. If you created two flows like - do this IF this thing exists, else do something else if this thing does NOT exist - it's impossible for a robot to understand when it should or not should give up trying.

I might be way off on my bearings for your question, so let's approach it more pragmatically:

By default Cypress assumes whenever you cy.get an element - for that element to exist. It will wait around until it does exist or it will time out. If it times, the test fails.

If you want Cypress to wait until the element DOES NOT EXIST, you simply add that as an assertion.

cy.get("button").should("not.exist")

Now we know to retry until the element does not exist, or we time out and the test errors.

However if what you're asking is - how do I tell Cypress to do something different IF THE ELEMENT DOES NOT EXIST - then that's the whole problem. How does Cypress know when or when not the element should exist? Should it wait for an arbitrary amount of time? If so how much? It's logically impossible to dictate fallback strategies because it cannot be known when something will happen, it can only be known when it has already happened.

You could achieve this yourself but if you do this, your tests will not consistently pass or fail if you are using a modern JS framework - because there is no guarantee that what you're querying for is about to exist.

// we DO NOT RECOMMEND doing this
cy.get("body").then(($body) => {
  // synchronously query for element
  if ($body.find("element").length) {
    // do something
   } else {
    // do something else
   }
})

If what I've written is way off, please provide some code to further explain what you're trying to do.

I create a project for testing of my site and my team is working on that. And I want when my test if fails then I can give a meaning full error according to my project. Because my team members are not familiar with the cypress, and with cypress error. Is there is any way of changing the errors? Please answer this...

@nishant-sngl
Copy link

nishant-sngl commented Jun 25, 2019

// we DO NOT RECOMMEND doing this
cy.get("body").then(($body) => {
  // synchronously query for element
  if ($body.find("element").length) {
    // do something
   } else {
    // do something else
   }
})

In the above suggested code, if i have to use the length of the element outside. How can i do that as i cant keep my remaining code inside then().
I tried the below code. But it prints 0 outside the loop BUT 1 inside the loop. Why is the value of x not reflecting outside.

let x = 0;
cy.get("body").then(($body) => {
  x = $body.find("element").length;
  cy.log(x);
})
cy.log(x);

Please someone suggest on this.
Or if there is some other way to find the number of elements present, without throwing error of Element not found.

@waterlink
Copy link

@nishant-sngl cy.get(..).then(..) is asynchronous, so your cy.log(x) will be executed way before x = $body.find("element").length;.

@adamunchained
Copy link

There are loads of scenario in which a system under test could legitimately behave differently at a given time. It could be due to data variations, system readiness, preset conditions etc. So in web testing elementA or elementB may show at a given point in the web navigation, both scenario are valid. A good test script should be able to handle this. If cypress cannot handle this kind of if (get elementA) else (get elementB) scenario, it is a major drawback.

Completely agree!

@brian-mann your opinion on this problem is very idealistic. Every complex application has if this do that on certain elements and Cypress should support that.

@big-gulp

This comment has been minimized.

@flotwig
Copy link
Contributor

flotwig commented Feb 19, 2020

@big-gulp Cypress retries for you automatically. You can use the timeout option on cy.get to do what you're describing today:

https://docs.cypress.io/api/commands/get.html#Syntax

cy.get('.sometimes-slow-sometimes-fast', { timeout: 60000 })

@cjancsar
Copy link

So... is it possible to handle errors during get? Is there a hacky-way to do this?

@narsaynorath
Copy link

narsaynorath commented Jun 16, 2020

I'm also interested in any solution to this. I have a webpage that is only updated through refreshing, but there's some data/state I want to test that's updated by a background process.

If I setup my tests through the API and then navigate to the page, I'm finding that my background process hasn't completed in time for me to test the scenario I want. This would be in addition to the tests I have before the background process finishes.

@hungdao-testing
Copy link

hungdao-testing commented Jun 27, 2020

In case I need to use both of 2 ways to query elements

Cypress.Commands.overwrite("get", (element) => {
  if(cy.get(element) return error) return cy.xpath(element)
return cy.xpath(element)
});

It helps team member use 1 command cy.get() through our framework built on cypress, no need to switch between cy.xpath and cy.get. So it's really helpful we could have Error handling in get method.

Please let me know if my concerns are not suitable to this thread.

@spokerman12
Copy link

Hi! First of all, thank you all for maintaining Cypress. It's been quite useful!
But I must weigh in on this issue. It's necessary for .get to have perhaps a flag/option to not return an assertion. The reason is simple:
get(x) assumes that x exists, but asserting existence of elements is a standard testing procedure.
This comes from get(x) not being the same as "if exists(x), get(x)", but rather "just get(x) and let's see what happens"

@thaotrant
Copy link

There are loads of scenario in which a system under test could legitimately behave differently at a given time. It could be due to data variations, system readiness, preset conditions etc. So in web testing elementA or elementB may show at a given point in the web navigation, both scenario are valid. A good test script should be able to handle this. If cypress cannot handle this kind of if (get elementA) else (get elementB) scenario, it is a major drawback.

Totally agree with you. I have same problem about could not handle the not existed element. Looking forward to having this feature...

@VinothKumar-Tester
Copy link

Whether anyone got solution for this? At some places, I dont want Cypress to throw Assertion error when cy.get() fails. How can be done? And also How can we handle exception in cypress ?

@jennifer-shehane
Copy link
Member

I'd suggest following along with this issue for tracking conditional testing in Cypress: #3757

@jacobprouse
Copy link

I've had the same issue, in our tests we don't know what is on the page at load. So if we are testing a page of content for images, we want to skip the image tests if there are no images. We conditionally run our tests by running a command that checks the DOM using the selector with some vanilla js, and either returns it or skips the test.

describe('Images', function () {
  beforeEach(function () {
    cy.getIfExists({ selector: 'img', variableName: 'images' })
  })

  // This test will only run if the there are elements matching the selector on the page.
  it('no external images', function () {
    cy.get('@images')
      ...checks
  })
})

using a custom function that checks the DOM.

// cypress/support/commands.js
/**
 * Conditionally run tests based on the result of a selector. Using this is a (`before`|`beforEach`) hook
 * will skip the entire suite if it doesn't exist on the page by default. In an `it` block it will just skip the current test.
 * To disable this behaviour pass in false for the `skip` parameter.
 * @param {object} param The selector to see if the test subject is on the page.
 * @param {string} param.selector The selector to see if the test subject is on the page.
 * @param {string=} param.variableName The name of the variable to be added to `this` context.
 * @param {boolean=} param.skip Whether or not to call the `this.skip` method in the current block if its not on the page.
 * @returns {Cypress.Chainable<any>} The result of a cy.get() query using the provided selector.
 */
function getIfExists ({ selector, variableName, skip = true }) {
  // Access the document object.
  cy.document().then(($document) => {
  // Perform a search query with the selector.
    const documentResult = $document.querySelectorAll(selector)

    // If there is a result, we want to use Cypress.get() to store the cypress result instead of the vanilla js result.
    if (documentResult.length) {
    // Store it as this.<variable> and return the result. It will be accessiblein siblings and descendants.
    // If we want to store the result as a variable.
      if (variableName) {
      // Store it as this.<variable> and return the result. It will be accessiblein siblings and descendants, and via alias in Cypress commands (i.e. cy.get('@variableName')).
        return cy.get(selector)
          .should('exist')
          .as(variableName)
      } else {
        return cy.get(selector)
          .should('exist')
      }
      // If there are no results, end the test early.
    } else if (!documentResult.length && skip) {
    // Log the reason.
      cy.log('Test subject not in DOM, skipping this test.')
        .then(() => {
        // End the test.
          this.skip()
        })
    } else if (!documentResult.length && !skip) {
      return cy.get(selector)
    }
  })
}

Cypress.Commands.add('getIfExists', getIfExists)

Hope this helps someone! And thank you Cypress team, you've made testing a lot easier!

@theoturner
Copy link

Simplified version of @jacobprouse's solution

function ifExists (selector) {
  cy.document().then(($document) => {
    const documentResult = $document.querySelectorAll(selector)
    if (documentResult.length) {
      // Do something
    }
  })
}
Cypress.Commands.add('ifExists', ifExists)

Use with cy.ifExists('.myclass')

@mohanhbk
Copy link

// we DO NOT RECOMMEND doing this
cy.get("body").then(($body) => {
  // synchronously query for element
  if ($body.find("element").length) {
    // do something
   } else {
    // do something else
   }
})

In the above suggested code, if i have to use the length of the element outside. How can i do that as i cant keep my remaining code inside then(). I tried the below code. But it prints 0 outside the loop BUT 1 inside the loop. Why is the value of x not reflecting outside.

let x = 0;
cy.get("body").then(($body) => {
  x = $body.find("element").length;
  cy.log(x);
})
cy.log(x);

Please someone suggest on this. Or if there is some other way to find the number of elements present, without throwing error of Element not found.

Is there any solution for this scenario?

@camiloesub
Copy link

The problem here is likely your approach.

There is not and will never be a way to catch or recover from errors in Cypress. The moment error handling is introduced would create a scenario where it becomes logically impossible to consistently reproduce a test case.

It's like trying to write a test that tests whether a process may crash. The problem is that you have no idea if or when it would crash. So to write a test you'd basically have to construct arbitrary time requirements. If the process does not crash in 10 seconds, or if the process does not crash in 10 days. Else you'd be waiting potentially until the heat death of the universe because in fact the process may never crash.

Testing in Cypress is the same way. You cannot recover from errors because you the programmer must tell us what and when you expect state to be reached in your application. If you created two flows like - do this IF this thing exists, else do something else if this thing does NOT exist - it's impossible for a robot to understand when it should or not should give up trying.

I might be way off on my bearings for your question, so let's approach it more pragmatically:

By default Cypress assumes whenever you cy.get an element - for that element to exist. It will wait around until it does exist or it will time out. If it times, the test fails.

If you want Cypress to wait until the element DOES NOT EXIST, you simply add that as an assertion.

cy.get("button").should("not.exist")

Now we know to retry until the element does not exist, or we time out and the test errors.

However if what you're asking is - how do I tell Cypress to do something different IF THE ELEMENT DOES NOT EXIST - then that's the whole problem. How does Cypress know when or when not the element should exist? Should it wait for an arbitrary amount of time? If so how much? It's logically impossible to dictate fallback strategies because it cannot be known when something will happen, it can only be known when it has already happened.

You could achieve this yourself but if you do this, your tests will not consistently pass or fail if you are using a modern JS framework - because there is no guarantee that what you're querying for is about to exist.

// we DO NOT RECOMMEND doing this
cy.get("body").then(($body) => {
  // synchronously query for element
  if ($body.find("element").length) {
    // do something
   } else {
    // do something else
   }
})

If what I've written is way off, please provide some code to further explain what you're trying to do.

This is a relly bad answer to the case that he is presenting

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests