TesterArmyTesterArmy
DemoDemo/
How it worksHow it works/
PricingPricing/
FAQFAQ/
BlogBlog/
DocsDocs/
ContactContact
Sign inGet started
HomeBlogHow to Handle Auth (SSO/Email) in E2E?

How to Handle Auth (SSO/Email) in E2E?

SSO redirects and email-based login break most E2E suites because the login flow leaves your app. Here's how to test Google, Okta, SAML, magic links, and OTP codes without flaky third-party UI.

Szymon Rybczak
Szymon RybczakCEO
July 6, 202611 min read
How to Handle Auth (SSO/Email) in E2E?

Email and password login is the easy case. You fill two fields, click a button, and you are in. Our post on handling authentication in Playwright E2E tests covers that setup end to end: save the session with storageState, keep one real login test, split roles, and fail loudly when auth goes stale.

The trouble starts when login is not a form on your own domain.

Two flows break most suites. The first is single sign-on, where clicking "Sign in" bounces the browser to Google, Microsoft, or Okta and back. The second is email-based login, where there is no password at all, just a magic link or a six-digit code sitting in an inbox you cannot see. In both cases the thing the test needs to do next lives outside your application, and that is exactly where E2E suites turn flaky.

This post is about those two cases. The baseline from the previous post still applies; here we cover only the parts that leave your app.

The rule that makes SSO testable

The mistake almost everyone makes first is trying to automate the Google login screen. You write a test that clicks "Sign in with Google", types an email, types a password, and clicks through the consent screen.

It works for a week. Then Google shows a "verify it's you" step, or a device-confirmation prompt, or it detects automation and blocks the login outright. Your suite is now red for a reason that has nothing to do with your product, and there is nothing in your code you can change to fix it.

The rule that fixes this: you do not own the identity provider, so you do not test it. Google proving that a password is correct is Google's job, and they test it far more thoroughly than you ever will. Your job is to prove that when a valid identity comes back from the provider, your app creates the right session, provisions the right user, and shows the right things.

That reframing is the whole game. Once you stop trying to drive the provider's UI, three practical options open up.

Option 1: mint the session directly (best)

If you control the backend, the fastest and most stable path is to skip the redirect entirely and create an authenticated session for a known user, the same way our previous post used an API login for the setup step.

SSO is just a mechanism for establishing identity. Once a user exists, becoming that user in a test does not require replaying the handshake. Expose a test-only endpoint, guarded so it can never ship to production, that issues a session for a seeded SSO user.

tests/auth.setup.ts

This proves the part you actually own: given a valid SSO identity, the session, provisioning, and access all work. It says nothing about whether the redirect to Google succeeds, which is fine, because that is covered separately below.

Guard the endpoint hard. It should be off in production by default, gated behind an environment flag and a shared secret, and ideally compiled out of the production bundle entirely. A test login route that ships to prod is a full authentication bypass.

Option 2: drive a real login, once, behind storageState

Sometimes you cannot mint a session. The identity provider owns everything, the backend is not yours to extend, or the whole point of the test is that the real handshake works.

In that case, drive the real login exactly once, in a setup project, and reuse the saved state everywhere else. This is the same storageState pattern from the previous post, just pointed at a provider redirect instead of a local form. You pay the cost of the slow, occasionally fragile provider flow one time per run instead of once per test.

A few things make this survivable:

  • Use a dedicated test account that exists only for E2E. Never a real employee's account, which can get locked, prompt for re-consent, or trip fraud checks.
  • Turn off or automate MFA for that account. If MFA cannot be disabled, use a provider that exposes a TOTP secret and generate the code with a library like otplib instead of reading an authenticator app.
  • Allowlist the account for automation. Many identity providers treat headless traffic as suspicious. A real browser channel and a persistent context help; some providers also have a break-glass allowlist for known test users.
  • Prefer a dev or staging tenant. Okta, Auth0, and most SAML providers let you run a separate tenant with relaxed security policies, which is far friendlier to automation than your production tenant.

Then treat that setup step the same as any other saved session: verify it landed on authenticated UI before saving, and let it fail loudly in setup if the provider changed something.

Option 3: swap in a fake identity provider

The middle ground, when you want to exercise the redirect round-trip without depending on a real provider being up, is a mock identity provider.

For SAML and OIDC there are drop-in fakes that speak the protocol and return whatever identity you configure: tools like saml-idp, mock-oauth2-server, or Keycloak in a test realm. You point your app's SSO config at the mock in the test environment, and the redirect, callback, and session creation all run for real. Only the human-facing consent screen is replaced by something deterministic.

This is the highest-fidelity option that stays reliable. It genuinely tests your redirect and callback handling, which Option 1 skips, without the flakiness of Option 2. The catch is setup cost: standing up the mock is real work, and it only pays off if the SSO round-trip is a flow you cannot afford to break.

For most teams the answer is a mix: Option 1 for the bulk of authenticated tests, and one Option 2 or Option 3 test that proves the actual SSO handshake still works. Same philosophy as keeping a single real login test.

Email login: you need to read the inbox

Magic links and one-time codes have the same core problem from a different angle. There is no password to type. The credential is delivered to an email inbox, and your test has to read it.

The wrong instinct is to reach into your own database to fish out the token. That skips the part that most often breaks: whether the email actually got sent and rendered with a working link. The right approach is to send it for real and read it back through an inbox your test can query over an API.

