How to Automate Forgot Password Testing (Step-by-Step)

How to Automate Forgot Password Testing (Step-by-Step)

May 23, 2026 · 14 min read · How-To Guides

How to Automate Forgot Password Testing (Step-by-Step)

Forgot password flows are a critical security and usability touchpoint; automating them catches broken links, mis‑routed emails, token expiration bugs, and UI regressions that manual spot‑checking often misses. This guide walks you through a repeatable process—from deciding whether to automate, through framework selection, locator design, flake‑free execution, data handling, CI integration, reporting, and finally how autonomous exploration can bootstrap the effort without writing a single script. Each section contains concrete examples, code snippets, and tables you can copy into your repository today.

How to Automate Forgot Password Testing (Step-by-Step): Overview

Start by mapping the flow you intend to cover. A typical forgot password journey includes: (1) landing on the login page, (2) clicking a “Forgot password?” link, (3) entering an email or username, (4) submitting the form, (5) receiving a reset link via email or SMS, (6) opening that link, (7) setting a new password, and (8) logging in with the new credential. Identify which steps are under your test harness control (usually steps 1‑4 and 6‑8) and which require external systems (email/SMS gateways). Decide the verification points: presence of success toast, HTTP 200 on reset‑link request, token validity period, and final login success. Document these as a test matrix (see Table 1) before writing any code; it becomes the acceptance criteria for automation.

StepActionAutomatable?VerificationNotes
1Navigate to login pageYesPage title / URLUse base URL from config
2Click “Forgot password?”YesButton enabled stateEnsure locator survives UI tweaks
3Enter email/usernameYesInput value matchesMask sensitive data in logs
4Submit formYesNetwork request to /resetCheck status 200, response JSON
5Email/SMS deliveryPartially (via mock or real inbox)Inbox contains linkUse mailinator, Gmail API, or SMS gateway
6Open reset linkYes (if link is captured)Landing on reset‑password pageLink may be time‑bound
7Set new passwordYesPassword strength UI feedbackEnforce policy in test data
8Login with new passwordYesAuth token or redirect to dashboardClean up after test

If any step cannot be automated reliably (e.g., reliance on a third‑party email provider that throttles), consider stubbing it with a controlled mock service for CI runs while retaining a nightly check against the real provider.

How to Automate Forgot Password Testing (Step-by-Step): When Automation Pays Off

Automation yields ROI when the flow is exercised frequently, has high failure impact, or is prone to regression due to frequent UI changes. Quantify the effort: manual execution of the full flow takes ~2 minutes per tester; a suite of 5 email providers × 3 locales × 2 device types = 30 runs. At 2 minutes each, that’s 1 hour per regression cycle. Automating reduces that to <5 minutes plus maintenance overhead. Conversely, if the flow is static, rarely touched, and your team already has exploratory testing covering it, the automation cost may not justify the gain. Use the following heuristic: automate if (a) the flow is executed ≥ 5 times per sprint, (b) a defect in any step could lead to account takeover or support burden, and (c) the UI layer is stable enough to maintain locators without constant rewrites.

How to Automate Forgot Password Testing (Step-by-Step): Choosing the Right Test Framework

Select a framework that matches your application type, language stack, and existing CI tooling. The table below compares the most common choices for web and mobile forgot password tests.

FrameworkLanguageWeb SupportMobile SupportParallel ExecutionBuilt‑in WaitsLearning Curve
Selenium WebDriverJava, C#, Python, JS✔️via Appium✔️ (Grid)Explicit/WebDriverWaitModerate
CypressJavaScript/TypeScript✔️ (Chrome‑family)Limited (single‑browser)Automatic retriesLow
PlaywrightJavaScript/TypeScript, Python, .NET✔️ (Chromium, Firefox, WebKit)via Playwright‑mobile✔️ (multiple contexts)Auto‑wait + explicitLow‑Medium
AppiumJava, Python, JS, Rubyvia Selendroid/XCUITest✔️ (Android/iOS)✔️ (Grid)Explicit/WebDriverWaitModerate
SUSA (autonomous)CLI (Python)✔️ (explores web)✔️ (explores APK)✔️ (agent‑based)AI‑driven timingVery Low (no scripts)

If your team already writes UI tests in Java with Selenium, extending that suite is the path of least resistance. For greenfield projects or teams comfortable with TypeScript, Playwright offers concise syntax, automatic waiting, and easy multi‑context handling—ideal for capturing email links in a separate browser context. Mobile‑only teams should lean on Appium, but note that handling SMS gateways often requires a separate service (e.g., Twilio test credentials). The autonomous SUSA agent can generate baseline Appium or Playwright scripts after an exploratory run, which you then refine; this is covered later.

Example: Setting Up Playwright for Web


