Skip to content

Handling Auth Flows in Testing Without Always Doing a "Real" Login

I often see teams hitting the same wall when writing E2E tests: should we go through the actual login process every single time, or just manipulate the data?

3 min read
Playwright
Auth
E2E Testing
Storage State
Frontend
Security

I often see teams hitting the same wall when writing E2E tests: should we go through the actual login process every single time, or just manipulate the data?

If you go the "real" route, typing a username and password into an Identity Provider (IdP) every time a test runs, you’ll feel the pain pretty quickly. It’s slow, it’s incredibly flaky due to network issues, and sometimes you even get blocked because the system thinks you're a bot. But, if you create a fake authentication that doesn't touch cookies or redirect systems at all, you risk missing crucial bugs. Your tests might stay green, even though the app would actually crash in production due to auth issues.

For me, there’s a middle ground that makes more sense if you want to keep CI fast without losing trust in your test results.

Using Storage State

The most sensible approach is using storage state. Instead of logging in repeatedly for every scenario, you just perform the authentication once at the beginning, save the resulting cookies or headers to a file, and then let all the other tests use that file.

If getting that session is difficult, maybe because of CAPTCHAs or IdP limitations, you can use a backend test helper to generate a signed test cookie that mimics production. You only do this once during the setup phase, not for every single test scenario.

Stubbing in the Right Places

I live by one principle: stub the network, but don't stub the gateway.

If you want to test scenarios like "expired token" or "user lacks permission," don't just set a global variable like window.isLoggedIn = true. That doesn't prove much other than proving your own fake variable works.

It’s much better to stub the API endpoint itself. Let the client-side logic, like the redirect flow or the "unauthorized" UI, run as it actually would. That way, you're actually testing how the app handles authentication failures.

Avoiding the "Happy Path" Trap

One thing I see constantly is an over-reliance on storage state. Because we always start tests in a logged-in state, we often forget to test what happens when a user first arrives at the app.

In reality, many auth bugs pop up in deep links. For example, a user tries to access /order/123 while logged out, but after they successfully sign in, they get dumped onto the dashboard instead of being sent back to their order.

Not everything needs a slow, full login process, but you should have at least one "cold-start" scenario that runs the entire flow from logged-out to logged-in. One test like this is worth much more than dozens of dashboard screenshots that only prove your cookies exist.

A Few Extra Notes

Don't overlook the technical details. Use role-specific credentials so bugs don't stay hidden behind a single "super admin" account. And remember to add your session files to .gitignore; you don't want active sessions sitting in your repository.

There’s no perfect balance between speed and accuracy, and every approach has its downsides. But by being clear about when to use storage state versus when to stub the API, you can build a test suite that actually lets you sleep at night.

More another time.