How to get cypress to block datadog requests - events

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

Related

Cypress test - Do not intercept api request

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

How do you wait on multiple XHR in Cypress that match the same intercept

In my app after login in from a clean state, there are a series of sync queries that are being fired to make sure that the local data is updated. There is a loading screen while this is happening. I just need to cypress to wait for all these calls to finish before performing the test.
cy.intercept() is identifying the call, but cy.wait() only waits for the first one to be finished.
Is there a way to create the alias dynamilcally or have the application wait for the spinner to disapper?
describe('Navigation', function () {
beforeEach(function () {
// Programmatically login via Amazon Cognito API
cy.intercept('POST', '**/graphql').as('dataStore');
cy.loginByCognitoApi(Cypress.env('cognito_username'), Cypress.env('cognito_password'));
cy.wait(['#dataStore']);
});
it('shows logged in', function () {
cy.get('[data-test=logo]').should('be.visible');
});
You can repeat wait on a single intercept, so count up the number of orange dataStore tags (looks like 11) and wait that amount of times
cy.intercept('POST', '**/graphql').as('dataStore');
cy.loginByCognitoApi(Cypress.env('cognito_username'), Cypress.env('cognito_password'));
Cypress._.times(11, () => {
cy.wait('#dataStore')
})
Or it might be 10 - looking at the route defn. In any case, experiment. The app should be consistent in the calls it makes.
I had a similar case. What I do is store an array of objects in a different file and each object represents a specific test scenario. That way you can iterate through your test cases an assign an alias dynamically.
So you could do something like this:
beforeEach(function () {
yourArray.forEach((testcase) => {
cy.intercept('POST', '**/graphql').as(`${testcase.testname}datastore`);
cy.loginByCognitoApi(Cypress.env('cognito_username'),
Cypress.env('cognito_password'));
cy.wait(`#${testcase.testname}datastore`);
}
});
If the number of requests aren't consistent, something I've done is the following (I've since put this in a command to use in multiple places):
cy.intercept('POST', '**/graphql').as('dataStore');
cy.loginByCognitoApi(Cypress.env('cognito_username'),Cypress.env('cognito_password'));
cy.get('#dataStore.all').then(xhrs => cy.wait(Array(xhrs.length).fill('#dataStore')));
Doing a wait on the alias with "all" returns all of the calls made to aliased route that Cypress has seen since the alias was made.
#user16695029 is a great solution.
If you run into the issue of API calls not being predictable (kicked off by a timer async etc), then keeping track of API call count might be useful:
at the start of your test code
let responseCounter = 0;
cy.intercept({ method: 'POST', url: '/save', middleware: true }, req => {
req.on('response', (res) => {
responseCounter++;
})
}).as('save')
then later
let expectedSaveCount = 12;
Cypress._.times(expectedSaveCount - responseCounter, () => {
cy.wait('#save')
})
cy.get('#save.all').should('have.length', expectedSaveCount)

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

Allow cy.wait to fail

After reading a lot of docs and try to find a solution to my problem I didn't find anything, so here we go.
I have the following problem testing my end to end flow, the flow I'm testing does launch requests continuously but in one case I expect these requests to stop. In other words, I want to throw an error if the request is made and continue with errors when hits the timeout without any request.
cy.wait('#my-request', { timeout: 20000 })
I expect this to timeout if the app works fine so I tried to do this.
cy.wait('#my-request', { timeout: 20000 })
.its('status').should('not.eq', 404)
.its('status').should('not.eq', 200);
I expected to execute the chained tasks but this only happens when the request is made, as well as tried to use a .then but I have the same problem.
Adding a global on fail event can help us but also limits to not execute additional code when this test is failing and we force it to be marked as done.
In the test definition we can add the done callback like in the example.
it('Description', (done) => {
// other test stuff
cy.on('fail', (err) => {
if (err.name === 'CypressError' && err.message.includes('routeAlias') && err.message.includes('Timed out')) {
done();
return true;
}
throw err;
});
cy.wait('#routeAlias', { timeout: 20000 })
.then(() => {
throw new Error('Error request found.');
});
});
// Any remaining code won't be executed if you need to reset something you need to create a new step, like in my case I did a new step to click a cancel button and prepare the app for the next test.
Now our test passes when this specific error is caught but any other error will lead to a test error.
This kind of workarounds are not recommended by cypress but unless cypress adds a catch to manage some errors it's the only way to fix my problem.

Can't intercept Cypress API call

I have stuck with Cypress fixtures. Can't intercept an XHR request with SSR and navigation routing.
cypress/integration/page.js:
const fetch = require("unfetch")
describe("/about", () => {
beforeEach(() => {
cy.visit("/", { // Visit home page to trigger SSR
onBeforeLoad (win) {
win.fetch = fetch // replace fetch with xhr implementation
},
})
})
it("Has a correct title", () => {
cy.server()
cy.fixture("about").then(about => {
// about object is correct here, like {title: "About+"}
cy.route("GET", "http://localhost:8080/api/documents/url", about) // Not sure where .route should be
cy.get(".main > :nth-child(1) > a").click() // Navigate to the /about page
cy.route("GET", "http://localhost:8080/api/documents/url", about) // Tried both ways
// This hits my server API without stubbing, getting {title: "About"}
cy.title().should("eq", "About+") // About != About+
})
})
})
cypress/fixtures/about.json:
{"title": "About+"}
I see an XHR request (type=xhr) in Dev Tools and it doesn't use the above about stub object but hits real API instead. Why? Double checked URL and method – 100% the same. Can it be that route is coupled to visit and ignores click-based routing?!
Rechecking this once again, I've found a solution. Let me share the details for everyone interested:
1) I use Next.js which is an excellent tool for SSR but it doesn't allow you to disable server-side rendering (yet) according to this and this issues.
2) You can use Cypress with SSR pages but, in this way, you're limited to testing real HTML. Which means you have to either couple tests to real data (not good in most cases) or stub the database itself (slow). In general, you want to stub HTTP requests.
3) Cypress can't stub fetch requests and mocking fetch with XHR-based implementation was trickier than I thought.
First you need to:
// cypress/integration/your-test.js
Cypress.on('window:before:load', (win) => {
delete win.fetch
})
Then:
// pages/your-page.js
Entry.getInitialProps = async function() {
window.fetch = require("unfetch").default
...
}
Other combinations of delete & update code lines I tried didn't yield positive results. For example, when I had window.fetch = line in the test file it didn't work and fetch.toString() gave "native code". Not sure why, no time to explore further.
Axios solves the above but I don't like to bloat my bundle with extra stuff. You can inject XHR-based fetch for tests only.
4) The most important missing piece. You need to wait for route.
it("Has a correct title", () => {
cy.visit("/")
cy.server()
cy.route("GET", "http://localhost:8080/api/documents/url/about", {title: "About+"}).as("about")
cy.get("[href='/about']").click()
cy.wait("#about") // !!!
cy.get("h1").contains("About+")
})

Resources