Test a login with authenticator-app 2FA
Logins with a 6-digit authenticator code are where hand-written end-to-end tests usually give up. Here is a real one TestWatch wrote for the open-source demo shop.
The test it wrote
await test.step('Sign in as the 2FA shopper', async () => {
await page.getByRole('link', { name: 'Sign in' }).click();
await page.getByRole('textbox', { name: 'Username' }).fill(process.env.TARGET_EMAIL!);
await page.getByRole('textbox', { name: 'Password' }).fill(process.env.TARGET_PASSWORD!);
await page.getByRole('button', { name: 'Sign in' }).click();
await page.getByRole('textbox', { name: 'Authentication code' }).fill(otp());
await page.getByRole('button', { name: 'Verify' }).click();
await expect(page.getByRole('button', { name: 'Sign out' })).toBeVisible();
});
From checkout-with-2fa-sign-in.spec.ts, generated 3 October 2026. Unedited.
How the 2FA code is handled
The code changes every 30 seconds, so it can’t be written into the test. You save the account’s 2FA secret once, with its username and password, as a test account. When a run reaches the code field, otp() computes the current code from that secret.
Secrets are encrypted at rest and scrubbed from the page snapshots and logs we store. While writing this test, the agent’s log showed “Computing the current 2FA code” at that step.
The run
The sign-in step took 310 ms on a passing run from 3 October 2026, with no AI calls: a passing run is plain Playwright.
When the login breaks
If the password is changed or the 2FA secret no longer works, TestWatch says the login no longer works instead of reporting a bug in your app, and the run doesn’t try to “fix” the test.
Other kinds of codes
Codes and links sent by email work too: a test can read them from a built-in test inbox, on every plan. See testing email verification.
Not supported: codes sent by SMS, and signing in through Google, GitHub or SSO in the app you test.
Related
Free: 5 tests, 500 runs a month, 2 apps and one daily schedule. Unlimited teammates, no card.