import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
WordPress 6.9 added assertEqualHTML(), which removes a whole category of brittle test failures caused by formatting-only HTML differences. I reviewed the implementation and mapped out concrete migration patterns for plugin and theme test suites.
If your test suite has ever failed because of a whitespace difference in rendered HTML, this is for you.
The Problem
"In plugin and theme suites, many HTML tests still use strict string equality and fail on whitespace, indentation, or equivalent tag formatting instead of real behavior regressions."
ℹ️ Info: Context
assertEqualHTML()landed in WordPress core test tools in 6.9. It compares HTML equivalence, not raw string identity. The method normalizes whitespace, attribute order, and insignificant formatting differences before comparison. This means<a class="btn" href="/docs">and<a href="/docs" class="btn">are treated as equivalent.
Migration Patterns
Before:
```php title="tests/test-output.php"
$this->assertSame(
'
Saved
',$actual_html
);
**After:**
```php title="tests/test-output.php"
// highlight-next-line
$this->assertEqualHTML(
'<p class="notice">Saved</p>',
$actual_html
);
Before:
```php title="tests/test-render.php"
$normalize = static fn( $html ) => preg_replace( '/\s+/', ' ', trim( $html ) );
$this->assertSame( $normalize( $expected ), $normalize( $actual ) );
**After:**
```php title="tests/test-render.php"
// highlight-next-line
$this->assertEqualHTML( $expected, $actual );
No more hand-rolled normalization functions.
Before:
```php title="tests/test-blocks.php"
ob_start();
render_banner_block( array( 'message' => 'Hi' ) );
$actual = ob_get_clean();
$this->assertSame(
'
Hi
',$actual
);
**After:**
```php title="tests/test-blocks.php"
ob_start();
render_banner_block( array( 'message' => 'Hi' ) );
$actual = ob_get_clean();
// highlight-next-line
$this->assertEqualHTML(
'<section class="banner"><p>Hi</p></section>',
$actual
);
Before (fails):
```php title="tests/test-links.php"
$expected = 'Docs';
$actual = 'Docs';
$this->assertSame( $expected, $actual ); // FAILS
**After (passes):**
```php title="tests/test-links.php"
// highlight-next-line
$this->assertEqualHTML( $expected, $actual ); // PASSES
When to Use Which Assertion
| Assertion | Use When |
|---|---|
assertEqualHTML() |
Rendered markup where DOM meaning matters more than exact serialization |
assertSame() |
Exact escaping, spacing, or deterministic serialization matters |
assertEqualHTML() |
Tests compensating for formatting differences |
assertSame() |
Security assertions verifying exact output |
Version-Safe Bridge
If your suite runs against WordPress versions older than 6.9:
```php title="tests/helpers/compat.php"
private function assertHtmlEquivalent( string $expected, string $actual ): void {
if ( method_exists( $this, 'assertEqualHTML' ) ) {
// highlight-next-line
$this->assertEqualHTML( $expected, $actual );
return;
}
// Fallback for pre-6.9
$this->assertSame( trim( $expected ), trim( $actual ) );
}
> **⚠️ Caution: Reality Check**
>
> Do not over-migrate. Keep `assertSame()` for escaping/security assertions and exact output contracts. A two-lane assertion policy (`assertEqualHTML()` for semantics, `assertSame()` for exactness) keeps intent explicit. If you replace every assertion with `assertEqualHTML()`, you lose the ability to catch real escaping bugs.
## Rollout Guidance
```mermaid
flowchart TD
A[Start with render-heavy tests] --> B[Convert tests with custom normalization code]
B --> C[Convert output-buffer tests]
C --> D[Convert attribute-order-sensitive tests]
D --> E{Does test verify exact escaping/security?}
E -->|Yes| F[Keep assertSame]
E -->|No| G[Migrate to assertEqualHTML]
Full rollout checklist
- Start with render-heavy tests (
render_callback, shortcode output, template helpers) - Convert only tests currently compensating for formatting differences
- Keep
assertSame()for escaping/security assertions and exact output contracts - Add a short team note: "Use
assertEqualHTML()for semantic markup checks" - Delete hand-rolled normalization helper functions
- Run full suite after migration to catch any tests that were silently relying on exact formatting
- Document the two-lane policy in your testing guidelines
Why this matters for Drupal and WordPress
WordPress plugin and theme developers can immediately reduce flaky CI failures by migrating render tests to assertEqualHTML(). Drupal developers writing Kernel or Functional tests for render arrays and Twig output face the same problem: comparing rendered HTML with assertEquals() breaks on insignificant whitespace and attribute order changes between Drupal core versions. Drupal teams can adopt the same two-lane assertion strategy (semantic comparison for markup, strict comparison for escaping) in their PHPUnit suites, even without a built-in assertEqualHTML() equivalent, by writing a thin normalization wrapper.
What I Learned
-
assertEqualHTML()landed in WordPress core test tools in 6.9 and compares HTML equivalence, not raw string identity. - Most migration wins come from deleting custom normalization code and reducing flaky failures.
- A two-lane assertion policy (
assertEqualHTML()for semantics,assertSame()for exactness) keeps intent explicit and avoids over-migration. - This is one of the most underrated testing improvements in recent WordPress history.
References
Looking for an Architect who doesn't just write code, but builds the AI systems that multiply your team's output? View my enterprise CMS case studies at victorjimenezdev.github.io or connect with me on LinkedIn.
Looking for an Architect who doesn't just write code, but builds the AI systems that multiply your team's output? View my enterprise CMS case studies at victorjimenezdev.github.io or connect with me on LinkedIn.
Originally published at VictorStack AI — Drupal & WordPress Reference
Top comments (0)