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

  1. Pass a literal role string: `await expect(locator).toHaveRole('button')`, `toHaveRole('heading')`, `toHaveRole('link')`.
  2. If the value comes from data, verify `typeof expected === 'string'` before asserting (see typeGuard).
  3. 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

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


AI-assisted analysis of microsoft/playwright@deda92d15e (2026-08-21). Data as JSON: /api/errors/6386cb2fc57e7f08. Report an issue: GitHub.