How to Test Two-Factor Authentication on Web (Complete Guide)
Two‑factor authentication (2FA) has moved from an optional extra to a baseline security control for most web applications. When a login flow requires a second factor, attackers must compromise both th
Why Testing Two‑Factor Authentication Matters
Two‑factor authentication (2FA) has moved from an optional extra to a baseline security control for most web applications. When a login flow requires a second factor, attackers must compromise both the password and something the user possesses—or a biometric trait—to gain access. In production, a broken 2FA implementation can open the door to account takeover, credential stuffing, or even regulatory penalties if the failure exposes personal data.
Beyond security, 2FA touches usability. Users abandon flows that are confusing, slow, or inaccessible. A poorly designed OTP entry field, a missing fallback for users without phones, or an inaccessible error message can drive away legitimate traffic and increase support costs.
Testing 2FA therefore serves two goals: verify that the security guarantee holds under realistic conditions, and confirm that the experience works for the full spectrum of users who will encounter it in the wild. The following guide walks through a complete test matrix, manual and automated techniques, persona‑driven exploration, and production‑only edge cases that often slip past scripted suites.
Understanding Web‑Based 2FA Flows
Before designing tests, clarify which second‑factor mechanisms your application supports. Each type introduces distinct failure modes and test considerations.
Common 2FA Types on the Web
| Factor | Typical Implementation | User Interaction | Common Failure Points |
|---|---|---|---|
| SMS OTP | Twilio, AWS SNS, or custom gateway sends a 6‑digit code via text | User copies code from SMS into a form field | Carrier delays, number reuse, SIM‑swap, missing fallback when SMS fails |
| Email OTP | SMTP or transactional email service sends a code or magic link | User checks inbox, copies code or clicks link | Spam filtering, latency, link expiration, email provider blocking |
| TOTP (Authenticator app) | Server shares a secret via QR code; apps like Google Authenticator, Authy generate time‑based codes | User opens app, types current 6‑digit code | Clock drift, secret leakage during QR scan, backup‑code handling |
| Push notification | Server pushes a challenge to a registered mobile app; user taps Approve/Deny | User responds in the app | Network loss, app not installed, push token mismatch |
| Hardware token (U2F/WebAuthn) | Browser performs a cryptographic challenge with a USB/NFC/Bluetooth device | User taps/button on device | Browser support, missing user gesture, transport errors |
| Backup codes | Pre‑generated list of one‑time use codes shown during enrollment | User enters a code when primary factor unavailable | Codes leaked, not regenerated after use, insufficient entropy |
Each factor can be combined with others (e.g., password + TOTP + backup codes) or offered as alternatives (user chooses SMS or email). Your test matrix must cover the specific combination(s) your product ships.
Comprehensive Test Matrix
Below is a detailed matrix that separates test ideas into categories. Use it as a checklist when designing manual or automated suites. Each row includes a brief description, the factor(s) it targets, and the expected outcome.
| Category | Test ID | Description | Factors Covered | Expected Result |
|---|---|---|---|---|
| Happy Path | HP‑1 | Successful login with correct password and valid OTP from primary factor | All | User reaches post‑login page, session cookie set |
| HP‑2 | Login using backup code after losing primary device | Backup codes | Access granted, backup code marked as used | |
| HP‑3 | Login via push notification approval on registered device | Push | Session established after user taps Approve | |
| HP‑4 | Login with TOTP generated 30 seconds before expiration | TOTP | Access granted; code still valid | |
| HP‑5 | Login with email OTP after clicking magic link (no code entry) | Email (link) | Direct redirect to authenticated state | |
| Error Paths – User Input | EP‑1 | Entering incorrect OTP (wrong digits) | All | Error message shown, login remains blocked, retry counter increments |
| EP‑2 | Submitting empty OTP field | All | Validation error, focus remains on OTP input | |
| EP‑3 | Pasting OTP with extra whitespace or newline | All | System trims input or rejects with clear message | |
| EP‑4 | Entering OTP after expiration (e.g., waiting 2 minutes for a 60‑second code) | TOTP, SMS, Email | Error indicating code expired, option to resend | |
| EP‑5 | Using a previously used backup code | Backup codes | Error stating code already consumed | |
| Error Paths – System Behavior | EP‑6 | Rate limiting after N failed OTP attempts (e.g., lockout for 15 min) | All | Further OTP submissions blocked, lockout timer displayed |
| EP‑7 | Server returns 429 Too Many Requests on OTP resend | All | User sees friendly message, resend button disabled temporarily | |
| EP‑8 | Network failure during OTP delivery (simulate latency or dropout) | SMS, Email, Push | User can request resend; no infinite spinner | |
| EP‑9 | Server clock skew causing TOTP validation failure | TOTP | Clear error about time mismatch, offer to resync | |
| EP‑10 | Invalid QR code (corrupted or wrong secret) during enrollment | TOTP | Enrollment fails, user prompted to retry or use manual entry | |
| Edge Cases – Device & Account Changes | EC‑1 | Changing phone number and attempting SMS OTP with old number | SMS | System sends OTP to new number only; old number receives nothing |
| EC‑2 | Adding a second authenticator app (multiple TOTP devices) | TOTP | Both devices generate valid codes; revoking one does not affect the other | |
| EC‑3 | Losing access to primary factor and using account recovery flow (email link + backup code) | Email, Backup codes | Recovery succeeds, forces re‑enrollment of 2FA | |
| EC‑4 | Switching browsers mid‑session (e.g., login in Chrome, continue in Firefox) | All | Session not shared; user prompted for 2FA again in second browser | |
| EC‑5 | Using a private/incognito window after login | All | No session persisted; re‑authentication required | |
| Accessibility | AC‑1 | OTP field readable by screen readers (proper label, aria‑live for errors) | All | Announced correctly, error messages announced without delay |
| AC‑2 | Sufficient color contrast for OTP input and error text (WCAG AA) | All | Contrast ratio ≥ 4.5:1 | |
| AC‑3 | Ability to complete 2FA using keyboard only (Tab to field, Enter to submit) | All | No mouse required; focus trap avoided | |
| AC‑4 | Providing alternative OTP delivery for users with disabilities (e.g., voice call for SMS) | SMS | Option to receive voice call available and functional | |
| AC‑5 | Error messages avoid reliance on color alone (e.g., icon + text) | All | Users with color blindness can discern error state | |
| Security & Privacy | SP‑1 | OTP not leaked in URL, referrer header, or logs | All | No OTP appears in network tokens, server logs masked |
| SP‑2 | OTP resend endpoint enforces per‑IP/user throttling | All | Abuse attempts blocked after threshold | |
| SP‑3 | Backup codes displayed only once, then encrypted storage | Backup codes | Codes not recoverable after initial view | |
| SP‑4 | QR code secret transmitted over TLS only, no caching | TOTP | No intermediate storage of secret | |
| SP‑5 | Push notification payload contains no reusable secret | Push | Replay attack ineffective | |
| SP‑6 | Session cookie marked Secure, HttpOnly, SameSite=Strict after 2FA | All | Cookie not accessible to JS, not sent on cross‑site requests | |
| SP‑7 | No password‑reset link sent after successful 2FA login (to avoid credential leakage) | All | Password reset flow requires re‑authentication |
The matrix above can be copied into a test‑management tool; each test ID becomes a traceable item for regression tracking.
Manual Testing Approach
Even with automation, a hands‑on pass catches nuances that scripts ignore—especially around timing, UI state, and human perception.
Preparation
- Create a test matrix spreadsheet with the IDs from the table above. Add columns for “Pass/Fail”, “Observations”, and “Bug ID”.
- Provision test accounts that have 2FA enabled but no existing session cookies. Use a password manager to store credentials securely.
- Set up factor simulators:
- For SMS, use a service like Twilio trial account or a virtual number provider (e.g., TextNow) that forwards to an email or webhook you can poll.
- For email, create a disposable mailbox (Mailinator, Guerrilla Mail) or configure a local SMTP server that captures messages.
- For TOTP, install an authenticator app on a separate device or use a CLI tool like
oathtoolto generate codes. - For push, install the vendor’s mobile app on a test device and register it with the test account.
- For hardware tokens, have a YubiKey or similar ready.
Execution Steps
- Login page – Enter username and password correctly. Verify that the OTP field appears only after successful primary credential validation (some apps show it up front, which can be a security issue).
- OTP delivery – Trigger the OTP send (often automatic after password). Note the latency; start a timer.
- Input OTP – Copy the code from the simulator and paste into the field. Observe whether the field auto‑trims spaces, whether the submit button enables only after a valid length entry, and whether any inline validation appears.
- Submit – Click the button or press Enter. Check for:
- Immediate feedback (spinner, success toast).
- Correct redirect to the intended post‑login page.
- Presence of a session cookie (
Set-Cookiewith Secure, HttpOnly flags).
- Error paths – Repeat steps 1‑4 while injecting the specific fault from the matrix (wrong OTP, empty field, expired code, etc.). Verify that:
- Error messages are clear, actionable, and announced by assistive tech.
- The system does not leak partial success (e.g., showing “invalid password” when the password was correct but OTP failed).
- Backup code flow – Log out, then attempt login with a backup code. Confirm the code is consumed and cannot be reused.
- Accessibility check – Navigate using only the keyboard. Run a screen reader (NVDA, VoiceOver) and confirm that:
- Each form field has a visible label associated via
oraria-label. - Live regions announce errors without requiring the user to move focus away.
- Contrast ratios meet WCAG AA (use a browser extension like axe).
- Security sniffing – With the browser’s devtools Network tab, ensure no OTP appears in request URLs, headers, or payloads. Check that any logging endpoints (if you have access) mask the OTP.
Reporting
When a test fails, capture:
- Screenshot of the UI state.
- Network request/response showing the OTP (if inadvertently exposed).
- Console errors.
- Steps to reproduce with exact timing (e.g., “OTP entered 90 seconds after send”).
- Expected vs. actual behavior.
Attach these to a bug ticket and link back to the test matrix ID for traceability.
Automated Testing Approaches
Automation shines for regression and for exercising the happy path at scale. The main challenge is handling the out‑of‑band nature of OTP delivery. Below are patterns for the most common web test frameworks.
General Strategy
- Decouple OTP retrieval from UI interaction – Have a separate helper that fetches the code from the simulated channel (email, SMS, etc.) and returns it to the test.
- Make the helper configurable – Switch between real services (for staging) and mocks (for CI) via environment variables.
- Keep tests deterministic – Use fixed seeds for TOTP (many libraries allow setting the clock) or pre‑generated backup codes.
Example: Cypress with Email OTP via Mailinator
// cypress/support/otpHelper.js
const imap = require('imap');
const { simpleParser } = require('mailparser');
function getMailinatorCode(email, password) {
return new Cypress.Promise((resolve, reject) => {
const imapConfig = {
user: email,
password,
host: 'imap.mailinator.com',
port: 993,
tls: true,
tlsOptions: { rejectUnauthorized: false }
};
const box = imap(imapConfig);
box.once('ready', () => {
box.openBox('INBOX', false, (err) => {
if (err) return reject(err);
box.search(['UNSEEN', ['SUBJECT', 'Your login code']], (err, results) => {
if (err) return box.end().then(() => reject(err));
if (results.length === 0) return box.end().then(() => reject('No matching email'));
const fetch = box.fetch(results[0], { bodies: '' });
fetch.on('message', (msg, seqno) => {
let body = '';
msg.on('body', (stream, info) => {
stream.on('data', (chunk) => { body += chunk.toString('utf8'); });
});
msg.once('end', () => {
simpleParser(body, (err, parsed) => {
if (err) return box.end().then(() => reject(err));
const code = parsed.text.match(/\b\d{6}\b/);
box.end().then(() => resolve(code ? code[0] : null));
});
});
});
});
});
});
box.once('error', (err) => reject(err));
box.connect();
});
}
// cypress/integration/2fa_login.spec.js
describe('Login with email OTP', () => {
const TEST_USER = Cypress.env('TEST_USER');
const TEST_PASS = Cypress.env('TEST_PASS');
const EMAIL = Cypress.env('MAILINATOR_EMAIL');
const EMAIL_PASS = Cypress.env('MAILINATOR_PASS');
beforeEach(() => {
cy.visit('/login');
cy.get('#username').type(TEST_USER);
cy.get('#password').type(TEST_PASS, { log: false });
cy.get('#loginSubmit').click();
});
it('should succeed with OTP from email', () => {
// wait for OTP email (max 30 s)
cy.task('getOtpCode', { email: EMAIL, password: EMAIL_PASS, timeout: 30000 })
.then((code) => {
expect(code).to.be.a('string').and.have.lengthOf(6);
cy.get('#otpCode').type(code);
cy.get('#otpSubmit').click();
cy.url().should('include', '/dashboard');
cy.getCookie('session').should('exist');
});
});
});
Explanation
cy.tasklets you run Node.js code (the IMAP helper) from within Cypress.- The helper searches for an unseen email with a known subject, extracts a six‑digit code using a regex, and returns it.
- The test types the code, submits, and asserts on the final URL and session cookie.
Example: Playwright with TOTP Clock Control
// tests/2fa-totp.test.js
const { test, expect } = require('@playwright/test');
const speakeasy = require('speakeasy');
test.describe('Login with TOTP', () => {
const SECRET = 'JBSWY3DPEHPK3PXP'; // base32 secret for known test account
const TEST_USER = process.env.TEST_USER;
const TEST_PASS = process.env.TEST_PASS;
test.beforeEach(async ({ page }) => {
await page.goto('/login');
await page.fill('#username', TEST_USER);
await page.fill('#password', TEST_PASS);
await page.click('#loginSubmit');
});
test('should accept a valid TOTP', async ({ page }) => {
// Freeze time to a known timestamp so OTP is predictable
const token = speakeasy.totp({
secret: SECRET,
encoding: 'base32',
step: 30,
window: 0,
time: Math.floor(Date.now() / 1000) * 1000 // align to 30‑second boundary
});
await page.fill('#otpCode', token);
await page.click('#otpSubmit');
await expect(page).toHaveURL(/.*\/dashboard/);
const cookie = await page.context().cookies();
expect(cookie.some(c => c.name === 'session' && c.secure && c.httpOnly)).toBeTruthy();
});
test('should reject an expired TOTP', async ({ page }) => {
// Use a token from 2 minutes ago
const past = Math.floor(Date.now() / 1000) - 120;
const token = speakeasy.totp({
secret: SECRET,
encoding: 'base32',
step: 30,
window: 0,
time: past * 1000
});
await page.fill('#otpCode', token);
await page.click('#otpSubmit');
const error = await page.locator('.otp-error');
await expect(error).toBeVisible();
await expect(error).toHaveText(/code is expired|invalid/);
});
});
Explanation
speakeasygenerates a TOTP given a shared secret and a Unix timestamp.- By supplying a timestamp from the past, we simulate an expired code and verify the error path.
- The test asserts on the presence of a secure, HttpOnly session cookie after success.
Handling SMS with a Mock Gateway
Many teams run a local mock SMS service (e.g., using express and nexmo-mock) that writes incoming messages to a file or a REST endpoint. The test then polls that endpoint for the latest message.
// pseudo‑code for a helper
async function fetchSmtpOtp(phoneNumber) {
const resp = await fetch(`http://localhost:4000/messages?to=${encodeURIComponent(phoneNumber)}`);
const msgs = await resp.json();
const latest = msgs.sort((a, b) => b.timestamp - a.timestamp)[0];
const code = latest.body.match(/\b\d{6}\b/);
return code ? code[0] : null;
}
Integrate this helper as a cy.task or Playwright request step, just like the email example.
Tool Comparison
| Framework | Language | Built‑in OTP helpers | Ease of mocking external channels | Typical CI integration | Comments |
|---|---|---|---|---|---|
| Cypress | JavaScript/TypeScript | None (rely on cy.task) | Good – can spin up mock servers or use email/SMS APIs | Cypress Dashboard, GitHub Actions | Strong UI debugging, time‑travel; requires Node for async helpers |
| Playwright | JavaScript/TypeScript, Python, .NET, Java | None (but easy to call external libs) | Very good – built‑in request/context isolation | Playwright Test, GitHub Actions | Multi‑browser, auto‑wait, traces; works headless out of the box |
| Selenium | Java, C#, Python, Ruby, JS | None | Moderate – need external libraries for mail/SMS | Selenium Grid, Docker | Mature, but verbose; slower start‑up |
| TestCafe | JavaScript/TypeScript | None | Good – can use Node helpers | TestCafe Studio, CI | No WebDriver needed, automatic waiting |
Pick the framework that matches your team’s existing test stack; the patterns above translate easily across them.
Persona‑Driven Autonomous Exploration
Scripted tests excel at verifying known paths, but they often miss surprises that arise from real‑world user behavior. Autonomous QA platforms that simulate diverse personas can uncover issues such as confusing error wording, unexpected UI states, or accessibility gaps that only appear under certain interaction styles.
How Autonomous Exploration Works
- Profile Creation – Each persona defines a set of tendencies:
- *Curious*: clicks every link, explores help text, tries alternative OTP methods.
- *Impatient*: submits forms quickly, may skip reading instructions, often triggers rate limits.
- *Novice*: relies on placeholders, prefers visible buttons, may miss keyboard‑only navigation.
- *Adversarial*: attempts SQLi, XSS, or OTP brute force, tries to tamper with requests.
- *Elderly*: larger touch targets, prefers high contrast, may need longer timeouts.
- *Accessibility*: uses screen reader, keyboard only, high‑contrast mode.
- *Power user*: uses keyboard shortcuts, pastes from clipboard, expects autofill.
- Exploration Engine – The platform drives a real browser (or mobile emulator) and, based on the persona’s profile, makes weighted decisions at each interaction point: which field to focus, whether to read helper text, how long to wait before submitting, whether to try an alternative OTP channel, etc.
- State Tracking – It records visited screens, DOM snapshots, and network calls. If it encounters a dead end (e.g., a button that does nothing) or a crash, it logs the exact sequence that led there.
- Learning Across Runs – Failed or blocked actions are stored; subsequent runs avoid repeating fruitless paths and focus on unexplored areas, increasing coverage over time.
Applying Persona Exploration to 2FA
| Persona | What They Might Do Differently | Potential Bug Surface |
|---|---|---|
| Curious | After entering password, clicks the “Having trouble?” link, tries voice call OTP, then returns to SMS. | Missing synchronization between OTP channels; voice call OTP not accepted after SMS attempt. |
| Impatient | Submits the login form before the OTP field appears, then rapidly pastes a code and hits Enter multiple times. | Race condition where OTP field is not yet attached to DOM, causing stale element, leading to silent failure. |
| Novice | Looks for a “Show code” button to reveal the OTP as they type (common in password fields). | No affordance to reveal OTP, causing frustration and increased support tickets. |
| Adversarial | Sends thousands of OTP requests with varied phone numbers, attempts to guess 6‑digit codes via brute force. | Insufficient rate limiting on OTP resend endpoint, enabling SMS pumping or DoS. |
| Elderly | Uses a tablet with zoom enabled; the OTP input becomes obscured by the virtual keyboard. | Layout breaks at 200 % zoom, making the field inaccessible. |
| Accessibility | Navigates with Tab, expects error messages to be announced instantly via aria-live. | Error message inserted into a static |
| Power user | Uses Ctrl+V to paste OTP from password manager, expects the field to accept pasted content without extra spaces. | Field trims leading/trailing spaces incorrectly, rejecting valid paste. |
| Backup‑code user | After losing phone, tries to log in with a backup code, then immediately attempts another login with the same code. | System does not mark backup code as used until after successful login, allowing reuse. |
These scenarios are rarely captured in a deterministic script because they depend on timing, exploratory clicks, or persona‑specific tolerances. An autonomous platform that simulates these behaviors will surface them early, often before they reach production.
#### Mentioning SUSA (first organic reference)
SUSA’s autonomous agent can be pointed at a staging URL with 2FA enabled. By assigning it a mixture of the personas above, it will automatically:
- Attempt login with each primary factor, then try fallback methods.
- Vary input speed from rapid bursts to deliberate pauses.
- Trigger accessibility modes (high contrast, screen reader) and verify announcements.
- Detect silent failures such as missing OTP field attachment or unannounced errors.
The resulting report includes a list of unique screen states, any JavaScript errors, and a flow‑by‑flow verdict (PASS/FAIL). Teams can then prioritize fixes based on the severity and frequency of the issues observed across personas.
#### Second SUSA Reference (optional, still within limit)
If your team already uses SUSA for regression, you can configure it to remember which OTP delivery method succeeded for a given account during a prior run. On the next execution, the agent will prioritize that method while still occasionally trying alternatives to catch regressions in the fallback paths. This cross‑session learning reduces redundant exploration while maintaining coverage of edge cases.
Production‑Only Edge Cases
Even the most thorough pre‑release suite can miss issues that only manifest under real‑world traffic, infrastructure quirks, or user‑specific conditions. Below are common production‑only pitfalls for web 2FA and how to detect or mitigate them.
| Issue | Why It Appears Only in Prod | Detection / Mitigation |
|---|---|---|
| Carrier‑specific SMS delays | Certain regions experience filtering or greylisting that adds seconds to minutes of latency. | Use real phone numbers from target markets in staging; monitor OTP delivery time SLO; set up alerts if median latency > 10 s. |
| SIM‑swap attacks | Attacker convinces carrier to port victim’s number; OTP goes to attacker’s device. | Implement secondary verification (e.g., email confirmation) when a new phone number is added; monitor for rapid number changes. |
| Email provider throttling | Bulk OTP sends trigger spam filters, causing delayed or blocked delivery. | Segment OTP traffic by domain; maintain good sender reputation; provide a “Resend via SMS” fallback when email fails. |
| Browser extension interference | Password managers or security extensions may auto‑fill OTP fields incorrectly or block submission. | Test with popular extensions (LastPass, 1Password, Bitdefender) enabled; provide a autocomplete="one-time-code" hint to help managers. |
| Timezone drift on user device | Users traveling across zones may have device clocks off by several minutes, breaking TOTP validation. | Allow a larger verification window (e.g., ±2 minutes) while logging anomalies; prompt users to sync time if failures exceed threshold. |
| Concurrent sessions | User logs in on two devices simultaneously; one session completes 2FA while the other is still waiting for OTP. | Tie OTP validation to a specific session ID; invalidate pending OTPs when a new login begins. |
| Backup code leakage via logs | Misconfigured logging might capture backup codes when users view them. | Audit all loggers for PII; mask backup codes; use hash‑only storage after first view. |
| Network partitioning during push | Mobile device loses data connection just after receiving push challenge; user approves but server never gets acknowledgment. | Make push approval idempotent; allow retry within a short window; surface a “Didn’t receive response?” option. |
| Upgrade of authenticator app | New version changes the way secrets are stored (e.g., moves to encrypted vault) causing previously working TOTP to fail. | Provide a “Re‑sync” flow that lets users re‑scan QR code without needing to disable/re‑enable 2FA. |
| Legal jurisdiction restrictions | Some countries prohibit storing OTPs or require data localization for SMS gateways. | Ensure your OTP provider complies with local laws; have region‑specific fallback (e.g., local SMS gateway). |
| High‑traffic burst causing rate‑limit false positives | During a sale or product launch, legitimate users may hit OTP resend limits unintentionally. | Dynamically adjust thresholds based on recent successful login rate; exempt known good IPs temporarily. |
Monitoring these signals in production (via logs, metrics, and user‑feedback tickets) helps you catch regressions that unit or UI tests cannot anticipate.
Concise Checklist for QA Engineers
Copy this list into your test‑management tool or keep it as a reference before a release.
- [ ] Happy path works for each enabled factor (SMS, email, TOTP, push, hardware).
- [ ] Backup codes grant access, are marked used, and cannot be reused.
- [ ] Error messages are clear, actionable, and announced by assistive tech.
- [ ] Rate limiting triggers after configured failed attempts; lockout timer displayed.
- [ ] OTP resend respects per‑user/IP throttling and does not leak the code in URLs/logs.
- [ ] Accessibility – labels, contrast, keyboard navigation, live regions, zoom tolerance.
- [ ] Security – OTP never appears in request URLs, headers, or logs; cookies Secure/HttpOnly/SameSite.
- [ ] Recovery flow – loss of primary factor leads to verified alternate path (email + backup).
- [ ] Cross‑browser/session – 2FA required in each new browser/incognito window.
- [ ] Locale & time – OTP validation tolerates modest clock drift; localized messages correct.
- [ ] Persona simulation – at least one run each with curious, impatient, novice, adversarial, elderly, accessibility, and power‑user profiles.
- [ ] Production monitoring – alert on OTP delivery latency > 10 s, spike in failed OTP attempts, or backup‑code reuse.
Run this checklist after any change to authentication, user‑settings, or notification services.
Closing Takeaways
Two‑factor authentication is more than a checkbox on a security compliance list; it is a dynamic interaction point that blends cryptography, delivery channels, UI design, and human behavior. Effective testing therefore needs:
- A comprehensive matrix that isolates happy paths, error conditions, accessibility, and security concerns.
- Manual exploratory sessions that catch timing‑sensitive bugs, confusing copy, and layout quirks only a human eye (or a persona‑driven bot) can spot.
- Automated regression that reliably verifies the core flow while delegating OTP retrieval to dedicated helpers or mocked services.
- Production awareness of carrier, email, device, and environmental factors that can turn a lab‑perfect flow into a real‑world failure.
- Persona‑driven autonomous testing—using tools like SUSA—to surface edge cases that scripted tests never consider, especially around alternative flows, accessibility, and misuse patterns.
By combining these approaches, you gain confidence that your 2FA implementation not only thwarts attackers but also serves every legitimate user with clarity, speed, and dignity. Treat 2FA as a living feature: revisit the matrix whenever you add a new factor, change a delivery vendor, or adjust security policies, and let your test suite evolve alongside the threats and user expectations it aims to protect.
Test Your App Autonomously
Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.
Try SUSA Free