Cypress test - Do not intercept api request - cypress

I need to test some pages on a project and this project do some APIs call to external services.
I need to make sure that these calls are made and check if my page change accordingly to the response.
This is my test:
describe('A logged in user', () =>{
it('can see his subscriptions', () => {
...... some checks .......
cy.intercept('https://example.com/api/v2/user-panel/get-subscription').as('userSubscription');
cy.wait('#userSubscription', { timeout: 35000 }).then(() => {
cy.contains('some text');
});
});
});
When I run the code seems that it can't se the API call but the page content change correctly.
This is the cypress response:
Timed out retrying after 35000ms: cy.wait() timed out waiting 35000ms
for the 1st request to the route: userSubscription. No request ever
occurred.
I tried to increase the timeout, event if the page loads in 1 second, but the result is the same.
There is something am I missing?

Doing the cy.wait() right after the cy.intercept() is not going to work.
Whatever triggers the API calls (a cy.visit() or a .click()) must occur after the intercept has been set up, and it therefore is ready to catch the API call.
From the Network Requests docs
cy.intercept('/activities/*', { fixture: 'activities' }).as('getActivities')
cy.intercept('/messages/*', { fixture: 'messages' }).as('getMessages')
// visit the dashboard, which should make requests that match
// the two routes above
cy.visit('http://localhost:8888/dashboard')
// pass an array of Route Aliases that forces Cypress to wait
// until it sees a response for each request that matches
// each of these aliases
cy.wait(['#getActivities', '#getMessages'])
// these commands will not run until the wait command resolves above
cy.get('h1').should('contain', 'Dashboard')

Related

How to make Cypress test fail if it makes a request that does not have an intercept associated with it?

How to make Cypress test fail if it makes a request that does not have an intercept associated with it?
Let's say cypress navigates to a page and makes 10 requests. How can I make cypress automatically fail if any of those requests are not intercepted. I don't want any requests made from cypress to NOT be mocked.
Is this possible?
If you want to check the cy.intercept() coverage of app requests, add a middleware intercept.
Generally you want the middleware to catch a broad range of URL's, for example all the API calls would be caught with
cy.intercept('**/api/**/*')
Middleware intercepts always see the captured calls first, and then pass them on to remaining intercepts.
This example checks the GET calls during loading of https://cypress.io/.
cy.intercept('https://www.cypress.io/_astro/**/*') // the intercept in my test
// want to check for unhandled calls
// Middleware intercept - catches all calls
const calls = []
cy.intercept('https://www.cypress.io/**/*', { middleware: true }, (req) => {
calls.push(req.url)
}).as('all')
cy.visit('https://cypress.io/') // now visit the site
cy.then(() => {
// Use routes state info to compare handled calls vs all calls
const handledUrls = Object.values(cy.state('routes'))
.filter(route => route.alias !== 'all')
.map(route => Object.values(route.requests)
.map(interceptedRequest => interceptedRequest.request.url)
})
.flat()
// Find the unhandled calls
const unhandled = calls.filter(call => !handledUrls.includes(call))
console.log('Unhandled calls: ', unhandled)
})
Logs two unhandled calls:
[
'https://www.cypress.io/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js',
'https://www.cypress.io/videos/home_page.webm'
]

How to get cypress to block datadog requests

We recently installed datadogRUM in our application and now so many DD events kick off in my cypress test that they cause a timeout and failure
I have tried cy.intercept in multiple ways:
cy.intercept('POST', 'https://rum.browser-intake-datadoghq.com/*', {
statusCode: 202,
body: {
},
}).as('datadogRUM');
cy.intercept('POST', 'https://rum-http-intake.logs.datadoghq.com/*', {});
cy.intercept(/\.*datadog.*$/, (req) => {
console.log('DATADOG INTERCEPTED');
req.reply("console.log('datadog intercept');");
});
cy.intercept({
method: 'POST',
url: '/\.*datadog.*$/'
}, req => {
req.destroy();
});
cy.intercept('POST', 'https://rum-http-intake.logs.datadoghq.com/*', { forceNetworkError: true });
just to start. I feel like I've tried every possible variation. I also created a cypress.json file in my /cypress folder
{
"blockHosts": "*datadoghq.com/*"
}
I get hundreds of calls back in my network tab to https://rum.browser-intake-datadoghq.com/api/v2/rum with the preview of console.log('datadog intercept') as I've intercepted them. They all display the solid blue line as if they are being intercepted and blocked. When I set the intercept to an alias I see the alias in my cypress runner window. But there are no 503s or 404s anywhere. The page still fills up with events, cypress gets overloaded, and my test times out.
I even tried copying the data-dog-rum.ts from the src/utils folder to cypress/utils and either commenting out everything or setting the sampleRate to 0, no dice.
EDIT: I am able to get the test passing by adding
Cypress.on('uncaught:exception', () => {
// returning false here prevents Cypress from
// failing the test
return false;
});
to my support/index.js but now whether I add a cy.intercept in my test makes absolutely no difference. The page still fills up with datadog requests regardless, and whether they come back as 200/pending/cancelled, they still delay a single it block in a spec to where it takes 60 seconds to run instead of approx 10 seconds
You can use javascript to perform the stub inside the routeHandler
cy.intercept('*', (req) => { // look at everything
if (req.url.includes('datadoghq')) { // add more conditions if needed
req.reply({}) // prevent request reaching the server
}
})
blockhosts should work with
Pass only the host
{
"blockHosts": "*datadoghq.com"
}

