Best Tools for Forgot Password Testing (2026 Comparison)

Best Tools for Forgot Password Testing (2026 Comparison) starts with understanding what makes password recovery flows uniquely risky. A forgotten‑password feature is a high‑value target for attackers,

February 04, 2026 · 16 min read · Testing Guides

Best Tools for Forgot Password Testing (2026 Comparison) starts with understanding what makes password recovery flows uniquely risky. A forgotten‑password feature is a high‑value target for attackers, a frequent source of user frustration, and a common blind spot in test suites that focus only on happy‑path logins. In this guide you will find a practical, side‑by‑side look at the tools that teams are using in 2026 to verify that reset links arrive, tokens are valid, rate limits work, and accessibility requirements are met—without writing endless test scripts.

We begin by outlining why dedicated testing of the forgot‑password flow matters, then walk through manual techniques that still have value, followed by the foundations of automated verification. Next comes a detailed comparison matrix covering six tools that span open‑source frameworks, commercial suites, and autonomous agents. Each tool is examined in a dedicated deep‑dive section with realistic code snippets, setup notes, and pricing highlights. Finally we provide a decision‑making framework, a checklist for test execution, and a short list of pitfalls to avoid. The goal is to give you a bookmark‑ready reference you can apply the next time you need to validate a password‑recovery feature across web, native Android, or iOS clients.

Why Dedicated Forgot Password Testing Matters

Password recovery is not just another form submission. It touches several distinct system boundaries: the public‑facing entry point, the email or SMS gateway, the token generation service, and the reset‑password endpoint. Each boundary introduces failure modes that are easy to miss when tests only verify that a user can log in after a reset.

Because the flow is inherently asynchronous and multi‑channel, the most reliable verification combines three elements: (1) a scriptable client that can trigger the request, (2) a mechanism to capture the out‑of‑band message (email/SMS), and (3) assertions on the subsequent reset step. The tools reviewed below each address these pillars in different ways.

Manual Testing Approaches and Common Pitfalls

Even in highly automated shops, manual exploratory testing remains valuable for uncovering edge cases that scripts assume away. A typical manual session follows these steps:

  1. Trigger the reset – Enter a valid email or phone number on the login page and submit.
  2. Check the inbox – Log into the test email account, locate the reset message, and verify subject line, sender, and link integrity.
  3. Inspect the token – Extract the token from the URL or code, confirm it matches the expected format (e.g., UUID, base64‑url).
  4. Use the token – Navigate to the reset page, paste the token, enter a new password, and confirm.
  5. Validate post‑reset state – Log in with the new credentials, ensure the old password no longer works, and verify any required re‑authentication steps (e.g., 2FA prompt).

Common pitfalls in manual testing include:

To mitigate these issues, many teams augment manual checks with a simple mail‑capture service (e.g., Mailinator, MailSlurp) and a timer that waits for a configurable interval before declaring a timeout.

Automated Testing Foundations for Password Recovery

Automating the forgot‑password flow requires handling three distinct layers:

LayerResponsibilityTypical Implementation
TriggerInvoke the reset request via UI or APISelenium/WebDriver, Playwright, Cypress, Appium, or direct HTTP client
CaptureRetrieve the out‑of‑band message (email/SMS)MailSlurp API, Gmail SMTP IMAP, Twilio SMS logs, or a local SMTP server
ValidateAssert token correctness, submit reset, confirm new loginSame UI driver or API calls, plus assertions on response codes and DOM state

A robust automated test therefore looks like this pseudo‑flow:


1. POST /forgot-password  (email=test@example.com)
2. Wait ≤ 30s for inbound message via MailSlurp
3. Extract token from message body or subject
4. POST /reset-password   (token=…, password=NewPass!23)
5. GET /me               (verify 200 and user data)
6. DELETE /sessions      (logout old session)

Key considerations when choosing an automation approach:

With these foundations in mind, we now examine six tools that teams are adopting in 2026 for forgot‑password testing.

Tool Comparison Matrix

