vercel/next.js · error · Error

An instant() scope is already active. Nesting instant() call

Error message

An instant() scope is already active. Nesting instant() calls is not supported. Did you forget to await the previous instant() call?

What it means

Thrown by the `instant()` test helper in next-playwright when an instant() scope is already active on the same browser context. The lock is per-context (tracked via a WeakSet) because the instant-navigation cookie is shared per context. Nesting is unsupported because two scopes would compete for the same cookie/lock. The message hints the most common cause: forgetting to await the previous call.

Source

Thrown at packages/next-playwright/src/index.ts:77

 * automatically. For a fresh page (before any navigation), pass
 * `baseURL` so the cookie can be scoped to the correct domain:
 *
 *   await instant(page, async () => {
 *     await page.goto(url)
 *     // ...
 *   }, { baseURL: 'http://localhost:3000' })
 *
 * When `@playwright/test` is installed, acquire/release actions appear
 * as labeled steps in the Playwright UI.
 */
export async function instant<T>(
  page: PlaywrightPage,
  fn: () => Promise<T>,
  options?: { baseURL?: string }
): Promise<T> {
  const context = page.context()
  if (contextsWithActiveScope.has(context)) {
    throw new Error(
      'An instant() scope is already active. Nesting instant() ' +
        'calls is not supported. Did you forget to await the ' +
        'previous instant() call?'
    )
  }

  // Resolve the cookie's scope before touching any browser state, so misuse on
  // a fresh page (no baseURL and no prior navigation) fails with the
  // descriptive error from resolveURL rather than half-entering a scope.
  const { hostname } = new URL(resolveURL(page, options))

  contextsWithActiveScope.add(context)
  try {
    // A completed prior scope on this context can leave the cookie behind (its
    // client-side release races an in-flight captured-cookie write from a
    // locked MPA page load; see the note above). No scope is active for this
    // context, so a present cookie is always stale here — clear it before
    // acquiring so a completed prior scope never blocks this one.

View on GitHub (pinned to 0ae8c72462)

Solutions

  1. Ensure every `instant()` call is fully awaited before starting the next one on the same context.
  2. Do not nest instant() calls — move the inner logic outside the scope or run it after the outer scope completes.
  3. If you genuinely need concurrent instant scopes, use separate browser contexts (separate pages from different contexts).
  4. Wrap instant() calls in try/finally so an early throw doesn't leave the scope active.

Example fix

// before — second call overlaps the first
await instant(page, async () => { /* ... */ }) // missing await was here
await instant(page, async () => { /* ... */ })

// after — every call awaited
await instant(page, async () => { /* ... */ })
await instant(page, async () => { /* ... */ })
Defensive patterns

Strategy: validation

Validate before calling

// The library tracks active scopes internally via WeakSet.
// Caller-side: ensure no overlapping instant() calls by serializing them.
const instantQueue: Array<() => Promise<void>> = []
let instantRunning = false
async function safeInstant(page, fn) {
  // Serialize instant() calls on the same context
  await new Promise<void>(resolve => {
    instantQueue.push(async () => { resolve(); await fn() })
    if (!instantRunning) drain()
  })
}
async function drain() {
  instantRunning = true
  while (instantQueue.length) { await instantQueue.shift()!() }
  instantRunning = false
}

Try / catch

try {
  await instant(page, async () => { /* test code */ })
} catch (e) {
  if (e.message.includes('already active')) {
    // A previous instant() didn't finish — await it first
    await new Promise(r => setTimeout(r, 100))
  }
  throw e
}

Prevention

When it happens

Trigger: Calling `instant(page, fn1)` and then calling `instant(page, fn2)` (or the same page's context) before fn1 has resolved — e.g., missing `await`, or calling instant() inside another instant() callback.

Common situations: Forgot to `await` the first `instant()` call; sequential instant() calls in a loop where one rejects silently; calling instant() inside another instant() callback body; using the same browser context for parallel instant() calls.

Related errors


AI-assisted analysis of vercel/next.js@0ae8c72462 (2026-08-06). Data as JSON: /api/errors/b2ac0f257ee4df5c. Report an issue: GitHub.