Problem
The page emits request and response events — as of #1602 they actually fire — but there is no way to await one:
typeof page.waitForResponse // 'undefined'
typeof page.waitForRequest // 'undefined'
typeof page.waitForEvent // 'undefined'wildloop's Playwright specs call waitForResponse 37 times, more than any other API except the assertions. The pattern it exists for:
const [response] = await Promise.all([
page.waitForResponse(r => r.url().includes('/api/trails') && r.status() === 200),
page.getByRole('button', { name: 'Search' }).click(),
])
expect((await response.json()).trails).toHaveLength(3)The nearest thing available is to register a handler and hope:
let seen: Response | null = null
page.on('response', (r) => { if (r.url.includes('/api/trails')) seen = r })
await page.getByRole('button', { name: 'Search' }).click()
expect(seen).not.toBeNull() // races the requestWhy it matters
The rewrite has to guess how long to wait, and guessing wrong fails in both directions: too short and it flakes, too long and every spec pays for it. waitForResponse is the thing that removes the guess — it is a promise that settles on the event, subscribed before the action that triggers it, which is why the Promise.all shape is the documented one.
Without it there is also no way to assert on a request that never happens, which is the other half of what these are used for: subscribe, act, and require a rejection.
The events are already there, so this is a small adapter over existing machinery, not new plumbing.
Proposed scope
page.waitForEvent(event, optionsOrPredicate)as the primitive: subscribe, resolve on the first event satisfying the predicate, reject on timeout with the event name in the message.page.waitForResponse(urlOrPredicate, { timeout })andpage.waitForRequest(...)on top of it, accepting a string (glob, matchingroute()'s existingRoutePatternhandling), a RegExp, or a predicate.page.waitForLoadState('load' | 'domcontentloaded'), which is the same adapter over the two events that already fire, and resolves immediately when the state has passed.page.waitForURL(urlOrPredicate)over navigation.- Reuse
RouteRegistry's pattern matching rather than adding a second URL matcher — two matchers that disagree about a glob would be worse than none.
Note that Response.url was only populated in #1602; a predicate matching on URL depends on that fix, so this should include a test that the predicate actually sees a URL.
Acceptance
- The
Promise.allreproduction above resolves with the matching response and its body is readable. - A predicate that never matches rejects on timeout, naming the event.
- Subscribing after the action still works if the event has not fired yet, and does not hang forever when it already has (documented either way).
waitForLoadStateresolves immediately for a state already reached.
Found while assessing whether wildloop's Playwright specs could port here: 37 call sites, spread across all 15 specs.