GopeedLab/gopeed · error

unexpected waitForFunction payload: %T

Error message

unexpected waitForFunction payload: %T

What it means

PageHandle.WaitForFunction evals a wrapper that must resolve to {matched, value}; the Go side requires the Execute result to deserialize as map[string]any. If the page-side evaluation returns anything else (undefined after a navigation destroyed the context, a serialized primitive, or an unexpected payload), this error names the actual Go type in %T.

Source

Thrown at pkg/download/engine/webview/runtime.go:356

			waitOpts = parseWaitOptions(raw)
			callArgs = args[1:]
		}
	}

	value, matched, err := p.poll(waitOpts, func() (any, bool, error) {
		result, err := p.Execute(`(expression, args) => {
			const target = (0, eval)(expression);
			return Promise.resolve(typeof target === "function" ? target(...args) : target).then((value) => ({
				matched: !!value,
				value: value ?? null,
			}));
		}`, expression, callArgs)
		if err != nil {
			return nil, false, err
		}
		payload, ok := result.(map[string]any)
		if !ok {
			return nil, false, fmt.Errorf("unexpected waitForFunction payload: %T", result)
		}
		return payload["value"], truthy(payload["matched"]), nil
	})
	if err != nil {
		return nil, err
	}
	if !matched {
		return nil, nil
	}
	return value, nil
}

func (p *PageHandle) GetCookies() ([]Cookie, error) {
	page, err := p.page()
	if err != nil {
		return nil, err
	}
	return page.GetCookies()

View on GitHub (pinned to 7b7327ffb3)

Solutions

  1. Make the waited expression side-effect free so it cannot trigger navigation
  2. Catch this error and re-issue WaitForFunction after the navigation settles (it is polled, so a re-call is cheap)
  3. Prefer WaitForSelector for element-based conditions — it tolerates navigation

Example fix

// before
await page.waitForFunction(() => window.location.href.includes('/done'));
// after (navigation-tolerant)
let ok = false;
for (let i = 0; i < 10 && !ok; i++) {
  try { ok = await page.waitForFunction(() => window.ready === true, { timeoutMs: 2000 }); }
  catch (e) { await page.waitForNavigation?.() ?? null; }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// JS: keep the polled expression pure and navigation-free
await page.waitForFunction(() => document.readyState === 'complete' && window.__data != null, { timeoutMs: 5000 });

Try / catch

try {
  const v = await page.waitForFunction(fn, { timeoutMs: 3000 });
} catch (e) {
  if (String(e).includes('unexpected waitForFunction payload')) {
    // context likely navigated mid-poll: wait for settle, then re-issue once
    await page.goto(page.url(), { waitUntil: 'domcontentloaded' });
    return page.waitForFunction(fn, { timeoutMs: 3000 });
  }
  throw e;
}

Prevention

When it happens

Trigger: Waiting on a function while the page navigates, so the evaluation context is torn down and Execute yields nil; an expression whose eval throws and leaves a non-object result; a webview build whose evaluate bridge returns JSON primitives instead of objects for promise results.

Common situations: waitForFunction racing a SPA route change or form submit; waiting on functions that trigger navigation as a side effect; embedded webviews with older CDP/bridge semantics.

Related errors


AI-assisted analysis of GopeedLab/gopeed@7b7327ffb3 (2026-08-16). Data as JSON: /api/errors/e7fc90c6b0bc7297. Report an issue: GitHub.