How to apply condition in cypress intercept - cypress

When I do cy.intercept("URL"), it returns me 409-conflict error ,
My target is to call a function upon 409 error .
How to achieve conditional intercept ,or how can I check in cy.intercept(URL) returns http status as 409, if so I want to call a function (). want to achieve something like the below , how to do so in cypress ?
if(cy.intercept(url(.getHttpStatus==409){
//function call
}

The Cypress team recommends avoiding conditional testing. For a negative test case, you should take the steps to have the URL return a 409 response. With that you will need to the following, to tell Cypress you are expecting a status code other than 2xx or 3xx:
cy.intercept({
url: "URL",
failOnStatusCode: false
})

The conditional statement goes inside the callback, docs are here
cy.intercept(URL, (req) => {
req.continue((res) => {
if (res.statusCode == 409) {
// function call
}
})
})

Related

How do I log a specific field in API requests within Cypress

I want to cypress.log() out a specific field in the request header whenever my webapp makes requests that way when it fails and adds screenshots/logs I can grab that that requestId that failed.
Is there a way to setup cypress so that for all network requests it checks for this field and log it?
I can add a cy.intercept within each individual file but I want a more generic way to handle this.
Cypress.log is the synchronous version of cy.log().
Add middleware: true to the intercept to pass request to other intercepts.
cy.intercept({ url: '*', middleware: true }, (req) => {
const headerValue = req.headers?['x-custom-headers']
if (headerValue) {
Cypress.log({
name: 'x-custom-header',
message: headerValue
})
}
})
You'll get an Cypress promise error if you try to use cy.log() to log out every request header in an cy.intercept() within a routeHandler callback. This would also make it kind of tough to log to a CI terminal as well.
Instead you can console.log to dev tools. To make it apply to all tests, you can wrap it in a beforeEach() and place it in the support/index.js file.
// support/index.js
beforeEach(() => {
cy.intercept('*', (req) => {
req.continue((res) => {
console.log(JSON.stringify(req.headers))
})
})
})

Cypress intercept does not match route with status 204

I am implementing cypress tests in our Angular application and have a problem waiting for a request to finish. I am guessing it has to do with the status of the Request being 204 instead of 200.
This is the function/command I am calling in my test:
export function logout() {
cy.intercept('/api/security/logout').as('logoutRequest');
cy.getCookie('SESSION').then((cookie) => {
if (cookie != null) {
cy.request(
{
method: 'POST',
url: '/api/security/logout',
}
);
}
});
cy.wait('#logoutRequest');
}
My problem is, that the route /api/security/logout is not recognized as alias #logoutRequest and therefore the wait always timeouts. Even though there is a vaild request. As you can see here in the test protocol:
I have tried modifing the route with * or ** but without success. I would be very glad if you could help me out.
You can't use cy.intercept() to catch cy.request().
cy.intercept(), cy.server(), and cy.route()
cy.request() sends requests to actual endpoints, bypassing those defined using cy.route() or cy.intercept()
Just chain .then() off the request to handle the reply
cy.request({method: 'POST', url: '/api/security/logout', failOnStatusCode: false})
.then(response => {
expect(response.status).to.eq(200)
})

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
})

Cypress MOCK api response for different status

I am testing my login component with Cypress (just started with it) and I want to handle three different cases where the API returns status 200, 400 or 500. I want to mock these responses to see how the frontend responds to that.
I want to mock the response for three different cases (200, 400 and 500) when sending a request to my API endpoint http://localhost:9999/api/login
I have written some code based on the docs but I still am not where I want to be.
describe('Login Approach', () => {
it('login', () => {
cy.visit('/login')
// these values email and pw shouldn't matter if mocking is done right
cy.get('#email')
.type('test')
.should('have.value', 'test')
cy.get('#password')
.type('123456')
.should('have.value', '123456')
cy.server()
cy.route({
method: 'POST',
url: 'http://localhost:9999/api/login', // this is the api that I send the request to
})
cy.location('pathname', { timeout: 10000 }).should('eq', '/login');
cy.title().should('include', 'Condeo')
cy.get('#notification').should('exist')
})
})
I am not getting status in the details of the test:
Method Url Stubbed Alias #
POST http://localhost:9999/api/login Yes -
You should use the wait method of cypress.
You can find the cypress documentation here.
For your use case, make sure you start the server and define the route before you visit the link. Just after visiting the link, use the cy.wait() method which will wait for that API call to finish.
Eg.
describe('Login Approach', () => {
it('login', () => {
cy.visit('/login')
// these values email and pw shouldn't matter if mocking is done right
cy.get('#email')
.type('test')
.should('have.value', 'test')
cy.get('#password')
.type('123456')
.should('have.value', '123456')
cy.server()
cy.route({
method: 'POST',
url: 'http://localhost:9999/api/login', // this is the api that I send the request to
}).as('login')
cy.location('pathname', { timeout: 10000 }).should('eq', '/login');
cy.title().should('include', 'Condeo')
cy.get('#notification').should('exist')
// Code which will try to visit the login API.
cy.wait('#login').then((xhr)=> {
if(xhr.status === 200) {
// Code to test when status is 200
} else if(xhr.status === 400) {
// Code to test when status is 400
} else {
// Code to test when status is none of the above.
}
})
})
})

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