Version

Theme

Admin Panel

Testing

All examples in this guide will be written using Pest. However, you can easily adapt this to a PHPUnit.

Since all pages in the admin panel are Livewire components, we're just using Livewire testing helpers everywhere. If you've never tested Livewire components before, please read this guide from the Livewire docs.

Getting started

Ensure that you are authenticated to access the admin panel in your TestCase:

protected function setUp(): void
{
parent::setUp();
 
$this->actingAs(User::factory()->create());
}

Resources

Pages

List

Routing & render

To ensure that the List page for the PostResource is able to render successfully, generate a page URL, perform a request to this URL and ensure that it is successful:

it('can render page', function () {
$this->get(PostResource::getUrl('index'))->assertSuccessful();
});
Table

Filament includes a selection of helpers for testing tables. A full guide to testing tables can be found in the Table Builder documentation.

To use a table testing helper, make assertions on the resource's List page class, which holds the table:

use function Pest\Livewire\livewire;
 
it('can list posts', function () {
$posts = Post::factory()->count(10)->create();
 
livewire(PostResource\Pages\ListPosts::class)
->assertCanSeeTableRecords($posts);
});

Create

Routing & render

To ensure that the Create page for the PostResource is able to render successfully, generate a page URL, perform a request to this URL and ensure that it is successful:

it('can render page', function () {
$this->get(PostResource::getUrl('create'))->assertSuccessful();
});
Creating

You may check that data is correctly saved into the database by calling fillForm() with your form data, and then asserting that the database contains a matching record:

use function Pest\Livewire\livewire;
 
it('can create', function () {
$newData = Post::factory()->make();
 
livewire(PostResource\Pages\CreatePost::class)
->fillForm([
'author_id' => $newData->author->getKey(),
'content' => $newData->content,
'tags' => $newData->tags,
'title' => $newData->title,
])
->call('create')
->assertHasNoFormErrors();
 
$this->assertDatabaseHas(Post::class, [
'author_id' => $newData->author->getKey(),
'content' => $newData->content,
'tags' => json_encode($newData->tags),
'title' => $newData->title,
]);
});
Validation

Use assertHasFormErrors() to ensure that data is properly validated in a form:

use function Pest\Livewire\livewire;
 
it('can validate input', function () {
 
livewire(PostResource\Pages\CreatePost::class)
->fillForm([
'title' => null,
])
->call('create')
->assertHasFormErrors(['title' => 'required']);
});

Edit

Routing & render

To ensure that the Edit page for the PostResource is able to render successfully, generate a page URL, perform a request to this URL and ensure that it is successful:

it('can render page', function () {
$this->get(PostResource::getUrl('edit', [
'record' => Post::factory()->create(),
]))->assertSuccessful();
});
Filling existing data

To check that the form is filled with the correct data from the database, you may assertFormSet() that the data in the form matches that of the record:

use function Pest\Livewire\livewire;
 
it('can retrieve data', function () {
$post = Post::factory()->create();
 
livewire(PostResource\Pages\EditPost::class, [
'record' => $post->getRouteKey(),
])
->assertFormSet([
'author_id' => $post->author->getKey(),
'content' => $post->content,
'tags' => $post->tags,
'title' => $post->title,
]);
});
Saving

You may check that data is correctly saved into the database by calling fillForm() with your form data, and then asserting that the database contains a matching record:

use function Pest\Livewire\livewire;
 
it('can save', function () {
$post = Post::factory()->create();
$newData = Post::factory()->make();
 
livewire(PostResource\Pages\EditPost::class, [
'record' => $post->getRouteKey(),
])
->fillForm([
'author_id' => $newData->author->getKey(),
'content' => $newData->content,
'tags' => $newData->tags,
'title' => $newData->title,
])
->call('save')
->assertHasNoFormErrors();
 
expect($post->refresh())
->author_id->toBe($newData->author->getKey())
->content->toBe($newData->content)
->tags->toBe($newData->tags)
->title->toBe($newData->title);
});
Validation

Use assertHasFormErrors() to ensure that data is properly validated in a form:

use function Pest\Livewire\livewire;
 
it('can validate input', function () {
$post = Post::factory()->create();
 
livewire(PostResource\Pages\EditPost::class, [
'record' => $post->getRouteKey(),
])
->fillForm([
'title' => null,
])
->call('save')
->assertHasFormErrors(['title' => 'required']);
});
Deleting

You can test the DeleteAction using callPageAction():

use Filament\Pages\Actions\DeleteAction;
use function Pest\Livewire\livewire;
 
