Email testing in Cypress
Two custom commands, cy.createInbox() and cy.waitForCode(), and your signup, OTP login and password-reset flows can be tested end to end.
1. Store the key
Cypress reads environment variables that start with CYPRESS_. Keep the key out of the repo:
export CYPRESS_MAILFORNET_API_KEY="mf_live_your_key"
2. Add the commands
In cypress/support/commands.js:
const API = 'https://api.mailfornet.com/v1';
const auth = () => ({ Authorization: `Bearer ${Cypress.env('MAILFORNET_API_KEY')}` });
// A fresh address for this test
Cypress.Commands.add('createInbox', () =>
cy.request({ method: 'POST', url: `${API}/inboxes`, headers: auth(), body: { ttlMinutes: 30 } })
.its('body.address')
);
// Holds until a verification code or confirm link arrives, then yields { code, link, ... }
Cypress.Commands.add('waitForCode', (address, { subject, from, timeout = 60 } = {}) =>
cy.request({
url: `${API}/inboxes/${encodeURIComponent(address)}/code`,
headers: auth(),
qs: { wait: Math.min(timeout, 60), ...(subject && { subject }), ...(from && { from }) },
timeout: (timeout + 10) * 1000,
}).its('body')
);
Cypress.Commands.add('deleteInbox', (address) =>
cy.request({ method: 'DELETE', url: `${API}/inboxes/${encodeURIComponent(address)}`, headers: auth() })
);
3. Test signup verification
describe('signup', () => {
it('verifies the email with a code', () => {
cy.createInbox().then((address) => {
cy.visit('/signup');
cy.get('[name=email]').type(address);
cy.get('[name=password]').type('Correct-Horse-9');
cy.contains('button', 'Create account').click();
cy.waitForCode(address, { subject: 'Verify' }).then(({ code }) => {
expect(code).to.match(/^\d{6}$/);
cy.get('[name=code]').type(code);
});
cy.contains('button', 'Verify').click();
cy.contains('Welcome').should('be.visible');
cy.deleteInbox(address);
});
});
});
4. OTP login
it('logs in with a one-time code', () => {
cy.createInbox().then((address) => {
cy.task('createUser', address); // seed the user however your app does
cy.visit('/login');
cy.get('[name=email]').type(address);
cy.contains('button', 'Send code').click();
cy.waitForCode(address, { subject: 'login' })
.its('code')
.then((code) => cy.get('[name=otp]').type(code));
cy.url().should('include', '/dashboard');
});
});
5. Magic links
cy.waitForCode(address, { subject: 'Sign in' })
.its('link')
.then((link) => cy.visit(link));
If the link points at a different domain from your baseUrl, wrap the steps that follow in
cy.origin().
Things that trip people up
- Raise the request timeout.
cy.requestdefaults to 30 seconds. The command above sets it just above the wait. - Don't use
cy.wait(5000)for mail. The API returns the moment the mail lands. - Filter by subject if a welcome email arrives alongside the code. The endpoint skips mail with no code in it, but a filter makes the test clearer.
- A 404 means no code arrived. The body's
error.codeiscode_not_found. Check that your app actually sent the mail, and to that address.
Related
Playwright guide · Selenium & Python · Code examples · API reference