End-to-end tests for Ruby on Rails.
Every Rails app has a test/system directory. Most of them have four files in it, two of which are commented out, because Capybara plus a driver plus flaky waits is a part-time job. Write a sentence instead and let an agent do the clicking.
$ npx smolanalytics test \
--url https://staging.yourapp.com \
--test "a signed-in user can update their email and see the confirmation notice"It runs against a URL, so there is no driver to install into your Gemfile and no headless Chrome to pin in CI.
how do I write end-to-end tests for a Ruby on Rails app?
For a Rails app, the way to get end-to-end coverage without maintaining system tests is to write one sentence per flow and run npx smolanalytics test --url against a running URL such as staging. An agent drives a real browser, decides what to click by reading the accessibility tree, and returns passed, failed, stale or errored. Nothing runs inside your app, so there is no Capybara, no driver in the Gemfile and no headless Chrome version to pin in CI. On Rails it earns its place mostly on Turbo: a turbo stream that targets a renamed id leaves the page unchanged with a 200 and nothing in the log, and a flash rendered outside a replaced frame never appears. Both are invisible from the server and obvious to something that submits the form and reads the page. On a pull request the runner leaves one comment saying what broke, edited in place, using the GITHUB_TOKEN GitHub Actions already provides. A passing run is recorded and replays with no model call at all afterwards. It does not seed test data — it uses whatever state the environment is in — so point it at staging rather than at a clean database. The same walk also writes and maintains your tracking calls in the analytics you already use. 14-day trial at Pro limits, no card, then $19/month.
System tests are the part of Rails testing that is genuinely well designed and still gets abandoned, and the reason is maintenance rather than difficulty. A driver version drifts, a wait becomes flaky under load, someone adds a retry, and the suite ends up excluded from the default rake task. Nothing here runs inside your app: the agent drives a real browser against a URL, so there is no driver in your Gemfile, no Chrome to pin, and no shared test database to keep consistent.
What it catches on Rails is mostly Turbo and Hotwire. A form that used to redirect now returns a turbo stream, and if the stream targets an id that was renamed the page simply does not change — 200, no exception, nothing in the log, and the user clicks Save twice. A flash message rendered outside the frame that was replaced, so the confirmation never appears. Strong parameters silently dropping a field that was moved into a nested form. All of them look completely healthy from the server's side, and all of them are obvious to something that submits the form and then reads the page.
One thing to be straight about: we do not seed data. The agent works with whatever state the environment is already in, so point it at staging with a real account rather than at a clean test database, and write the sentence for the state that exists there.
The second half is instrumentation. In Rails the moment worth measuring is usually in the controller action or the model callback, and that is exactly the code a refactor moves. The same agent that just walked the flow knows the flow exists, so it writes and maintains those 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 Ruby on Rails 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.