hamcrest/hamcrest-php · error · InvalidArgumentException

Must pass a valid HTML or XHTML document

Error message

Must pass a valid HTML or XHTML document

What it means

The HTML branch of HasXPath::createDocument(): when the input does not start with an XML declaration, it is loaded as HTML via loadHTML(); failure throws 'Must pass a valid HTML or XHTML document'. loadHTML errors are suppressed, so any parse failure surfaces as this InvalidArgumentException.

Solutions

  1. Check the string is non-empty and contains markup before calling hasXPath
  2. Validate with a standalone loadHTML call in a test precondition
  3. If the content is XML, add the <?xml declaration so the loadXML branch runs
  4. Fix the upstream producer emitting empty/invalid HTML

Example fix

// before
assertThat($emptyBody, hasXPath('//p'));
// after
if (trim($emptyBody) === '') { throw new RuntimeException('Empty HTML body'); }
assertThat($emptyBody, hasXPath('//p'));
Defensive patterns

Strategy: validation

Validate before calling

function isNonEmptyHtml(string $text): bool {
    $t = trim($text);
    if ($t === '') return false;
    $d = new DOMDocument();
    libxml_use_internal_errors(true);
    $ok = @$d->loadHTML($t);
    libxml_clear_errors();
    return $ok;
}

Try / catch

try {
    assertThat($html, hasXPath('//p'));
} catch (\InvalidArgumentException $e) {
    if (strpos($e->getMessage(), 'HTML or XHTML') === false) throw $e;
    fail('Invalid/empty HTML body: '.var_export(substr($html, 0, 200), true));
}

Prevention

When it happens

Trigger: Passing empty strings, plain text, or badly broken markup (that libxml refuses) to hasXPath() without an XML declaration; passing XML that lacks the <?xml header but is not valid HTML either.

Common situations: Asserting against empty or whitespace-only response bodies; fragments with unclosed critical tags; passing JSON or plain text mistakenly expecting HTML.

Related errors


AI-assisted analysis of hamcrest/hamcrest-php@aa726aeff9 (2026-09-15). Data as JSON: /api/errors/ba3289cbc287f5e1. Report an issue: GitHub.

Appendix: source

Thrown at hamcrest/Hamcrest/Xml/HasXPath.php:85

    /**
     * Creates and returns a <code>DOMDocument</code> from the given
     * XML or HTML string.
     *
     * @param string $text
     * @return \DOMDocument built from <code>$text</code>
     * @throws \InvalidArgumentException if the document is not valid
     */
    protected function createDocument($text)
    {
        $document = new \DOMDocument();
        if (preg_match('/^\s*<\?xml/', $text)) {
            if (!@$document->loadXML($text)) {
                throw new \InvalidArgumentException('Must pass a valid XML document');
            }
        } else {
            if (!@$document->loadHTML($text)) {
                throw new \InvalidArgumentException('Must pass a valid HTML or XHTML document');
            }
        }

        return $document;
    }

    /**
     * Applies the configured XPath to the DOM node and returns either
     * the result if it's an expression or the node list if it's a query.
     *
     * @param \DOMNode $node context from which to issue query
     * @return mixed result of expression or DOMNodeList from query
     */
    protected function evaluate(\DOMNode $node)
    {
        if ($node instanceof \DOMDocument) {
            $xpathDocument = new \DOMXPath($node);

View on GitHub (pinned to aa726aeff9)