grafana/k6 · error
nil result
Error message
nil result
What it means
ExecutionContext.EvalHandle (execution_context.go:314-338) backs page.evaluateHandle/frame.evaluateHandle. The CDP evaluation itself succeeded, but it produced no remote-object result (the expression returned null or undefined), so there is nothing to wrap in a JSHandle and k6 returns 'nil result'.
Source
Thrown at internal/js/modules/k6/browser/common/execution_context.go:329
// EvalHandle evaluates the provided JavaScript within this execution context
// and returns a JSHandle.
func (e *ExecutionContext) EvalHandle(apiCtx context.Context, js string, args ...any) (JSHandleAPI, error) {
if escapesSobekValues(args...) {
return nil, errors.New("sobek.Value escaped")
}
opts := evalOptions{
forceCallable: true,
returnByValue: false,
}
evalArgs := make([]any, 0, len(args))
evalArgs = append(evalArgs, args...)
res, err := e.eval(apiCtx, opts, js, evalArgs...)
if err != nil {
return nil, err
}
if res == nil {
return nil, errors.New("nil result")
}
r, ok := res.(JSHandleAPI)
if !ok {
return nil, ErrJSHandleInvalid
}
return r, nil
}
// Frame returns the frame that this execution context belongs to.
func (e *ExecutionContext) Frame() *Frame {
return e.frame
}
// ID returns the CDP runtime ID of this execution context.
func (e *ExecutionContext) ID() runtime.ExecutionContextID {
return e.idView on GitHub (pinned to 01ffac6f24)
Solutions
- Use page.$(selector) / frame.$(selector) for element lookups — they return null instead of erroring
- Make the evaluated function always return a real object or element (e.g. document.querySelector(sel) || ({}))
- If you only need a value, not a handle, use page.evaluate and null-check the result
- Guard for null inside the page function and return a sentinel object you check afterwards
Example fix
// before
const h = await page.evaluateHandle(() => document.getElementById('missing'));
// after
const h = await page.$('#missing'); // null, no throw
if (!h) throw new Error('element #missing not found'); Defensive patterns
Strategy: try-catch
Validate before calling
// Prefer $ for element lookups; null instead of throw
const h = await page.$('#maybe-missing');
if (h === null) {
// handle absence explicitly
} Type guard
// Guard evaluateHandle results inside the page function
const h = await page.evaluateHandle(
() => document.querySelector('#x') || { missing: true }
);
const props = await h.getProperties();
if (props.get('missing')) { /* absent */ } Try / catch
try {
const h = await page.evaluateHandle(fn);
} catch (e) {
if (/nil result/.test(e.message)) {
// expression returned null/undefined — treat as missing
} else throw e;
} Prevention
- Use page.$ for DOM lookups; reserve evaluateHandle for JS values you know exist
- Make evaluated functions return a sentinel object on miss
- Check that the expression has an explicit return statement
When it happens
Trigger: page.evaluateHandle(() => null); evaluateHandle('document.getElementById("does-not-exist")') (querySelector miss returns null); an arrow function with a body that has no return statement (implicit undefined).
Common situations: Querying handles for optional/conditional elements; typos in ids or selectors used inside the expression; code ported from page.$ where the miss would instead return null cleanly.
Related errors
- Unexpected end of selector while parsing selector `${selecto
- Error while parsing selector `${selector}` - cannot use ${op
- Error while parsing selector `${selector}` - selector cannot
- "level" attribute must be compared to a number
- "name" attribute must be a string or a regular expression
AI-assisted analysis of grafana/k6@01ffac6f24 (2026-08-18).
Data as JSON: /api/errors/d0d83796afab1028.
Report an issue: GitHub.