Automating 2FA with Playwright, Stagehand & Browserbase
Step-by-step recipes to solve out-of-band Email OTP, Magic-Link, Carrier SMS verification, and KMS-vaulted TOTP authenticator hurdles in headless browser automation—including how to pair Sentry Relay with 1Password and Browserbase Contexts for 100% unattended execution.
Pattern 1: Playwright Email or SMS OTP Verification
The relay.wait() helper holds the connection open until the exact code arrives. Because Sentry Relay combines an Upstash Redis 15s lookback ZSET with Pub/Sub push notifications in under 200ms, your automation never suffers from "arrived-before-wait" race conditions.
import { test, expect } from '@playwright/test';
import { SentryRelay } from '@sentryrelay/sdk';
const relay = new SentryRelay({
apiKey: process.env.SENTRY_RELAY_API_KEY!,
});
test('Complete customer login with SMS OTP', async ({ page }) => {
// 1. Fill in mobile number associated with Identity DID
await page.goto('https://banking.example.com/login');
await page.fill('input#phone', '+12035550142');
await page.click('button#send-code');
// 2. Await SMS OTP (blocks up to 45 seconds, returns in <200ms of arrival)
const auth = await relay.wait('banking-bot', {
channel: 'sms',
timeout: 45,
});
console.log(`Received OTP: ${auth.event?.otpCode} (latency: ${auth.waitDurationMs}ms)`);
// 3. Fill code into inputs
await page.fill('input#verification-code', auth.event!.otpCode!);
await page.click('button#submit-verification');
// 4. Assert logged-in state
await expect(page.locator('h1')).toContainText('Account Overview');
});Pattern 2: Stagehand & Browser Use Autonomous Agents
When using Stagehand or Browser Use agents, query Sentry Relay when the agent detects a 2FA or magic-link challenge:
import { Stagehand } from '@browserbasehq/stagehand';
import { SentryRelay } from '@sentryrelay/sdk';
async function runAutonomousWorkflow() {
const stagehand = new Stagehand({ env: 'BROWSERBASE' });
await stagehand.init();
const relay = new SentryRelay({
apiKey: process.env.SENTRY_RELAY_API_KEY!,
});
const page = stagehand.page;
await page.goto('https://portal.saas-vendor.com/login');
// Use natural language actions to log in
await page.act({ action: 'Type "agent-prod@in.sentryrelay.com" into email field and click Continue' });
// Wait for 2FA screen to render
await page.waitForSelector('text=Enter the 6-digit code');
// Fetch the latest OTP from Sentry Relay
const auth = await relay.wait('agent-prod', {
channel: 'email',
timeout: 30,
});
// Supply the code to the AI agent
await page.act({ action: `Enter ${auth.event?.otpCode} into verification input and submit` });
await stagehand.close();
}Pattern 3: Cloud KMS-Vaulted TOTP Passcodes (≤4s Rollover Protection)
For services that enforce Google Authenticator or RFC 6238 TOTP (e.g., GitHub, AWS IAM, Okta), generate live 6-digit passcodes on-demand. If a 30-second window has <4s remaining, Sentry Relay automatically waits out the rollover so slow LLM browser steps never type an expired code:
import { SentryRelay } from '@sentryrelay/sdk';
const relay = new SentryRelay({ apiKey: process.env.SENTRY_RELAY_API_KEY! });
// Auto-waits <4s if cycle is about to expire, guaranteeing a fresh 26-30s code
const token = await relay.totp.get('github-runner', 'github_auth');
console.log(`Generated TOTP: ${token.code} (valid for ${token.remainingSeconds}s)`);
await page.fill('input#app_totp', token.code);Pattern 4: Co-Deploying Sentry Relay with 1Password & Browserbase
Many production startups use 1Password Service Accounts (@1password/sdk) or Browserbase Contexts to store static usernames and passwords, while relying on Sentry Relay when the portal triggers an out-of-band Email OTP, Magic Link, or Carrier SMS shortcode that 1Password cannot receive—achieving 100% unattended execution without human mobile approval prompts:
import { createClient } from '@1password/sdk';
import { SentryRelay } from '@sentryrelay/sdk';
const op = await createClient({
auth: process.env.OP_SERVICE_ACCOUNT_TOKEN!,
integrationName: 'unattended-worker',
integrationVersion: '1.0.0',
});
const relay = new SentryRelay({ apiKey: process.env.SENTRY_RELAY_API_KEY! });
// 1. Pull static password from 1Password Service Account Vault
const password = await op.secrets.resolve('op://Production-Bots/Stripe-Portal/password');
await page.fill('input[name="email"]', 'ops@company.com'); // Auto-forwards 2FA to @in.sentryrelay.com
await page.fill('input[name="password"]', password);
await page.click('button[type="submit"]');
// 2. Intercept out-of-band SMS or Email 2FA challenge via Sentry Relay (<200ms)
const auth = await relay.wait('stripe-prod-bot', { sender: 'stripe.com', timeout: 30 });
if (auth.matched && auth.event?.otpCode) {
await page.fill('input[name="code"]', auth.event.otpCode);
await page.click('button[type="submit"]');
}Production Best Practices
- Enable
burnAfterRead: truein Workspace Settings: Ensures that every OTP is atomically deleted from Redis inside the Lua claim script the moment your worker claims it, eliminating stale replays. - Set realistic timeout windows: Carrier SMS delivery averages 1.2–3.5 seconds; email takes 1.0–4.0 seconds. A timeout of 30–45s accommodates upstream sender retries.
- Zero plaintext keys in Git: Store
SENTRY_RELAY_API_KEYas an encrypted GitHub Actions / Cloud Run secret.