Mailfornet

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

Related

Playwright guide · Selenium & Python · Code examples · API reference

Get a free API key