{"record":{"id":"6386cb2fc57e7f08","repo":"microsoft/playwright","slug":"role-argument-in-tohaverole-must-be-a-string","errorCode":null,"errorMessage":"\"role\" argument in toHaveRole must be a string","messagePattern":"\"role\" argument in toHaveRole must be a string","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/playwright/src/matchers/matchers.ts","lineNumber":386,"sourceCode":"  this: ExpectMatcherStateInternal,\n  locator: LocatorEx,\n  name: string,\n  expected: any,\n  options?: { timeout?: number, signal?: AbortSignal },\n) {\n  return toEqual.call(this, 'toHaveJSProperty', locator, 'Locator', async (isNot, timeout, signal) => {\n    return await locator._expect('to.have.property', { expressionArg: name, expectedValue: expected, isNot, timeout, signal, title: this.title });\n  }, expected, options);\n}\n\nexport function toHaveRole(\n  this: ExpectMatcherStateInternal,\n  locator: LocatorEx,\n  expected: string,\n  options?: { timeout?: number, ignoreCase?: boolean, signal?: AbortSignal },\n) {\n  if (!isString(expected))\n    throw new Error(`\"role\" argument in toHaveRole must be a string`);\n  return toMatchText.call(this, 'toHaveRole', locator, 'Locator', async (isNot, timeout, signal) => {\n    const expectedText = serializeExpectedTextValues([expected]);\n    return await locator._expect('to.have.role', { expectedText, isNot, timeout, signal, title: this.title });\n  }, expected, options);\n}\n\nexport function toHaveText(\n  this: ExpectMatcherStateInternal,\n  locator: LocatorEx,\n  expected: string | RegExp | (string | RegExp)[],\n  options: { timeout?: number, useInnerText?: boolean, ignoreCase?: boolean, signal?: AbortSignal } = {},\n) {\n  if (Array.isArray(expected)) {\n    return toEqual.call(this, 'toHaveText', locator, 'Locator', async (isNot, timeout, signal) => {\n      const expectedText = serializeExpectedTextValues(expected, { normalizeWhiteSpace: true, ignoreCase: options.ignoreCase });\n      return await locator._expect('to.have.text.array', { expectedText, isNot, useInnerText: options?.useInnerText, timeout, signal, title: this.title });\n    }, expected, options);\n  } else {","sourceCodeStart":368,"sourceCodeEnd":404,"githubUrl":"https://github.com/microsoft/playwright/blob/deda92d15e7d771aacaae47eb2b6e47a562c30ff/packages/playwright/src/matchers/matchers.ts#L368-L404","documentation":"expect(locator).toHaveRole() asserts an exact ARIA role and therefore requires `expected` to be a plain string; matchers.ts:385-386 throws when `isString(expected)` fails. The value is serialized via serializeExpectedTextValues and sent as 'to.have.role', a channel that only understands string comparison, so regexes or non-strings are rejected at call time as a caller error.","triggerScenarios":"Calling `await expect(locator).toHaveRole(/heading/)` (RegExp), `toHaveRole(null)`, or passing a value typed as any that is not a string. Common with dynamic role variables or copy-paste from toHaveText-style regex assertions.","commonSituations":"Assuming role matchers share toHaveText's `string | RegExp` support; passing the result of an untyped selector helper or API response that is undefined; slight API confusion between getByRole('heading') and toHaveRole('heading').","solutions":["Pass a literal role string: `await expect(locator).toHaveRole('button')`, `toHaveRole('heading')`, `toHaveRole('link')`.","If the value comes from data, verify `typeof expected === 'string'` before asserting (see typeGuard).","For fuzzy matching of the role attribute, use `await expect(locator).toHaveAttribute('role', /head/)` instead."],"exampleFix":"// before\nawait expect(page.getByText('Docs')).toHaveRole(/heading/);\n// Error: \"role\" argument in toHaveRole must be a string\n\n// after\nawait expect(page.getByText('Docs')).toHaveRole('heading');","handlingStrategy":"type-guard","validationCode":"const role: unknown = getRoleFromData();\nif (typeof role !== 'string')\n  throw new Error(`toHaveRole requires an ARIA role string like 'button', got: ${String(role)}`);\nawait expect(locator).toHaveRole(role);","typeGuard":"function isRoleString(value: unknown): value is string {\n  return typeof value === 'string' && value.length > 0;\n}\n\n// usage\nif (!isRoleString(role)) throw new Error(`Invalid role: ${String(role)}`);\nawait expect(locator).toHaveRole(role);","tryCatchPattern":null,"preventionTips":["Pass literal ARIA role names ('button', 'link', 'heading') — never regexes — to toHaveRole.","Keep expected roles in a typed constant list (e.g. `const ROLES = ['button','link'] as const`).","For non-exact matching of the role attribute, use toHaveAttribute('role', /pattern/) instead."],"tags":["assertions","expect","matchers","accessibility","aria-role","typescript"],"backgroundTag":"assertion-argument-type-mismatch","analyzedSha":"deda92d15e7d771aacaae47eb2b6e47a562c30ff","analyzedAt":"2026-08-21T16:56:32.694Z","contentChangedAt":"2026-08-21T16:56:32.694Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}