<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Juan Pablo</title>
    <description>The latest articles on DEV Community by Juan Pablo (@jprealini).</description>
    <link>https://dev.to/jprealini</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F368589%2F39ed8618-4282-4876-bbaa-9b817117de61.jpeg</url>
      <title>DEV Community: Juan Pablo</title>
      <link>https://dev.to/jprealini</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jprealini"/>
    <language>en</language>
    <item>
      <title>Extended "run all specs" feature for Cypress 10</title>
      <dc:creator>Juan Pablo</dc:creator>
      <pubDate>Fri, 07 Oct 2022 20:14:54 +0000</pubDate>
      <link>https://dev.to/jprealini/extended-run-all-specs-feature-for-cypress-10-303c</link>
      <guid>https://dev.to/jprealini/extended-run-all-specs-feature-for-cypress-10-303c</guid>
      <description>&lt;p&gt;Required node packages:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/rimraf"&gt;rimraf&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As probably all Cypress fans know already, Cypress 10 was shipped without the "run all tests" feature due to "technical limitations" (they are apparently working on a solution, and &lt;a href="https://github.com/cypress-io/cypress/discussions/21628"&gt;here &lt;/a&gt;is where you can read all about why they removed it, what are the technical limitations, and all the complaints about it too)&lt;/p&gt;

&lt;p&gt;I was, as most of us, in desperate need for the ability to run all tests from the test runner interface, and I stumbled upon this &lt;a href="https://glebbahmutov.com/blog/run-all-specs-cypress-v10"&gt;great post&lt;/a&gt; from &lt;a href="https://glebbahmutov.com/"&gt;Gleb Bahmutov&lt;/a&gt; regarding a workaround/hack to be able to do it until Cypress decides what to do.&lt;/p&gt;

&lt;p&gt;The solution that Gleb proposes involves manually importing every new spec to the "all.spec.cy/ts", and then run "all.spec.cy/ts" in Cypress (read the post for more details). &lt;/p&gt;

&lt;p&gt;So I saw an opportunity to adapt this to my particular need. In my scenario, I had many tests grouped by folder/feature under e2e. And I knew many people were eventually going to be involved in adding tests to each folder, and that at some point someone was going to forget to add the import statement, and we were going to be in trouble. Then I thought "What if... I can create a script that will, everytime Cypress is executed, navigate the e2e folder, find every spec, and then create the all.spec.cy/ts with the corresponding imports?" And then I went even crazyer "What if I can create a "groupspec" for each folder I have, in case I want to run all tests for a given feature"... &lt;/p&gt;

&lt;p&gt;And this is what I came up with&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create the "generateGroups.js" file in your root
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/* eslint-disable */
const fs = require('fs');
const folders = fs.readdirSync('cypress/e2e', { withFileTypes: true })

/* Create and populate all.cy.ts with all existing tests imports */
writeFile("all")

addAllImports("all")

/* Create and populate a "group" cy.ts file per folder with corresponding imports */
folders.filter(dirent =&amp;gt; dirent.isDirectory())
  .map(dirent =&amp;gt; dirent.name).forEach(folder =&amp;gt; {
    writeFile(folder);
    addGroupImports(folder)
  })

function addGroupImports(spec) {
  readImports(spec, spec);
}

/* Add all imports to all.cy.ts file */
function addAllImports(spec) {
  folders.filter(dirent =&amp;gt; dirent.isDirectory())
    .map(dirent =&amp;gt; dirent.name).forEach(folder =&amp;gt; {
      readImports("all", folder);
    })
}

/* Build the "import" commands and insert it
in each corresponding cy.ts file */

function readImports(spec, folder) {
  let files = fs.readdirSync(`cypress/e2e/${folder}`)
  let newImport = "";
  files.forEach(file =&amp;gt; {
    newImport = `import "./${folder}/${file}";\n`;
    appendImport(spec, newImport);
  })
}

/* Insert the import lines in each corresponding file */
function appendImport(fileName, newImport) {
  fs.appendFile(`cypress/e2e/${fileName}.cy.ts`, newImport, function (err) {
    if (err) throw err;
  })
}

