End-to-end tests for Astro.
Astro ships static HTML with islands of JavaScript, so the page looks completely finished several hundred milliseconds before the one button on it does anything. Write a sentence describing what should work and an agent waits for the control to be real before it clicks.
$ npx smolanalytics test \
--url https://your-site.pages.dev \
--test "the newsletter form on the homepage accepts an email and confirms it"No account, no config file, nothing written to your repo. It prints a verdict and exits non-zero if the app did not do what the sentence describes.
how do I write end-to-end tests for a Astro app?
To test an Astro site end to end without writing test code, run npx smolanalytics test --url https://your-site --test with one sentence describing what should work. An agent opens a real browser, waits for the island carrying the control to actually be interactive, does the thing, and returns passed or failed. That waiting is the whole point on Astro: with client:visible or client:idle the markup exists long before the JavaScript that makes it do anything, so any check that reads the HTML says the button is there while a real visitor clicks it and nothing happens. On a pull request one comment says what broke, edited in place. A passing run is recorded and every run after it replays with no model call at all — measured on our own site, 8.0 seconds for the agent and 1.4 seconds for the replay — and the agent comes back only when the recording stops fitting the page, which is reported as stale rather than as a failure because a rename and a removal look the same to a replay. Setup is a URL you already have: a Netlify or Cloudflare Pages deploy preview, a staging build, or localhost through a tunnel. Nothing is written to your repo. The same walk also maintains your tracking calls in the analytics you already use. 14-day trial at Pro limits, no card, then $19/month.
The bug this catches on Astro sites is almost always the same one, and it is invisible to every check that reads HTML. A client:visible or client:idle island renders its markup at build time and becomes interactive whenever the browser gets round to it, so the button is on the page and does nothing. A crawler sees a button. A snapshot test sees a button. A person clicks it, nothing happens, and they leave. The agent drives a real browser, so it waits for the control to be actionable and then reports what happened after the click — which is the only way to tell a rendered button from a working one.
The second Astro-shaped failure is the directive itself: change client:load to client:visible on a component that is above the fold on desktop and below it on a phone, and the flow works for you and breaks for a third of your traffic. A test that says "submit the form on the homepage" runs the same either way and only goes red on the version that is actually broken.
Then the tracking. Astro pages are mostly static, so the events worth having are the few real interactions, and they live inside the islands. The same agent that just used the form knows the form exists, so it writes and maintains those tracking calls inside the SDK you already run — PostHog, Mixpanel, Amplitude, Google Analytics, Plausible or Segment — in the island component rather than in a global script that fires on a page nobody interacted with. If you have no analytics at all, the same engine can be it: autocapture, funnels, retention and paths are included.
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 Astro 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.