vitest-dev/vitest · error · TypeError

.toContainHTML() expects a string value, got

Error message

.toContainHTML() expects a string value, got ${htmlText}

What it means

The .toContainHTML() matcher requires its second argument (the expected HTML snippet) to be a string so it can be normalized and injected into a detached div for comparison. Passing a non-string (number, object, null, undefined) throws a TypeError before any comparison occurs. This is a strict input-type guard, not a comparison failure.

Solutions

  1. Ensure the expected argument is always a string literal or a string-typed variable.
  2. Add a typeof check before the assertion if the value comes from dynamic input.
  3. Use String(value) only if a coercion is genuinely intended.

Example fix

// before
const html = parts.length
expect(el).toContainHTML(html)
// after
const html = parts.join('')
expect(el).toContainHTML(html)
Defensive patterns

Strategy: validation

Validate before calling

if (typeof htmlText !== 'string') {
  throw new TypeError('expected HTML must be a string')
}
expect(el).toContainHTML(htmlText)

Type guard

function isString(v: unknown): v is string {
  return typeof v === 'string'
}

Prevention

When it happens

Trigger: Calling expect(el).toContainHTML(123) or .toContainHTML({ tag: 'div' }); passing a variable holding null because the expected HTML was never assigned; interpolating a non-string template result.

Common situations: Building expected HTML dynamically and accidentally passing a number/object; refactoring that changed a string constant to an object; null defaults from optional config.

Related errors


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

Appendix: source

Thrown at packages/browser/src/client/tester/expect/toContainHTML.ts:34

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

function getNormalizedHtml(container: HTMLElement | SVGElement, htmlText: string) {
  const div = container.ownerDocument.createElement('div')
  div.innerHTML = htmlText
  return div.innerHTML
}

export default function toContainHTML(
  this: MatcherState,
  actual: Element | Locator,
  htmlText: string,
): MatcherResult {
  const htmlElement = getElementFromUserInput(actual, toContainHTML, this)

  if (typeof htmlText !== 'string') {
    throw new TypeError(`.toContainHTML() expects a string value, got ${htmlText}`)
  }

  return {
    pass: htmlElement.outerHTML.includes(getNormalizedHtml(htmlElement, htmlText)),
    message: () => {
      return [
        this.utils.matcherHint(
          `${this.isNot ? '.not' : ''}.toContainHTML`,
          'element',
          '',
        ),
        'Expected:',
        `  ${this.utils.EXPECTED_COLOR(htmlText)}`,
        'Received:',
        `  ${this.utils.printReceived(htmlElement.cloneNode(true))}`,
      ].join('\n')
    },
  }

View on GitHub (pinned to 1fa9837ec2)