Playwright & Selenium — Email Verification Testing
Automate signup flows that require email OTP verification using disposable inboxes. No shared test accounts, no flaky email state, no cleanup overhead. The official SDKs handle polling and extraction automatically.
What you'll need
- A FreeCustom.Email API key
- Playwright (TypeScript) or Selenium (Python) installed in your project
npm install freecustom-email(for Playwright) orpip install freecustom-email(for Selenium)
Playwright (TypeScript)
Using the Node SDK, you can generate an inbox, trigger the signup, and wait for the OTP in just three lines of code. The SDK uses long-polling internally so tests remain perfectly fast and stable.
Full Playwright Test
import { test, expect } from "@playwright/test";
import { FreecustomEmailClient } from "freecustom-email";
// Initialize the FCE client globally for your tests
const fce = new FreecustomEmailClient({ apiKey: process.env.FCE_API_KEY! });
test("signup with email verification", async ({ page }) => {
// 1. Create a dynamic inbox
const inbox = `pw-test-${Date.now()}@ditapi.info`;
await fce.inboxes.register(inbox, true); // true = enable zero-latency testing timelines
await fce.inboxes.startTest(inbox, "e2e-signup");
// 2. Fill the signup form in your app
await page.goto("https://your-app.com/signup");
await page.fill('[name="email"]', inbox);
await page.fill('[name="password"]', "Str0ng!Pass#99");
await page.click('[type="submit"]');
// 3. Wait for the OTP email and extract the code automatically (Wait API)
const otp = await fce.otp.waitFor(inbox, { timeoutMs: 30_000 });
console.log(`OTP: ${otp}`);
// 4. Enter OTP in the verification step
await page.fill('[name="otp"]', otp);
await page.click('[data-testid="verify-btn"]');
// Assert successful verification
await expect(page).toHaveURL("/dashboard");
});
// Optionally cleanup (though not strictly necessary as inboxes are isolated)
test.afterEach(async ({}, testInfo) => {
if (testInfo.status === testInfo.expectedStatus) {
// Only cleanup on success; leave failed tests for debugging
// await fce.inboxes.unregister(inbox);
}
});Debugging Failing Tests
When an E2E test fails, you don't want to dig through CI/CD logs. FreeCustom.Email automatically tracks the lifecycle of every email requested during your test runs.
NewAuth Flow Debugger
Open the Test Runs & Events tab in your dashboard to view the exact execution of your Playwright flows in real-time.
- Sub-ms Latency Tracking: Identify if the SMTP relay or your app's worker queue is causing the 30-second timeout.
- Test Run Grouping: Use
client.inboxes.startTestto group events into a single visual block in the dashboard. - Failure Insights: Automatically detects if multiple emails were sent by accident or if an OTP was missing from the template.
Selenium (Python)
The Python SDK supports a sync=True mode, which is perfect for traditional, synchronous Selenium test runners like pytest without needing asyncio wrappers.
import time, os
from selenium import webdriver
from selenium.webdriver.common.by import By
from freecustom_email import FreeCustomEmail
# Initialize synchronous client
fce = FreeCustomEmail(api_key=os.environ["FCE_API_KEY"], sync=True)
def test_signup_with_otp():
inbox = f"sel-{int(time.time())}@ditapi.info"
fce.inboxes.register(inbox, is_testing=True) # Enables zero-latency timeline
fce.inboxes.start_test(inbox, "sel-signup-test")
driver = webdriver.Chrome()
try:
driver.get("https://your-app.com/signup")
driver.find_element(By.NAME, "email").send_keys(inbox)
driver.find_element(By.NAME, "password").send_keys("Str0ng!Pass#99")
driver.find_element(By.CSS_SELECTOR, '[type="submit"]').click()
# SDK automatically waits for the email and extracts the 6 digit OTP
otp = fce.otp.wait_for(inbox, timeout_ms=30000)
print(f"OTP: {otp}")
driver.find_element(By.NAME, "otp").send_keys(otp)
driver.find_element(By.CSS_SELECTOR, '[data-testid="verify-btn"]').click()
assert "/dashboard" in driver.current_url
finally:
fce.inboxes.unregister(inbox)
driver.quit()
if __name__ == "__main__":
test_signup_with_otp()
print("✓ Signup with OTP verification passed")Parallel Tests
Because each test generates a unique inbox address, parallel test runs never interfere with each other. With the Startup plan and above you can run dozens of concurrent inbox-creation + OTP-wait cycles within the ops/second limit.
// playwright.config.ts — safe to run fully parallel
import { defineConfig } from "@playwright/test";
export default defineConfig({
workers: 10, // 10 parallel workers
timeout: 60_000, // 60s per test (covers OTP wait)
use: { baseURL: "https://your-app.com" },
});