Skip to content

Cypress recipe

Cypress doesn’t love async polling out of the box, so we wrap MailFade in a custom command and a small task.

1. Add a custom command

cypress/support/commands.ts:

const API = Cypress.env("MAILFADE_API_URL") ?? "https://api.mailfade.dev";
const KEY = Cypress.env("MAILFADE_KEY") as string | undefined;

declare global {
  namespace Cypress {
    interface Chainable {
      freshInbox(prefix?: string): Chainable<string>;
      waitForEmail(
        inbox: string,
        opts?: { subject?: RegExp; from?: RegExp; timeout?: number },
      ): Chainable<any>;
    }
  }
}

Cypress.Commands.add("freshInbox", (prefix = "cy") => {
  const inbox = `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}@mailfade.dev`;
  return cy.wrap(inbox, { log: false });
});

Cypress.Commands.add("waitForEmail", (inbox, opts = {}) => {
  const { subject, from, timeout = 30_000 } = opts;
  const start = Date.now();
  const poll = (): Cypress.Chainable<any> => {
    const auth = KEY ? { Authorization: `Bearer ${KEY}` } : undefined;
    return cy.request({
      url: `${API}/inbox/${encodeURIComponent(inbox)}`,
      headers: auth,
    }).then((r) => {
      const hit = (r.body.emails ?? []).find((e: any) => {
        if (subject && !subject.test(e.subject ?? "")) return false;
        if (from && !from.test(e.sender ?? "")) return false;
        return true;
      });
      if (hit) {
        return cy.request({ url: `${API}/message/${hit.id}`, headers: auth }).its("body");
      }
      if (Date.now() - start > timeout) throw new Error(`no email at ${inbox}`);
      return cy.wait(1000).then(poll);
    });
  };
  return poll();
});

2. Use it

it("verifies a new signup", () => {
  cy.freshInbox("signup").then((inbox) => {
    cy.visit("/signup");
    cy.get("#email").type(inbox);
    cy.get("#password").type("hunter2hunter2");
    cy.contains("button", "Sign up").click();

    cy.waitForEmail(inbox, { subject: /confirm/i }).then((msg: any) => {
      const link = (msg.text as string).match(/https?:\/\/\S+/)![0];
      cy.visit(link);
      cy.contains("Welcome").should("be.visible");
    });
  });
});

3. Config

cypress.config.ts:

import { defineConfig } from "cypress";

export default defineConfig({
  e2e: {
    env: {
      MAILFADE_API_URL: "https://api.mailfade.dev",
      MAILFADE_KEY: process.env.MAILFADE_KEY,
    },
  },
});