# Install Playwright and browsers
npm init -y
npm i -D @playwright/test
npx playwright install

# Create a config file (playwright.config.js)
module.exports = {
  testDir: './tests',
  use: {
    baseURL: 'https://auth.example.com',
    headless: true,
    viewport: { width: 1280, height: 720 },
  },
};

The baseURL lets you reference relative paths in tests, reducing duplication.

How to Automate Forgot Password Testing (Step-by-Step): Building a Stable Locator Strategy

Flaky tests often stem from brittle selectors. Adopt a hierarchy: (1) data‑testid attributes, (2) ARIA labels, (3) visible text, (4) CSS selectors as a last resort. Work with developers to add stable hooks like data-test="forgot-password-link"; these survive redesigns and are searchable in the DOM.

Example: Locators in Playwright


// forgot-password.test.js
const { test, expect } = require('@playwright/test');

test.describe('Forgot password flow', () => {
  test('happy path with mocked email', async ({ page }) => {
    await page.goto('/login');
    await page.click('[data-test="forgot-password-link"]');
    await page.fill('[data-test="email-input"]', 'tester+reset@example.com');
    await page.click('[data-test="submit-reset"]');

    // Wait for network call and toast
    await expect(page.locator('[data-test="toast-success"]')).toBeVisible({ timeout: 5000 });

    // Capture reset link from mock mailbox (see data‑setup section)
    const resetLink = await getResetLinkFromMockMailbox('tester+reset@example.com');
    await page.goto(resetLink);
    await page.fill('[data-test="new-password"]', 'StrongPass!23');
    await page.fill('[data-test="confirm-password"]', 'StrongPass!23');
    await page.click('[data-test="set-password"]');

    // Verify login with new creds
    await page.goto('/login');
    await page.fill('[data-test="username-input"]', 'tester+reset@example.com');
    await page.fill('[data-test="password-input"]', 'StrongPass!23');
    await page.click('[data-test="login-button"]');
    await expect(page.locator('[data-test="dashboard"]')).toBeVisible();
  });
});

Notice the exclusive use of data-test attributes; if the UI changes the visual layout but retains these attributes, the test continues to pass.

Handling Dynamic IDs

If you cannot modify the source, fallback to XPath that relies on neighboring static text:


//label[text()='Email address']/following-sibling::input

Combine with page.waitForSelector to avoid race conditions.

How to Automate Forgot Password Testing (Step-by-Step): Handling Waits, Synchronization, and Flakiness

Explicit waits trump implicit ones. Use framework‑provided utilities that wait for a condition rather than a fixed timeout. In Playwright, actions like click and fill auto‑wait for elements to be attached, stable, and visible. For network responses, use waitForResponse or expect(response).toBeOK().

Example: Waiting for Email Delivery with a Mock Service


async function getResetLinkFromMockMailbox(email) {
  const mailboxUrl = `https://mockmail.example.com/api/v1/messages?to=${encodeURIComponent(email)}`;
  let attempts = 0;
  while (attempts < 10) {
    const res = await fetch(mailboxUrl);
    const msgs = await res.json();
    const match = msgs.find(m => m.subject.includes('Password reset'));
    if (match) return extractLinkFromHtml(match.body);
    await new Promise(r => setTimeout(r, 2000)); // 2‑second backoff
    attempts++;
  }
  throw new Error('Reset link not received');
}

The loop implements exponential backoff (fixed here for brevity) and avoids hard sleep. In CI, set a reasonable timeout (e.g., 30 seconds) and fail fast if the mock service does not deliver.

Dealing with Flaky Animations

If a button appears after a CSS transition, wait for the transition to end:


await page.waitForFunction(() => {
  const btn = document.querySelector('[data-test="submit-reset"]');
  return btn && getComputedStyle(btn).opacity === '1';
});

Alternatively, disable animations in test environments via a feature flag or CSS override (* { transition: none !important; }).

How to Automate Forgot Password Testing (Step-by-Step): Data Management: Setup, Teardown, and Secure Secrets

Tests need predictable starting states: a user account that exists, is not locked, and has a known email address that the mock mailbox can monitor. Avoid using real production credentials; instead, leverage a test‑data API or a seed script that creates a temporary account before each test and deletes it after.

Example: Using a Test‑Data Factory (Node.js)


// fixtures/userFactory.js
const axios = require('axios');
const API_BASE = process.env.TEST_API_URL || 'https://api.example.com';

async function createTestUser(suffix = Date.now()) {
  const payload = {
    email: `tester+${suffix}@example.com`,
    password: 'TempPass!23',
    username: `tester${suffix}`
  };
  const { data } = await axios.post(`${API_BASE}/users`, payload, {
    headers: { Authorization: `Bearer ${process.env.TEST_API_TOKEN}` }
  });
  return data; // { id, email, ... }
}