ToolPrimary ApproachSupported PlatformsScripting RequiredNotable StrengthsApprox. Pricing (2026)
Selenium 4 + TestNG/JavaCode‑driven WebDriverWeb (Chrome, Firefox, Edge, Safari)Yes (Java, C#, Python, JS)Mature, extensive grid support, language flexibilityOpen‑source (free)
Cypress 12In‑browser JavaScript runnerWeb (Chrome, Edge, Firefox via experimental)Yes (JavaScript/TypeScript)Automatic waiting, rich debugging UI, built‑in network stubbingOpen‑source (free); Cypress Dashboard $75/mo per user
Playwright 1.40Multi‑language automationWeb (Chromium, WebKit, Firefox)Yes (JS/TS, Python, Java, .NET)Cross‑browser, auto‑wait, tracing, APIRequest for mail captureOpen‑source (free)
Appium 2.0 + Java/KotlinMobile UI automationAndroid, iOS (real devices/emulators)Yes (Java, Kotlin, JS, Python)Native gestures, works with hybrid/webviews, integrates with Sauce LabsOpen‑source (free); cloud device minutes vary
Katalon Studio 9.5Low‑code automation with scripting fallbackWeb, Mobile, DesktopOptional (Groovy/Java)Record‑and‑playback, built‑in keywords for email, CI pluginsFree tier; Studio Enterprise $159/mo per user
SUSA Autonomous AgentScript‑less exploratory testingWeb (Chrome), Android (APK)No (config‑driven)Autonomous persona‑based flows, auto‑generated regression scripts, cross‑session learningTeam plan $199/mo (up to 5k test minutes); Enterprise custom

The table highlights the trade‑offs: pure code frameworks give you full control but require writing and maintaining scripts; low‑code tools reduce boilerplate but may limit deep customization; autonomous agents eliminate script authoring altogether while still producing executable artifacts for regression.

Deep Dive: Selenium 4 + TestNG/Java

Selenium remains the workhorse for teams that need granular control over browser behavior and want to stay within a JVM ecosystem. A typical forgot‑password test using Selenium and the MailSlurp API looks like this:


@Test
public void testForgotPasswordFlow() throws Exception {
    WebDriver driver = new ChromeDriver();
    driver.get("https://auth.example.com/login");

    // 1. Trigger reset
    driver.findElement(By.id("email")).sendKeys("test@example.com");
    driver.findElement(By.id("forgot-btn")).click();

    // 2. Wait for email via MailSlurp (polling)
    MailSlurpClient mailSlurp = new MailSlurpClient(System.getenv("MAILSLURP_API_KEY"));
    Email email = null;
    long deadline = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(30);
    while (System.currentTimeMillis() < deadline && email == null) {
        email = mailSlurp.waitForLatestEmail("test@example.com", Duration.ofSeconds(5));
        Thread.sleep(1000);
    }
    assertNotNull(email, "Reset email not received within timeout");

    // 3. Extract token (assuming URL contains ?token=…)
    String token = extractTokenFromUrl(email.getBody());
    assertNotNull(token, "Token not found in email body");

    // 4. Submit reset
    driver.get("https://auth.example.com/reset?token=" + token);
    driver.findElement(By.id("new-password")).sendKeys("NewPass!23");
    driver.findElement(By.id("confirm-password")).sendKeys("NewPass!23");
    driver.findElement(By.id("reset-btn")).click();

    // 5. Verify login with new password
    driver.get("https://auth.example.com/login");
    driver.findElement(By.id("email")).sendKeys("test@example.com");
    driver.findElement(By.id("password")).sendKeys("NewPass!23");
    driver.findElement(By.id("login-btn")).click();
    assertTrue(driver.getCurrentValue().contains("/dashboard"));

    driver.quit();
}

Strengths

Weaknesses

Setup Effort

Deep Dive: Cypress 12

Cypress runs directly in the browser, giving it automatic waiting and rich debugging capabilities. Forgot‑password tests benefit from Cypress’s ability to stub network requests and to read incoming emails via a custom task that talks to MailSlurp.


// cypress/support/index.js – register a custom task
const { MailSlurpClient } = require('mailslurp');
const mailSlurp = new MailSlurpClient(process.env.MAILSLURP_API_KEY);

module.exports = (on, config) => {
  on('task', {
    async fetchResetEmail({ address }) {
      const email = await mailSlurp.waitForLatestEmail(address, { timeout: 30000 });
      return { id: email.id, body: email.body, subject: email.subject };
    }
  });
};

// cypress/integration/forgot_password_spec.js
describe('Forgot password flow', () => {
  const TEST_EMAIL = `cypress-${Date.now()}@example.com`;

  it('sends reset link and allows password change', () => {
    cy.visit('https://auth.example.com/login');
    cy.get('#email').type(TEST_EMAIL);
    cy.get('#forgot-btn').click();

    // Wait for email via custom task
    cy.task('fetchResetEmail', { address: TEST_EMAIL })
      .then(({ body }) => {
        const token = new URLSearchParams(body.match(/token=([^&]+)/)[1]).get('token');
        expect(token).to.be.a('string').and.have.length.greaterThan(10);

        cy.visit(`https://auth.example.com/reset?token=${token}`);
        cy.get('#new-password').type('NewPass!23{enter}');
        cy.get('#confirm-password').type('NewPass!23{enter}');
        cy.get('#reset-btn').click();

        cy.url().should('include', '/login');
        cy.get('#email').type(TEST_EMAIL);
        cy.get('#password').type('NewPass!23{enter}');
        cy.get('#welcome-message').should('contain', 'Welcome back');
      });
  });
});

Strengths

Weaknesses

Setup Effort

Deep Dive: Playwright 1.40

Playwright offers a unified API across Chromium, WebKit, and Firefox, plus a powerful APIRequest context that can directly call internal services (e.g., a mail‑catcher HTTP endpoint) without leaving the test process.


# tests/test_forgot_password.py
import os, re, time
from playwright.sync_api import sync_playwright, expect

MAILSLURP_BASE = "https://api.mailslurp.com"
API_KEY = os.getenv("MAILSLURP_API_KEY")

def fetch_latest_email(inbox_id):
    url = f"{MAILSLURP_BASE}/emails/latest?inboxId={inbox_id}"
    headers = {"x-api-key": API_KEY}
    with sync_playwright() as p:
        request = p.request.new_context()
        resp = request.get(url, headers=headers, timeout=30000)
        return resp.json()

def test_forgot_password():
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)
        context = browser.new_context()
        page = context.new_page()
        page.goto("https://auth.example.com/login")

        # Trigger reset
        test_email = f"playwright-{int(time.time())}@example.com"
        page.fill("#email", test_email)
        page.click("#forgot-btn")

        # Create a temporary inbox via MailSlurp (simplified)
        inbox_resp = page.request.post(
            f"{MAILSLURP_BASE}/inboxes",
            headers={"x-api-key": API_KEY},
            data={"name": "test-inbox"}
        )
        inbox_id = inbox_resp.json()["id"]

        # Poll for email
        deadline = time.time() + 30
        email = None
        while time.time() < deadline and not email:
            email = fetch_latest_email(inbox_id)
            time.sleep(2)

        assert email, "No email received"
        token_match = re.search(r"token=([^\s&]+)", email["body"])
        token = token_match.group(1) if token_match else None
        assert token, "Token not found"

        # Reset password
        page.goto(f"https://auth.example.com/reset?token={token}")
        page.fill("#new-password", "NewPass!23")
        page.fill("#confirm-password", "NewPass!23")
        page.click("#reset-btn")
        expect(page).to_have_url(re.compile(".*/login$"))

        # Login with new password
        page.fill("#email", test_email)
        page.fill("#password", "NewPass!23")
        page.click("#login-btn")
        expect(page.locator("text=Welcome")).to_be_visible()

        browser.close()

