How to Automate Forgot Password Testing (Step-by-Step)
How to Automate Forgot Password Testing (Step-by-Step)
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.
| Step | Action | Automatable? | Verification | Notes |
|---|---|---|---|---|
| 1 | Navigate to login page | Yes | Page title / URL | Use base URL from config |
| 2 | Click “Forgot password?” | Yes | Button enabled state | Ensure locator survives UI tweaks |
| 3 | Enter email/username | Yes | Input value matches | Mask sensitive data in logs |
| 4 | Submit form | Yes | Network request to /reset | Check status 200, response JSON |
| 5 | Email/SMS delivery | Partially (via mock or real inbox) | Inbox contains link | Use mailinator, Gmail API, or SMS gateway |
| 6 | Open reset link | Yes (if link is captured) | Landing on reset‑password page | Link may be time‑bound |
| 7 | Set new password | Yes | Password strength UI feedback | Enforce policy in test data |
| 8 | Login with new password | Yes | Auth token or redirect to dashboard | Clean 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.
| Framework | Language | Web Support | Mobile Support | Parallel Execution | Built‑in Waits | Learning Curve |
|---|---|---|---|---|---|---|
| Selenium WebDriver | Java, C#, Python, JS | ✔️ | via Appium | ✔️ (Grid) | Explicit/WebDriverWait | Moderate |
| Cypress | JavaScript/TypeScript | ✔️ (Chrome‑family) | ❌ | Limited (single‑browser) | Automatic retries | Low |
| Playwright | JavaScript/TypeScript, Python, .NET | ✔️ (Chromium, Firefox, WebKit) | via Playwright‑mobile | ✔️ (multiple contexts) | Auto‑wait + explicit | Low‑Medium |
| Appium | Java, Python, JS, Ruby | via Selendroid/XCUITest | ✔️ (Android/iOS) | ✔️ (Grid) | Explicit/WebDriverWait | Moderate |
| SUSA (autonomous) | CLI (Python) | ✔️ (explores web) | ✔️ (explores APK) | ✔️ (agent‑based) | AI‑driven timing | Very 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
- Flake Rate: percentage of runs where a test passes in some attempts and fails in others over a window (e.g., last 20 runs). Tools like
playwright-reportinclude a retries column; compute flake = (retries > 0) / total runs. - Mean Time to Detect (MTTD): average time from commit to failure notification. Track via CI timestamps.
- Resource Usage: CPU/memory per test; helps identify tests that spawn unnecessary browsers or leak contexts.
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
- Review flaky tests each sprint; replace brittle locators or tighten waits.
- Add new edge cases discovered in production (see next section) as dedicated test cases.
- Retire redundant tests that duplicate coverage already provided by unit or integration layers.
- 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
- Upload the APK or provide the web URL via the CLI:
susatest-agent explore --url https://auth.example.com. - 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.
- 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.
- The output includes a test script with selectors derived from stable attributes (data‑testid, ARIA labels) and explicit waits tuned to observed latencies.
- You can run the exported script locally:
npx playwright test exported/forgotPassword.spec.jsor integrate it into your CI as described earlier.
Benefits for Automation Initiatives
- Zero‑script bootstrap: eliminates the initial learning curve for locators and API contracts.
- Cross‑persona coverage: the agent’s curious, impatient, and adversarial profiles surface edge cases such as rapid double‑clicks, malformed emails, and token reuse attempts that a manual tester might overlook.
- Regression baseline: each subsequent run re‑uses the explored state map, reducing exploration time and highlighting new deviations (new buttons, changed URLs) as potential regressions.
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.
- [ ] Flow steps documented in a test matrix with clear pass/fail criteria.
- [ ] All UI interactions use stable locators (
data-testid, ARIA labels) or robust fallbacks. - [ ] No hard sleeps (
await page.waitForTimeout) – only explicit waits or auto‑waiting framework features. - [ ] Test data (users, emails) generated via a secure factory and cleaned up in
afterEach. - [ ] Secrets (API tokens, mailbox credentials) injected exclusively through CI secret stores.
- [ ] Mock or controlled email/SMS service used for fast runs; a separate nightly job validates against the real provider.
- [ ] Network responses verified (status codes, payload shape) before proceeding to next step.
- [ ] Screenshots and traces captured on failure for rapid triage.
- [ ] Flake rate monitored; any test with > 5 % flakiness triggers a review ticket.
- [ ] Suite runs in parallel on all target browsers/devices without resource exhaustion.
- [ ] Exported reports (JUnit, JSON, or custom) ingested by your test‑analytics dashboard.
- [ ] Documentation stored alongside tests (README) explaining how to add new test users or adjust password policy.
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 Case | Symptoms | Automation Extension |
|---|---|---|
| Race condition on token generation | Rapid 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 messages | Spanish 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 throttling | After 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‑sprint | New 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 interference | Saved 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 skew | Reset 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 attempts | After 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 hijacking | User 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 formats | SMS 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 blocker | Screen‑reader users cannot activate the reset button because it’s a | 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