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.id

View on GitHub (pinned to 01ffac6f24)

Solutions

  1. Use page.$(selector) / frame.$(selector) for element lookups — they return null instead of erroring
  2. Make the evaluated function always return a real object or element (e.g. document.querySelector(sel) || ({}))
  3. If you only need a value, not a handle, use page.evaluate and null-check the result
  4. 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

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


AI-assisted analysis of grafana/k6@01ffac6f24 (2026-08-18). Data as JSON: /api/errors/d0d83796afab1028. Report an issue: GitHub.