async function deleteTestUser(userId) {
  await axios.delete(`${API_BASE}/users/${userId}`, {
    headers: { Authorization: `Bearer ${process.env.TEST_API_TOKEN}` }
  });
}

module.exports = { createTestUser, deleteTestUser };

In your test hook:


let testUser;
test.beforeEach(async () => {
  testUser = await createTestUser();
});
test.afterEach(async () => {
  if (testUser) await deleteTestUser(testUser.id);
});

Secret Management

Never hard‑code API tokens or mailbox credentials. Store them in CI secret stores (GitHub Actions secrets, GitLab CI CI_JOB_TOKEN, or Jenkins Credentials Plugin) and inject as environment variables. In local development, use a .env file excluded from version control and load via dotenv.

Teardown of Email Mocks

If you use a real mailbox (e.g., Gmail API) for a subset of runs, delete the fetched message after extracting the link to keep the mailbox clean:


await gmail.users.messages.delete({ userId: 'me', id: msgId });

How to Automate Forgot Password Testing (Step-by-Step): Running Tests in CI/CD Pipelines

Integrate the suite into your pipeline so that every pull request triggers validation. Use matrix strategies to run against multiple browsers and, if applicable, device emulators.

GitHub Actions Example (Playwright)


name: Forgot Password Tests
on: [pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        browser: [chromium, firefox, webkit]
    steps:
      - uses: actions/checkout@v3
      - name: Setup Node
        uses: actions/setup-node@v3
        with:
          node-version: '20'
      - run: npm ci
      - run: npx playwright test --project=${{ matrix.browser }}
        env:
          TEST_API_URL: ${{ secrets.TEST_API_URL }}
          TEST_API_TOKEN: ${{ secrets.TEST_API_TOKEN }}
          MOCKMAIL_URL: ${{ secrets.MOCKMAIL_URL }}
      - uses: actions/upload-artifact@v3
        if: failure()
        with:
          name: playwright-report
          path: playwright-report/

The strategy.matrix expands the job to three parallel runs, each exercising the same test file against a different engine. Artifacts preserve traces and videos for debugging.

Mobile CI with Appium (GitLab Example)


forgot_password_android:
  image: node:20
  services:
    - name: appium/appium:latest
      alias: appium
  variables:
    APPIUM_HOST: appium
    APPIUM_PORT: '4722'
  script:
    - npm ci
    - npx wdio wdio.android.conf.js
  only:
    - merge_requests

Ensure the Android emulator or real device farm is provisioned before the job starts; many cloud providers (Sauce Labs, BrowserStack) offer ready‑to‑use Appium tunnels.

Parallelism and Resource Limits

If your CI agent has limited CPU, cap parallelism to avoid OOM kills. In Playwright, use --workers=2; in Selenium Grid, configure maxSession per node. Monitor queue times; if tests start queuing, consider splitting the suite into smoke (critical paths) and full (edge‑case) runs, running smoke on every PR and full on nightly.

How to Automate Forgot Password Testing (Step-by-Step): Reporting, Metrics, and Continuous Improvement

Raw pass/fail counts are insufficient; capture data that helps prioritize fixes and detect regressions early.

Test‑Level Metrics

Implement a simple JSON reporter that emits these metrics to a monitoring system (Prometheus, Datadog). Example Playwright custom reporter:


// customReporter.js
const { BaseReporter } = require('@playwright/test');

class MetricsReporter extends BaseReporter {
  onTestEnd(test, result) {
    const flake = test._retryCount > 0 ? 1 : 0;
    this.write(JSON.stringify({
      test: test.title,
      status: result.status,
      duration: result.duration,
      flake,
      timestamp: new Date().toISOString()
    }) + '\n');
  }
}
module.exports = MetricsReporter;

Add to playwright.config.js:


reporter: [['dot'], ['./customReporter.js']];

Consume the NDJSON output in a log aggregator and alert when flake rate exceeds a threshold (e.g., 5 %).

Continuous Improvement Loop

  1. Review flaky tests each sprint; replace brittle locators or tighten waits.
  2. Add new edge cases discovered in production (see next section) as dedicated test cases.
  3. Retire redundant tests that duplicate coverage already provided by unit or integration layers.
  4. Update baseline data when the API contract changes (e.g., new password policy). Regenerate test users via the factory.

Autonomous Exploration as a Bootstrap (SUSA Mention)

When starting from scratch, writing the first forgot password test can be time‑consuming, especially if you need to reverse‑engineer the exact UI flow and network contracts. SUSA’s autonomous agent can explore the application without any test scripts, automatically discovering the forgot password link, submitting forms, capturing reset links via its built‑in email monitor, and validating the final login. After an exploratory run, SUSA exports a ready‑to‑run Playwright (web) or Appium (mobile) test suite that you can commit to your repository and then refine.

How It Works

  1. Upload the APK or provide the web URL via the CLI: susatest-agent explore --url https://auth.example.com.
  2. The agent creates a behavior profile for each persona (e.g., “novice” and “power user”). It attempts the forgot password flow under each profile, varying timing, input errors, and retry patterns.
  3. During exploration, it logs every network request, DOM mutation, and screenshot. When it detects a password‑reset email (via its integrated mailbox listener), it follows the link and attempts to set a new password.
  4. The output includes a test script with selectors derived from stable attributes (data‑testid, ARIA labels) and explicit waits tuned to observed latencies.
  5. You can run the exported script locally: npx playwright test exported/forgotPassword.spec.js or integrate it into your CI as described earlier.

Benefits for Automation Initiatives

While SUSA provides a strong starting point, treat the generated code as a first draft. Review selectors, replace any generated placeholders with your own data‑factory calls, and add assertions that match your internal security policies (e.g., password strength enforcement, audit‑log verification). This hybrid approach leverages autonomous exploration for speed while retaining engineer control for maintainability.

Checklist for Reliable Forgot Password Automation

Use this list before marking a test suite as ready for CI.

Real‑World Edge Cases Seen Only in Production

Even the most thorough test matrix can miss scenarios that only manifest under load, locale‑specific quirks, or unusual user behavior. Below are a few observed in production environments and how to augment your automation to catch them.

Edge CaseSymptomsAutomation Extension
Race condition on token generationRapid successive requests produce the same reset token, leading to “link already used” error.In the test, simulate two concurrent submit requests using Promise.all and assert the server returns distinct tokens or rejects the second with 429.
Localized error messagesSpanish UI shows “Correo no encontrado” while English expects “Email not found”.Parameterize the test with locale cookies or Accept‑Language header; assert the correct translated string appears.
Email provider throttlingAfter 5 reset requests/minute, the mailbox returns HTTP 429, causing test timeout.Implement retry‑after header handling; in CI, lower the test frequency or use a dedicated mailbox with higher limits.
Password policy change mid‑sprintNew policy requires special characters; existing test uses “Password1” and fails silently.Pull the current policy from an endpoint (/api/password-policy) at test start and generate a compliant password dynamically.
Browser autofill interferenceSaved credentials cause the email field to be pre‑filled with a stale address, leading to wrong‑user reset.Disable autofill via page.evaluate(() => { document.querySelector('input[autocomplete]').autocomplete = 'off'; }) before filling.
Link expiration clock skewReset link valid for 15 minutes; test runs slower than expected on a loaded CI node, causing expiration.Measure elapsed time between email receipt and link navigation; assert it’s < policy‑limit – 10 seconds buffer.
CAPTCHA after failed attemptsAfter three bad email entries, a CAPTCHA appears, blocking further automation.Detect the CAPTCHA iframe; if present, mark test as “expected failure” for adversarial persona and log for manual review.
Social login hijackingUser clicks “Forgot password?”, then opts to sign in with Google, bypassing reset flow.Ensure the test does not click any social‑login buttons; assert that the reset‑email request is the only network call after form submit.
International phone number formatsSMS OTP expects E.164 format; test enters a local format, causing delivery failure.Use a library like libphonenumber-js to format the number based on detected country code before submitting.
Accessibility blockerScreen‑reader users cannot activate the reset button because it’s a
without role.
Run an axe‑core scan (await page.injectAxe(); const results = await page.analyze();) and fail if any WCAG 2.1 AA violation is present on the reset page.

Incorporate these checks as separate test cases or as optional flags (--run-adversarial, --run-accessibility) so your core smoke suite stays fast while extended suites run nightly or on release branches.

Takeaways and Next Steps

Automating forgot password testing transforms a fragile, manual checkpoint into a repeatable safety net that guards against security flaws, usability regressions, and costly support incidents. Begin by defining a precise test matrix, then select a framework that aligns with your stack and offers strong auto‑waiting capabilities. Invest in stable locators—prefer data-testid or ARIA tags—eliminate hard sleeps, and manage test data through a secure factory with teardown hooks. Integrate the suite into CI using matrix strategies, monitor flake rates, and enrich reports with custom metrics that signal regressions early. Leverage autonomous exploration tools like SUSA to jump‑start script creation, but always refine the output to meet your team’s maintainability standards. Finally, enrich your coverage with production‑derived edge cases such as token races, localization, throttling, and accessibility violations, ensuring that your automated suite remains both fast and exhaustive. By following the steps and checklist above, you’ll build a forg‑password test suite that pays dividends every time your authentication flow evolves.

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