grafana/k6 · error

unexpected type %T

Error message

unexpected type %T

What it means

fill() evaluates an injected script that must return one of the protocol strings 'done', 'needsinput', or 'error:...'. This error means the evaluation returned a non-string Go value (nil, bool, object), i.e. the injected-script contract was broken. This is an internal invariant failure (bug or version mismatch), not a wrong fill value.

Source

Thrown at internal/js/modules/k6/browser/common/element_handle.go:270

}

func (h *ElementHandle) fill(_ context.Context, value string) error {
	fn := `
		(node, injected, value) => {
			return injected.fill(node, value);
		}
	`
	opts := evalOptions{
		forceCallable: true,
		returnByValue: true,
	}
	result, err := h.evalWithScript(h.ctx, opts, fn, value)
	if err != nil {
		return err
	}
	s, ok := result.(string)
	if !ok {
		return fmt.Errorf("unexpected type %T", result)
	}

	if s == resultNeedsInput {
		if err := h.frame.page.Keyboard.InsertText(value); err != nil {
			return fmt.Errorf("fill: %w", err)
		}
	} else if s != resultDone {
		// Either we're done or an error happened (returned as "error:..." from JS)
		return errorFromDOMError(s)
	}

	return nil
}

func (h *ElementHandle) focus(apiCtx context.Context, resetSelectionIfNotFocused bool) error {
	fn := `
		(node, injected, resetSelectionIfNotFocused) => {
			return injected.focusNode(node, resetSelectionIfNotFocused);

View on GitHub (pinned to 93accf6570)

Solutions

  1. Rebuild/upgrade with a single clean k6 (or xk6) version so the injected script and Go code match
  2. Re-query the element and retry fill after the page settles (wait for load state)
  3. Verify the target really is input/textarea/contenteditable to rule out the normal not-fillable path
  4. If reproducible on a stock k6 release, report it with the script and page URL

Example fix

// before
await handle.fill('hello'); // unexpected type <nil>

// after
const el = await page.waitForSelector('#name'); // fresh handle in a live context
await el.fill('hello');
Defensive patterns

Strategy: try-catch

Validate before calling

// Confirm the target is fillable before calling fill
const tag = await handle.evaluate((el) => el.tagName);
const editable = await handle.evaluate((el) => el.isContentEditable);
if (!['INPUT', 'TEXTAREA'].includes(tag) && !editable) {
  throw new Error('target is not fillable');
}

Try / catch

try {
  await el.fill('hello');
} catch (e) {
  if (String(e).includes('unexpected type')) {
    // internal contract break: re-locate and retry once, then upgrade k6
    await (await page.$(sel)).fill('hello');
  } else { throw e; }
}

Prevention

When it happens

Trigger: handle.fill(value) when the injected script returns undefined (execution context destroyed mid-eval) or when the bundled injected JS does not match the Go module, e.g. after a partial upgrade or a custom xk6 build mixing browser module versions.

Common situations: Custom xk6 builds bundling a stale injected script; page navigating while fill runs; rarely, a genuine k6 bug. Note: filling a non-fillable element normally yields 'element is not an <input>, <textarea> or [contenteditable] element' via errorFromDOMError, not this error.

Related errors


AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15). Data as JSON: /api/errors/690cad54141e4329. Report an issue: GitHub.