grafana/k6 · error

Role must not be empty

Error message

Role must not be empty

What it means

After the inputValue evaluation succeeds (frame.go:1385), k6 asserts the CDP-returned value is a string; a non-string yields 'unexpected type %T'. The injected JS returns element.value, which for input/textarea/select is always a string per spec, so this defensive check fires only on abnormal pages: a patched value getter on the prototype, or a protocol conversion quirk. It indicates page tampering or a k6/browser bug rather than a wrong API call.

Source

Thrown at internal/js/modules/k6/browser/common/js/injected_script.js:1284

      shadows.push(root.shadowRoot);
    for (const element of root.querySelectorAll("*")) {
      match(element);
      if (element.shadowRoot)
        shadows.push(element.shadowRoot);
    }
    shadows.forEach(query);
  };
  query(scope);
  return result;
}

function createRoleEngine(internal) {
  return {
    queryAll: (scope, selector) => {
      const parsed = parseAttributeSelector(selector, true);
      const role = parsed.name.toLowerCase();
      if (!role)
        throw new Error(`Role must not be empty`);
      const options = validateAttributes(parsed.attributes, role);
      beginAriaCaches();
      try {
        return queryRole(scope, options, internal);
      } finally {
        endAriaCaches();
      }
    }
  };
}

// packages/injected/src/selectorEvaluator.ts
var SelectorEvaluatorImpl = class {
  constructor() {
    this._retainCacheCounter = 0;
    this._cacheText = new k6BrowserNative.Map();
    this._cacheQueryCSS = new k6BrowserNative.Map();
  }

View on GitHub (pinned to 01ffac6f24)

Solutions

  1. Inspect directly: const v = await page.evaluate(`document.querySelector(sel).value`); check typeof v.
  2. Read the value via page.evaluate when the getter is patched.
  3. Report a grafana/k6 issue with a minimal repro if the raw property is a string but the error persists.
  4. Use a pinned, standard Chromium build.

Example fix

// before
const v = await page.inputValue('#email'); // unexpected type ...

// after
const v = await page.evaluate(`document.querySelector('#email').value`);
if (typeof v !== 'string') throw new Error('value getter patched');
Defensive patterns

Strategy: fallback

Validate before calling

const t = await page.evaluate(`
  (() => { const el = document.querySelector('#email'); return el ? typeof el.value : 'no-el'; })()
`);
if (t !== 'string') throw new Error('value getter patched');

Type guard

function isValueString(v) { return typeof v === 'string'; }

Try / catch

try {
  return await page.inputValue(sel);
} catch (e) {
  if (String(e).includes('unexpected type')) {
    return await page.evaluate(`document.querySelector('${sel}').value`);
  }
  throw e;
}

Prevention

When it happens

Trigger: Page defines a getter for HTMLInputElement.prototype.value returning non-strings; anti-bot frameworks or frameworks like older React versions interfering with native accessors; CDP value conversion edge cases; Chromium version mismatch.

Common situations: Testing protected login/checkout flows where bot detection patches input values; custom form libraries overriding value; k6 browser module paired with a non-standard Chromium.

Related errors


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