Several services do exactly this: Mailosaur, testmail.app, and MailSlurp give you real, API-accessible mailboxes. For local runs, Mailpit or MailHog capture outgoing mail so nothing real is sent. The pattern is the same across all of them.

Generate a unique address per test

The single most important habit here is a fresh address for every test. Most providers support tag-based aliases, so one mailbox becomes infinitely many addresses. This keeps tests isolated and safe to run in parallel, which matters even more than usual because email is shared, stateful, and slow.

tests/helpers/mailbox.ts

Never point tests at a shared inbox with a fixed address. Two tests polling the same mailbox will grab each other's emails, and you get a flake that only shows up under parallelism, exactly like the shared-account problem from the previous post.

Poll for the email, extract the credential

Once the address is unique, the flow is: trigger the send, poll the provider's API for the newest message to that address, and pull out the link or code. Do not read email over IMAP with a fixed sleep; use the provider's API with a real timeout so a slow send fails cleanly instead of hanging.

tests/magic-link.spec.ts

For a one-time code the shape is identical, you just pull the code out of the body instead of following a link.

tests/otp.spec.ts

Note the empty storageState on these tests. Just like the real login test in the previous post, an email-login test that inherits a saved session will pass even when sending or verifying is completely broken, which is the opposite of what it should prove.

Then stop doing it on every test

Reading an inbox is slow and involves a third party, so treat it exactly like the UI login: do it in one real test to prove the flow works, then use API session creation or storageState for everything else. The point of the email test is to catch a broken send or a bad verification link. It is not a login mechanism for your other two hundred specs.

Where TesterArmy fits

Everything above is the right baseline if you maintain your own suite, and it is close to what we would build ourselves. TesterArmy handles both of these for you, automatically: SSO logins and email-based flows, without you writing or maintaining any of the setup above.

We built it because SSO and email login are exactly the flows where hand-maintained fixtures cost the most and break the quietest. Test accounts get locked, the provider adds a new prompt, a magic-link template changes and the regex stops matching, MFA policy tightens on the test tenant. TesterArmy runs these flows from plain-language steps, reads real inboxes for links and codes, and hands you the evidence when one breaks: screenshots, video, logs, and a readable summary of what went wrong.

It is most useful for the journeys you cannot afford to ship broken:

  • Sign in with Google or another SSO provider
  • Complete a magic-link or one-time-code login
  • Finish onboarding for a freshly provisioned SSO user
  • Invite a teammate and have them accept
  • Change account or workspace settings

That's a wrap

SSO and email login break E2E suites for the same reason: the credential lives somewhere you do not control. The fix in both cases is to stop testing the part you do not own and start testing the part you do.

For SSO, mint the session directly when you can, drive the real handshake once behind storageState when you cannot, and reach for a mock identity provider when the round-trip itself is what you need to prove. For email, send for real and read it back through an API-accessible inbox with a unique address per test, then reuse the session everywhere else.

Get that right and auth stops being the flakiest part of your suite. If you have not set up the fundamentals yet, start with How to Handle Authentication in Playwright E2E Tests.

ON THIS PAGE

  • The rule that makes SSO testable
  • Option 1: mint the session directly (best)
  • Option 2: drive a real login, once, behind storageState
  • Option 3: swap in a fake identity provider
  • Email login: you need to read the inbox
  • Where TesterArmy fits
  • That's a wrap

SHARE THIS ARTICLE

  • X

Check other TesterArmy insights

July 6, 2026

How to Handle Auth (SSO/Email) in E2E?

Email/password login is the easy case. Here's how to handle the hard ones in E2E: SSO redirects to Google/Okta/SAML, magic links, and one-time codes, without driving flaky third-party UI on every run.

Read article
June 10, 2026

Inside Playwright CLI: Browser Automation Built for Coding Agents

Playwright CLI is a thin wrapper around a daemon that reuses the exact same tool layer as Playwright MCP - minus the token cost. Here's how it works under the hood and how to use it well.

Read article
June 6, 2026

How to Handle Authentication in Playwright E2E Tests

Stop logging in through the UI before every Playwright test. Use storageState, keep one real login test, isolate roles, and make stale auth fail loudly in CI.

Read article
Contact us

Let's connect

Contact usGet a demo
XLinkedInDiscord
TesterArmyTesterArmy

AI-powered QA testing for modern teams. Ship faster with confidence.

SOC 2 Type 2 badge
GDPR badge

© 2026 TesterArmy, Inc.

Solutions
  • AI app testingAI app testing
  • EcommerceEcommerce
  • MobileMobile
  • Production monitoringProduction monitoring
  • WebWeb
  • WordPress testingWordPress testing
Quick links
  • HomeHome
  • DemoDemo
  • StackStack
  • How it worksHow it works
  • FAQFAQ
  • PricingPricing
  • Get a demoGet a demo
  • Contact usContact us
Resources
  • DocumentationDocumentation
  • BlogBlog
  • API referenceAPI reference
  • Getting startedGetting started
Legal
  • Privacy policyPrivacy policy
  • Terms of serviceTerms of service
TesterArmyTesterArmy
DemoDemo/
How it worksHow it works/
PricingPricing/
FAQFAQ/
BlogBlog/
DocsDocs/
ContactContact
Sign inGet started