Strengths

Weaknesses

Setup Effort

Deep Dive: Appium 2.0 + Java/Kotlin

Mobile apps often expose the forgot‑password flow via a native screen or a webview wrapped inside a hybrid container. Appium drives the UI on real devices or emulators, allowing you to validate platform‑specific behaviors such as Android’s autofill framework or iOS’s password‑rule UI.


// src/test/java/com/example/ForgotPasswordTest.kt
import io.appium.java_client.AppiumDriver
import io.appium.java_client.android.AndroidDriver
import org.junit.jupiter.api.*
import java.net.URL
import java.util.*
import javax.mail.*

class ForgotPasswordTest {

    private lateinit var driver: AppiumDriver<*>

    @BeforeEach
    fun setUp() {
        val caps = mutableMapOf<String, Any>()
        caps["platformName"] = "Android"
        caps["automationName"] = "UiAutomator2"
        caps["deviceName"] = "Pixel_8_API_34"
        caps["appPackage"] = "com.example.authapp"
        caps["appActivity"] = ".ui.LoginActivity"
        caps["noReset"] = true
        driver = AndroidDriver(new URL("http://localhost:4723/wd/hub"), caps)
    }

    @AfterEach
    fun tearDown() {
        driver.quit()
    }

    @Test
    fun `should receive reset SMS and update password`() {
        val testPhone = "+1555123${System.currentTimeMillis()}"
        // 1. Enter phone and request reset
        driver.findElement(By.id("phone_input")).sendKeys(testPhone)
        driver.findElement(By.id("forgot_button")).click()

        // 2. Wait for SMS via a simple polling of a test SIM API (e.g., Twilio Verify sandbox)
        val smsCode = waitForSms(testPhone, timeoutSec = 30)
        Assertions.assertNotNull(smsCode, "SMS not received")

        // 3. Enter code and new password
        driver.findElement(By.id("sms_code_input")).sendKeys(smsCode)
        driver.findElement(By.id("new_password_input")).sendKeys("NewPass!23")
        driver.findElement(By.id("confirm_password_input")).sendKeys("NewPass!23")
        driver.findElement(By.id("reset_button")).click()

        // 4. Verify login with new credentials
        driver.findElement(By.id("phone_input")).sendKeys(testPhone)
        driver.findElement(By.id("password_input")).sendKeys("NewPass!23")
        driver.findElement(By.id("login_button")).click()
        val welcome = driver.findElement(By.id("welcome_text"))
        Assertions.assertTrue(welcome.getText().contains("Welcome"))
    }

