-
Notifications
You must be signed in to change notification settings - Fork 2
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 all resources to load before indicating Watcher is idle #16
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you missed an important but hairy case -- when a request triggers a second request.
Also I think we need to figure out a way to test this (the timeouts and the cascading requests). Might be something useful in the capture tests for that.
src/resource-archive/index.ts
Outdated
// eslint-disable-next-line @typescript-eslint/no-implied-eval | ||
let timeoutId: any; | ||
const timeoutPromise = new Promise((r) => { | ||
timeoutId = setTimeout(r, 4000); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add the unresolved requests/promises to the log?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think that's a good idea. I'll add logging information for this.
src/resource-archive/index.ts
Outdated
|
||
await Promise.race([ | ||
timeoutPromise, | ||
Promise.all([...this.unfulfilledRequests.values(), ...this.unsettledResolvers.values()]), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if a request creates further requests in resolving?
I think probably we need to do something more complicated, ie check at the end of each request if "all requests are resolved".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I can add this logic. I had another concern, too, that Promise.all
might resolve before any of the requests are actually added to the set. I suppose I could add another 500ms wait at the very beginning, but this seems like the incorrect solution.
src/resource-archive/index.ts
Outdated
private unfulfilledRequests: Map<string, Promise<any>>; | ||
|
||
private unsettledResolvers: Map<string, any>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think these names could be more descriptive. Also why is the second one string=>any
?
I'm not sure I quite see why they need to be separate lists either? But maybe I am missing something (if so can you put a comment explaining ;) )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first one holds Promise
s, but the second one holds functions that resolve Promise
s. They probably don't need to be separate lists - I'm not 100% sure why I separated them, other than I think I was experimenting with a different way of doing some of these. Honestly, I don't think that the unsettledResolvers
really needs to exist. I'll refactor this a bit and see if I can coalesce them.
src/resource-archive/index.ts
Outdated
let resolver: any = null; | ||
const prom = new Promise((resolve, reject) => { | ||
resolver = resolve; | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably worth pulling this out to a helper to make it clearer, maybe something like:
const complete = this.registerUnfulfilledRequest(request.url);
// later
complete();
Then the registerUnfulfilledRequest
could look something like:
registerUnfulfilledRequest() {
const complete = // something similar to what you have
complete.then(() => {
if (/* all requests are resolved */) {
this.allRequestsDoneComplete(); // this triggers a "super" promise representing all requests
}
}
return complete;
}
6ef64af
to
0517b98
Compare
How would you recommend approaching the testing of this? I can look through the capture tests, which is a good idea, but my naive initial idea was to add something like this to the express server:
Then make a test similar to the following:
This doesn't achieve what I want, though, because the test itself times out, rather than the setTimeout promise rejecting. Perhaps I'm doing something wrong? @codykaup or @jmhobbs thoughts on how I might be able to implement a test for timeouts? |
It looks like we have some capture tests for this and we accomplish it by setting the timeout to basically 0 then run the test. Since all the requests take longer than our timeout, we get the error we want. I'll admit, I'm not up-to-date on this project so maybe this is warranted but it seems like we're doing a lot of heavy lifting with promises while watching these requests. The spirit of our internal navigation watcher is much like Playwright's waitUntil argument. We're essentially keeping track of requests that |
I think setting the timeout much lower in the test is probably the key unlock here. Otherwise your testing strategy sounds right to me @jwir3.
I think this a matter of personal preference and so I would definitely leave it up to @jwir3 or y'all. But I would say the technique of To my mind, a conceptually similar approach is a single "summary" promise of "are all requests done?" that we (maybe) resolve when each request is done. So something like: wait() {
await Promise.race([ timeoutPromise, this.allRequestsDonePromise ])
}
// In handler
this.client
.send(method, params)
.then((value) => {
// Or simply remove from list
this.pendingRequests.set(request.url, true);
if (!Object.values(this.pendingRequests).any(done => !done)) {
this.this.allRequestsDoneResolver()
}
}); |
1a5e701
to
8820447
Compare
I think this is ready for review now. I'm actually using playwright's built-in functionality to wait for the network to be idle. This is similar to what we do in capture, but, for some reason, we weren't able to use playwright's functionality there. It's possible that the capture use case is so much more complex than this one that this is the reason, but I'm honestly not sure. Note: |
fe63176
to
941a61b
Compare
This was previously aliased instead of being actually renamed. This renames both the file in which the function is declared, as well as the function itself.
This replaces the 1s wait inside of Watcher with waiting on a set of two promises. The first is a global network timer. The global network timer will reject if it expires and there are still network requests pending. The second promise is a network idle promise that will resolve if playwright detects that the network has not been used in some amount of time. Fixes CAP-1082.
This adds a jest test for when the archiver times out (i.e. the resources requested do not load in the total time allotted), which, by default, is 2s. Refs CAP-1082.
941a61b
to
982fd10
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh neat! Well that's simpler :)
I wonder if we can use that same thing in capture somehow. I guess we'll learn more in this project.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, looks good to me!
🚀 PR was released in |
Issue: CAP-1082
What Changed
This adds a set of promises to the
Watcher
class that represent network requests made by the client prior to the archive being created. For each of the network requests, as long as it is not ignored (i.e. within thebaseUrl
), the promise will resolve when the network request resolves. Once all of these promises are resolved, OR when a 4s timer has expired, theWatcher
will indicate it is idle.How to test
You can run the tests using
yarn test
. A new test was added with a large image that takes an amount of time to load. Alternatively, run thetest-archiver
after having linked a project that can utilize it withyarn link
.Change Type
maintenance
documentation
patch
- This should introduce no new behavior and instead should be backward compatible.minor
major
📦 Published PR as canary version:
0.0.25--canary.16.982fd10.0
✨ Test out this PR locally via:
npm install @chromaui/test-archiver@0.0.25--canary.16.982fd10.0 # or yarn add @chromaui/test-archiver@0.0.25--canary.16.982fd10.0