Add support for tracking work scheduled via setTimeout#24
Add support for tracking work scheduled via setTimeout#24stephanmoneybird wants to merge 16 commits intomakandra:mainfrom
Conversation
Wait for asynchronous work scheduled through `setTimeout` with delays under 5000ms. Wrapped `window.setTimeout` and`window.clearTimeout` to track and signal ongoing work.
|
Thanks for looking into the synchronization of timeouts @stephanmoneybird . I'm afraid merging it like this would freeze synchronization in many apps I have seen, which use a pattern like this: async function startPinging() {
await fetch('/ping') // or do any other async work
setTimeout(startPinging, 1000)
}
startPinging()This is the correct implementation of polling, as What we could do is to lower the threshold so something like 10ms. This would synchronize all the cases where people use It would probably be nice to have a configuration for the threshold, maybe like this: Capybara::Lockstep.wait_timeout_max_delay = 10 # default
Capybara::Lockstep.wait_timeout_max_delay = 2000 # custom setting
Capybara::Lockstep.wait_timeout_max_delay = -1 # disable setTimeout() syncWe already have precedent for a config setting in I only have two comments about the diff itself:
Thanks for writing a test! |
|
I've processed your feedback, let me know what you think! |
afterWaitTasks used originalSetTimeout (macrotask) to schedule idle checks, but IntersectionObserver callbacks fire during the browser's rendering step. There's no guaranteed ordering between them, so lockstep sometimes declared idle before IntersectionObserver triggered work like a lazy Turbo frame fetch. Wait for a requestAnimationFrame before each macrotask hop. This ensures rendering-triggered async work executes before lockstep's idle check.
When synchronizing, wait one requestAnimationFrame + one task before declaring the page idle. This gives layout-dependent callbacks like IntersectionObserver a chance to fire and register new async work (e.g. setTimeout, fetch) that lockstep can then track. Without this, lockstep could declare the page idle before Stimulus controllers using IntersectionObserver had a chance to start their async work, causing flaky test failures.
Turbo defers its fetch() call behind an await, creating a microtask gap that capybara-lockstep's fetch wrapper can miss. This adds explicit tracking of turbo:before-fetch-request/response events to bridge that gap.
…chronization Track Turbo fetch requests to prevent premature idle detection
Turbo's AbortError handling skips turbo:fetch-request-error, which left lockstep permanently busy. Instead of tracking the full Turbo request lifecycle, only use turbo:before-fetch-request to bridge the microtask gap until trackFetch picks up the actual fetch() call.
…chronization Fix permanent busy state when Turbo requests are aborted
Ad support for tracking asynchronous work scheduled via
setTimeout. AnysetTimeoutcall with a delay less than 5000ms is wrapped so that capybara-lockstep knows to wait for that work to complete before proceeding.