cypress-io / cypress

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

Clearing previously intercepted routes #23192

Open ezrag26 opened 2 years ago

ezrag26 commented 2 years ago

What would you like?

I'd like to be able to clear previously intercepted routes so that waiting on an aliased intercept will now start from the next interception.

Example:

describe('Interceptions', () => {
  it('Resets interceptions', () => {
    cy.intercept('GET', '/foo').as('foo')

    cy.wait('@foo') // request #1 to /foo

    // Get some variable to see current state

    // request #2 to #n to /foo // there could potentially be more requests, too

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

    cy.resetIntercepts('@foo') // would like to have something like this so that request #2 to #n will be forgotten about

    // request #n+1 to /foo

    cy.wait('@foo') // will yield request #n+1
  })
})

Why is this needed?

If the number of requests is unknown beforehand, using cy.wait() on the same alias is not useful.

For example, I have a request that is called every X seconds, and I want to compare the state after taking some action. There may or may not have been one or more requests between:

  1. the first request where I record the state that I know won't change by the time the action is taken, and
  2. the request after the action was taken

I cannot rely on cy.wait() being called two times, or three times, etc.

Currently I do the following:

describe('Interceptions', () => {
  it('Uses the correct request', () => {
    cy.intercept('GET', '/foo').as('foo')

    cy.wait('@foo') // request #1 to /foo

    // Get some variable to see current state

    // request #2 to #n to /foo

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

    cy.intercept('GET', '/foo').as('bar') // intercept same request, but use a different alias to reference new interceptions without needing to wait on the previous ones

    // request #n+1 to /foo

    cy.wait('@bar') // will yield request #n+1, but with more complex arguments to cy.intercept could be annoying
  })
})

But, as noted, it's not ideal to created a new cy.intercept() call and alias for really the same intercept.

Other

23190 - discussion on the same point

aleixsuau commented 2 years ago

I just want to support this request and add some more context:

   cy.intercept('**/modules/*').as('courseModuleData');
   cy.intercept('**/lessons/*').as('courseLessonData');

   // Trigger 1 - Triggers @courseLessonData' and '@courseModuleData' requests
   // I'm not interested in these ones
   cy.get('.module-overview__action a').click();

   // Wait A - Commented out, not active
   // cy.wait(['@courseLessonData', '@courseModuleData']);

   // Trigger 2 - Triggers @courseLessonData' and '@courseModuleData' requests
   // I want to wait for these ones
   cy.get('.activity-select-right__submit').click(); 

   // Wait B - Refers to the requests from the Trigger 1, not from Trigger 2
   cy.wait(['@courseLessonData', '@courseModuleData']);

This seems to happen even when the previous requests (Trigger 1) have finished, I can see that Wait B passes straight away. This is a bit counter-intuitive, probably it should move to at least the next active request, but I believe it would be more intuitive if cy.wait would target the last request triggered.

If I un-comment the Wait A, then it works fine and the Wait B waits for the requests made by Trigger 2. This seems to mean that cy. wait relies on the order of the requests (Trigger 1 >> Wait B) and not on the order of the code/events (Trigger 2 >> Wait B).

I couldn't find this behavior documented in the docs. If that is right, it would be nice to have it documented.

Thanks!

ezrag26 commented 2 years ago

If I un-comment the Wait A, then it works fine and the Wait B waits for the requests made by Trigger 2. This seems to mean that cy. wait relies on the order of the requests (Trigger 1 >> Wait B) and not on the order of the code/events (Trigger 2 >> Wait B).

I couldn't find the behavior documented in the docs. If that is right, it would be nice to have it documented.

I do believe it's documented here regarding cy.wait(), where it says:

Each time we use cy.wait() for an alias, Cypress waits for the next nth matching request.

So if there were 5 requests matched, no matter when the the 1st use of cy.wait() is made, it will wait on the 1st matched request to finish, the 2nd use on the 2nd matched request, etc. And if the nth request already finished before that nth use of cy.wait(), the code will immediately continue.

It seems that in your case you may have a more determinate behavior where you can wait on that first matched request (albeit it do nothing) as you know there will be just 1 before Trigger 2, while for me the number of matched requests will be unknown. But I do agree that our cases are both similar in that we don't care about the previous interceptions up until some specific trigger, and adding a feature like this would certainly seem to be useful.

aleixsuau commented 2 years ago

I found https://github.com/bahmutov/cypress-wait-if-happens really helpful to achieve the desired functionality, among others (conditional requests).

cy.waitIfHappens({
  alias: '@users',
  timeout: 100,
  lastCall: true,
  yieldResponseBody: true,
})
.should('have.length', 4)
ezrag26 commented 2 years ago

Interesting, I have thought about doing this before (looping through all the calls in order to get to the last one). I would still love a built-in solution from Cypress, but this is certainly more manageable and understandable than what I currently have in place.

Thanks for sharing the find!

piotrpalek commented 2 years ago

I'm using Cypress mostly for Component Testing and this would be great, it would be even better if Cypress would reset the intercept calls between tests, as now they're "global" and leaking over to the next test making it hard to manage them.

emilyrohrbough commented 1 year ago

@piotrpalek You are referring to https://github.com/cypress-io/cypress/issues/20397.

The ask in this issue is to clear intercepts mid-test and/or provide a way to verify the intercept call out to verify the requests/responses.

dmbartle commented 1 year ago

I hit the same issue, found a workaround which will loop over the current list of intercepted requests and run cy.wait on any that haven't been waited on yet. Then can trigger the action and know that the next time I call wait it will be for the brand new request.

I've put this into a custom command so it's easy to call from the tests.

Cypress.Commands.add('clearInterceptList', (interceptAlias) => {
  // clears the list of intercepted requests by waiting for each one
  // intercept alias should be in the form '@postExample' etc.
  cy.get(interceptAlias + '.all').then((browserRequests) => {
    for (let request of browserRequests) {
      if (request.responseWaited === false) {
        cy.wait(interceptAlias)
      }
    }
  })
})

Then can call this from the test like cy.clearInterceptList('@requestAlias') etc.

EduardoRomaguera commented 1 year ago

I hit the same issue. The workaround from @dmbartle worked like a charm with a small mod: The request I was aliasing was being hit 6 times. I actually just wanted to wait on the last one. But because of Cypress speed (and my test flow) some times some of those requests were being canceled. When that happened the for loop was still invoking 6 waits, but because some were canceled a wait was just timing out. The fix is simple, only wait for the request if it was not received; which is the equivalent of a canceled request for the browser. if (request.requestWaited === false && request.state !== "Received") {

I hope this helps someone else until we get the real feature.

olkanevska commented 1 month ago

I have the same issue. Native cypress method to clear previously intercepted routes will be super useful. The workaround from @dmbartle works perfect for me with a small mod:

 Cypress.Commands.add('clearInterceptList', (interceptAlias) => {
  // clears the list of intercepted requests for the same alias by waiting for each one
  cy.get(`@${interceptAlias}.all`).then((browserRequests: JQuery<HTMLElement>) => {
    for (let request of browserRequests) {
      const req = request as unknown as { responseWaited: boolean };
      if (req.responseWaited === false) {
        cy.wait(`@${interceptAlias}`)
      }
    }
  })
});

Thank you @dmbartle .