-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
wait for the network idle #1773
Comments
This seems to be referencing puppeteer's options for their equivalent "visit" method. https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagegotourl-options
This is quite brilliant actually. I wonder if @brian-mann knows a way to do this today in our current API, but this would likely be something that goes into our network refactor, at least I'm a 👍on adding support for something like this. #687 |
The use case of network idle is not really about Example: cy.get('#search').type('foo')
cy.wait('@networkIdle0') It sounds good in theory but this ultimately runs us right back to the same problem with non-determinism. If you're waiting for the network to become idle who's to say that it's not already idle. What if the network request hasn't gone out yet? What if your app is polling for things that aren't necessarily related to the thing you want to wait on? What if your app makes multiple requests but in between those request there's a delay? What about websockets? In that case you may not ever know when data is about to come down the pipe from the server. Maybe there are answer for this, or maybe it works better than a preliminary guess, but I still prefer explicitly saying what you want to wait on - because then we know whether it has or hasn't happened. If it has happened we immediately resolve, and if is hasn't we wait until we know it has. Networkidle is a better catch-all, but since its less specific it could lead to non-deterministic results that are left up to chance and are hard to debug. |
@jennifer-shehane to answer your original question - yes we can do this without any kind of refactor or rewrite because it can be done exclusively at the proxy layer without changing much in the driver. |
I also prefer to explicitly wait on specific XHR requests as I believe this is a stronger testing strategy, but some of our users seem to be continually frustrated with having to do this and want a 'wait for all requests' solution. |
I think adding this would be a great idea. Calling cy.visit() right now causes in progress network requests to be aborted. I can guarantee that the request is launched synchronously so the non-determinism draw back wouldn't matter at all. |
@jennifer-shehane @brian-mann ... actually in relation to one of our case in which I am solving ... when to exactly call Our app is Angular based SPA application where almost everything is based on async XHR calls, and where the whole DOM is created and maintained via JS ... so even a single click on a button could lead to several XHR calls which are resolved in unknown time and the whole app logic uses observable / subscribe pattern to run the code in the moment when all dependency things are ready .... and this moment is the right one when we need to call that It wouldn't be a big problem to create some state management which would allow us to exactly know when the app state is stable for each tested functionality. We can do it right know but it would be much more powerful if we could use some standardized way for it ... to use a feature like It's not the super heroes generic solution like |
I have a similar issue where i have additional resources being pulled in via requirejs and I'd like to wait until all resources are downloaded before continuing the test. I hope this feature request would copy that case as well. |
As a workaround, this has worked for me: cy.window().then({
timeout: 120000
}, win => new Cypress.Promise((resolve, reject) => win.requestIdleCallback(resolve))); This uses the requestIdleCallback method, which is experimental. Also weird, because I would have expected the options is the second arg of |
@jennifer-shehane Here
you mentioned how to wait for a specific XHR request. Is this only working for network stubs using |
@bierik I'm trying to solve the same problem right now with no luck. Let me know if you figure it out... I have this code: it('can be marked as incomplete', () => {
cy.server();
cy.route('/graphql').as('api');
cy.get('[data-hook=flagArticleButton]').click();
cy.wait('@api');
cy.get('[data-hook=incompleteFlag]').contains('Steve Steve');
cy.reload();
// more stuff
}); The expected behavior is that after clicking the button, cypress would wait for the api call to finish. In reality, it never catches the api call. Here's my runner: |
Hi @baleeds I guess we have a different problem. You are trying to await a request mocked by the |
@bierik I also have a real backend. The docs indicate that
cy.server()
cy.route('activities/*', 'fixture:activities').as('getActivities')
cy.route('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']) The above is from the docs on network requests. It seems like they are returning a fake response here, but the text says it should work with or without stubbing. But I still couldn't get it to work in my testing. |
@baleeds I did try it out the |
Hello, I am really interested in it as I try to deploy Cypress on a medium sized project. Thank you |
Hello, Trying to “wait for all current requests” as well, where number of requests is unknown. My use case is a quick test to go through all app's pages, and, when all requests complete, check for a standard error popup showing. I can't find a way to do it without specifying an arbitrary delay in milliseconds (which makes the run-though very slow). I've seen references to alias.all “undocumented feature” in #3516 (with cy.wait, but on additional questions the user is instructed to “please check the docs”, odd for an undocumented feature) and #4700 (with cy.get this time — not useful for my case). I'd be convenient to create a catch-all aliased route, and cy.wait for .all currently known requests on that alias. From the above discussion looks like it's not currently possible, but perhaps it's a simple enhancement? |
I came up with a workaround solution which seems to work for me so far. On the site I'm testing I'll keep track of all the running network requests I need to wait for by just incrementing and decrementing a value on a window property on start / end of the request. The app I'm currently building is based on https://github.com/marmelab/react-admin, so it's easy, as they already do this for their loading indicator. Just implement another redux saga, that listens on the dispatched fetch start and fetch end actions and writes the loading state into window.fetch_loading or something like this. You might have to implement your own logic for this, which might need a little bit of setup, but at least it works and currently is the only solution I am satisfied with. I am only running this when the Node Env is not production, because I won't need it in production build. Now I can use something like https://github.com/NoriSte/cypress-wait-until to just wait until this value is 0. cy.waitUntil(() => cy.window().then(win => win.fetch_loading > 0))
cy.waitUntil(() => cy.window().then(win => win.fetch_loading === 0)) This is not beautiful, but it works. I am always open for other, more elegant solutions though. |
We're running into this situation where a previous test loads a dashboard which has a fist-full of GraphQL requests, the next test then starts while the server is still processing the requests from the previous test. The responses then come in (during the wrong test) and break things. Ideally, we would have a way of idling a a test until all requests have finished. We could stub the requests, but really the point of the test is to just check that it got to the dashboard, not test the requests the dashboard itself (including its requests). I did try the snippet here: #1773 (comment) but it didn't work, unfortunately. |
Curious as to whether there would be any downsides with this dirty approach; Cypress.Commands.add('runAndAwait', actionTriggeringGetRequests => {
const requestId = `apiRequest-${uuid()}`;
cy.server();
cy.route('**').as(requestId); // start recording requests
actionTriggeringGetRequests();
cy.wait(`@${requestId}`);
cy.route('**').as('untrackedRequest'); // stop recording requests
}
) |
We prefer to use Wiremock as a stub server, because it integrates with Spring Cloud Contract well. So a feature like this would be much appreciated. |
Couldn't you add a command that checks |
@jennifer-shehane I notice here that you're differentiating between waiting on specific XHR requests versus all requests. Is there a way to wait for specific requests without mocking them? How could I tell Cypress "Hey, visit this page, then wait for request X, Y, and Z to finish before moving on"? |
@JasonTheAdams without stubbing sure: in an end-to-end test, you can use How would you specify the requests to wait for without mocking? And what is the problem with mocking? Documentation is at: https://docs.cypress.io/api/commands/route.html. |
I'm currently using the routes and aliases and it's cumbersome. An example:
It's Dutch, but you probably recognize the Given/When/Then. So what happens here is I have to define all my stubs and explicitly wait for specific ones to finish. My cucumber scenario gets polluted with technical stuff. |
I think this will wait for first request to complete if you need all the request it won't wait, we need to wait for all request to complete. |
Some sort of functionality to wait for idle network or all xhr requests to finish would be very helpful. I am also in a situation of writing a test that clicks through each menu item on our site, and verifies successful page load. So knowing each route to watch for is not viable, as pages can change often. |
Dropping in to point out that many developers use services like |
I'm also trying to use |
I have managed a workaround thanks to this article. My code is modified for my needs but I think this function should be good enough for most use cases. It basically polls the browser's resources to see if it's still loading something or not. It checks every 2 seconds and will resolve the promise when it has seen the same thing 3 times (no new progress). You can also pass it an array of specific resources that you want to wait for and the number of each resource that you require. Cypress.Commands.add("waitForResources", function (resources = []) {
const globalTimeout = 20000
const resourceCheckInterval = 2000
const idleTimesInit = 3
let idleTimes = idleTimesInit
let resourcesLengthPrevious
let timeout
return new Cypress.Promise((resolve, reject) => {
const checkIfResourcesLoaded = () => {
const resourcesLoaded = cy.state("window")
.performance.getEntriesByType("resource")
.filter(r => !["script", "xmlhttprequest"].includes(r.initiatorType))
const allFilesFound = resources.every(
resource => {
const found = resourcesLoaded.filter(
resourceLoaded => {
return resourceLoaded.name.includes(resource.name)
},
)
if (found.length === 0) {
return false
}
return !resource.number || found.length >= resource.number
},
)
if (allFilesFound) {
if (resourcesLoaded.length === resourcesLengthPrevious) {
idleTimes--
}
else {
idleTimes = idleTimesInit
resourcesLengthPrevious = resourcesLoaded.length
}
}
if (!idleTimes) {
resolve()
return
}
timeout = setTimeout(checkIfResourcesLoaded, resourceCheckInterval)
}
checkIfResourcesLoaded()
setTimeout(() => {
reject()
clearTimeout(timeout)
}, globalTimeout)
})
}) usage: cy.waitForResources() // wait for networkidle
cy.waitForResources([
{ name: "fa-solid-900.woff2" },
{ name: "fonts.gstatic.com/s/worksans", number: 2 }, // won't resolve until it has loaded 2 matching resources
]) |
This worked, personally I was using a different loading indicator specific to Vue. Initially I was just checking if .nprogress-busy was existent, however to my dismay this class can come up after being removed (say for 3 API calls it will come and go 3 times). So I'm using this approach. I just can't be bothered to sit here and intercept 6+ API calls for one test at a time, and then the test will fail if other requests are added. So thumbs up for the approach, not perfect but it's a blanket approach that saves time. |
I rewrote @JGJP 's solution using a Otherwise, I believe this is a solid solution and should be implemented in Cypress with some adjustments. let totalRunningQueries = 0;
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.initiatorType === "fetch") {
totalRunningQueries++;
}
}
});
observer.observe({
entryTypes: ["resource"]
});
Cypress.Commands.add("waitForResources", function (resources = []) {
let tries = 0;
return new Cypress.Promise((resolve, reject) => {
const check = () => {
const requests = window.performance
.getEntriesByType("resource")
.filter(n => n.initiatorType === "fetch");
if (requests.length === totalRunningQueries) {
tries++;
if (tries === 3) {
resolve();
} else {
setTimeout(check, 100);
}
} else {
tries = 0;
setTimeout(check, 100);
}
};
check();
});
}); |
The |
Use this plugin to wait for network to idle, Thank you @bahmutov !!! |
Current behavior:
Currently, only way to wait for certain time is that using wait function. However, it is not reliable because internet speed and other variable is always different.
Desired behavior:
I would like to have wait for the network idle function.
Versions
^2.1.0
The text was updated successfully, but these errors were encountered: