How to Automate OTP & 2FA Login Testing
Two-factor login is exactly the kind of flow teams skip in automated tests because it's awkward — but it's also where regressions hurt most. There are two cases, and both are automatable. For closely related flows, see testing password-reset emails and magic-link login. This is part of our email testing for developers guide.
Case 1: email or SMS OTP
If the code arrives by email, the recipe is the same as any email test: provision a disposable inbox, trigger the login, read the message through the API, and extract the digits. Create one inbox per test and poll (or use a webhook) until the OTP lands.
const inbox = await createInbox()
// ...submit the login form with inbox.address...
const msg = await waitForMessage(inbox.id, 30000)
const otp = msg.text.match(/[0-9]{6}/)[0]
// ...type otp into the verification field...
See the Playwright email testing tutorial for the full browser-driven version, and the disposable email API guide for the inbox helpers.
Case 2: authenticator app (TOTP)
If 2FA uses an authenticator app, you don't need a phone — the codes are derived from a shared secret with the TOTP algorithm. Store that secret as a test env var and generate the code in your test with a library such as otplib:
import { authenticator } from 'otplib'
const token = authenticator.generate(process.env.TOTP_SECRET)
// ...type token into the 2FA field...
The secret is the same one the app encodes in the setup QR code; reveal it during enrolment and save it for the test environment only.
Test-environment strategies
When neither path is convenient, talk to your team about test-only escape hatches: a special token that always works in staging, disabling 2FA for designated test users, or skipping it for requests from CI IPs. Pick the smallest bypass that still exercises the real login path.
Get started
For email-based codes, create a mailbox and read it via the OpenAPI; for TOTP, wire up the shared secret. Either way, keep one inbox or secret per test and your 2FA suite stays stable.