Authentication testing guide

How to test password reset emails end to end

Password-reset email is a privileged account-recovery channel. A useful test covers the public request response, the message sent to the real known user, the trust boundary of the reset URL, the password change, and the fate of the token and existing sessions.

Start with two public request cases

Create a known user whose address is a fresh InboxTap recipient, then submit a reset request for that account. Submit the same public form with a separate unknown address and compare the status and user-facing response.

The endpoint should not reveal whether an account exists. Avoid exact elapsed-time equality in a browser suite because scheduler and database noise make that assertion brittle; use the provider's security guidance and focused lower-level tests for timing mitigations.

Validate the reset destination

Wait for a URL containing the application's reset path, then parse it before navigation. Compare the exact expected origin and pathname so a malformed base URL, untrusted host, or incorrect callback cannot be hidden by a browser redirect.

Treat the full URL as a bearer credential. Do not print it, interpolate it into the test title, or attach an unredacted browser screenshot that exposes the address bar.

password-reset.spec.ts
await requestPasswordReset(inbox.address);

const rawUrl = await inbox.waitForLink({
  contains: "/reset-password",
  timeoutMs: 20_000,
});

const resetUrl = new URL(rawUrl);
expect(resetUrl.origin).toBe(appOrigin);
expect(resetUrl.pathname).toBe("/reset-password");

await completePasswordReset(resetUrl.href, newPassword);
expect(await signIn(inbox.address, oldPassword)).toBe(false);
expect(await signIn(inbox.address, newPassword)).toBe(true);

Prove the password was replaced

Complete the real reset form using a password that meets the application's current policy. Then sign out or create a clean browser context, confirm that the old password fails, and confirm that the new password authenticates the same account.

Check server-backed identity or session state rather than relying only on a success banner. Also assert validation for a weak or mismatched new password when that logic is part of the reset page.

Reject replay, expiry, and tampering

Attempt to reuse the successfully redeemed URL and verify that it cannot change the password again. Exercise an expired token with a controlled clock or test-specific lifetime, and alter the token to prove that malformed input is rejected.

Failure should not expose the token, password policy internals, or account details in the page or routine server logs. The user may receive a safe path to request a new reset instead.

Assert the session policy

Decide whether a password reset revokes existing sessions, only other sessions, or none. Better Auth, for example, exposes revokeSessionsOnPasswordReset rather than imposing one result for every application.

Create the relevant pre-reset sessions and inspect them after the change. InboxTap cannot infer this policy from the email; it must be asserted against the authentication system.

Separate delivery from business deduplication

Use toHaveDeliveredOnce() after the first reset message is known to exist when the product promises one delivery for one request. Its optional quiet window can observe an immediate retry but does not prove that no later job will run.

Queue persistence, idempotency keys, rate limiting, and deduplication belong to application tests. InboxTap can inject a 451 or disconnect to trigger those paths and can show which SMTP attempts completed, but it does not own the job system.

Produce minimal, safe evidence

Prefer evidence that records the assertion result, pseudonymized participants, URL shape, and final application outcome without retaining the reusable secret. Exclude raw RFC source unless a specific debugging need outweighs the disclosure risk.

InboxTap reports redact common token and address surfaces, escape captured markup, and remain bounded, but redaction is explicitly best-effort. Add project-specific patterns and review the artifact before sharing it.

Exercise the reset in a real browser

Use the Playwright guide to connect the isolated reset recipient, captured URL, browser form, and final session assertions.

Read the Playwright guide