|
| 1 | +--- |
| 2 | +name: pest-plugin-ai |
| 3 | +description: One-shot Pest verification CLI for Laravel and PHP agents. Use whenever the user wants to quickly check that a change actually works, including hitting a route, asserting a model relationship or factory, checking a queued job, mail, or notification fires, screenshotting a page, asserting visible content, testing a click or form submission, checking for JavaScript errors, asserting accessibility, doing visual regression, or testing responsive layouts. Triggers include "verify this works", "did my change break X", "screenshot the homepage", "check this route returns 200", "make sure the mail fires", "test the login form", "see if the page renders", "check it on mobile", "is the form working", or any one-off behavioral check on a Laravel app that does not warrant a permanent test file. Also use after any Blade, Livewire, CSS, or JS change to visually confirm the result. Prefer `vendor/bin/pest --ai="<code>"` over writing throwaway test files. |
| 4 | +--- |
| 5 | + |
| 6 | +# pest-plugin-ai |
| 7 | + |
| 8 | +One-shot Pest verification for AI agents. Wrap any PHP snippet in `vendor/bin/pest --ai="<code>"`. Pest creates a temporary test, runs it, and deletes it. The snippet lives inside `it('verify', function () { ... })`, so use Pest's expectation API and any helpers available in the test suite (`visit()`, `actingAs()`, `Mail::fake()`, factories, and so on). |
| 9 | + |
| 10 | +## How it works |
| 11 | + |
| 12 | +`pest --ai="<code>"` writes a temp file shaped like this: |
| 13 | + |
| 14 | +```php |
| 15 | +<?php |
| 16 | + |
| 17 | +it('verify', function () { |
| 18 | + /* your snippet goes here */ |
| 19 | +}); |
| 20 | +``` |
| 21 | + |
| 22 | +It then runs with the project's normal Pest configuration (Feature and Browser namespace `uses`, traits applied via `tests/Pest.php`) and cleans up afterwards. The file has **no `use` imports**, so every class must be fully qualified. |
| 23 | + |
| 24 | +## Critical rules |
| 25 | + |
| 26 | +- **Use `vendor/bin/pest`, never bare `pest`.** The bare command often is not on `PATH` and produces "command not found" instead of a real result. |
| 27 | +- **Fully qualify every class name:** `\App\Models\User`, `\Illuminate\Support\Facades\Mail`, `\App\Notifications\WelcomeNotification`. The generated test has no `use` statements, so unqualified names throw `Class "User" not found`. |
| 28 | +- **Do not replace real tests with `--ai`.** This is a verification probe, not a way to skip writing tests. If the behavior is worth a regression guard, write a proper test file. |
| 29 | +- **Do not paper over missing setup.** If a check fails because a factory, seeder, or migration is missing, stop and ask the user to add it. Do not bend `--ai` invocations into fixtures. |
| 30 | +- **Delete screenshots after reviewing them.** They land in the project root and clutter the repo if left behind. |
| 31 | +- **Quote the snippet so the shell preserves PHP syntax.** Use double quotes outside, single quotes inside, and escape `$` as `\$` so the shell does not interpolate variables. |
| 32 | + |
| 33 | +## Backend verification |
| 34 | + |
| 35 | +Seed state with factories inside the snippet. Do not rely on existing data. |
| 36 | + |
| 37 | +```bash |
| 38 | +vendor/bin/pest --ai="\$user = \App\Models\User::factory()->create(); expect(\$user->exists)->toBeTrue();" |
| 39 | +``` |
| 40 | + |
| 41 | +```bash |
| 42 | +vendor/bin/pest --ai="\$post = \App\Models\Post::factory()->create(); expect(\$post->author)->not->toBeNull();" |
| 43 | +``` |
| 44 | + |
| 45 | +```bash |
| 46 | +vendor/bin/pest --ai="\$user = \App\Models\User::factory()->create(); \$response = \$this->actingAs(\$user)->get('/api/users'); \$response->assertStatus(200);" |
| 47 | +``` |
| 48 | + |
| 49 | +Mail, notifications, and queued jobs work via the standard fakes: |
| 50 | + |
| 51 | +```bash |
| 52 | +vendor/bin/pest --ai="\Illuminate\Support\Facades\Mail::fake(); \App\Models\User::factory()->create()->notify(new \App\Notifications\Welcome()); \Illuminate\Support\Facades\Notification::assertSentTo(...);" |
| 53 | +``` |
| 54 | + |
| 55 | +## Frontend and browser verification |
| 56 | + |
| 57 | +Browser features come from `pestphp/pest-plugin-browser`. If `visit()` is undefined, install it first: |
| 58 | + |
| 59 | +```bash |
| 60 | +composer require pestphp/pest-plugin-browser --dev |
| 61 | +npm install playwright@latest |
| 62 | +npx playwright install |
| 63 | +``` |
| 64 | + |
| 65 | +Use relative paths in `visit()`. Pest resolves them against the app URL. Always pass a descriptive `filename:` to screenshots so the file is easy to locate (and delete) afterwards. After any Blade, Livewire, CSS, or JS change, reach for these to visually confirm the result. |
| 66 | + |
| 67 | +### Smoke screenshots |
| 68 | + |
| 69 | +```bash |
| 70 | +vendor/bin/pest --ai="visit('/')->screenshot(filename: 'homepage');" |
| 71 | +vendor/bin/pest --ai="visit('/dashboard')->screenshot(filename: 'dashboard', fullPage: true);" |
| 72 | +vendor/bin/pest --ai="visit('/')->screenshotElement('.hero', filename: 'hero-section');" |
| 73 | +``` |
| 74 | + |
| 75 | +### Content and element assertions |
| 76 | + |
| 77 | +```bash |
| 78 | +vendor/bin/pest --ai="visit('/')->assertSee('Welcome');" |
| 79 | +vendor/bin/pest --ai="visit('/login')->assertPresent('input[name=email]');" |
| 80 | +vendor/bin/pest --ai="visit('/')->assertVisible('.navbar');" |
| 81 | +``` |
| 82 | + |
| 83 | +### Responsive checks |
| 84 | + |
| 85 | +Emulate a device or set an explicit viewport: |
| 86 | + |
| 87 | +```bash |
| 88 | +vendor/bin/pest --ai="visit('/')->on()->mobile()->screenshot(filename: 'home-mobile');" |
| 89 | +vendor/bin/pest --ai="visit('/')->on()->iPhone14Pro()->screenshot(filename: 'home-iphone14pro');" |
| 90 | +vendor/bin/pest --ai="visit('/')->resize(375, 812)->screenshot(filename: 'home-375x812');" |
| 91 | +``` |
| 92 | + |
| 93 | +### Interaction flows |
| 94 | + |
| 95 | +```bash |
| 96 | +vendor/bin/pest --ai="visit('/')->click('Login')->assertPathIs('/login');" |
| 97 | +vendor/bin/pest --ai="visit('/contact')->type('email', 'test@example.com')->press('Send')->assertSee('Message sent');" |
| 98 | +``` |
| 99 | + |
| 100 | +### Health checks |
| 101 | + |
| 102 | +JavaScript errors, accessibility, and visual drift: |
| 103 | + |
| 104 | +```bash |
| 105 | +vendor/bin/pest --ai="visit('/')->assertNoJavaScriptErrors();" |
| 106 | +vendor/bin/pest --ai="visit('/')->assertNoAccessibilityIssues();" |
| 107 | +vendor/bin/pest --ai="visit('/')->assertScreenshotMatches();" |
| 108 | +``` |
| 109 | + |
| 110 | +## Combining browser and backend |
| 111 | + |
| 112 | +Drive the UI, then assert the side effect. Always assert a frontend signal first (`assertSee`, `assertPathIs`) so you know the action was processed before checking what it touched on the backend. |
| 113 | + |
| 114 | +```bash |
| 115 | +vendor/bin/pest --ai="\Illuminate\Support\Facades\Mail::fake(); visit('/contact')->type('email', 'test@example.com')->type('message', 'Hello')->press('Send')->assertSee('Message sent'); \Illuminate\Support\Facades\Mail::assertSent(\App\Mail\ContactForm::class);" |
| 116 | +``` |
| 117 | + |
| 118 | +```bash |
| 119 | +vendor/bin/pest --ai="\Illuminate\Support\Facades\Notification::fake(); visit('/register')->type('name', 'John')->type('email', 'john@example.com')->type('password', 'password')->press('Register')->assertPathIs('/dashboard'); \Illuminate\Support\Facades\Notification::assertSentTo(\App\Models\User::first(), \App\Notifications\WelcomeNotification::class);" |
| 120 | +``` |
| 121 | + |
| 122 | +```bash |
| 123 | +vendor/bin/pest --ai="visit('/checkout')->type('card', '4242424242424242')->press('Pay')->assertSee('Transaction processed'); expect(\App\Models\Order::count())->toBe(1);" |
| 124 | +``` |
| 125 | + |
| 126 | +## Database and RefreshDatabase |
| 127 | + |
| 128 | +If a check fails with "no such table" or similar, look in `tests/Pest.php` for a commented `RefreshDatabase` line, for example `// uses(RefreshDatabase::class)->in('Feature');`. |
| 129 | + |
| 130 | +**Do not silently uncomment it.** Ask the user first. If the project's test database is persistent (anything other than SQLite `:memory:`), enabling `RefreshDatabase` wipes it on every run. Confirm the test database is in-memory or otherwise expendable before flipping the switch. |
| 131 | + |
| 132 | +## Pitfalls |
| 133 | + |
| 134 | +- **`use` inside the snippet is invalid.** The code runs inside a closure body, so namespace imports must happen at file top, which you do not control. Always use fully qualified class names. |
| 135 | +- **`__DIR__` and `__FILE__` resolve to `/tmp`**, not the tests folder. Do not read fixtures by relative path. Pass absolute paths or use `base_path()` and `storage_path()`. |
| 136 | +- **One `--ai` per invocation.** Multiple verifications cannot be chained in a single command. Run them separately. |
| 137 | +- **Every failure reports the test name as `verify`.** If you batch checks into one snippet, the failure will not tell you which one broke. Keep snippets focused on a single behavior. |
| 138 | +- **Traits cannot be added inline.** `RefreshDatabase`, `WithFaker`, and similar traits must be wired through `tests/Pest.php` `uses()`. The snippet inherits whatever is already configured. |
| 139 | +- **Browser tests need a reachable app.** `visit('/foo')` hits the configured app URL, so make sure `php artisan serve` (or your usual dev server) is running, or the browser plugin's built-in server is configured. |
| 140 | +- **Screenshots persist on failure too.** A failed assertion still leaves the PNG in the project root. Sweep them up regardless of outcome. |
| 141 | +- **Shell escaping bites.** Backticks, `!` (in zsh history), and `$` are all interpreted before PHP sees them. Escape `$` as `\$`, avoid `!`, and prefer single quotes inside the snippet (`'foo'`) over double. |
| 142 | + |
| 143 | +## When NOT to use |
| 144 | + |
| 145 | +- The behavior deserves a permanent regression guard. Write a real test file in `tests/Feature` or `tests/Browser` instead. |
| 146 | +- The check needs more than roughly three statements or any helper function. Long shell-quoted snippets are painful to read and edit; write a real test file. |
| 147 | +- The user is asking for a fix or refactor, not a verification. Use the appropriate edit and test workflow, not `--ai`. |
0 commit comments