microsoft/playwright · error · Error
"role" argument in toHaveRole must be a string
Error message
"role" argument in toHaveRole must be a string
What it means
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.
Source
Thrown at packages/playwright/src/matchers/matchers.ts:386
this: ExpectMatcherStateInternal,
locator: LocatorEx,
name: string,
expected: any,
options?: { timeout?: number, signal?: AbortSignal },
) {
return toEqual.call(this, 'toHaveJSProperty', locator, 'Locator', async (isNot, timeout, signal) => {
return await locator._expect('to.have.property', { expressionArg: name, expectedValue: expected, isNot, timeout, signal, title: this.title });
}, expected, options);
}
export function toHaveRole(
this: ExpectMatcherStateInternal,
locator: LocatorEx,
expected: string,
options?: { timeout?: number, ignoreCase?: boolean, signal?: AbortSignal },
) {
if (!isString(expected))
throw new Error(`"role" argument in toHaveRole must be a string`);
return toMatchText.call(this, 'toHaveRole', locator, 'Locator', async (isNot, timeout, signal) => {
const expectedText = serializeExpectedTextValues([expected]);
return await locator._expect('to.have.role', { expectedText, isNot, timeout, signal, title: this.title });
}, expected, options);
}
export function toHaveText(
this: ExpectMatcherStateInternal,
locator: LocatorEx,
expected: string | RegExp | (string | RegExp)[],
options: { timeout?: number, useInnerText?: boolean, ignoreCase?: boolean, signal?: AbortSignal } = {},
) {
if (Array.isArray(expected)) {
return toEqual.call(this, 'toHaveText', locator, 'Locator', async (isNot, timeout, signal) => {
const expectedText = serializeExpectedTextValues(expected, { normalizeWhiteSpace: true, ignoreCase: options.ignoreCase });
return await locator._expect('to.have.text.array', { expectedText, isNot, useInnerText: options?.useInnerText, timeout, signal, title: this.title });
}, expected, options);
} else {View on GitHub (pinned to deda92d15e)
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.
Example fix
// before
await expect(page.getByText('Docs')).toHaveRole(/heading/);
// Error: "role" argument in toHaveRole must be a string
// after
await expect(page.getByText('Docs')).toHaveRole('heading'); Defensive patterns
Strategy: type-guard
Validate before calling
const role: unknown = getRoleFromData();
if (typeof role !== 'string')
throw new Error(`toHaveRole requires an ARIA role string like 'button', got: ${String(role)}`);
await expect(locator).toHaveRole(role); Type guard
function isRoleString(value: unknown): value is string {
return typeof value === 'string' && value.length > 0;
}
// usage
if (!isRoleString(role)) throw new Error(`Invalid role: ${String(role)}`);
await expect(locator).toHaveRole(role); Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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').
Related errors
- "expected" argument in toContainClass cannot contain RegExp
- "expected" argument in toContainClass cannot be a RegExp val
- "not" matcher requires expected result
AI-assisted analysis of microsoft/playwright@deda92d15e (2026-08-21).
Data as JSON: /api/errors/6386cb2fc57e7f08.
Report an issue: GitHub.