    private fun waitForSms(phoneNumber: String, timeoutSec: Int): String? {
        val end = System.currentTimeMillis() + timeoutSec * 1000
        while (System.currentTimeMillis() < end) {
            // Imagine a helper that queries your test SMS provider
            val code = TestSmsProvider.fetchLatestCode(phoneNumber)
            if (code != null) return code
            Thread.sleep(1500)
        }
        return null
    }
}

Strengths

Weaknesses

Setup Effort

Deep Dive: Katalon Studio 9.5

Katalon offers a low‑code interface with built‑in keywords for email verification, making it attractive for teams that want to minimize hand‑written code while still being able to drop into Groovy when needed.

  1. Create a Test Case – Use the Record‑and‑Playback wizard to walk through the login page, click the “Forgot password?” button, and enter a test email.
  2. Add an Email Keyword – Insert the built‑in Retrieve Email keyword (configured with a MailSlurp or Gmail IMAP account). Set the timeout to 30 seconds and specify a subject filter containing “Reset your password”.
  3. Extract Token – Use a Groovy snippet to parse the token from the email body:

   def body = findTestData('Email Body').getValue()
   def token = body =~ /token=([^\s&]+)/ ? matcher.group(1) : null
   assert token, "Token not found"
  1. Proceed with Reset – Continue the recorded steps: navigate to the reset URL, fill in the new password, submit, and then log in with the new credentials.

Strengths

Weaknesses

Setup Effort

Deep Dive: SUSA Autonomous Agent

SUSA (SUSATest) takes a different approach: instead of writing scripts, you point the agent at a running application (web URL or APK) and let it explore the forgot‑password flow using a set of predefined personas. The agent automatically generates a test script (Appium for Android, Playwright for web) that you can later run in CI.

How it works for forgot‑password testing

  1. Upload – In the SUSA dashboard, upload the latest APK or provide the staging URL of your auth service.
  2. Select Personas – Enable the “Novice”, “Impatient”, and “Security‑Aware” personas; each has a distinct timing and error‑prone behavior (e.g., the impatient persona may repeatedly tap the resend button).
  3. Run Exploration – The agent starts a session, attempts to trigger a password reset via the UI, captures any out‑of‑band messages using its built‑in mailcatcher (configurable to connect to MailSlurp, SendGrid, or a local SMTP server), and validates the token.
  4. Outcome – After the run, SUSA reports PASS/FAIL for each persona, highlighting issues such as missing rate limiting, delayed email delivery, or accessibility violations on the reset screen.
  5. Regression Scripts – If the flow passes, SUSA exports a Playwright script (web) or an Appium script (Android) that you can commit to your repository and run in your pipeline.

