pestphp/pest-plugin-agent
Pest Plugin Agent extends Pest with agent-focused testing helpers and expectations, making it simple to write expressive, reliable tests for AI agent workflows in Laravel and PHP projects. Integrates with Pest's familiar syntax for confident agent testing.
What it is: a thin wrapper that turns vendor/bin/pest --agent="<php snippet>" into a one-shot verification run — materialize a temp it('verify', fn () => …) test, run it against your existing Pest config, delete it. It is a probe, not a test framework.
Minimum steps:
tests/Pest.php uses() bindings already wired (factory, seeder, actingAs() helpers available). Without those bindings the plugin has nothing to hang onto.composer require --dev pestphp/pest-plugin-agent
Optional, only if you want visual/design verification: pestphp/pest-plugin-browser plus npx playwright install.SKILL.md first. It is the contract. Most failure modes (quoting, FQCN, browser API drift) are documented there, and skipping it is why people think the tool "doesn't work."RefreshDatabase against a persistent database.vendor/bin/pest --agent='$user = \App\Models\User::factory()->create(); expect($user->exists)->toBeTrue();'
First real use case: agent (or you) finishes a backend edit, self-verifies before handoff:
vendor/bin/pest --agent='$user = \App\Models\User::factory()->create(); $response = $this->actingAs($user)->get("/api/users"); $response->assertStatus(200);'
Pass/fail, no scratch file on disk, no throwaway commit. That is the entire value proposition — everything else is detail.
Backend self-verification after edits. Seed inline with factories; never rely on pre-existing state (the in-memory DB starts empty each run):
$post = \App\Models\Post::factory()->create();
expect($post->author)->not->toBeNull();
Mail / notification / queue assertions. Standard Laravel fakes work because you're inside a bootstrapped app with the project's Pest bindings:
\Illuminate\Support\Facades\Mail::fake();
\App\Models\User::factory()->create()->notify(new \App\Notifications\Welcome());
\Illuminate\Support\Facades\Notification::assertSentTo(/* … */);
Visual / design review loop. Drive a page after a Blade/Livewire/CSS change, capture across viewports:
visit("/dashboard")->on()->mobile()->assertSee("Overview");
Use the documented chain — ->on()->mobile(), ->on()->iPhone14Pro(), ->resize(w, h). Do not invent onMobile(); it doesn't exist.
Full-stack smoke. Drive the UI, assert a frontend signal, then assert the backend side effect — mail sent, order row created. This is where agents claim "looks right" and this pattern catches it.
Agent workflow integration (the real pattern).
AGENTS.md. Load the skill the moment a prompt is about checking, confirming, or making sure something works.--agent out of production and release pipelines. It's a dev/CI-local tool.Pre-handoff review evidence. Have the agent paste the --agent command and result into the PR description. A reviewer gets a reproduced check instead of a claim — that's your review-cycle saver.
Seeding for pages that need data. Feed/list/dashboard pages render empty state unless you seed first inside the snippet ($this->seed(...)) — the DB is ephemeral per run.
Apostrophe fallback. When the snippet needs a literal ' (e.g. ->type("bio", "I'm here")), write it to a scratch .php file — plain body statements, no <?php, no use — and pass it verbatim:
vendor/bin/pest --agent="$(cat /path/to/snippet.php)"
The plugin resolves suite uses bindings itself, so the scratch file's location doesn't matter. Delete the scratch file after.
Double outer quotes are the trap. --agent="…$user…" makes the shell interpolate $user to an empty string before PHP sees it — the login-form check silently weakens or produces a false pass. Always single outer quotes: --agent='…'. Never hand-escape \$. If you catch yourself typing \$, you're doing it wrong.
No use statements in the generated file. Every class must be fully qualified — \App\Models\User, \Illuminate\Support\Facades\Mail, \App\Notifications\WelcomeNotification. Unqualified User yields Class "User" not found.
Valid PHP, not natural language. --agent="visit '/' and check it works" is a parse error. Translate the request into statements before invoking.
Use vendor/bin/pest, never bare pest. Bare pest often isn't on PATH; you'll get "command not found" and misread it as a plugin failure.
RefreshDatabase against a persistent DB is destructive. It's commented out by default for a reason — leave it that way unless you know the target DB is disposable. This is the single most dangerous failure mode.
Temp files are cleaned up by Pest, not by you — and not on SIGKILL. Expect residue on hard crashes. Snippet scratch files from the apostrophe fallback are your responsibility — delete them.
Screenshots churn into tests/Browser/Screenshots/ (hardcoded). Delete throwaway smoke screenshots after eyeballing. For design-review workflows, keep them in a gitignored subfolder and clean periodically.
Browser API surface drifts. onMobile() doesn't exist; waitForText() is deprecated. Only use methods documented in SKILL.md. Unsupported browser methods are a top source of confusing failures.
Don't paper over missing setup. If a probe fails because a factory, seeder, or migration is missing, stop and fix the underlying setup — don't bend --agent invocations into a fixture harness.
Probe ≠ regression test. Institutionalize the boundary: probes answer "does this work right now," committed tests answer "this must never break again." If a behavior deserves a guard, write a real test file. Shipping agent-verified code with zero regression coverage is the classic way this gets misused.
It executes arbitrary PHP inside a bootstrapped app by design. Dev/CI-only, sandboxed DB, no production-adjacent environments.
Young package, thin bus factor (7 stars, 0 dependents). Pin the version, subscribe to releases, budget upgrade work when Pest/PHP/Laravel majors move. Undocumented version support matrix — validate before rollout. Migration cost to inline-it-yourself is near zero, so treat this as a reversible, contained bet rather than load-bearing infrastructure.
Measure it if you adopt it. Track % of agent tasks that self-verify before handoff, verification runs per task, false-negative rate (watch the quote-interpolation trap), screenshot churn, and review cycles saved. Those numbers — not the star count — justify keeping it.
How can I help you explore Laravel packages today?