grafana/k6 · error · InvalidSelectorError

Error while parsing selector `${selector}` - cannot use ${op

Error message

Error while parsing selector `${selector}` - cannot use ${operator} in attribute with regular expression

What it means

Thrown as a GoError JavaScript exception by Selection.adjacentUntil (js/modules/k6/html/html.go:128-164), the shared implementation for nextUntil(), prevUntil() and parentsUntil(). After switching on the argument count (0, 1 or 2) and on def[0].Export() accepting only string, Selection or nil, any other type falls out of the inner switch and hits the panic at line 163-164. Unlike varargFnCall-based methods (add/find/closest/has), this path does NOT accept an Element, and the 2-argument form coerces the filter with def[1].String() while the first argument must still be string/Selection/nil.

Source

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

    const jsonPath = [];
    jsonPath.push(readAttributeToken());
    skipSpaces();
    while (next() === ".") {
      eat1();
      jsonPath.push(readAttributeToken());
      skipSpaces();
    }
    if (next() === "]") {
      eat1();
      return { name: jsonPath.join("."), jsonPath, op: "<truthy>", value: null, caseSensitive: false };
    }
    const operator = readOperator();
    let value = void 0;
    let caseSensitive = true;
    skipSpaces();
    if (next() === "/") {
      if (operator !== "=")
        throw new InvalidSelectorError(`Error while parsing selector \`${selector}\` - cannot use ${operator} in attribute with regular expression`);
      value = readRegularExpression();
    } else if (next() === `'` || next() === `"`) {
      value = readQuotedString(next()).slice(1, -1);
      skipSpaces();
      if (next() === "i" || next() === "I") {
        caseSensitive = false;
        eat1();
      } else if (next() === "s" || next() === "S") {
        caseSensitive = true;
        eat1();
      }
    } else {
      value = "";
      while (!EOL && (isCSSNameChar(next()) || next() === "+" || next() === "."))
        value += eat1();
      if (value === "true") {
        value = true;
      } else if (value === "false") {

View on GitHub (pinned to 93accf6570)

Solutions

  1. Use a selector string: `sel.nextUntil('h2')`
  2. Use a Selection as boundary: `sel.nextUntil(doc.find('h2'))`
  3. Use null/undefined (or no argument) to mean 'until the end': `sel.nextUntil(null)`
  4. Do not pass an Element here — if you hold `sel.get(0)`, go through a selector or Selection instead
  5. Guard dynamic boundaries: `const until = (typeof b === 'string' || b instanceof Object) ? b : null;` before calling

Example fix

// before
const boundary = doc.find('h2').get(0); // an Element — not accepted by adjacentUntil
items.prevUntil(boundary); // GoError: cannot use a '*html.Element' as a selector

// after
const boundary = doc.find('h2'); // keep the Selection
items.prevUntil(boundary);
// or use the selector string directly:
items.prevUntil('h2');
Defensive patterns

Strategy: type-guard

Validate before calling

const isUntilArg = (a) =>
  a == null || // until the end
  typeof a === 'string' || // selector
  (typeof a === 'object' && typeof a.find === 'function'); // Selection — Element is NOT valid

function safeNextUntil(sel, boundary) {
  if (!isUntilArg(boundary)) throw new Error(`invalid until-argument: ${typeof boundary}`);
  return sel.nextUntil(boundary);
}

Type guard

function isUntilBoundary(a) {
  if (a == null) return true;
  if (typeof a === 'string') return true;
  return a && typeof a === 'object' && typeof a.find === 'function' && typeof a.size === 'number';
}

Try / catch

try {
  sel.nextUntil(arg);
} catch (e) {
  if (/cannot use a '.*' as a selector/.test(String(e.message))) {
    sel.nextUntil(); // unbounded traversal as fallback
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Calling `sel.nextUntil(x)`, `sel.prevUntil(x)` or `sel.parentsUntil(x)` where x is an Element (e.g. `sel.get(0)`), a number, boolean, plain object or array. Also `sel.parentsUntil(elem, '.filter')` with two args and an Element/number first argument — the switch falls through both for the 1-arg and 2-arg forms.

Common situations: Porting jQuery traversals that pass DOM nodes to *Until methods; mixing up Element (accepted by find/add) with Selection (the only object type accepted here); passing an index or a boolean as the until-boundary; undefined boundary variables from shared config.

Related errors


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