vitest-dev/vitest · error · Error

Invalid element or locator: ${elementOrLocator}. Expected an

Error message

Invalid element or locator: ${elementOrLocator}. Expected an instance of HTMLElement, SVGElement or Locator, received ${getType(elementOrLocator)}

What it means

Thrown by the element() helper used by expect.element() (packages/browser/src/client/tester/expect-element.ts:14-17) when the argument is non-null but is not an HTMLElement, an SVGElement, or a Locator (detected by the Symbol.for('$$vitest:locator') marker). expect.element is the entry point for DOM matchers like toHaveText, toBeVisible, toBeInTheDocument, so a wrong type fails fast before polling begins.

Source

Thrown at packages/browser/src/client/tester/expect-element.ts:16

import type { Assertion, ExpectPollOptions } from 'vitest'
import type { Locator } from 'vitest/browser'
import type { BrowserTraceEntryStatus } from './trace'
import { chai, expect } from 'vitest'
import { getType } from 'vitest/internal/browser'
import { getBrowserState, getWorkerState, now } from '../utils'
import { ariaMatchers } from './aria'
import { matchers } from './expect'
import { processTimeoutOptions } from './tester-utils'
import { createBrowserTraceRangeId, recordBrowserTraceEntry } from './trace'

const kLocator = Symbol.for('$$vitest:locator')

function element<T extends HTMLElement | SVGElement | null | Locator>(elementOrLocator: T, options?: ExpectPollOptions): Assertion<Promise<void>, HTMLElement | SVGElement | null> {
  if (elementOrLocator != null && !(elementOrLocator instanceof HTMLElement) && !(elementOrLocator instanceof SVGElement) && !(kLocator in elementOrLocator)) {
    throw new Error(`Invalid element or locator: ${elementOrLocator}. Expected an instance of HTMLElement, SVGElement or Locator, received ${getType(elementOrLocator)}`)
  }

  const pollOptions = processTimeoutOptions(options)
  const deadline = pollOptions?.timeout ? now() + pollOptions.timeout : undefined
  const expectElement = expect.poll(async function element(this: object): Promise<HTMLElement | SVGElement | null> {
    if (elementOrLocator instanceof Element || elementOrLocator == null) {
      return elementOrLocator
    }

    const isNot = chai.util.flag(this, 'negate') as boolean
    const name = chai.util.flag(this, '_name') as string
    // special case for `toBeInTheDocument` matcher
    if (isNot && name === 'toBeInTheDocument') {
      return elementOrLocator.query()
    }
    if (name === 'toHaveLength') {
      // we know that `toHaveLength` requires multiple elements,
      // but types generally expect a single one

View on GitHub (pinned to d568f8ce37)

Solutions

  1. Pass a single Element or a Vitest Locator: expect.element(page.getByText('Hi')) or expect.element(node).
  2. If you have an array of locators, index first: expect.element(locators[0]).
  3. For raw selectors, build a locator first: expect.element(page.locator('.btn')).

Example fix

// before
expect.element('.save-button').toHaveText('Save')
// after
expect.element(page.locator('.save-button')).toHaveText('Save')
Defensive patterns

Strategy: type-guard

Validate before calling

import type { Locator } from 'vitest/browser'
const kLocator = Symbol.for('$$vitest:locator')
function isExpectableElement(v: unknown): v is HTMLElement | SVGElement | Locator {
  return v == null
    || v instanceof HTMLElement
    || v instanceof SVGElement
    || (v != null && typeof v === 'object' && kLocator in (v as object))
}
if (isExpectableElement(target)) {
  expect.element(target as any)
}

Type guard

function isElementOrLocator(v: unknown): v is HTMLElement | SVGElement | Locator {
  if (v == null) return true
  if (v instanceof HTMLElement || v instanceof SVGElement) return true
  return typeof v === 'object' && Symbol.for('$$vitest:locator') in (v as object)
}

Prevention

When it happens

Trigger: Calling expect.element(value) where value is a string selector, a plain object, a number, a NodeList, or a component wrapper that doesn't expose the locator symbol. Common with expect.element(locator.findAll()) (returns array) instead of a single locator.

Common situations: Mixing testing-library queries (return elements) with Vitest Locator API; passing a CSS selector string instead of a locator; wrapping a locator in another object.

Related errors


AI-assisted analysis of vitest-dev/vitest@d568f8ce37 (2026-08-03). Data as JSON: /data/errors/646f8d5bf7ce2ca4.json. Report an issue: GitHub.