I am trying to end to end test a file upload page in Cypress, which includes testing if a file upload progress bar works.
Unfortunately, in the local environment the upload happens instantly so the progress bar is not shown.
I am aware that you can throttle the response speed using cy.intercept() to simulate slow networks. However, this isn't slowing the request upload speed:
cy.intercept('post', `/route`, (req) => {
req.on('response', (res) => {
res.setThrottle(1000)
})
}).as('post')
Is there any way that I can apply a throttle to the outgoing request?
I think Cypress itself can only throttle or delay responses using cy.intercept() as mentioned in the question and described here: Throttle or delay response all incoming responses
However, you can probably use the Chrome Debugger Protocol from Chrome DevTools for that. Here you have the function Network.emulateNetworkConditions that allows you to throttle both download and upload throughput. This approach then assumes that your tests run in Chrome.
If you're unsure how to use this in your Cypress tests, here is a blog post about testing an application in offline network mode that uses a similar approach with Network.enable: Testing an Application in Offline Network Mode
In the routeHandler, use setTimeout() to delay the call to req.continue().
To get the command queue to wait for setTimeout, return a Promise wrapper. See Request phase
If the handler returned a Promise, wait for the Promise to resolve.
If you want a delay longer than the default command timeout of 4 seconds, you will need to increase the config since cy.intercept does not take a timeout option.
Cypress.config('defaultCommandTimeout', 6000) // timeout in 6 seconds
cy.intercept('POST', '/route', (req) => {
return new Promise(resolve => {
setTimeout(() => resolve(req.continue()), 5000) // delay by 5 seconds
})
}).as('delayedRequest')
// trigger POST
cy.wait('#delayedRequest')
Cypress.config('defaultCommandTimeout', 4000) // revert to normal timeout
Middleware
If you already have a complex intercept, you can set the delay in a middleware intercept.
Middleware is always executed first, but if does not handle the request the call is passed to the next intercept.
// request is delayed here
cy.intercept('POST', '/route', {
middleware: true // middleware always fires first
},
(req) => new Promise(resolve =>
setTimeout(() => resolve(), 5000) // no handler, just delay
)
)
// then passed to here
cy.intercept('POST', '/route',
(req) => {
req.continue() // handler for request
}
).as('delayedRequest')
// trigger POST
cy.wait('#delayedRequest')
Related
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'
]
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"
}
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')
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 are about to deprecate cy.route() and cy.server() in use of cy.intercept()
Here is my OLD Code, that would capture XHR time and output
cy.server()
cy.route('POST', api_URL_Live).as('CONS');
cy.wait('#CONS').then((xhr) => {
CONSTime = Number(JSON.stringify(xhr.duration));
});
This worked perfectly and would out put the duration to a file
New code that no longer captures duration
cy.intercept('POST', api_URL_Live).as('CONS');
cy.wait('#CONS').then((xhr) => {
CONSTime = Number(JSON.stringify(xhr.duration));
});
Does anyone know why this functionality no longer works, any help appreciated?
The APIs of cy.intercept and cy.route have some overlap, but are not the same.
duration is an undocumented property of cy.route, but it does work, although it is not formally supported.
Because it was undocumented and rarely used in cy.route, it was not considered for implementation in cy.intercept.
You can still measure a request's duration using cy.route - cy.route is not going to be deleted, leaving your code broken. It will most likely be moved to a plugin once cy.intercept is stable enough for most common use cases.
If you would like to do the same with cy.intercept, you can measure by using callbacks:
cy.intercept('POST', url, (req) => {
const startTime = Date.now()
req.reply(res => {
// measure the time between request received and response received
totalTime = Date.now() - startTime
})
})
There is an open feature request to add timing data to cy.intercept: https://github.com/cypress-io/cypress/issues/15969