ziadoz/assertable-html
As a backend project, ziadoz/assertable-html has picked up 4 stars on GitHub (PHP). Assertable HTML is an elegantly designed PHPUnit library that makes performing assertions on HTML responses from PHP and Laravel applications quick and enjoyable.
Snapshot summary built from the project's own GitHub metadata — there's no written TopGit review yet. The page will update automatically when a full review is published.
TopGit writes full reviews for the most-starred, most-requested repositories. This page is a snapshot until then — see the READ ME tab for the original README in full.
Snapshot
Top contributors
Show top contributors

Assertable HTML takes a fresh approach to testing HTML responses generated by templating engines such as Blade and Twig in PHP and Laravel web applications. It provides an elegant interface that allows developers to fluently navigate and target their HTML using modern CSS selectors, and then write effective test assertions against the results.
Key Features:
- Fluent Interface: Navigate and target HTML elements using an API similar to native JavaScript.
- Minimal Interface: Work with a stripped back API that’s focused on testing.
- Chainable Assertions: Quickly chain together multiple assertions on elements.
- Flexible Assertions: Supply callbacks for complex element assertions if the built-in ones aren’t sufficient.
- Element-Specific Assertions: Use element-specific assertions to quickly test forms and other elements (Coming Soon).
[!IMPORTANT]
There may be some breaking changes with this package until it hits v1.0.0.
Table Of Contents
- Get Started
- Requirements
- Installation
- PHPUnit Installation
- Laravel Installation
- Usage
- Basics
- Scopes
- Assertions
- Assertion Messages
- Flexible Assertions
- Element-Specific Assertions
- HTML Output
🚀 Get Started
Requirements
- PHP 8.4+
- Composer
- Laravel >=11.41.0 (if applicable)
Installation
You can install the package using Composer:
composer install ziadoz/assertable-html
PHPUnit Installation
If you're using PHPUnit, simply include the trait in your test class:
<?php
use PHPUnit\Framework\TestCase;
use Ziadoz\AssertableHtml\Traits\AssertsHtml;
use Ziadoz\AssertableHtml\Dom\AssertableDocument;
class MyTest extends TestCase
{
use AssertsHtml;
// Available methods: assertableHtml(), assertHtml(), assertHead(), assertBody(), assertElement()
public function testHtml(): void
{
$html = <<<'HTML'
<html>
<body>
<h1>Welcome, Archie!</h1>
</body>
</html>
HTML;
$this->assertHtml($html, function (AssertableDocument $html) {
$html->querySelector('h1')
->assertTextEquals('Welcome, Archie!');
});
}
}
Alternatively you can use the Ziadoz\AssertableHtml\Dom\AssertableDocument::createFromString() and Ziadoz\AssertableHtml\Dom\AssertableDocument::createFromFile() methods directly as needed:
public function testHtml(): void
{
AssertableDocument::createFromString('<p>Foo</p>', LIBXML_HTML_NOIMPLIED)
->querySelector('p')
->assertTextEquals('Foo');
}
Laravel Installation
If you're using Laravel, Assertable HTML will be automatically discovered, however you can register it manually if needed:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Ziadoz\AssertableHtml\AssertableHtmlServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->register(AssertableHtmlServiceProvider::class);
}
}
Assertable HTML adds several new methods to the TestResponse, TestView and TestComponent classes in Laravel:
// Responses...
// Available methods: assertableHtml(), assertHtml(), assertHead(), assertBody(), assertElement()
public function testResponse(): void
{
/*
<html>
<body>
<h1>Welcome, Archie!</h1>
</body>
</html>
*/
$this->get('/')->assertBody(function (AssertableElement $body) {
$body->querySelector('h1')
->assertTextEquals('Welcome, Archie!');
});
};
// Views...
// Available methods: assertableHtml(), assertView(), assertElement()
public function testView(): void
{
/*
<nav>
<ul>
<li class="nav-link">Foo<li>
<li class="nav-link active-link">Bar<li>
<li class="nav-link">Baz<li>
<li class="nav-link">Qux<li>
</ul>
</nav>
*/
$this->view('nav')->assertView(function (AssertableDocument $div) {
$div->assertTag('div');
$lis = $div->querySelectorAll('ul li')
->assertCount(4)
->assertAll(function (AssertableElement $li) {
return $li->classes->contains('nav-link');
});
$lis[1]->assertClassContains('active-link');
});
}
// Components...
// Available methods: assertableHtml(), assertComponent(), assertElement()
// Note: Only available in Laravel >= 11.41.0
public function testComponent: void
{
/*
<form method="post" action="/foo/bar">
<input name="action" value="My New Action" class="form-input" required>
<!-- ... -->
</form>
*/
$this->component('action')->assertComponent(function (AssertableDocument $form) {
$form->assertTag('form')
->assertAttributeEquals('method', 'post')
->assertAttributeEquals('action', '/foo/bar');
$form->one('input[name="name"]', function (AssertableElement $input) {
$input->assertAttributeEquals('value', 'My New Action');
$input->assertAttributePresent('required');
$input->assertClassContains('form-input');
});
});
}
🔨 Usage
Basics
To start performing assertions on HTML, create an assertable document:
$document = AssertableDocument::createFromString(<<<'HTML'
<ul>
<li id="foo" class="foo" data-foo="foo"><strong>Foo</strong></li>
<li id="bar" class="bar" data-bar="bar"><strong>Bar</strong></li>
<li id="baz" class="baz" data-baz="baz"><strong>Baz</strong></li>
<li id="qux" class="qux" data-qux="foo"><strong>Qux</strong></li>
</ul>
HTML, LIBXML_HTML_NOIMPLIED);
Now you can begin performing queries using querySelector() and querySelectorAll(), exactly like working with the DOM in JavaScript:
The querySelector() method will return a Ziadoz\AssertableHtml\Dom\AssertableElement instance containing the first matching element, which you can use to perform assertions on the element:
$element = $document->querySelector('li:first-of-type');
$element->assertIdEquals('foo');
$element->assertTextEquals('Foo');
If there are no elements matching the selector, your test will fail:
$element = $document->querySelector('foobar');
// The document doesn't contain an element matching the given selector [foobar].
Assertions are fluent, so if you prefer, you can chain then together:
$element = $document->querySelector('li:first-of-type')
->assertIdEquals('foo')
->assertTextEquals('Foo');
An assertable element has a handful of properties you can access:
echo $element->tag; // 'Foo'
echo $element->html; // '<strong>Foo</strong>''
echo $element->id; // 'foo'
echo $element->text; // 'Foo'
echo $element->classes->toArray(); // ['foo']
echo $elements->attributes->toArray(); // ['id' => 'foo', 'class' => 'foo']
The text, classes and attributes properties refer to further assertable classes:
text:Ziadoz\AssertableHtml\Dom\AssertableText.classes:Ziadoz\AssertableHtml\Dom\AssertableClassesList.attributes:Ziadoz\AssertableHtml\Dom\AssertableAttributesList.
// Text
echo $element->text->value(); // ' Foo Bar '
echo $element->text->value(normaliseWhitespace: true) // 'Foo Bar'
// Classes
echo $element->classes->value(); // ' foo bar '
echo $element->classes->value(normaliseWhitespace: true); // 'foo bar'
$element->classes->toArray(); // ['foo', 'bar']
$element->classes->empty(); // false
$element->classes->contains('foo'); // true
$element->classes->any(['foo', 'qux']); // true
$element->classes->all(['foo', 'qux']); // false
$element->classes->each(function (string $class, int $index) {
echo $class; // 'foo'
echo $index; // 0
});
$element->classes->sequence(
fn (string $class, int $sequence): => $this->assertSame('foo', $class),
fn (string $class, int $sequence): => $this->assertSame('bar', $class),
);
// Attributes
echo $element->attributes->value('data-foo'); // ' bar '
echo $element->attributes->value('data-foo', normaliseWhitespace: true); // 'bar'
$element->attributes->toArray(); // ['class' => 'foo bar', 'data-foo' => 'bar']
$element->attributes->empty(); // false
$element->attributes->names(); // ['class', 'data-foo']
$element->attributes->has('data-foo'); // true
$element->attributes->each(function (string $attribute, ?string $value, int $index) {
echo $attribute; // 'class'
echo $value; // 'foo-bar'
echo $index; // 0
});
$element->attributes->sequence(
fn (string $attribute, ?string $value, int $sequence): => $this->assertSame('class', $attribute),
fn (string $attribute, ?string $value, int $sequence): => $this->assertSame('data-foo', $attribute),
);
You can perform assertions using these classes, however, in most cases the element has a proxy method that makes it more convenient to do from the element:
$element->assertClassContains('foo');
$element->assertIdEquals('foo');
$element->assertAttributEquals('foo', 'foo');
These classes can be useful when you want to perform more advanced custom assertions.
The querySelectorAll() method returns a Ziadoz\AssertableHtml\Dom\AssertableElementsList instance containing every matching element, which allows you to work with the matching elements as an array:
$elements = $document->querySelectorAll('ul > li');
// Access using methods...
echo $elements->first()->id; // foo
echo $elements->nth(1)->id // bar
echo $elements->nth(2)->id // baz
echo $elements->last()->id; // qux
// Or regular array syntax...
echo $elements[0]->id; // foo
echo $elements[1]->id // bar
echo $elements[2]->id // baz
echo $elements[3]->id; // qux
If there are no elements matching the selector, you'll still get back an AssertableElementsList, just in case you want to check there are no elements:
$elements->assertEmpty();
$elements->assertNotEmpty();
You can perform assertions and chain assertions on the matching elements:
$elements->assertCount(4);
$elements->assertAll(function (AssertableElmeent $element) {
return $element->attributes->has('class');
})->assertAny(function (AssertableElmeent $element) {
return $element->classes->contains('foo');
})
$element->each(function (AssertableElement $element) {
$element->assertClassContains('foo');
});
$element->sequence(
fn (AssertableElement $el, int $sequence) => $el->assertTextEquals('Foo'),
fn (AssertableElement $el, int $sequence) => $el->assertTextEquals('Bar'),
fn (AssertableElement $el, int $sequence) => $el->assertTextEquals('Baz'),
fn (AssertableElement $el, int $sequence) => $el->assertTextEquals('Qux'),
);
$elements[0]->assertIdEquals('foo')->assertTextEquals('Foo');
$elements[1]->assertIdEquals('bar')->assertTextEquals('Bar');
$elements[2]->assertIdEquals('baz')->assertTextEquals('Baz');
$elements[3]->assertIdEquals('qux')->assertTextEquals('Qux');
You can also use getElementsByTagName() or getElementById() to query for elements if needed:
$document->getElementsByTagName('li');
$document->getElementById('bar');
Scopes
Sometimes your assertions need room to breathe. For this you can use one(), many(), elsewhere() and with() to filter elements into a callback for better readability.
one(): The first matching element in the current scope usingquerySelector().many(): Every matching element in the current scope usingquerySelectorAll(),elsewhere(): The first matching element in the document scope usingquerySelector().with(): The current element.
Let's give them a try:
$document = AssertableDocument::createFromString(<<<'HTML'
<div id="outer">
<div id="inner">
<div class="innermost"></div>
<div class="innermost"></div>
<div class="innermost"></div>
</div>
<div id="another-inner">
<div class="another-innermost"></div>
<div class="another-innermost"></div>
<div class="another-innermost"></div>
</div>
</div>
HTML, LIBXML_HTML_NOIMPLIED);
$document->one('div#inner', function (AssertableElement $inner) {
$inner->assertIdEquals('inner');
$inner->many('div.innermost', function (AssertableElementsList $innerMosts) {
$innerMosts->assertCount(3);
});
$inner->elsewhere('div#another-inner', function (AssertableElement $anotherInner) {
$anotherInner->assertIdEquals('another-inner');
});
$inner->with(function (AssertableElement $inner) {
$inner->assertIdEquals('inner');
});
});
The when() method makes it possible to perform assertions conditionally, which can be useful when working with data providers or more complex tests:
$element->when(
// Condition can be a boolean, or a callable that evaluates to a boolean...
$condition,
// Called when condition is true...
fn (AssertableElement $element) => $element->assertTextEquals('Foo'),
// Called when condition is false...
fn (AssertableElement $element) => $element->assertTextEquals('Bar'),
);
Assertions
Assertable HTML provides loads of assertions to help you test your HTML is exactly as expected. The majority of these assertions live on the AssertableElement instance, and can be categorised as follows:
- Tag: Assert the element's tag.
- Matches: Assert the element does or doesn't match a selector.
- Count: Assert the number of child elements matching a selector.
- Text: Assert the element's text.
- IDs: Assert the element's ID attribute.
- Classes: Assert the element's classes.
- Attributes: Assert the element's attributes.
Here are some examples:
$element->assertTagEquals('div');
$element->assertIdEquals('foo');
$element->assertMatchesSelector('span.foo');
$element->assertDoesntMatchSelector(':has(img)');
$element->assertElementsNotCount('ul', 0);
$element->assertElementsCount('li.bullet', 3);
$element->assertNumberOfElements('li.odd', '>', 42); // Supports =, !=, >, >=, < and <= comparisons.
$element->assertTextContains('Welcome');
$element->assertTextDoesntContain('Foo!');
$element->assertClassContains('heading');
$element->assertClassDoesntContain('subheading');
$element->assertAttributeEquals('data-foo', 'bar');
$element->assertAttributeMissing('data-bar');
If you're using an IDE such as PhpStorm or VSCode, it should auto-complete the dozens of assertions available for you, along with their parameters.
Assertion Messages
All assertions include a final $message parameter, which allows you to customise the failure message in your tests to your application:
$document->assertElementsCount('img.avatar', 0, 'The profile page is missing an avatar image.');
This can be useful when you need to identify test failures that are specific to your web application.
Flexible Assertions
Sometimes you have a scenario that just isn't possible to test with a built-in assertion. For those scenarios Assertable HTML provides various assertions that accept a callback. If the callback returns true, the test will pass, otherwise it will fail:
// This is a good place to use the $element->classes, $element->attributes and $element->text properties...
$element->assertElement(function (AssertableElement $element) {
return (
$element->classes->contains('foo') &&
str_contains('-Bar-', $element->text->value()) &&
$element->attributes->has('data-foo');
);
});
Every assertable class has flexible assertions available, just in case:
- AssertableElement:
assertElement(),assertText(),assertClass(),assertAttributes()andassertAttribute(). - AssertableElementsList:
assertElements(). - AssertableClassesList:
assertClasses(). - AssertableAttributesList:
assertAttributes()andassertAttribute(). - AssertableText:
assertText().
Element-Specific Assertions
Coming Soon
HTML Output
If you ever need to see the HTML of the element(s) you're working with, you can call dump() and dd() on the assertable instance:
$element->querySelector('p')->dump();
// <p>Foo</p>
$element->querySelectorAll('p, span')->dump();
// <p>Foo</p>
// <span>Bar</span>
You can also call getHtml() to retrieve the HTML as a string:
echo $element->querySelector('p')->getHtml(); // <p>Foo</p>
👏 Thanks
This package wouldn't be possible without the following people and projects:
- Rachel ❤️, Archie 🐶 and Rigby 🐶
- Niels Dossche (PHP 8.4 HTML parsing API author)
- Laravel DOM Assertions for showing me the possibilities of HTML assertions
- Laravel Dusk for showing me the
one()andelsewhere()scoping syntax - Lexbor (the library that powers PHP 8.4's HTML parsing API)
Related repositories
Laravel is an open-source PHP web application framework maintained at laravel/laravel, bundling a routing engine, dependency injection, the Eloquent ORM, schema migrations, queue processing, and event broadcasting into one framework. It's MIT-licensed, documents support for AI coding agents through the separate Laravel Boost package, and points to Laracasts and Laravel Learn for getting started.
Public APIs is a community-curated GitHub repository listing free, publicly accessible APIs across a wide range of categories, with auth type, HTTPS, and CORS noted for each entry. It's a browsable reference, not a library to install.
Awesome Python is a curated list maintained at vinta/awesome-python that groups Python frameworks, libraries, tools, and other resources into 14 categories, from AI & ML to Security and Other. The README calls it an opinionated guide rather than an exhaustive one, and points readers to a companion site, awesome-python.com, for searching and filtering entries instead of scrolling the raw file.
Awesome-Selfhosted is a community-maintained GitHub list at awesome-selfhosted/awesome-selfhosted that catalogs Free Software network-service and web-app projects meant for running on a server you control rather than someone else's cloud. Entries are grouped by category, each tagged with a license and a language or deployment option, and the project keeps proprietary alternatives on a separate Non-Free page.
Quick answers
How active is development on ziadoz/assertable-html?
The most recent commit recorded on ziadoz/assertable-html was 10 months ago, based on the GitHub push timestamp. The repository has 2 forks — one of the better signals of community interest.
How does ziadoz/assertable-html compare to other Backend projects?
ziadoz/assertable-html is tracked by TopGit in the Backend category, with 4 GitHub stars and written in PHP. Browse the Backend topic page on TopGit to compare it against similar projects by stars and activity.
How many stars does ziadoz/assertable-html have?
ziadoz/assertable-html has 4 GitHub stars — refresh the page for the live number, or check github.com/ziadoz/assertable-html. TopGit mirrors GitHub's count but does not claim minute-by-minute accuracy.
What is ziadoz/assertable-html?
ziadoz/assertable-html (ziadoz/assertable-html) is a PHP project on GitHub. From the project's own README: Assertable HTML is an elegantly designed PHPUnit library that makes performing assertions on HTML responses from PHP and Laravel applications quick and enjoyable.
What language is ziadoz/assertable-html written in?
ziadoz/assertable-html is written primarily in PHP. GitHub's language field is based on the largest share of bytes in the default branch.
What topics is ziadoz/assertable-html associated with?
GitHub's repository topics for ziadoz/assertable-html: "blade", "laravel", "php", "php-library", "phpunit-assertions", "ssr", "testing-library", "twig". TopGit's editorial category is Backend.
Why is ziadoz/assertable-html categorized under Backend?
TopGit places ziadoz/assertable-html in the Backend category based on its GitHub topics and description (tagged: "blade", "laravel", "php"). Categories are assigned from real repository metadata, not editorial guesswork.
Read full README in the tab above.
Is assertable-html worth your time?
ChatGPT, Claude and Perplexity can all read this page. Ask one of them what it makes of assertable-html.