Cypress interception is not waiting

I'm using Cypress 6.0.0 new way of interception.
Waiting on a request
I need to wait for the "templatecontract" response in order to click the #template-button-next because otherwise is disabled. But is trying to click it before getting the response from the API. The documentation seems pretty straight forward.
Am I wrong here?
I have also tried just like:
cy.wait('#templatecontract')
cy.get('#template-button-next').click()
it("Test", function() {
cy.intercept(Cypress.env("baseUrl")+`/api/v1/contract-type/templatecontract`).as('templatecontract')
cy.login(Cypress.env('testUserInviteEmail'), Cypress.env('testUserInvitePassword')).then((token) => {
cy.visit(Cypress.env('baseUrl')+"/templates", {headers: {
Authorization: token,
},
});
cy.get('a[href="/create-template"]').click();
cy.get('.template-usecasetitle').contains('UBO-Formular')
cy.get('button[cy-data="Formular"]').click();
cy.get('#title').type("Title for testing");
cy.get('#usecasetitle').type("Usecasetitle for testing")
cy.get('#description').type("Description just for testing")
cy.wait('#templatecontract').then(interceptions => {
cy.get('#template-button-next').click()
});
});
});
I'm not sure why but just setting the method type (POST in this case) have solved the problem.
cy.intercept('POST', Cypress.env("baseUrl")+`/api/v1/contract-type/templatecontract`).as('templatecontract')
I had a similar problem, and the issue was that the first request my application sent was an OPTIONS request.
If you do not include the method as the first argument, all methods (including OPTIONS) are now matched. This can be puzzling as your .wait will be satisfied by the OPTIONS request, not by your second POST request.
Reference: https://docs.cypress.io/api/commands/intercept.html#Comparison-to-cy-route
Reference: https://docs.cypress.io/api/commands/intercept.html#Matching-URL
Interestingly I am getting different results for
cy.intercept("POST", "https://backend.rocketgraph.app/api/signup").as("doSignup")
and
cy.intercept("POST", `${BACKEND_URL}/signup`).as("doSignup")
Not sure what is the issue. Also have to set POST as one of the users have mentioned
The first one is actually intercepted. Weird but happened.
If you spy a route at the top of your test, as you do here, cy.wait() will return immediately if there have already been responses to that route by the time it's called.
As an example, say you notice this in your network tab:
GET some-route: 200
GET some-route: 200
GET some-route: 200
GET some-route: 200
POST something-unique: 200
GET some-route: 500
^ some-route is 500ing at some clearly-identifiable point. Should be easy to catch in a test, right? Well:
it('Should fail on this 500, but doesn't???', () => {
// start spying our indicator; seems good:
cy.intercept('GET', 'something-unique').as('indicator')
// but if we start spying the route here:
cy.intercept('POST', 'some-route').as('route')
// then hit some-route a bunch of times & return:
foo()
// then expect to catch the failure after something-unique fires:
cy.wait('#indicator').its('response.statusCode').should('eq', 200).then( () => {
// then expect the test to fail on the 500:
cy.wait('#route').its('response.statusCode).should('not.eq', 500)
// ...this won't work! this test will pass because cy.wait() will
// succeed and return the *first* GET some-route: 200 !
})
I personally find this to be pretty counter-intuitive - it feels reasonable that a wait() on a spied route would always actually wait for the next request! - but that's apparently not how it works.
One way around this is to not start spying until you're actually ready:
it('Fails on a 500', () => {
cy.intercept('GET', 'something-unique').as('indicator')
foo()
cy.wait('#indicator').its('response.statusCode').should('eq', 200).then( () => {
// don't start spying until ready:
cy.intercept('POST', 'some-route').as('route')
cy.wait('#route').its('response.statusCode).should('not.eq', 500)
// and the test fails correctly; response.statusCode 500 should not equal 500
})

cy.wait(#someXhr) taimeouts

I'm having a problem with stubbing a simple request to an API using the cypress' cy.server() and cy.route().
Here's the failing test:
it.only("should show an error message for server errors", () => {
const name = "It doesnt matter";
const email = "takenemail#yopmail.com";
const pass = "123123";
// run the server and define the stubbed route
cy.server();
cy.route(
"POST",
`${serverBaseUrl}/auth/register`,
"fixture:register-fail.json"
).as("postRegister");
// fill in the registration form and hit submit
cy.visit("/auth/register");
cy.get(selectors.registerForm.name).type(name);
cy.get(selectors.registerForm.email).type(email);
cy.get(selectors.registerForm.password).type(pass);
cy.get(selectors.registerForm.registerButton).click();
// intercept the request and mock it
cy.wait("#postRegister"); // this fails.
cy.get(selectors.registerForm.genericErrors).contains(
"This email has already been taken"
);
});
and the error:
cy.wait() timed out waiting 5000ms for the 1st request to the route: postRegister. No request ever occurred.
Note: even though it says that No request ever occurred. I can still see the request being send and a response received in the console's Network tab (which means the stub has been bypassed and a regular request's been made).
Any ideas what's happening?
Thanks in advance.
Ok, seems like i have found the problem.
It turns out that using the fetch API is not supported by cypress.
The workaround - using whatwg-fetch, which is basically a polyfill for the fetch api, working with XHR behind the scenes.
Install the whatwg-fetch package: npm install whatwg-fetch --save
Import it in your project: import "whatwg-fetch";
Last, but very important - remove the fetch object from the window before every page load in the cypress environment like this:
// you can define this in the commands file for example...
Cypress.on("window:before:load", (win) => delete win.fetch);
or an alternative, per-visit approach:
it("some test", () => {
cy.visit("/url", {
onBeforeLoad: win => delete win.fetch // <---
});
// ...the rest of the test
});
Doing this will kick-in the polyfill and the stubbing should be working properly after this intervention.

How to wait for a successful response in Cypress tests

Background
I use 3 back-end servers to provide fault tolerance for one of my online SaaS application. All important API calls, such as getting user data, contact all 3 servers and use value of first successfully resolved response, if any.
export function getSuccessValueOrThrow$<T>(
observables$: Observable<T>[],
tryUntilMillies = 30000,
): Observable<T> {
return race(
...observables$.map(observable$ => {
return observable$.pipe(
timeout(tryUntilMillies),
catchError(err => {
return of(err).pipe(delay(5000), mergeMap(_err => throwError(_err)));
}),
);
})
);
}
getSuccessValueOrThrow$ get called as following:
const shuffledApiDomainList = ['server1-domain', 'server2-domain', 'server3-domain';
const sessionInfo = await RequestUtils.getSuccessValueOrThrow(
...(shuffledApiDomainList.map(shuffledDomain => this.http.get<SessionDetails>(`${shuffledDomain}/file/converter/comm/session/info`))),
).toPromise();
Note: if one request resolve faster than others, usually the case, race rxjs function will cancel the other two requests. On Chrome dev network tab it will look like bellow where first request sent out was cancelled due to being too slow.
Question:
I use /file/converter/comm/session/info (lets call it Endpoint 1) to get some data related to a user. This request dispatched to all 3 back-end servers. If one resolve, then remaining 2 request will be cancelled, i.e. they will return null.
On my Cypress E2E test I have
cy.route('GET', '/file/converter/comm/session/info').as('getSessionInfo');
cy.visit('https://www.ps2pdf.com/compress-mp4');
cy.wait('#getSessionInfo').its('status').should('eq', 200)
This sometimes fails if the since getSessionInfo alias was hooked on to a request that ultimately get cancelled by getSuccessValueOrThrow$ because it wasn't the request that succeeded.Bellow image shows how 1 out of 3 request aliased with getSessionInfo succeeded but the test failed since the first request failed.
In Cypress, how do I wait for a successful i.e. status = 200 request?
Approach 1
Use .should() callback and repeat the cy.wait call if status was not 200:
function waitFor200(routeAlias, retries = 2) {
cy.wait(routeAlias).then(xhr => {
if (xhr.status === 200) return // OK
else if (retries > 0) waitFor200(routeAlias, retries - 1); // wait for the next response
else throw "All requests returned non-200 response";
})
}
// Usage example.
// Note that no assertions are chained here,
// the check has been performed inside this function already.
waitFor200('#getSessionInfo');
// Proceed with your test
cy.get('button').click(); // ...
Approach 2
Revise what it is that you want to test in the first place.
Chances are - there is something on the page that tells the user about a successful operation. E.g. show/hide a spinner or a progress bar, or just that the page content is updated to show new data fetched from the backend.
So in this approach you would remove cy.wait() altogether, and focus on what the user sees on the page - do some assertions on the actual page content.
cy.wait() yields an object containing the HTTP request and response properties of the XHR. The error you're getting is because you're looking for property status in the XHR object, but it is a property of the Response Object. You first have to get to the Response Object:
cy.wait('#getSessionInfo').should(xhr => {
expect(xhr.response).to.have.property('status', 200);
});
Edit: Since our backend uses graphql, all calls use the single /graphql endpoint. So I had to come up with a solution to differentiate each call.
I did that by using the onResponse() method of cy.route() and accumulating the data in Cypress environment object:
cy.route({
method: 'GET',
url: '/file/converter/comm/session/info',
onResponse(xhr) {
if (xhr.status === 200) {
Cypress.env('sessionInfo200') = xhr;
}
}
})
You can then use it like this:
cy.wrap(Cypress.env()).should('have.property', 'sessionInfo200');
I wait like this:
const isOk = cy.wait("#getSessionInfo").then((xhr) => {
return (xhr.status === 200);
});

Resources