grafana/k6 · error
waiting for function, getting handle: %w
Error message
waiting for function, getting handle: %w
What it means
waitForFunction's next step evaluates the user-supplied predicate JS itself (non-callable, returnByValue=false) to obtain a handle; failure is wrapped as 'waiting for function, getting handle: <cause>'. Causes include syntax errors in the predicate string, the context dying before evaluation, or serialization failure of the function.
Source
Thrown at internal/js/modules/k6/browser/common/frame.go:2004
if execCtx == nil {
return nil, fmt.Errorf("waiting for function: execution context %q not found", world)
}
injected, err := execCtx.getInjectedScript(apiCtx)
if err != nil {
return nil, fmt.Errorf("getting injected script: %w", err)
}
pageFn := `
(injected, predicate, polling, timeout, ...args) => {
return injected.waitForPredicateFunction(predicate, polling, timeout, ...args);
}
`
// First evaluate the predicate function itself to get its handle.
opts := evalOptions{forceCallable: false, returnByValue: false}
handle, err := execCtx.eval(apiCtx, opts, js)
if err != nil {
return nil, fmt.Errorf("waiting for function, getting handle: %w", err)
}
// Then evaluate the injected function call, passing it the predicate
// function handle and the rest of the arguments.
opts = evalOptions{forceCallable: true, returnByValue: false}
result, err := execCtx.eval(
apiCtx, opts, pageFn, append([]any{
injected,
handle,
polling,
timeout.Milliseconds(), // The JS value is in ms integers
}, args...)...)
if err != nil {
return nil, fmt.Errorf("waiting for function, polling: %w", err)
}
// prevent passing a non-nil interface to the upper layers.
if result == nil {
return nil, nil //nolint:nilnilView on GitHub (pinned to 93accf6570)
Solutions
- Pass a plain function reference: waitForFunction(() => document.querySelector('.done') !== null) — no closure over external variables, no string surgery.
- If using a string, make it a full function expression: waitForFunction('() => window.ready === true').
- Move dynamic values into args: waitForFunction((v) => window.value === v, expectedValue).
- Wait for domcontentloaded before polling to avoid the navigation race.
Example fix
// before
await frame.waitForFunction(`window.count == ${n}`); // not a function, closure leak
// after
await frame.waitForFunction((v) => window.count === v, n, { timeout: 30_000 }); Defensive patterns
Strategy: validation
Validate before calling
// predicate must be a self-contained function; pass data via args
const pred = () => window.count === undefined || window.count >= 3;
await frame.waitForLoadState('domcontentloaded'); Type guard
const isFn = (f) => typeof f === 'function';
if (!isFn(pred)) throw new TypeError('predicate must be a function'); Try / catch
try { await frame.waitForFunction(pred, arg, { timeout }); }
catch (e) { if (/getting handle/.test(e.message)) { /* fix predicate shape: function expr, no closures, args for data */ } throw e; } Prevention
- Pass function references, not interpolated strings
- Never close over k6-side variables inside the predicate; use the args parameter
- Ensure the string form is a full function expression like '() => ...'
When it happens
Trigger: Passing a malformed predicate (e.g. waitForFunction('window.x' as a plain expression string that is not a function, unbalanced braces, or PageFunction shape the module cannot serialize); navigation destroying the context between the previous step and this eval.
Common situations: Building predicate strings dynamically with bad interpolation; passing an arrow function that closes over k6-side variables (not serializable); racing page transitions at poll start.
Related errors
- waiting for function: execution context %q not found
- getting injected script: %w
- waitFor retry threshold reached
- waiting for states %v of element %q
- waiting for element state %q: %w
AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15).
Data as JSON: /api/errors/d6aa88e086833d9e.
Report an issue: GitHub.