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
- Check the string is non-empty and contains markup before calling hasXPath
- Validate with a standalone loadHTML call in a test precondition
- If the content is XML, add the <?xml declaration so the loadXML branch runs
- 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
- Assert response bodies are non-empty before XPath assertions
- Preflight loadHTML validation on dynamic content
- Add the <?xml declaration when content is XML/XHTML to route to loadXML
- Check upstream content-type headers match the expected markup
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
- Must pass a valid XML document
- Must pass an object, array, or class name
- assertThat() requires one to three arguments
- Each argument or element must be a Hamcrest matcher
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)