Sample CLI invocation


# Install the agent (once)
pip install susatest-agent

# Run a test against a staging URL
susatest run \
  --url https://auth-staging.example.com \
  --apikey $SUSA_API_KEY \
  --personas novice impatient security-aware \
  --output ./susa-reports \
  --export-playwright ./generated-tests/forgot_password.spec.ts

Strengths

Weaknesses

Setup Effort

How to Choose the Right Tool for Your Team

Selecting a tool for forgot‑password testing is less about picking the “most powerful” option and more about aligning with your team’s existing skills, release cadence, and the platforms you support.

Decision FactorRecommended Tool(s)Rationale
Team already writes Java/Selenium testsSelenium 4 + TestNGMinimal context shift; leverage existing grid infrastructure.
Prefer JavaScript/TypeScript and want fast feedbackCypress 12 or Playwright 1.40Automatic waiting, rich debugging, easy to run in parallel.
Need mobile native coverage (Android/iOS)Appium 2.0 (Java/Kotlin) or SUSA (APK)Direct UI interaction; SUSA removes script authoring for exploratory passes.
Want low‑code with built‑in email/SMS handlingKatalon Studio 9.5Record‑and‑playback reduces manual scripting effort; suitable for QA‑lead teams.
Desire zero‑script exploratory testing plus auto‑generated regressionSUSA Autonomous AgentBest for teams that want fast coverage on each build and the option to retain generated scripts.
Budget constraints (zero licensing cost)Selenium, Cypress, Playwright, Appium (all open‑source)Free core frameworks; only external services (mailcatcher, device cloud) may incur cost.
Require cross‑browser testing with a single codebasePlaywrightOne API covers Chromium, WebKit, Firefox.
Need to test with varied user behaviors (impulsive, novice, etc.)SUSA (personas) or Cypress with custom commandsPersona‑driven exploration captures real‑world usage patterns; Cypress can simulate via custom loops.

Process to decide

  1. Inventory your stack – List languages, test runners, and CI tools you already use.
  2. Map platform coverage – Identify whether you need web only, mobile only, or both.
  3. Estimate script maintenance – If your team prefers to avoid writing and maintaining test code, lean toward Katalon or SUSA.
  4. Consider exploratory value – If you want to surface unexpected UX issues (e.g., a user repeatedly tapping “Resend”), SUSA’s persona engine adds unique insight.
  5. Run a pilot – Pick a single forgotten‑password flow, implement a quick test with two candidate tools, compare effort, flakiness, and insight gained.
  6. Finalize – Choose the tool that gives the best trade‑off between coverage, maintenance overhead, and cost for your specific context.

Setup Effort and Maintenance Overview

Below is a concise rundown of the typical time investment for each tool, broken into initial setup, first test creation, and ongoing maintenance (per month, assuming a moderate test suite of 20‑30 flows).

ToolInitial SetupFirst Test CreationOngoing Maintenance (hrs/mo)
Selenium 4 + TestNG/Java2‑4 h (JDK, Selenium Grid, MailSlurp)1‑2 h (write test, add waits)2‑4 h (update locators, handle browser version changes)
Cypress 12<1 h (Node, Cypress, MailSlurp npm)30‑45 min (record‑like syntax)1‑2 h (fix flaky waits, update Cypress version)
Playwright 1.40<1 h (install bindings, configure mailcatcher)45‑60 min (write test)1‑2 h (browser updates, API changes)
Appium 2.0 + Java/Kotlin3‑5 h (Appium server, device lab or cloud, Java bindings)1‑2 h (write test, handle device-specific waits)3‑5 h (device OS updates, app version changes)
Katalon Studio 9.5<1 h (download, configure email integration)30‑45 min (record + tweak)1‑2 h (update test objects, license renewal)
SUSA Autonomous Agent

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