vitest-dev/vitest · warning · TypeError

Expecting a valid DOM element, but got ${typeName}.

Error message

Expecting a valid DOM element, but got ${typeName}.

What it means

Thrown by prettyDOM (packages/browser/src/client/tester/context.ts:547-568), which is invoked by page.debug(). After resolving a Locator to its element and defaulting null to document.body, the function checks that the value is an object with an outerHTML property; anything else (number, string, plain object without outerHTML, undefined leaked through) is rejected as a TypeError with the constructor name or typeof result.

Source

Thrown at packages/browser/src/client/tester/context.ts:567

  maxLength: number = Number(defaultOptions?.maxLength ?? import.meta.env.DEBUG_PRINT_LIMIT ?? 7000),
  prettyFormatOptions: PrettyDOMOptions = {},
): string {
  if (maxLength === 0) {
    return ''
  }

  if (!dom) {
    dom = document.body
  }

  if ('element' in dom && 'all' in dom) {
    dom = dom.element()
  }

  const type = typeof dom
  if (type !== 'object' || !dom.outerHTML) {
    const typeName = type === 'object' ? dom.constructor.name : type
    throw new TypeError(`Expecting a valid DOM element, but got ${typeName}.`)
  }

  const pretty = stringify(dom, Number.POSITIVE_INFINITY, {
    maxLength,
    highlight: true,
    ...defaultOptions,
    ...prettyFormatOptions,
  })
  return dom.outerHTML.length > maxLength
    ? `${pretty.slice(0, maxLength)}...`
    : pretty
}

function getElementError(selector: string | Locator, container: Element): Error {
  const locator = typeof selector === 'string' ? __INTERNAL._asLocator('javascript', selector) : selector.asLocator()
  const formatted = formatDOM(container)
  const error = new Error(`Cannot find element with locator: ${locator}\n\n${formatted}`)
  error.name = 'VitestBrowserElementError'

View on GitHub (pinned to d568f8ce37)

Solutions

  1. Pass a single Element or Locator: page.debug(document.querySelector('.x')).
  2. For a NodeList/array, iterate: nodes.forEach(n => page.debug(n)) or use page.debug(Array.from(nodes)) (debug accepts arrays at context.ts:537-540).
  3. Coerce before debugging: page.debug(typeof v === 'string' ? document.body : v).

Example fix

// before
page.debug(document.querySelectorAll('.item'))
// after
page.debug(Array.from(document.querySelectorAll('.item')))
Defensive patterns

Strategy: type-guard

Validate before calling

function isDebuggable(v: unknown): v is Element | Locator | (Element | Locator)[] {
  if (Array.isArray(v)) return v.every(isDebuggable)
  return v instanceof Element || (v != null && typeof v === 'object' && 'element' in v && 'all' in v)
}
if (isDebuggable(maybeNode)) page.debug(maybeNode as any)

Type guard

function isDebuggableNode(v: unknown): v is Element | Locator {
  return v instanceof Element || (v != null && typeof v === 'object' && 'element' in (v as any) && 'all' in (v as any))
}

Prevention

When it happens

Trigger: Calling page.debug(value) where value is a primitive (string/number), a plain JS object, a NodeList (which has no outerHTML), or an array element passed singularly that isn't a DOM Element/Locator. Also triggered if a Locator.element() returned something unexpected.

Common situations: Passing the result of querySelectorAll (a NodeList) instead of a single node; passing an event target that isn't an Element; debugging with console.log-style arguments into page.debug.

Related errors


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