sakanjo/filament-easy-testings
Simplify testing Filament admin panels with sakanjo/filament-easy-testings. This Laravel package provides convenient helpers and assertions for resources, pages, forms, tables, and actions, helping you write reliable feature tests faster with less boilerplate.
Install (dev only):
composer require --dev sakanjo/filament-easy-testings:^3.0
v3.0.0 is a major bump and the release notes are only a bare changelog link — before adopting, check the composer.json constraints of the tag (php, filament/filament, livewire/livewire, pestphp/pest) and skim the full changelog at https://github.com/sakanjo/filament-easy-testings/compare/v2.0.0...v3.0.0 for renamed traits/namespaces. If you were on v2, expect a short upgrade pass (namespace or helper renames) rather than a drop-in.
Where to look first:
README.md — the available traits/helpers and their signatures (this package is thin sugar over Livewire::test() + Filament's own test helpers, so the surface is small).src/ — the base TestCase / trait you are meant to use in tests/Pest.php or tests/TestCase.php.php artisan vendor:publish --tag="filament-easy-testings-config" (skip if the tag doesn't exist — the package may be config-free).tests/ directory — it doubles as the most accurate usage documentation.First use case — one smoke test for a resource list page:
it('lists posts', function () {
$user = User::factory()->create();
$post = Post::factory()->for($user)->create();
$this->actingAs($user)
->get(PostResource::getUrl('index'))
->assertOk()
->assertCanSeeTableRecords([$post]);
});
That single test proves your panel boots, the resource resolves, the policy allows access, and the table query is wired — the four things that break most often after a Filament upgrade.
Extend, don't duplicate. Put the package's test case/trait in tests/Pest.php (uses(YourPanelTestCase::class)->in('Feature')) so every Filament test inherits panel bootstrapping, setUp() panel selection, and DB refresh. One place to fix when v4 lands.
Data-provider over resources. Because Filament resources are statically discoverable, iterate them:
it('renders index pages', function (string $resource) {
$this->actingAs(admin())->get($resource::getUrl('index'))->assertOk();
})->with(fn () => collect(Filament::getPanel('admin')->getResources())
->map(fn ($r) => $r::class) // Pest resolves class-strings lazily
->all());
Guard with $resource::canViewAny() so permission-gated resources don't produce false failures.
Resource CRUD as three tests, not one. (a) list + table assertions, (b) create form happy path + validation failure, (c) edit + delete via callAction. Splitting keeps failures diagnosable and lets you skip (b)/(c) for read-only resources.
Table interactions — chain against the page component: assertCanSeeTableRecords, assertCanNotSeeTableRecords, searchTable('foo'), sortTable('title','desc'), filterTable('status', 'published'), assertTableColumnStateSet('status','published',$record). Always pass records, not raw IDs; the helpers hydrate models and compare against the rendered collection, which is what you actually care about.
Forms — fillForm([...]) then call('create'), and assert both directions: assertHasNoFormErrors() on the happy path, assertHasFormErrors(['title' => 'required']) on the failure path. fillForm bypasses the browser but not Filament's validation, so it is a genuine test of your rules — including ->live() dependent fields, which require a call('$refresh') or a subsequent fillForm to settle.
Actions — table actions need callTableAction('delete', $record); page/header actions need callAction(). Assert side effects in the DB (assertDatabaseMissing) and the UI signal (assertNotified()), because Filament catches exceptions and converts them to notifications — a swallowed 500 looks green if you only assert the redirect.
Auth realism. Seed roles/permissions in the base case and test at least one denied path per resource. Most Filament production incidents are policy/shield misconfigurations, not rendering bugs, and the cheap assertForbidden() covers a huge class of them.
Tenancy. For tenant-scoped panels, always ->for($tenant) on factories and set the tenant explicitly before get(); otherwise getUrl() returns a URL with the wrong tenant slug and you get a 404 that costs 20 minutes to diagnose.
CI. sqlite :memory: + RefreshDatabase is enough. If you run --parallel, remember Filament's panel registry is static per-process — that's fine — but seeded reference data must be created per-test, not in a global setUpBeforeClass.
composer.json requirements first. If the package pins filament/filament:^4.0, it will silently drag your panel along.Filament::setCurrentPanel() and Filament::setServingStatus() persist across tests in the same process. Set them in setUp() and, if you have multiple panels, reset in tearDown() — otherwise panel-A tests pass or fail depending on file order.Livewire::test() takes the page class. Passing the resource class gives a confusing "component not found" or a bare 500. The resource is for getUrl(); the page is for Livewire::test().route:cache ran in a deploy step on your CI image, getUrl() can 404. Clear it in the CI script before php artisan test.assertSee('Create') breaks the moment someone adds a lang/ file. Prefer assertSee(__('filament-actions::create.single.label')) or assert on the record's attribute.assertCanSeeTableRecords. Records on page 2 are not visible. Either set ->defaultPaginationPageOption(50) in the test or keep fixtures small; a "flaky" test here is usually just 26 seeded rows.searchTable() behaves differently on MySQL/Postgres (LIKE vs ILIKE, collation). Anything asserting search/sort semantics deserves one run against a real engine or a database connection in CI.->withoutExceptionHandling() while writing a test, then remove it once it passes, and check storage/logs/laravel.log whenever an action "just didn't work."dump($component->html()) (Livewire's html()) or ->assertSee('marker') beats guessing; $component->errors() exposes the error bag directly when assertHasFormErrors feels opaque.TestCase (panel boot + roles + factories) around whatever the package exposes over copying its helpers; when you need behaviour it lacks, Livewire::test() + Filament's native assertions (assertFormExists, assertTableExists, assertActionExists) are stable public APIs and won't move with this package's majors. Wrap them in your own assertions trait so a v4 upgrade touches one file."sakanjo/filament-easy-testings": "^3.0" — with release notes this thin, a * or dev-main constraint is asking for a surprise.How can I help you explore Laravel packages today?