vitest-dev/vitest · error · Error

expected selection must be a string or undefined

Error message

expected selection must be a string or undefined

What it means

Thrown by the `toHaveSelection` matcher when the expected-selection argument is defined (not undefined) but is not a string. The matcher reads `selection.toString()` from the DOM and compares it to a string; a non-string expected value cannot be compared meaningfully, so it rejects the call up front. Passing `undefined` is allowed and means 'assert that some selection exists'.

Solutions

  1. Pass the exact selected text string, e.g. `expect(el).toHaveSelection('hello')`.
  2. To assert only that a selection exists, omit the argument or pass `undefined`: `expect(el).toHaveSelection()`.
  3. If you need offset-based assertions, read `element.selectionStart`/`selectionEnd` directly and assert with `toBe`.

Example fix

// before
expect(input).toHaveSelection(5)

// after
expect(input).toHaveSelection('hello')
Defensive patterns

Strategy: type-guard

Validate before calling

if (expected !== undefined && typeof expected !== 'string') {
  throw new TypeError('expectedSelection must be a string or undefined')
}
expect(el).toHaveSelection(expected)

Type guard

function isSelectionArg(v: unknown): v is string | undefined {
  return v === undefined || typeof v === 'string'
}

Prevention

When it happens

Trigger: Calling `expect(el).toHaveSelection(42)`, `.toHaveSelection(['a','b'])`, or `.toHaveSelection({ start: 0 })`. Also any value whose `typeof` is not `'string'` and not `'undefined'`.

Common situations: Passing a number where the test meant to assert selected text length; passing an array of strings; confusing `toHaveSelection` (text content) with a hypothetical range/offset API; TypeScript callers bypassing types with `as any`.

Related errors


AI-assisted analysis of vitest-dev/vitest@1fa9837ec2 (2026-08-11). Data as JSON: /api/errors/2acada4c7fda6269. Report an issue: GitHub.

Appendix: source

Thrown at packages/browser/src/client/tester/expect/toHaveSelection.ts:30

 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 */

import type { MatcherResult, MatcherState } from 'vitest'
import type { Locator } from '../locators'
import { arrayAsSetComparison, getElementFromUserInput, getMessage, getTag } from './utils'

export default function toHaveSelection(
  this: MatcherState,
  element: HTMLElement | SVGElement | Locator,
  expectedSelection: string,
): MatcherResult {
  const htmlElement = getElementFromUserInput(element, toHaveSelection, this)

  const expectsSelection = expectedSelection !== undefined

  if (expectsSelection && typeof expectedSelection !== 'string') {
    throw new Error(`expected selection must be a string or undefined`)
  }

  const receivedSelection = getSelection(htmlElement)

  return {
    pass: expectsSelection
      ? this.equals(receivedSelection, expectedSelection, [arrayAsSetComparison, ...this.customTesters])
      : Boolean(receivedSelection),
    message: () => {
      const to = this.isNot ? 'not to' : 'to'
      const matcher = this.utils.matcherHint(
        `${this.isNot ? '.not' : ''}.toHaveSelection`,
        'element',
        expectedSelection,
      )
      return getMessage(
        this,
        matcher,

View on GitHub (pinned to 1fa9837ec2)