tests/checkout.md · one sentence per flow · no test code

End-to-end tests for Remix.

Remix is unusually good at failing gracefully, which is exactly the problem: a loader throws, the nested error boundary renders a tidy little panel, and the page looks deliberate. Write a sentence describing what should be there and an agent goes and checks whether it is.

tests/checkout.md
---
title: "A returning customer can check out with a saved card"
criticality: critical
---

Open the cart, go to checkout, pick the saved Visa ending
in 4242, place the order, and confirm the order number
appears.

criticality is the only knob on a test. Everything else about how to do it is worked out by the agent at run time.

how do I write end-to-end tests for a Remix app?

For a Remix app, end-to-end tests without test code work like this: one sentence per flow in a markdown file, and npx smolanalytics test --suite tests/ --url against any running URL. An agent drives a real browser, decides what to click by reading the accessibility tree, and returns passed, failed, stale or errored. It is worth having on Remix specifically because of error boundaries: a loader that throws is caught, the boundary renders a neat panel, the response is still a 200, and a page that is missing the thing a customer came for looks entirely deliberate. Nothing but walking the flow and looking for the expected result catches that. On a pull request the runner leaves one comment saying what broke, edited in place rather than added to, and it runs on your own CI runner and comments with the GITHUB_TOKEN GitHub Actions already provides, so there is no GitHub App to install and nothing is written to your repo. A run that passes is recorded and replays afterwards with no model call at all; when the recording stops fitting, the run is reported as stale, not as a bug in your app. The same walk also writes and maintains your tracking calls in PostHog, Mixpanel, Amplitude, Google Analytics, Plausible or Segment. 14-day trial at Pro limits, no card, then $19/month.

Nested routes and error boundaries are Remix's best idea and the reason a broken page can look fine. One loader in the middle of the route tree throws, its boundary catches it, and the rest of the page renders normally around a small apology. The response is a 200. The layout is intact. Nobody's monitoring fires. The only way to find out that the section a customer needs is missing is for something to go and look at the page expecting it to be there — which is what a sentence like "confirm the order number appears" makes the agent do.

Actions have the mirror-image problem. A form posts, the action returns validation errors instead of a redirect, and the UI re-renders in place looking like it is waiting for you. Nothing is red anywhere. An agent that submits the form and then reads the page sees no order number and reports the flow as failed, with what it saw where the order number should have been.

The instrumentation half runs on the same walk. Remix puts the moment worth measuring in the action, not the component, which is where hand-written tracking most often goes missing after a refactor moves a form. Because the agent has just used the flow, it knows which actions exist, so it writes and maintains the tracking calls in the SDK you already run — PostHog, Mixpanel, Amplitude, Google Analytics, Plausible or Segment — beside the redirect that means it worked.

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 Remix 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.

questions

We test our loaders and actions already. What does this add?
Those tests answer whether a function returns the right data. This answers whether a person can get through the flow in a browser, which is a different question and the one your customers ask. Most of what it catches is between the units: a loader that is correct but throws for a state your unit tests do not construct, an action that returns a fail the UI does not render, a redirect that lands somewhere reasonable and wrong. Keep the loader tests. This is for the flows nobody got round to covering end to end.
Where does it run — do you need access to our repo?
No. It runs on your own CI runner, against a URL you give it, and posts its comment using the GITHUB_TOKEN that GitHub Actions provides to every workflow for free. There is no GitHub App to install, no preview environment for us to build, and nothing written into your repository. If you would rather not run it in CI at all, the same command works from your terminal against staging.
What do I actually get back after a run on Remix?
One of four words, and the difference between them is the product. Passed. Failed, which means your app did not do what the test describes, so it is a bug report. Stale, which means a recording no longer fits the page — a rename and a removal look identical to a replay, so this is never worded or coloured as a failure and the agent goes and works out which it was. Errored, which means our runner could not run at all: no browser, no network, no key. That one is our fault and it says so, because telling you your checkout is broken when our own runner fell over is the fastest way to lose you.

keep reading