it('can delete', function () {
$post = Post::factory()->create();
 
livewire(PostResource\Pages\EditPost::class, [
'record' => $post->getRouteKey(),
])
->callPageAction(DeleteAction::class);
 
$this->assertModelMissing($post);
});

You can ensure that a particular user is not able to see a DeleteAction using assertPageActionHidden():

use Filament\Pages\Actions\DeleteAction;
use function Pest\Livewire\livewire;
 
it('can not delete', function () {
$post = Post::factory()->create();
 
livewire(PostResource\Pages\EditPost::class, [
'record' => $post->getRouteKey(),
])
->assertPageActionHidden(DeleteAction::class);
});

View

Routing & render

To ensure that the View page for the PostResource is able to render successfully, generate a page URL, perform a request to this URL and ensure that it is successful:

it('can render page', function () {
$this->get(PostResource::getUrl('view', [
'record' => Post::factory()->create(),
]))->assertSuccessful();
});
Filling existing data

To check that the form is filled with the correct data from the database, you may assertSet() that the data in the form matches that of the record:

use function Pest\Livewire\livewire;
 
it('can retrieve data', function () {
$post = Post::factory()->create();
 
livewire(PostResource\Pages\ViewPost::class, [
'record' => $post->getRouteKey(),
])
->assertFormSet([
'author_id' => $post->author->getKey(),
'content' => $post->content,
'tags' => $post->tags,
'title' => $post->title,
]);
});

Relation managers

Render

To ensure that a relation manager is able to render successfully, mount the Livewire component:

use function Pest\Livewire\livewire;
 
it('can render relation manager', function () {
$category = Category::factory()
->has(Post::factory()->count(10))
->create();
 
livewire(CategoryResource\RelationManagers\PostsRelationManager::class, [
'ownerRecord' => $category,
])
->assertSuccessful();
});
Table

Filament includes a selection of helpers for testing tables. A full guide to testing tables can be found in the Table Builder documentation.

To use a table testing helper, make assertions on the relation manager class, which holds the table:

use function Pest\Livewire\livewire;
 
it('can list posts', function () {
$category = Category::factory()
->has(Post::factory()->count(10))
->create();
 
livewire(CategoryResource\RelationManagers\PostsRelationManager::class, [
'ownerRecord' => $category,
])
->assertCanSeeTableRecords($category->posts);
});

Page actions

Calling actions

You can call a page action by passing its name or class to callPageAction():

use function Pest\Livewire\livewire;
 
it('can send invoices', function () {
$invoice = Invoice::factory()->create();
 
livewire(EditInvoice::class, [
'invoice' => $invoice,
])
->callPageAction('send');
 
expect($invoice->refresh())
->isSent()->toBeTrue();
});

To pass an array of data into an action, use the data parameter:

use function Pest\Livewire\livewire;
 
it('can send invoices', function () {
$invoice = Invoice::factory()->create();
 
livewire(EditInvoice::class, [
'invoice' => $invoice,
])
->callPageAction('send', data: [
'email' => $email = fake()->email(),
])
->assertHasNoPageActionErrors();
 
expect($invoice->refresh())
->isSent()->toBeTrue()
->recipient_email->toBe($email);
});

If you ever need to only set a page action's data without immediately calling it, you can use setPageActionData():

use function Pest\Livewire\livewire;
 
it('can send invoices', function () {
$invoice = Invoice::factory()->create();
 
livewire(EditInvoice::class, [
'invoice' => $invoice,
])
->mountPageAction('send')
->setPageActionData('send', data: [
'email' => $email = fake()->email(),
])
});

Execution

To check if an action has been halted, you can use assertPageActionHalted():

use function Pest\Livewire\livewire;
 
it('stops sending if invoice has no email address', function () {
$invoice = Invoice::factory(['email' => null])->create();
 
livewire(EditInvoice::class, [
'invoice' => $invoice,
])
->callPageAction('send')
->assertPageActionHalted('send');
});

Errors

assertHasNoPageActionErrors() is used to assert that no validation errors occurred when submitting the action form.

To check if a validation error has occurred with the data, use assertHasPageActionErrors(), similar to assertHasErrors() in Livewire:

use function Pest\Livewire\livewire;
 
it('can validate invoice recipient email', function () {
$invoice = Invoice::factory()->create();
 
livewire(EditInvoice::class, [
'invoice' => $invoice,
])
->callPageAction('send', data: [
'email' => Str::random(),
])
->assertHasPageActionErrors(['email' => ['email']]);
});

Pre-filled data

