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

End-to-end tests for Django.

StaticLiveServerTestCase plus Selenium is the documented way to test a Django flow in a browser, and it is also the reason most Django projects have no browser tests at all. Describe the flow in a sentence and run it against a URL instead.

tests/signup.md
---
title: "A new user can register and land on the dashboard"
---

Open /accounts/signup/, fill in the form with a fresh
email, submit it, and confirm the dashboard heading is
shown.

The suite is a folder of these. Nothing in it names a CSS class, an element id or a URL pattern beyond the ones you would tell a colleague.

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

To test a Django app end to end without Selenium, write one sentence per flow in a markdown file and run npx smolanalytics test --suite tests/ --url against a running URL. An agent opens a real browser, reads the page through its accessibility tree, works out what to click, and returns passed, failed, stale or errored. There is no LiveServerTestCase, no webdriver to install and nothing added to your requirements file, because nothing runs inside your project — the URL is the entire setup. On Django it is worth having because server-rendered forms fail politely: a form that re-renders with validation errors is a 200, a CSRF cookie dropped by a proxy fails only on staging, and a redirect can end somewhere sensible and wrong. All of those are invisible to a response-status check and obvious to something that submits the form and looks at the next page. On a pull request one comment says what broke, edited in place, posted with the GITHUB_TOKEN GitHub Actions provides free. A run that passes is recorded and replays afterwards with no model call at all. It does not seed the database, so run it against staging and keep your fixture-based TestCase suite. 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.

Django renders forms on the server, which makes the interesting failures the ones where the server is perfectly happy. A form that re-renders with errors returns 200, so a status check passes while the user is stuck. A CSRF cookie that does not survive a proxy in front of the app rejects the POST on staging and never in development. A redirect chain that ends somewhere sensible and wrong. Every one of those is a 200 to anything watching responses, and every one of them is caught by a test that submits the form and then looks for the thing that should be on the next page.

The admin is the other place worth a sentence, precisely because nobody tests it: it is generated, it is where the internal work happens, and a custom ModelAdmin or an inline formset that stops saving is discovered by a colleague rather than by CI.

Be aware of what it does not do: it does not seed the database. The agent uses whatever state the environment is in, so this is a check against staging with real data, not a replacement for your fixtures and your TestCase suite. Keep those.

Then the tracking. In Django the event worth measuring usually lives in a view or a signal handler, which is code that moves during refactors and takes the analytics call with it. The same agent that just registered an account knows registration exists, so it writes and maintains the tracking calls in the SDK you already run — PostHog, Mixpanel, Amplitude, Google Analytics, Plausible or Segment. When an event in your tracking plan stops arriving but the traffic behind it doesn't, smolanalytics finds the commit that deleted the track() call and opens a pull request putting it back.

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 Django 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

Does this replace my pytest-django suite?
No, and it should not. Unit and integration tests are fast, they run against a clean database, and they can construct states a browser cannot reach. This covers the other half: whether a person can get through the flow in a browser on the environment you actually deployed. The two catch different things, and the flows nobody covered end to end are the ones customers find first.
Can it get through login and into the admin?
Yes, with credentials you supply through environment variables and a sentence that says to use them. The admin is one of the better things to point it at, because it is generated code that nobody writes browser tests for and it is where a broken inline formset costs your own team an afternoon. What it will not do is create the account for you — it does not seed data — so keep a staff test user on staging.
What do I actually get back after a run on Django?
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