{"record":{"id":"6d449530cfd221a8","repo":"grafana/k6","slug":"error-while-parsing-selector-selector-selec","errorCode":null,"errorMessage":"Error while parsing selector `${selector}` - selector cannot be empty","messagePattern":"Error while parsing selector `(.+?)` - selector cannot be empty","errorType":"exception","errorClass":"InvalidSelectorError","httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/browser/common/js/injected_script.js","lineNumber":1141,"sourceCode":"    eat1();\n    if (operator !== \"=\" && typeof value !== \"string\")\n      throw new InvalidSelectorError(`Error while parsing selector \\`${selector}\\` - cannot use ${operator} in attribute with non-string matching value - ${value}`);\n    return { name: jsonPath.join(\".\"), jsonPath, op: operator, value, caseSensitive };\n  }\n  const result = {\n    name: \"\",\n    attributes: []\n  };\n  result.name = readIdentifier();\n  skipSpaces();\n  while (next() === \"[\") {\n    result.attributes.push(readAttribute());\n    skipSpaces();\n  }\n  if (!EOL)\n    syntaxError(void 0);\n  if (!result.name && !result.attributes.length)\n    throw new InvalidSelectorError(`Error while parsing selector \\`${selector}\\` - selector cannot be empty`);\n  return result;\n}\n\n// packages/injected/src/roleSelectorEngine.ts\nvar kSupportedAttributes = [\"selected\", \"checked\", \"pressed\", \"expanded\", \"level\", \"disabled\", \"name\", \"include-hidden\"];\nkSupportedAttributes.sort();\nfunction validateSupportedRole(attr, roles, role) {\n  if (!roles.includes(role))\n    throw new Error(`\"${attr}\" attribute is only supported for roles: ${roles.slice().sort().map((role2) => `\"${role2}\"`).join(\", \")}`);\n}\nfunction validateSupportedValues(attr, values) {\n  if (attr.op !== \"<truthy>\" && !values.includes(attr.value))\n    throw new Error(`\"${attr.name}\" must be one of ${values.map((v) => JSON.stringify(v)).join(\", \")}`);\n}\nfunction validateSupportedOp(attr, ops) {\n  if (!ops.includes(attr.op))\n    throw new Error(`\"${attr.name}\" does not support \"${attr.op}\" matcher`);\n}","sourceCodeStart":1123,"sourceCodeEnd":1159,"githubUrl":"https://github.com/grafana/k6/blob/3fcf5388d78cb382f0c0d42ec556a06bfd1b8dee/internal/js/modules/k6/browser/common/js/injected_script.js#L1123-L1159","documentation":"After a successful getAttribute evaluation (frame.go:1025), k6 type-asserts the CDP-returned value to string; if the DevTools protocol returned anything other than a string (or nil, which is handled), the assertion fails with 'unexpected type %T (expecting string)'. Because Element.getAttribute() in the DOM spec always returns string or null, a non-string means the page environment returned an abnormal value: page JavaScript overriding Element.prototype.getAttribute, or a protocol conversion quirk. This is a defensive invariant check, so hitting it usually indicates page tampering or a k6/browser bug.","triggerScenarios":"The page redefines Element.prototype.getAttribute to return an object/number/undefined; a browser extension or injected script mutates DOM prototypes; a CDP RemoteObject conversion edge case returns a non-string; version mismatch between k6's browser module and the launched Chromium.","commonSituations":"Testing sites with aggressive anti-bot monkey-patching; pages pulling in polyfills that replace native DOM getters; custom Chromium builds or unusual versions launched via executablePath.","solutions":["Fetch the attribute manually via evaluation to inspect what the page really returns: await page.evaluate(`document.querySelector(sel).getAttribute(name)`) and check typeof.","If a polyfill/override is the cause, target the element's property directly (el.href) or use a different attribute API (page.evaluate).","Report a k6 issue (grafana/k6) with the page snippet and k6 version if the value is a plain string in DevTools but the error persists.","Pin the browser: launch a known-good Chromium via the browser module's launch options to rule out version drift."],"exampleFix":"// before\nconst v = await page.getAttribute('#a', 'href'); // unexpected type ...\n\n// after\nconst v = await page.evaluate(`\n  document.querySelector('#a').getAttribute('href')\n`); // returns whatever the page really produces\nif (typeof v !== 'string' && v !== null) throw new Error('page overrides getAttribute');","handlingStrategy":"fallback","validationCode":"const t = await page.evaluate(`\n  (() => { const el = document.querySelector('#a'); return el ? typeof el.getAttribute('href') : 'no-el'; })()\n`);\nif (t !== 'string' && t !== 'object') throw new Error('getAttribute patched on this page');","typeGuard":"function isAttrResult(v) {\n  return v === null || typeof v === 'string';\n}","tryCatchPattern":"try {\n  return await page.getAttribute(sel, name);\n} catch (e) {\n  if (String(e).includes('unexpected type')) {\n    // fall back to raw evaluation\n    return await page.evaluate(`document.querySelector('${sel}').getAttribute('${name}')`);\n  }\n  throw e;\n}","preventionTips":["On sites known to patch DOM prototypes, read attributes via page.evaluate from the start.","Keep k6 and its bundled/launched Chromium aligned in version.","Report reproducible unexpected-type results upstream with a minimal page."],"tags":["k6","browser","getattribute","type-assertion","cdp"],"backgroundTag":"unexpected-eval-return-type","analyzedSha":"3fcf5388d78cb382f0c0d42ec556a06bfd1b8dee","analyzedAt":"2026-08-18T03:05:52.393Z","contentChangedAt":"2026-08-18T03:05:52.393Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}