Cypressintermediate

Cypress Password Reset Test

Test password reset flow with Cypress

Cypress Password Reset Test (typescript)typescript
// cypress/support/commands.ts
declare global {
  namespace Cypress {
    interface Chainable {
      getLatestEmail(to: string): Chainable<any>;
      waitForEmail(to: string, options?: { timeout?: number; subject?: string }): Chainable<any>;
    }
  }
}

Cypress.Commands.add('getLatestEmail', (to: string) => {
  return cy.request({
    method: 'GET',
    url: `${Cypress.env('PLOP_API_URL')}/messages/latest`,
    headers: {
      Authorization: `Bearer ${Cypress.env('PLOP_API_KEY')}`,
    },
    qs: { to },
    failOnStatusCode: false,
  }).its('body');
});

Cypress.Commands.add('waitForEmail', (to: string, options = {}) => {
  const { timeout = 10000, subject } = options;

  return cy.wrap(null, { timeout }).should(() => {
    return cy.getLatestEmail(to).then((email) => {
      if (!email || email.error) {
        throw new Error('Email not received yet');
      }
      if (subject && !email.subject.includes(subject)) {
        throw new Error(`Subject "${email.subject}" doesn't match "${subject}"`);
      }
      return email;
    });
  });
});

// cypress/e2e/password-reset.cy.ts
describe('Password Reset', () => {
  const testEmail = `reset+${Date.now()}@in.plop.email`;

  it('sends reset email with valid link', () => {
    // Request password reset
    cy.visit('/forgot-password');
    cy.get('[name="email"]').type(testEmail);
    cy.get('form').submit();

    // Verify success message
    cy.contains('Check your email').should('be.visible');

    // Wait for and fetch reset email
    cy.waitForEmail(testEmail, { subject: 'Reset', timeout: 15000 })
      .then((email) => {
        // Verify email content
        expect(email.subject).to.include('Reset');
        expect(email.htmlContent).to.include('reset your password');

        // Extract reset link
        const linkMatch = email.htmlContent.match(/href="([^"]*reset[^"]*)"/);
        expect(linkMatch).to.not.be.null;

        // Visit reset link
        cy.visit(linkMatch[1]);
      });

    // Complete password reset
    cy.get('[name="password"]').type('NewSecurePass123!');
    cy.get('[name="confirmPassword"]').type('NewSecurePass123!');
    cy.get('form').submit();

    // Verify success
    cy.contains('Password updated').should('be.visible');
  });

  it('reset link expires after use', () => {
    cy.visit('/forgot-password');
    cy.get('[name="email"]').type(`expire+${Date.now()}@in.plop.email`);
    cy.get('form').submit();

    cy.waitForEmail(`expire+${Date.now()}@in.plop.email`, { subject: 'Reset' })
      .then((email) => {
        const linkMatch = email.htmlContent.match(/href="([^"]*reset[^"]*)"/);

        // Use the link once
        cy.visit(linkMatch[1]);
        cy.get('[name="password"]').type('NewPass123!');
        cy.get('[name="confirmPassword"]').type('NewPass123!');
        cy.get('form').submit();

        // Try to use it again
        cy.visit(linkMatch[1]);
        cy.contains('Link expired').should('be.visible');
      });
  });
});

How It Works

1

Custom Commands

We define getLatestEmail and waitForEmail as reusable Cypress commands. waitForEmail includes retry logic with configurable timeout.

2

Retry Logic

The waitForEmail command uses Cypress's should() with a wrapper to retry until the email arrives or timeout is reached.

3

Link Extraction

We use regex to extract the reset link from the email HTML, then visit it to complete the flow.

4

Security Testing

The second test verifies that reset links can only be used once—important for security.

Try This Example

Get a free API key and test this example in minutes.