End-to-end tests for Laravel.
Dusk is good and it still needs ChromeDriver, a separate environment file, a test database and seeders that stay in step with your schema. That is why the Browser directory has three files in it. Write a sentence instead and point it at staging.
$ npx smolanalytics test \
--url https://staging.yourapp.com \
--test "a customer can add the annual plan to their subscription and see the new invoice"It runs against whatever state that environment is in. There is no seeder to keep in step, because we do not seed data.
how do I write end-to-end tests for a Laravel app?
To get browser-level coverage on a Laravel app without maintaining Dusk, write one sentence per flow and run npx smolanalytics test --url against a running URL such as staging. An agent drives a real browser, reads the page through its accessibility tree so no selector exists to break when a Blade component is refactored, and returns passed, failed, stale or errored. There is no ChromeDriver to keep current, no .env.dusk, and no test database to migrate, because nothing runs inside your application. It does not seed data either, which is the constraint to plan around: keep a long-lived test account on staging and write the sentences for the state it is actually in, and keep your factory-driven feature tests for everything else. On Laravel it earns its place on Livewire and Blade — a wire:model binding that stops updating submits stale values with no error, a validation partial a redesign moved out of the layout leaves the user on a page that did nothing, and a queued job that never runs shows Sent regardless. On a pull request one comment says what broke, edited in place, from your own CI runner using the GITHUB_TOKEN GitHub Actions provides. Passing runs are recorded and replay with no model call at all. 14-day trial at Pro limits, no card, then $19/month.
Dusk's cost is not writing the first test, it is everything around it: a driver binary to keep current, a .env.dusk that drifts from .env, a database that has to be migrated and seeded before each run, and selectors that break when a Blade component is refactored. Nothing here touches any of that. The agent drives a browser against a URL, reads the page through its accessibility tree so there is no selector to break, and works with the state the environment already has.
That last point is a real constraint and it changes how you write the sentences. We do not seed test data. So this is not a replacement for feature tests that build a world with factories — keep those — it is a check that the deployed app works for an account that exists. Keep a long-lived test customer on staging and describe flows in terms of what is true for them.
What it catches in Laravel is mostly Livewire and Blade. A Livewire component whose wire:model binding stops updating leaves a form that looks live and submits stale values, with no error anywhere. A validation error rendered in a Blade partial that a redesign moved out of the layout, so the user sees a page that did nothing. A queued job that the flow depends on, where the page says Sent and the mail never goes. All of them are healthy from the server's side.
The second half is your tracking. In Laravel the moment worth measuring is usually an event or a listener, which is exactly what gets moved when someone tidies up. The agent has just walked the flow, so it knows the flow exists, and it writes and maintains the tracking calls in the SDK you already run — PostHog, Mixpanel, Amplitude, Google Analytics, Plausible or Segment.
How it works walks the whole loop from the sentence to the comment on the pull request, instrumentation covers the second half — the tracking calls the same walk writes and maintains — and the free tools run without an account. The docs have the flags, the suite format and the CI step.
Pro $19/mo, 100 tested pull requests included, unlimited projects, after a 14-day trial at Pro limits with no card. full pricing, including overage →
One sentence, on your Laravel app, tonight.
Point it at a URL that is already running and describe what should work. The first run uses the agent; once it passes it is recorded, so every run after that replays with no model at all — measured on our own site, 8.0s the first time and 1.4s the second.