/* Create group cy.ts file */
function writeFile(file) {
  fs.writeFile(`cypress/e2e/${file}.cy.ts`, "", function (err) {
    if (err) throw err;
    console.log(`${file} Group Spec file was created successfully.`);
  })
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then this would be your scripts section in the package.json file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"scripts": {
    "open": "npm run groups &amp;amp;&amp;amp; cypress open --browser chrome --e2e",
    "test": "npm run cleangroups &amp;amp;&amp;amp; cypress run --e2e",           
    "groups": "node generateGroups.js",
    "cleangroups": "npx rimraf cypress/e2e/*.cy.ts",
  },
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where: &lt;/p&gt;

&lt;p&gt;"open" will create the group files and then open cypress, where you will have a .cy.ts file for each folder, and the all.cy.ts file&lt;/p&gt;

&lt;p&gt;"test" will use rimraf (a node package that will allow you to delete files/folder cross platform) to delete all group files, so when you run "cypress run" it will run all tests (if you don't delete the group files, all tests will be executed twice)&lt;/p&gt;

&lt;p&gt;"groups" executes generateGroups to create the group cy.ts files&lt;/p&gt;

&lt;p&gt;"cleangroups" to delete the files when needed&lt;/p&gt;

&lt;p&gt;This is how my folder structure looks normally (without group files)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sC7WtaPF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p1kfxj0y5o3z0t3mzned.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sC7WtaPF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p1kfxj0y5o3z0t3mzned.png" alt="Folder structure prior to running the script" width="615" height="243"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is how it looks when I run "npm run open"&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NKE3ZVQH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/te6pkogg7xc4y36n7l9m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NKE3ZVQH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/te6pkogg7xc4y36n7l9m.png" alt="Folder structure after the script ran, new group files created" width="628" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And this is Cypress: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UOz8r8f9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u3g6jk999afut87frw43.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UOz8r8f9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u3g6jk999afut87frw43.png" alt="Cypress with all folders and group files" width="819" height="698"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is what my all.cy.ts file looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iHlOlQCN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uptxfldgtbgh4eb0mynr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iHlOlQCN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uptxfldgtbgh4eb0mynr.png" alt="all.cy.ts file" width="880" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Again, I love the flexibility I have to adapt stuff to fit my needs... :D&lt;/p&gt;

&lt;p&gt;Hope this is useful for anyone... Have a great day and...&lt;/p&gt;

&lt;p&gt;Happy testing!&lt;/p&gt;

</description>
      <category>cypress</category>
      <category>hacks</category>
      <category>automation</category>
      <category>testing</category>
    </item>
    <item>
      <title>How to test sent and received emails with Cypress 10, Ethereal and Nodemailer</title>
      <dc:creator>Juan Pablo</dc:creator>
      <pubDate>Fri, 30 Sep 2022 19:55:34 +0000</pubDate>
      <link>https://dev.to/jprealini/how-to-test-sent-and-received-emails-with-cypress-10-ethereal-and-nodemailer-5h25</link>
      <guid>https://dev.to/jprealini/how-to-test-sent-and-received-emails-with-cypress-10-ethereal-and-nodemailer-5h25</guid>
      <description>&lt;p&gt;Last week I was assigned the task of finding out if Cypress was well suited to test email flows. Keep in mind that for different reasons the Cypress framework I built is not integrated into the app code, and even the application under test can't be deployed locally, so the tests always run against the live app deployed on 3 environments (dev, stage and some tests against prod)&lt;/p&gt;

&lt;p&gt;Since I am a Cypress fan my first answer to those assignments/questions is always "of course! Cypress can do anything!"... I must admit that I sometimes regret having said that, not because I found it impossible to do, but because it usually means that I have to spend quite some time researching and building POC's.. but what the heck, that's part of the fun! :D&lt;/p&gt;

&lt;p&gt;Anyway... for this particular assignment I quickly found a couple of posts with related information. The first one was "&lt;a href="https://www.cypress.io/blog/2021/05/11/testing-html-emails-using-cypress/"&gt;Testing HTML emails using Cypress&lt;/a&gt;" by Gleb Bahmutov. But I found a problem. The solution described makes use of a plugin called smtp-tester, which runs a local smtp server, and in order to us it you need to manipulate your app's (the app under test) configuration so that, when executed locally, you can send the emails using the local smtp configuration. For me that was a NO-GO for the reasons I mentiond above.&lt;/p&gt;

&lt;p&gt;So I kept digging, also asked some questions on the &lt;a href="https://gitter.im/cypress-io/cypress#"&gt;Cypress' gitter channel&lt;/a&gt; (I encourage everyone to subscribe to it), and Gleb directed me on the direction of Ethereal.email. I even found this post by him too "&lt;a href="https://www.cypress.io/blog/2021/05/24/full-testing-of-html-emails-using-ethereal-accounts/#retry-email-task"&gt;Full Testing of HTML Emails using SendGrid and Ethereal Accounts&lt;/a&gt;". I had never heard about it, but it's a really cool tool. It's basically an online service that creates a fake smtp server for you to use, where emails are actually never sent anywhere, and are deleted after a couple of hours.&lt;/p&gt;

&lt;p&gt;Then I needed the bit related to actually fetching the emails from the ethereal account, and I found &lt;a href="https://javascript.plainenglish.io/how-to-send-and-read-emails-using-node-js-the-easiest-way-48e137ea8eb4"&gt;this post&lt;/a&gt; which shows how to do it, using the imap-simple node package. But now my new problem was that the particular package seems outdated and not maintained any more, and I needed something more "reliable" (the company does regular security scans on everything used). So I kept searching and found &lt;strong&gt;&lt;a href="https://www.npmjs.com/package/imapflow"&gt;imapflow&lt;/a&gt;&lt;/strong&gt;, which basically does the same thing, but with some little differences.&lt;/p&gt;

&lt;p&gt;Here are the two main cases for email testing that we will find:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Signup process email verification - you want to test if the email that the signup process sends to the new user is correct (right format, right content, probably check if the verification code sent actually works, etc)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Email notifications: whatever processes in the app that trigger a notification email.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This example will use the cypress dashboard signup page for testing the flow (you can use the signup page of the app you are trying to test). To understand exactly how ethereal emails work and how they integrate with this email testing pattern I recommend you to visit the links I shared above (there are some nice diagrams there too)&lt;/p&gt;

&lt;p&gt;Too much talk (or typing)... let's get to work&lt;/p&gt;

&lt;p&gt;Requirements:&lt;/p&gt;

&lt;p&gt;Cypress 10.3^&lt;br&gt;
Plugins:&lt;br&gt;
&lt;a href="https://www.npmjs.com/package/cypress-recurse"&gt;recurse&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.npmjs.com/package/nodemailer"&gt;nodemailer&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.npmjs.com/package/imapflow"&gt;imapflow&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.npmjs.com/package/mailparser"&gt;mailparser&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note that a good part of the code I'll share was extracted from Gleb Bahmutov's solution for testing html emails, and also extracted some ideas from &lt;a href="https://javascript.plainenglish.io/how-to-send-and-read-emails-using-node-js-the-easiest-way-48e137ea8eb4"&gt;this post&lt;/a&gt;. All I did was put the pieces together, and give it my own flavor&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;These are three basic steps: (The code is properly commented to understand exactly what happens at each step)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create your plugins&lt;/li&gt;
&lt;li&gt;Prepare your config file&lt;/li&gt;
&lt;li&gt;Write your test&lt;/li&gt;
&lt;/ol&gt;



&lt;ol&gt;
&lt;li&gt;The original solution contains all methods in one plugin, but what I did was separate each method in its own plugin file. &lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;create-account.js&lt;/li&gt;
&lt;li&gt;get-last-email.js&lt;/li&gt;
&lt;li&gt;parse-email.js&lt;/li&gt;
&lt;li&gt;send-email.js&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(I'm sure I don't need to explain what each plugin does)&lt;/p&gt;

&lt;p&gt;&lt;em&gt;cypress/plugins/create-account.js&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const nodemailer = require("nodemailer")

const createAccount = async () =&amp;gt; {
    let testAccount
    testAccount = await nodemailer.createTestAccount()
    console.log("created new email account %s", testAccount.user)
    console.log("for debugging, the password is %s", testAccount.pass)

    return testAccount
}

module.exports = createAccount
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;cypress/plugins/get-last-email.js&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// use Nodemailer to get an Ethereal email inbox
// https://nodemailer.com/about/
const nodemailer = require("nodemailer")
// used to fetch emails from the inbox via imap protocol
// https://github.com/postalsys/imapflow
const { ImapFlow } = require("imapflow")

const getLastEmail = async (user, pass) =&amp;gt; {
    debugger
    let client = new ImapFlow({
        host: "ethereal.email",
        port: 993,
        secure: true,
        auth: {
            user: user,
            pass: pass
        }
    })
    await client.connect()
    let message
    // Select and lock a mailbox. Throws if mailbox does not exist
    let lock = await client.getMailboxLock("INBOX")
    try {
        message = await client.fetchOne(client.mailbox.exists, { source: true })
        // list subjects for all messages
        // uid value is always included in FETCH response, envelope strings are in unicode.
        // for await (let message of client.fetch("1:*", { envelope: true })) {
        //     console.log(`${message.uid}: ${message.envelope.subject}`)
        // }
    } finally {
        // Make sure lock is released, otherwise next `getMailboxLock()` never returns
        await client.messageFlagsAdd({ seen: false }, ["\\Seen"])
        lock.release()
    }
    await client.logout()

    //If no message was received (message = false) then return message, to ensure that the task will retry
    //If a message is received return the source in order to parse its content
    if (!message)
        return message
    else
        return message.source

}

module.exports = getLastEmail
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;cypress/plugins/parse-email.js&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// used to parse emails from the inbox
const simpleParser = require("mailparser").simpleParser

const parseEmail = async (message) =&amp;gt; {
    const source = Buffer.from(message)
    const mail = await simpleParser(
        source
    )

    return {
        subject: mail.subject,
        text: mail.text,
        html: mail.html,
        attachments: mail.attachments
    }
}

module.exports = parseEmail
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;cypress/plugins/send-email.js&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const nodemailer = require("nodemailer")

const sendEmail = async (user, pass, emailObject) =&amp;gt; {
    // create reusable transporter object using the default SMTP transport
    let transporter = nodemailer.createTransport({
        host: "smtp.ethereal.email",
        port: 587,
        secure: false, // true for 465, false for other ports
        auth: {
            user: user,
            pass: pass
        },
    })

    let info = await transporter.sendMail(emailObject)

    // Preview only available when sending through an Ethereal account
    console.log("Preview URL: %s", nodemailer.getTestMessageUrl(info))
    return info.messageId
}

module.exports = sendEmail
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Config file: we load the plugins, and create the tasks in the setupNodeEvents function that will call the plugin's methods&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;cypress.config.js&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { defineConfig } = require("cypress");
const getLastEmail = require('./cypress/plugins/get-last-email');
const sendEmail = require('./cypress/plugins/send-email')
const createTestEmail = require('./cypress/plugins/create-account')
const parseEmail = require('./cypress/plugins/parse-email')

module.exports = defineConfig({
  chromeWebSecurity: false,
  e2e: {
    baseUrl: "https://dashboard.cypress.io",
    setupNodeEvents: async (on, config) =&amp;gt; {

      on('task', {
        async createTestEmail() {
          const testAccount = await createTestEmail()
          return testAccount
        },
        async getLastEmail({ user, pass }) {
          const get_Email = await getLastEmail(user, pass)
          return get_Email
        },
        async sendEmail({ user, pass, emailObject }) {
          const send_Email = await sendEmail(user, pass, emailObject)
          return send_Email
        },
        async parseEmail({ message }) {
          const parse_Email = await parseEmail(message)
          return parse_Email
        }
      })
    },
  },
});

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Write your tests... &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;spec.cy.js&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/// &amp;lt;reference types="cypress" /&amp;gt;
const { recurse } = require('cypress-recurse')

describe('Email confirmation', () =&amp;gt; {
  let userEmail
  let userPass

  beforeEach(() =&amp;gt; {
    recurse(
      () =&amp;gt; cy.task("createTestEmail"),
      Cypress._.isObject, // keep retrying until the task returns an object
      {
        log: true,
        timeout: 20000, // retry up to 20 seconds
        delay: 5000, // wait 5 seconds between attempts
        error: "Could not create test email"
      }
    ).then((testAccount) =&amp;gt; {
      userEmail = testAccount.user
      userPass = testAccount.pass
      cy.log(`Email account created - (for debugging purposes): ${userEmail}`)
      cy.log(`Email account password - (for debugging purposes): ${userPass}`)
    })
  })

  it('Fill in signup form and validate confirmation email is received', () =&amp;gt; {
    cy.visit("/signup/email").pause()
    cy.get("#email").type(userEmail)
    cy.get("[type=password]").type(userPass)
    cy.get("button[type=submit]").click()

    cy.log("**redirects to /confirm**")
    cy.location("pathname").should("equal", "/verify")

    // retry fetching the email
    recurse(
      () =&amp;gt; cy.task("getLastEmail", { user: userEmail, pass: userPass }), // Cypress commands to retry
      Cypress._.isObject, // keep retrying until the task returns an object
      {
        log: true,
        timeout: 30000, // retry up to 30 seconds
        delay: 5000, // wait 5 seconds between attempts
        error: "Messages Not Found"
      }
    ).then((message) =&amp;gt; {
      cy.task("parseEmail", { message })
        .its("html")
        .then((html) =&amp;gt; {
          cy.document().then(document =&amp;gt; {
            document.body.innerHTML = html;
          });
        })
    })

    cy.log("**Email message content validation**")
    cy.get("h1").should("contain","Activate your account")
    cy.get("a.link-button").should("contain","Verify Email")
  })

  it("Send an email with attachment and validate", () =&amp;gt; {
    let emailObject = {
      from: "'Fred Foo 👻' &amp;lt;foo@example.com&amp;gt;", // sender address
      to: "bar@example.com, baz@example.com", // list of receivers
      subject: "Hello ✔", // Subject line
      text: "Hello world?", // plain text body
      html: "&amp;lt;b&amp;gt;Hello world?&amp;lt;/b&amp;gt;", // html body
      attachments: [
        {
          filename: "hello.json",
          content: JSON.stringify({
            name: "Hello World!"
          })
        }
      ]
    }

    cy.task("sendEmail", { user: userEmail, pass: userPass, emailObject: emailObject }).then((response) =&amp;gt; {
      cy.log("The message id: " + response)
      Cypress.env("messageId", response)
    })

    recurse(
      () =&amp;gt; cy.task("getLastEmail", { user: userEmail, pass: userPass }), // Cypress commands to retry
      Cypress._.isObject, // keep retrying until the task returns an object
      {
        log: true,
        timeout: 30000, // retry up to 30 seconds
        delay: 5000, // wait 5 seconds between attempts
        error: "Messages Not Found"
      }
    ).then((message) =&amp;gt; {
      console.log("THE SOURCE")
      console.log(message)
      cy.task("parseEmail", { message: message })
        .its("attachments")
        .then((attachments) =&amp;gt; {
          expect(attachments[0].filename).to.eq("hello.json")
        })
    })
  })
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code of the test is commented to describe exactly what each part does.&lt;/p&gt;

&lt;p&gt;And that's it. Of course, the part related to "sending emails" from you app, you would need to do some work to create the ethereal account and have your app send the notification emails (the second scenario I mentioned at the beginning) to the ethereal account. Then fetch and validate&lt;/p&gt;

&lt;p&gt;Hope this is clear enough and helpful... (And remember to use the Cypress Dashboard signup page only a couple of times if you want, just to understand how the code works. Let's try not to flood our Cypress folks' server with registration requests)&lt;/p&gt;

&lt;p&gt;Cheers! and happy testing!&lt;/p&gt;

</description>
      <category>cypress</category>
      <category>email</category>
      <category>ethereal</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