To check if a page action is pre-filled with data, you can use the assertPageActionDataSet() method:

use function Pest\Livewire\livewire;
 
it('can send invoices to the primary contact by default', function () {
$invoice = Invoice::factory()->create();
$recipientEmail = $invoice->company->primaryContact->email;
 
livewire(EditInvoice::class, [
'invoice' => $invoice,
])
->mountPageAction('send')
->assertPageActionDataSet([
'email' => $recipientEmail,
])
->callMountedPageAction()
->assertHasNoPageActionErrors();
 
expect($invoice->refresh())
->isSent()->toBeTrue()
->recipient_email->toBe($recipientEmail);
});

Action State

To ensure that an action exists or doesn't in a table, you can use the assertPageActionExists() or assertPageActionDoesNotExist() method:

use function Pest\Livewire\livewire;
 
it('can send but not unsend invoices', function () {
$invoice = Invoice::factory()->create();
 
livewire(EditInvoice::class, [
'invoice' => $invoice,
])
->assertPageActionExists('send')
->assertPageActionDoesNotExist('unsend');
});

To ensure a page action is hidden or visible for a user, you can use the assertPageActionHidden() or assertPageActionVisible() methods:

use function Pest\Livewire\livewire;
 
it('can only print invoices', function () {
$invoice = Invoice::factory()->create();
 
livewire(EditInvoice::class, [
'invoice' => $invoice,
])
->assertPageActionHidden('send')
->assertPageActionVisible('print');
});

To ensure a page action is enabled or disabled for a user, you can use the assertPageActionEnabled() or assertPageActionDisabled() methods:

use function Pest\Livewire\livewire;
 
it('can only print a sent invoice', function () {
$invoice = Invoice::factory()->create();
 
livewire(EditInvoice::class, [
'invoice' => $invoice,
])
->assertPageActionDisabled('send')
->assertPageActionEnabled('print');
});

To ensure sets of actions exist in the correct order, you can use assertPageActionsExistInOrder():

use function Pest\Livewire\livewire;
 
it('can not send invoices', function () {
$invoice = Invoice::factory()->create();
 
livewire(EditInvoice::class, [
'invoice' => $invoice,
])
->assertPageActionsExistInOrder(['send', 'export']);
});

Button appearance

To ensure an action has the correct label, you can use assertPageActionHasLabel() and assertPageActionDoesNotHaveLabel():

use function Pest\Livewire\livewire;
 
it('send action has correct label', function () {
$invoice = Invoice::factory()->create();
 
livewire(EditInvoice::class, [
'invoice' => $invoice,
])
->assertPageActionHasLabel('send', 'Email Invoice')
->assertPageActionDoesNotHaveLabel('send', 'Send');
});

To ensure an action's button is showing the correct icon, you can use assertPageActionHasIcon() or assertPageActionDoesNotHaveIcon():

use function Pest\Livewire\livewire;
 
it('when enabled the send button has correct icon', function () {
$invoice = Invoice::factory()->create();
 
livewire(EditInvoice::class, [
'invoice' => $invoice,
])
->assertPageActionEnabled('send')
->assertPageActionHasIcon('send', 'envelope-open')
->assertPageActionDoesNotHaveIcon('send', 'envelope');
});

To ensure an action's button is displaying the right color, you can use assertPageActionHasColor() or assertPageActionDoesNotHaveColor():

use function Pest\Livewire\livewire;
 
it('actions display proper colors', function () {
$invoice = Invoice::factory()->create();
 
livewire(EditInvoice::class, [
'invoice' => $invoice,
])
->assertPageActionHasColor('delete', 'danger')
->assertPageActionDoesNotHaveColor('print', 'danger');
});

URL

To ensure an action has the correct URL, you can use assertPageActionHasUrl(), assertPageActionDoesNotHaveUrl(), assertPageActionShouldOpenUrlInNewTab(), and assertPageActionShouldNotOpenUrlInNewTab():

use function Pest\Livewire\livewire;
 
it('links to the correct Filament sites', function () {
$invoice = Invoice::factory()->create();
 
livewire(EditInvoice::class, [
'invoice' => $invoice,
])
->assertPageActionHasUrl('filament', 'https://filamentphp.com/')
->assertPageActionDoesNotHaveUrl('filament', 'https://github.com/filamentphp/filament')
->assertPageActionShouldOpenUrlInNewTab('filament')
->assertPageActionShouldNotOpenUrlInNewTab('github');
});
Edit on GitHub

Still need help? Join our Discord community or open a GitHub discussion