vitest-dev/vitest · error · Error
Invalid element or locator
Error message
Invalid element or locator: ${elementOrLocator}. Expected an instance of HTMLElement, SVGElement or Locator, received ${getType(elementOrLocator)} What it means
expect.element() (the entry point for element assertions like expect(el).toBeVisible()) validates its argument up front: it must be null, undefined, an HTMLElement, an SVGElement, or an object carrying the internal Locator symbol ($$vitest:locator). Anything else throws immediately with the received type name. This guards the polling wrapper from receiving a value it cannot resolve to a DOM node.
Solutions
- Pass only HTMLElement/SVGElement (or null) or a Vitest Locator produced by page.getByRole/getByText etc.
- Await async lookups before passing: const el = await locator.element().
- If integrating a third-party locator, convert it to a Vitest Locator via page.elementLocator on its underlying Element.
Example fix
// before
expect(document.querySelectorAll('.btn')).toBeVisible() // NodeList
// after
expect(document.querySelector('.btn')).toBeVisible() Defensive patterns
Strategy: type-guard
Validate before calling
const kLocator = Symbol.for('$$vitest:locator')
function isValidTarget(v: unknown): boolean {
return v == null || v instanceof HTMLElement || v instanceof SVGElement || (typeof v === 'object' && v !== null && kLocator in v)
}
if (!isValidTarget(el)) throw new Error('invalid target')
expect(el).toBeVisible() Type guard
function isElementOrLocator(v: unknown): v is HTMLElement | SVGElement | Locator {
const kLocator = Symbol.for('$$vitest:locator')
return v instanceof HTMLElement || v instanceof SVGElement || (typeof v === 'object' && v !== null && kLocator in v)
} Prevention
- Pass only HTMLElement/SVGElement, null, or a Vitest Locator to expect.element-based assertions.
- Await async lookups before passing their result.
- Convert third-party locators to Vitest Locators via page.elementLocator.
When it happens
Trigger: Calling expect(myString).toBeVisible(); passing a number, plain object, NodeList, or a third-party Locator-like object that lacks the $$vitest:locator symbol; passing a Promise that was not awaited.
Common situations: Mixing Vitest Locators with Playwright/RTL locators; forgetting to await an async query; passing the result of a querySelector without null-checking when it actually returned a non-Element (e.g. due to a wrapper).
Related errors
- aria adapter expects an Element
- Element not found
- Expected element or locator to be an instance of Element or…
- Expecting a valid DOM element, but got
- received value must an HTMLElement or an SVGElement or a…
AI-assisted analysis of vitest-dev/vitest@1fa9837ec2 (2026-08-11).
Data as JSON: /api/errors/646f8d5bf7ce2ca4.
Report an issue: GitHub.
Appendix: 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 oneView on GitHub (pinned to 1fa9837ec2)