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
- Make the waited expression side-effect free so it cannot trigger navigation
- Catch this error and re-issue WaitForFunction after the navigation settles (it is polled, so a re-call is cheap)
- 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
- Never let the waited function mutate location or submit forms
- Prefer waitForSelector for element conditions — it survives navigation
- Treat one payload mismatch as transient; two in a row as a real bug
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.