Xircuit Blog

Product updates, developer logs, and news from behind the scenes at Xircuit.

Trust Through Testing: A Prod E2E Suite Against the Live Platform cover image

Trust Through Testing: A Prod E2E Suite Against the Live Platform


Trust Through Testing: A Prod E2E Suite Against the Live Platform

Quality inspection as a symbol for testing Photo: Kristin Hardwick · StockSnap (CC0 1.0), via Openverse

There's a moment after every deployment where I briefly hold my breath. The pipeline is green, all unit tests passed, the containers are running — and yet, in that moment, I don't know with absolute certainty whether a real person can actually sign in to Xircuit and log a measurement. Unit tests check the building blocks. They say nothing about whether the interplay of Auth0, the Blazor frontend, the API, and the database actually works in production.

This week is about exactly that gap, and about how I closed it with an end-to-end suite that doesn't test against a local stand-in, but against the running live platform. It's the more uncomfortable option, and that's exactly why it's so meaningful.

Why Test Against the Live Environment?

Most E2E suites run against a freshly spun-up test instance with an in-memory database and a mocked login. That's fast and deterministic, but it has a blind spot: it proves that my code is internally consistent, not that production is actually standing. That's exactly where the expensive mistakes happen — a misconfigured redirect in the Auth0 configuration, an expired certificate, a container that doesn't come up cleanly after deployment.

That's why the prod E2E suite drives a real browser with Playwright against the live platform. It doesn't call a mocked login page but walks through the real Auth0 sign-in: filling out the form, submitting it, waiting for the callback, checking the session cookie. What the test sees is exactly what a user sees. If that path works, it really works — not just under lab conditions.

The price for that is responsibility. A test that sends real requests against production and creates data there is a sharp tool. It needs guardrails so that it doesn't turn into data litter or, worse, an accidental intrusion into real customer data.

Strictly Opt-In — the Safety Switch

The most important of these guardrails is restraint as the default behavior. The suite is strictly opt-in: without the right environment variables set, every single test is skipped and touches nothing. No login, no request, no entity created. Anyone who wants to run the tests has to deliberately place the persona credentials and the target base URL into the environment — nothing happens by accident.

This decision has a very concrete background. These exact same test classes sit in the repository just like every other test suite, and they get picked up during a normal dotnet test run. If they ran unconditionally, every continuous integration pipeline and every developer on their own machine would unintentionally fire requests against production. The opt-in switch prevents that by interpreting the absence of configuration as a clear "no."

In CI, the suite is additionally switched off — a second, independent lock. Even if environment variables were ever accidentally set there, the prod E2E suite still wouldn't run as part of the regular build. It's something I trigger deliberately and intentionally, usually after a deployment, not something that runs uncontrolled in the background. Two locks instead of one, because when it comes to write access against production, caution is not an exaggeration.

Every Test Entity Has an Owner

When a test writes against the real database, it must be clear at all times what is test data and what isn't. Otherwise the artificial mixes with the real, and at some point you no longer dare to clean up because you can no longer reliably tell the difference.

That's why every entity created follows the same three rules. It belongs to a defined test persona — an account set up specifically for that purpose, not a real user. It's marked as test data at the data level, so a query can filter it out unambiguously. And it additionally carries a marker directly in the text, which makes identifying it trivial even when you're just looking at the UI. This redundancy of structural marking and a visible marker is deliberate: it lets you recognize test data both programmatically and at a glance.

Gears as a symbol for automation Photo: Joe deSousa · StockSnap (CC0 1.0), via Openverse

Cleaning Up Without a Trace, Before and After

Marking alone isn't enough — the data has to disappear again too. There's a gated cleanup endpoint for that, which kicks in before and after every run and removes the test data without a trace. "Gated" means: the endpoint deletes exclusively entities that are marked as test data and belong to a test persona. Real customer data is out of its reach, by design.

Running cleanup not just after but also before the run turned out to be one of the more useful decisions. If a test ever breaks off midway — a timeout, a network hiccup, an unexpected dialog — leftovers remain. The upfront cleanup sweeps away this legacy debris before the next run starts. That way, every run begins on clean ground and doesn't depend on the wreckage of the last one. In practice that means I can kick off the suite without having to wonder what state production is in from last time.

From Sign-In to the Whole Platform

At the start there was a modest goal: prove that a persona can sign in and carry out a handful of core actions. That was the core meant to defuse the deployment moment. But once the scaffolding of opt-in, personas, markers, and cleanup was in place, the expensive work was done — every additional test case cost only a little more.

So coverage grew. A few sign-in checks turned into a rendering smoke test across hundreds of routes: every page gets called, rendered, and then checked to make sure it runs through without errors. That catches a surprisingly large class of problems — a route that points nowhere after a refactor, a component that throws an exception under live data, a missing permission signal. On top of that came CRUD and interaction mutations: tests that don't just check whether a page loads, but actively create, change, and remove records, going the full way through real forms, dialogs, and API calls.

In hindsight, the order was right. If I had started out with the ambition of covering half the platform, I would have failed at the guardrails before the first test even turned green. First the small, safe foundation — and only then the breadth on top of it.

What the Suite Actually Gives Me

The real payoff isn't a number in a report, but a shift in mindset. After a deployment I kick off the suite, and when it's green, I don't just know that my code compiles — I know that a browser has actually signed in, loaded pages, written and deleted data, all against running production. The held breath from the beginning turns into a calm exhale.

Still, I want to stay honest: a suite like this is slower and more fragile than pure unit tests, because it depends on the availability of external systems. A stalled Auth0 service or a network hiccup can turn a run red without anything actually being wrong with my code. That's the price of testing reality instead of a replica. For me, it's worth paying.

What's Next?

Next week: the public face — a marketing website with industry pages, an audience router, and bilingual support.


Comments

No comments yet. Be the first to share your thoughts!

Leave a Comment