← Back to list

filamentphp-testing
by habibGamal
⭐ 0🍴 0📅 Jan 23, 2026
SKILL.md
name: filamentphp-testing description: Expert knowledge for testing FilamentPHP 4.x resources, tables, schemas, actions, and notifications using Pest and Livewire. Use this skill when writing or debugging tests for FilamentPHP components, implementing test coverage for admin panels, or ensuring FilamentPHP features work correctly. compatibility: Designed for FilamentPHP 4.x with Pest testing framework and Livewire metadata: author: AI Assistant version: "1.0.0" framework: FilamentPHP 4.x testing-framework: Pest
FilamentPHP Testing Skill
This skill provides comprehensive guidance for testing FilamentPHP 4.x applications using Pest and Livewire testing utilities.
When to Use This Skill
- Writing tests for FilamentPHP resources (Create, Edit, View, List pages)
- Testing FilamentPHP tables (columns, filters, sorting, searching, bulk actions)
- Testing FilamentPHP schemas (forms, infolists, wizards)
- Testing FilamentPHP actions (page actions, table actions, schema actions)
- Testing FilamentPHP notifications
- Implementing test coverage for FilamentPHP relation managers
- Debugging failing FilamentPHP tests
Core Testing Patterns
Testing Resources
Test Resource List Page
use App\Filament\Resources\Users\Pages\ListUsers;
use App\Models\User;
use function Pest\Livewire\livewire;
it('can load the page', function () {
$users = User::factory()->count(5)->create();
livewire(ListUsers::class)
->assertOk()
->assertCanSeeTableRecords($users);
});
Test Resource Create Page
use App\Filament\Resources\Users\Pages\CreateUser;
use App\Models\User;
use function Pest\Laravel\assertDatabaseHas;
use function Pest\Livewire\livewire;
it('can create a user', function () {
$newUserData = User::factory()->make();
livewire(CreateUser::class)
->fillForm([
'name' => $newUserData->name,
'email' => $newUserData->email,
])
->call('create')
->assertNotified()
->assertRedirect();
assertDatabaseHas(User::class, [
'name' => $newUserData->name,
'email' => $newUserData->email,
]);
});
Test Resource Edit Page
use App\Filament\Resources\Users\Pages\EditUser;
use App\Models\User;
use function Pest\Laravel\assertDatabaseHas;
use function Pest\Livewire\livewire;
it('can load the page', function () {
$user = User::factory()->create();
livewire(EditUser::class, [
'record' => $user->id,
])
->assertOk()
->assertSchemaStateSet([
'name' => $user->name,
'email' => $user->email,
]);
});
it('can update a user', function () {
$user = User::factory()->create();
$newUserData = User::factory()->make();
livewire(EditUser::class, [
'record' => $user->id,
])
->fillForm([
'name' => $newUserData->name,
'email' => $newUserData->email,
])
->call('save')
->assertNotified();
assertDatabaseHas(User::class, [
'id' => $user->id,
'name' => $newUserData->name,
'email' => $newUserData->email,
]);
});
Test Resource View Page
use App\Filament\Resources\Users\Pages\ViewUser;
use App\Models\User;
use function Pest\Livewire\livewire;
it('can load the page', function () {
$user = User::factory()->create();
livewire(ViewUser::class, [
'record' => $user->id,
])
->assertOk()
->assertSchemaStateSet([
'name' => $user->name,
'email' => $user->email,
]);
});
Testing Tables
Test Table Rendering
use function Pest\Livewire\livewire;
it('can render page', function () {
livewire(ListPosts::class)
->assertSuccessful();
});
Test Table Records Visibility
use function Pest\Livewire\livewire;
it('cannot display trashed posts by default', function () {
$posts = Post::factory()->count(4)->create();
$trashedPosts = Post::factory()->trashed()->count(6)->create();
livewire(PostResource\Pages\ListPosts::class)
->assertCanSeeTableRecords($posts)
->assertCanNotSeeTableRecords($trashedPosts)
->assertCountTableRecords(4);
});
Test Table Search
use function Pest\Livewire\livewire;
it('can search posts by title', function () {
$posts = Post::factory()->count(10)->create();
$title = $posts->first()->title;
livewire(PostResource\Pages\ListPosts::class)
->searchTable($title)
->assertCanSeeTableRecords($posts->where('title', $title))
->assertCanNotSeeTableRecords($posts->where('title', '!=', $title));
});
Test Table Filtering
use function Pest\Livewire\livewire;
it('can filter users by locale', function () {
$users = User::factory()->count(5)->create();
livewire(ListUsers::class)
->assertCanSeeTableRecords($users)
->filterTable('locale', $users->first()->locale)
->assertCanSeeTableRecords($users->where('locale', $users->first()->locale))
->assertCanNotSeeTableRecords($users->where('locale', '!=', $users->first()->locale));
});
Test Table Sorting
use function Pest\Livewire\livewire;
it('can sort users by name', function () {
$users = User::factory()->count(5)->create();
livewire(ListUsers::class)
->assertCanSeeTableRecords($users)
->sortTable('name')
->assertCanSeeTableRecords($users->sortBy('name'), inOrder: true)
->sortTable('name', 'desc')
->assertCanSeeTableRecords($users->sortByDesc('name'), inOrder: true);
});
Test Table Column Visibility
use function Pest\Livewire\livewire;
it('shows the correct columns', function () {
livewire(PostResource\Pages\ListPosts::class)
->assertTableColumnVisible('created_at')
->assertTableColumnHidden('author');
});
Test Toggle All Columns
use function Pest\Livewire\livewire;
it('can toggle all columns', function () {
livewire(PostResource\Pages\ListPosts::class)
->toggleAllTableColumns();
});
Testing Actions
Test Basic Page Actions
use function Pest\Livewire\livewire;
it('can send invoices', function () {
$invoice = Invoice::factory()->create();
livewire(EditInvoice::class, [
'invoice' => $invoice,
])
->callAction('send');
expect($invoice->refresh())
->isSent()->toBeTrue();
});
Test Table Row Actions
use Filament\Actions\Testing\TestAction;
use function Pest\Livewire\livewire;
it('can send invoice', function () {
$invoice = Invoice::factory()->create();
livewire(ListInvoices::class)
->callAction(TestAction::make('send')->table($invoice));
});
Test Table Header Actions
use Filament\Actions\Testing\TestAction;
use function Pest\Livewire\livewire;
it('can create invoice', function () {
livewire(ListInvoices::class)
->callAction(TestAction::make('create')->table());
});
Test Table Bulk Actions
use Filament\Actions\Testing\TestAction;
use function Pest\Livewire\livewire;
use function Pest\Laravel\assertDatabaseMissing;
it('can bulk delete users', function () {
$users = User::factory()->count(5)->create();
livewire(ListUsers::class)
->assertCanSeeTableRecords($users)
->selectTableRecords($users)
->callAction(TestAction::make(DeleteBulkAction::class)->table()->bulk())
->assertNotified()
->assertCanNotSeeTableRecords($users);
$users->each(fn (User $user) => assertDatabaseMissing($user));
});
Test Schema Component Actions
use Filament\Actions\Testing\TestAction;
use function Pest\Livewire\livewire;
it('can send from schema component', function () {
$invoice = Invoice::factory()->create();
livewire(EditInvoice::class)
->callAction(TestAction::make('send')->schemaComponent('customer_id'));
});
Test Nested Actions
use Filament\Actions\Testing\TestAction;
use function Pest\Livewire\livewire;
it('can access nested action', function () {
$invoice = Invoice::factory()->create();
livewire(ManageInvoices::class)
->callAction([
TestAction::make('view')->table($invoice),
TestAction::make('send')->schemaComponent('customer.name'),
]);
});
Test Prebuilt Action Classes
use Filament\Actions\CreateAction;
use Filament\Actions\DeleteAction;
use Filament\Actions\EditAction;
use function Pest\Livewire\livewire;
use function Pest\Laravel\assertDatabaseMissing;
it('can delete a user', function () {
$user = User::factory()->create();
livewire(EditUser::class, [
'record' => $user->id,
])
->callAction(DeleteAction::class)
->assertNotified()
->assertRedirect();
assertDatabaseMissing($user);
});
Test Action Visibility and Existence
use function Pest\Livewire\livewire;
it('can only print invoices', function () {
$invoice = Invoice::factory()->create();
livewire(EditInvoice::class, [
'invoice' => $invoice,
])
->assertActionHidden('send')
->assertActionVisible('print')
->assertActionExists('print')
->assertActionDoesNotExist('unsend');
});
Test Action Enabled/Disabled State
use function Pest\Livewire\livewire;
it('can only print a sent invoice', function () {
$invoice = Invoice::factory()->create();
livewire(EditInvoice::class, [
'invoice' => $invoice,
])
->assertActionDisabled('send')
->assertActionEnabled('print');
});
Test Action Halted State
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,
])
->callAction('send')
->assertActionHalted('send');
});
Test Action Modal Content
use function Pest\Livewire\livewire;
it('confirms the target address before sending', function () {
$invoice = Invoice::factory()->create();
$recipientEmail = $invoice->company->primaryContact->email;
livewire(EditInvoice::class, [
'invoice' => $invoice,
])
->mountAction('send')
->assertMountedActionModalSee($recipientEmail);
});
Test Action Pre-filled State
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,
])
->mountAction('send')
->assertSchemaStateSet([
'email' => $recipientEmail,
])
->callMountedAction()
->assertHasNoFormErrors();
expect($invoice->refresh())
->isSent()->toBeTrue()
->recipient_email->toBe($recipientEmail);
});
Test Action Configuration with Callback
use Filament\Actions\Action;
use function Pest\Livewire\livewire;
it('has the correct description', function () {
$invoice = Invoice::factory()->create();
livewire(EditInvoice::class, [
'invoice' => $invoice,
])
->assertActionExists('send', function (Action $action): bool {
return $action->getModalDescription() === 'This will send an email...';
});
});
Test Action URLs
use function Pest\Livewire\livewire;
it('links to the correct sites', function () {
$invoice = Invoice::factory()->create();
livewire(EditInvoice::class, [
'invoice' => $invoice,
])
->assertActionHasUrl('filament', 'https://filamentphp.com/')
->assertActionDoesNotHaveUrl('filament', 'https://github.com/filamentphp/filament')
->assertActionShouldOpenUrlInNewTab('filament')
->assertActionShouldNotOpenUrlInNewTab('github');
});
Testing Schemas (Forms & Infolists)
Test Form Exists
use function Pest\Livewire\livewire;
it('has a form', function () {
livewire(CreatePost::class)
->assertFormExists();
});
Test Form Field Visibility
use function Pest\Livewire\livewire;
it('has title field', function () {
livewire(CreatePost::class)
->assertSchemaComponentExists('title');
});
it('does not have conditional component', function () {
livewire(CreatePost::class)
->assertSchemaComponentDoesNotExist('no-such-section');
});
Test Form Field State
use function Pest\Livewire\livewire;
it('has correct enabled/disabled state', function () {
livewire(CreatePost::class)
->assertFormFieldEnabled('title')
->assertFormFieldDisabled('slug');
});
Test Schema State
use Illuminate\Support\Str;
use function Pest\Livewire\livewire;
it('can automatically generate a slug from the title', function () {
$title = fake()->sentence();
livewire(CreatePost::class)
->fillForm([
'title' => $title,
])
->assertSchemaStateSet([
'slug' => Str::slug($title),
]);
});
Test Schema State with Callback
use Illuminate\Support\Str;
use function Pest\Livewire\livewire;
it('can automatically generate a slug without spaces', function () {
$title = fake()->sentence();
livewire(CreatePost::class)
->fillForm([
'title' => $title,
])
->assertSchemaStateSet(function (array $state): array {
expect($state['slug'])
->not->toContain(' ');
return [
'slug' => Str::slug($title),
];
});
});
Test Schema Component Properties
use Filament\Schemas\Components\Section;
use Illuminate\Testing\Assert;
use function Pest\Livewire\livewire;
test('comments section has heading', function () {
livewire(EditPost::class)
->assertSchemaComponentExists(
'comments-section',
checkComponentUsing: function (Section $component): bool {
Assert::assertTrue(
$component->getHeading() === 'Comments',
'Failed asserting section has correct heading.',
);
return true;
},
);
});
Test Wizard Navigation
use function Pest\Livewire\livewire;
it('moves to next wizard step', function () {
livewire(CreatePost::class)
->goToNextWizardStep()
->assertHasFormErrors(['title']);
});
Test Repeater/Builder Item Count
use Filament\Forms\Components\Repeater;
use function Pest\Livewire\livewire;
it('has correct number of items', function () {
$post = Post::factory()->create();
$undoRepeaterFake = Repeater::fake();
livewire(EditPost::class, ['record' => $post])
->assertSchemaStateSet(function (array $state) {
expect($state['quotes'])
->toHaveCount(2);
});
$undoRepeaterFake();
});
Test Repeater Actions
use App\Models\Quote;
use Filament\Actions\Testing\TestAction;
use function Pest\Livewire\livewire;
it('can send quote', function () {
$post = Post::factory()->create();
$quote = Quote::first();
livewire(EditPost::class, ['record' => $post])
->callAction(TestAction::make('sendQuote')->schemaComponent('quotes')->arguments([
'item' => "record-{$quote->getKey()}",
]))
->assertNotified('Quote sent!');
});
Testing Notifications
Test Notification Sent
use Filament\Notifications\Notification;
use function Filament\Notifications\Testing\assertNotified;
use function Pest\Livewire\livewire;
// Using Livewire helper
it('sends a notification', function () {
livewire(CreatePost::class)
->assertNotified();
});
// Using dedicated helper
it('sends a notification', function () {
assertNotified();
});
// Using Notification facade
it('sends a notification', function () {
Notification::assertNotified();
});
Test Notification Not Sent
use Filament\Notifications\Notification;
use function Pest\Livewire\livewire;
it('does not send a notification', function () {
livewire(CreatePost::class)
->assertNotNotified()
// or with specific title
->assertNotNotified('Unable to create post')
// or with exact notification object
->assertNotNotified(
Notification::make()
->danger()
->title('Unable to create post')
->body('Something went wrong.'),
);
});
Test Exact Notification Object
use Filament\Notifications\Notification;
use function Pest\Livewire\livewire;
it('sends a notification', function () {
livewire(CreatePost::class)
->assertNotified(
Notification::make()
->danger()
->title('Unable to create post')
->body('Something went wrong.'),
);
});
Testing Relation Managers
Test Relation Manager Renders
use App\Filament\Resources\Users\Pages\EditUser;
use App\Filament\Resources\Users\RelationManagers\PostsRelationManager;
use App\Models\User;
use function Pest\Livewire\livewire;
it('can load the relation manager', function () {
$user = User::factory()->create();
livewire(EditUser::class, [
'record' => $user->id,
])
->assertSeeLivewire(PostsRelationManager::class);
});
Test Relation Manager Data
use App\Filament\Resources\Users\Pages\EditUser;
use App\Filament\Resources\Users\RelationManagers\PostsRelationManager;
use App\Models\Post;
use App\Models\User;
use function Pest\Livewire\livewire;
it('can load the relation manager', function () {
$user = User::factory()
->has(Post::factory()->count(5))
->create();
livewire(PostsRelationManager::class, [
'ownerRecord' => $user,
'pageClass' => EditUser::class,
])
->assertOk()
->assertCanSeeTableRecords($user->posts);
});
Test Relation Manager Actions
use App\Filament\Resources\Users\Pages\EditUser;
use App\Filament\Resources\Users\RelationManagers\PostsRelationManager;
use App\Models\Post;
use App\Models\User;
use Filament\Actions\Testing\TestAction;
use function Pest\Laravel\assertDatabaseHas;
use function Pest\Livewire\livewire;
it('can create a post', function () {
$user = User::factory()->create();
$newPostData = Post::factory()->make();
livewire(PostsRelationManager::class, [
'ownerRecord' => $user,
'pageClass' => EditUser::class,
])
->callAction(TestAction::make(CreateAction::class)->table(), [
'title' => $newPostData->title,
'content' => $newPostData->content,
])
->assertNotified();
assertDatabaseHas(Post::class, [
'title' => $newPostData->title,
'content' => $newPostData->content,
'user_id' => $user->id,
]);
});
Important Testing Conventions
Use Pest's Livewire Helper
Always import and use Pest\Livewire\livewire function for FilamentPHP tests:
use function Pest\Livewire\livewire;
Use RefreshDatabase
All FilamentPHP tests should use the RefreshDatabase trait or Pest's uses():
uses(RefreshDatabase::class);
Test Structure
Follow this pattern for FilamentPHP resource tests:
<?php
use App\Filament\Resources\Users\Pages\ListUsers;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use function Pest\Laravel\assertDatabaseHas;
use function Pest\Livewire\livewire;
uses(RefreshDatabase::class);
it('can load the page', function () {
// Test implementation
});
it('can create a user', function () {
// Test implementation
});
// More tests...
Respect Permissions
When testing resources with policies or permissions:
use function Pest\Laravel\actingAs;
it('authorized user can delete', function () {
$user = User::factory()->create();
$admin = User::factory()->admin()->create();
actingAs($admin);
livewire(EditUser::class, ['record' => $user->id])
->callAction(DeleteAction::class)
->assertNotified();
});
Use Factory Pattern
Always use factories to create test data:
$user = User::factory()->create();
$newUserData = User::factory()->make();
Common Assertion Methods
Livewire Assertions
assertOk()- Assert successful page loadassertNotified()- Assert notification was shownassertRedirect()- Assert redirect occurredassertSuccessful()- Assert component rendered successfully
Table Assertions
assertCanSeeTableRecords($records)- Assert records visibleassertCanNotSeeTableRecords($records)- Assert records not visibleassertCountTableRecords($count)- Assert record countassertTableColumnVisible($column)- Assert column visibleassertTableColumnHidden($column)- Assert column hiddenassertTableColumnFormattedStateSet($column, $value, record: $record)
Schema Assertions
assertSchemaStateSet($data)- Assert form/infolist dataassertSchemaComponentExists($key)- Assert component existsassertSchemaComponentDoesNotExist($key)- Assert component doesn't existassertFormFieldEnabled($field)- Assert field is enabledassertFormFieldDisabled($field)- Assert field is disabledassertFormExists()- Assert form existsassertHasFormErrors($fields)- Assert form has errorsassertHasNoFormErrors()- Assert no form errors
Action Assertions
assertActionExists($action)- Assert action existsassertActionDoesNotExist($action)- Assert action doesn't existassertActionVisible($action)- Assert action visibleassertActionHidden($action)- Assert action hiddenassertActionEnabled($action)- Assert action enabledassertActionDisabled($action)- Assert action disabledassertActionHalted($action)- Assert action was haltedassertActionHasUrl($action, $url)- Assert action has URLassertMountedActionModalSee($content)- Assert modal content
Database Assertions
assertDatabaseHas($model, $data)- Assert record existsassertDatabaseMissing($model)- Assert record doesn't exist
Tips and Best Practices
- Test One Thing at a Time: Each test should focus on a single behavior
- Use Descriptive Test Names: Use
it('can perform action')format - Clean Test Data: Use factories and RefreshDatabase
- Test Happy and Sad Paths: Test both success and failure scenarios
- Test Permissions: Verify authorization works correctly
- Test Notifications: Assert user feedback is displayed
- Test Redirects: Verify navigation after actions
- Use Type Hinting: Import classes for better IDE support
- Chain Assertions: Livewire tests support method chaining
- Test Table State: Verify filters, sorting, and searching work
Common Patterns in Arabic UI Apps
Since this app uses Arabic UI:
- Test RTL layout rendering
- Verify Arabic text appears correctly
- Test date/time formatting for Arabic locale
- Verify currency formatting (EGP)
- Test Arabic validation messages
References
Score
Total Score
40/100
Based on repository quality metrics
✓SKILL.md
SKILL.mdファイルが含まれている
+20
○LICENSE
ライセンスが設定されている
0/10
○説明文
100文字以上の説明がある
0/10
○人気
GitHub Stars 100以上
0/15
○最近の活動
3ヶ月以内に更新がある
0/10
○フォーク
10回以上フォークされている
0/5
✓Issue管理
オープンIssueが50未満
+5
✓言語
プログラミング言語が設定されている
+5
○タグ
1つ以上のタグが設定されている
0/5
Reviews
💬
Reviews coming soon