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.