Is the Usps Running Again in Cypress

Back to Cypress web log

In this weblog mail service, we will use a local SMTP server to receive emails sent past the app to the user. We will test the HTML emails to make sure they look and work correctly.

Note: you can find the source code shown in this blog mail service at cypress-email-example and watch a video explaining the testing process hither.

Sending emails

If you lot want to transport an email from a Node program, I would propose using the  nodemailer module. Here is an example script to send an email via a local SMTP server

          // ship.js const nodemailer = require('nodemailer')  // async..expect is non allowed in global scope, must utilise a wrapper async part main() {   // create reusable transporter object using the default SMTP transport   // the settings could come from .env file or environment variables   const transporter = nodemailer.createTransport({     host: 'localhost',     port: 7777,     secure: false, // truthful for 465, false for other ports   })    // send an electronic mail   const info = await transporter.sendMail({     from: '"Fred Blogger" <[email protected]>',     to: '[email protected]', // list of receivers     subject: 'Howdy ✔', // Subject line     text: 'Hello earth?', // plain text torso     html: '<b>Hello world?</b>', // html body   })    console.log('Message sent: %s', info.messageId) }  chief().catch(panel.error)        

In the production system the host and the port would be your organization's production SMTP server.  Simply when testing locally, let's assume the e-mail server is running at localhost:7777. Nosotros tin can brand the email-sending code reusable:

          // emailer.js const nodemailer = crave('nodemailer')  // create reusable transporter object using the default SMTP transport // the settings could come from .env file or environment variables const host = process.env.SMTP_HOST || 'localhost' const port = Number(process.env.SMTP_PORT || 7777)  const transporter = nodemailer.createTransport({   host,   port,   secure: port === 456, })  module.exports = transporter                  

Any part of our application can thus crave the above module and use information technology to send an email:

          const transporter = require('./emailer') wait transporter.sendMail({   from: '"Fred Blogger" <[email protected]>',   to: '[email protected]', // listing of receivers   field of study: 'Howdy ✔', // Subject line   text: 'How-do-you-do globe?', // apparently text body   html: '<b>Hello world?</b>', // html body })        

Bully, now let's receive an email.

Receiving emails

During testing, we want to use a local STMP server that would give united states access to the received emails. A simple implementation can be found in the smtp-tester NPM module. Permit'south create the server and print every incoming message:

          // mail-server.js const ms = require('smtp-tester') const port = 7777 const mailServer = ms.init(port) console.log('mail server at port %d', port)  // process all emails mailServer.bind((addr, id, email) => {   panel.log('--- email ---')   panel.log(addr, id, e-mail) })        

Showtime the server and then execute the send.js script

          $ node ./send.js  Message sent: <[email protected]>        

The mail server prints the detailed e-mail data

          $ node ./demo.js  mail service server at port 7777 --- email --- null 1 {   sender: '[email protected]',   receivers: { '[email protected]': true },   data: 'Content-Type: multipart/alternative;\r\n' +     ' boundary="--_NmP-7a4c358b72f79393-Part_1"\r\due north' +     'From: Fred Blogger <[email protected]>\r\n' +     'To: [email protected]\r\n' +     'Subject: =?UTF-8?Q?Hello_=E2=9C=94?=\r\due north' +     'Message-ID: <[email protected]>\r\north' +     'Engagement: Wed, 05 May 2021 fourteen:30:35 +0000\r\due north' +     'MIME-Version: i.0\r\due north' +     '\r\n' +     '----_NmP-7a4c358b72f79393-Part_1\r\n' +     'Content-Blazon: text/manifestly; charset=utf-viii\r\n' +     'Content-Transfer-Encoding: 7bit\r\n' +     '\r\n' +     'How-do-you-do world?\r\n' +     '----_NmP-7a4c358b72f79393-Part_1\r\n' +     'Content-Type: text/html; charset=utf-viii\r\north' +     'Content-Transfer-Encoding: 7bit\r\n' +     '\r\n' +     '<b>Hello globe?</b>\r\northward' +     '----_NmP-7a4c358b72f79393-Part_1--',   headers: {     'content-blazon': { value: 'multipart/alternative', params: [Object] },     from: 'Fred Blogger <[electronic mail protected]>',     to: '[e-mail protected]',     subject: 'Hello ✔',     'bulletin-id': '<[email protected]>',     engagement: 2021-05-05T14:30:35.000Z,     'mime-version': 'ane.0'   },   body: 'Hello world?',   html: '<b>Hello globe?</b>',   attachments: [] }        

The received electronic mail contains both the plain text and the "rich" HTML bodies we have sent.

The application

Imagine y'all have an awarding where the user enters their email and the application emails the confirmation code to the user.

The registration page

The user must enter the sent code into the "ostend" page.

The confirmation page expects the emailed code

In our instance, the application receives the registration grade inputs from the front-end as a JSON object. The backend role uses the e-mail utility we wrote above to ship the actual (hardcoded for at present) confirmation code.

          // pages/api/register.js const emailer = require('../../emailer')  consign default async (req, res) => {   if (req.method === 'Mail') {     const { name, email, companySize } = req.torso     // render to the caller right away     res.status(200).json({ proper name, email })      // and then send an email     const info = look emailer.sendMail({       from: '"Registration system" <[email protected]>',       to: electronic mail,       subject: 'Confirmation code 1️⃣2️⃣3️⃣',       text: 'Your confirmation code is 654agc',       html: 'Your confirmation code is 654agc',     })     console.log('sent a confirmation e-mail to %south', e-mail)      return   }    render res.condition(404)        

The confirmation folio can call the backend with the user-submitted lawmaking, or in my demo it simply verifies the input against the expected string "654agc".

The confirmation page shows success message if the code is valid

Confirmation page test

Every bit the first step, let'southward confirm the above folio works - it should show an error message for an invalid lawmaking, and a success message for the valid one. Our Cypress test can be this:

          // cypress/integration/confirm-spec.js /// <reference types="cypress" />  describe('Confirmation page', () => {   it('rejects invalid code', () => {     cy.visit('/confirm')     cy.become('#confirmation_code').type('wrongcode')     cy.go('button[type=submit]').click()     cy.get('[data-cy=incorrect-code]').should('be.visible')     cy.get('[information-cy=confirmed-code]').should('not.exist')      cy.log('**enter the correct code**')     cy.become('#confirmation_code').clear().blazon('654agc')     cy.become('push button[type=submit]').click()     cy.become('[data-cy=incorrect-code]').should('not.be')     cy.get('[information-cy=confirmed-lawmaking]').should('be.visible')   }) })                  

The examination passes

The Cypress exam for the confirmation folio

The STMP server inside Cypress

During testing nosotros want to receive the electronic mail the application is sending. Thus we demand access to the SMTP server receiving the emails - Cypress tin spawn such server using the smtp-tester module right from its plugin file! The plugin file runs in Node, thus it can bind to the local socket, listen for the incoming SMTP messages - yet be accessible from the test via cy.task control.

          // cypress/plugins/alphabetize.js /// <reference types="cypress" /> const ms = require('smtp-tester')  /**  * @type {Cypress.PluginConfig}  */ module.exports = (on, config) => {   // starts the SMTP server at localhost:7777   const port = 7777   const mailServer = ms.init(port)   panel.log('post server at port %d', port)    // process all emails   mailServer.bind((addr, id, email) => {     panel.log('--- email ---')     console.log(addr, id, email)   }) }        

The SMTP server will start when we run Cypress. Now we can write a test to fill the registration folio class and submit it - we should run into the email arrive.

          // cypress/integration/spec.js // enables intelligent lawmaking completion for Cypress commands // https://on.cypress.io/intelligent-code-completion /// <reference types="cypress" />  describe('E-mail confirmation', () => {   it('sends an electronic mail', () => {     cy.visit('/')     cy.get('#proper name').type('Joe Bravo')     cy.get('#e-mail').type('[email protected]')     cy.get('#company_size').select('iii')      cy.intercept('POST', '/api/register').as('register')     cy.get('button[type=submit]').click()      cy.log('**redirects to /confirm**')     cy.location('pathname').should('equal', '/confirm')      cy.log('**register API call**')     cy.wait('@register').its('request.body').should('deep.equal', {       name: 'Joe Bravo',       email: '[email protected]',       companySize: 'three',     })     // in one case we have waited for the ajax phone call once,     // we tin immediately get it once more using cy.become(<alias>)     cy.go('@register').its('response.body').should('deep.equal', {       // the response from the API should only       // include proper name and email       name: 'Joe Bravo',       electronic mail: '[electronic mail protected]',     })   }) })        

The above exam:

  • visits the registration folio
  • fills the registration class: proper noun, email, company size
  • clicks the submit button and checks the application ends upwardly at the /confirm page
  • spies on the network telephone call using cy.intercept command to brand sure the application does send the registration information to the backend
The registration test shows the SMTP server receives the sent email from the app

Utilize the text e-mail in the exam

You can see from the final letters that the SMTP server running within the Cypress plugin procedure receives the registration email with the confirmation code. Allow's get this email text and use it to continue our test - we need to enter this lawmaking on the confirmation page. We tin store the last email'south text for each user in an object inside the plugin file:

          // cypress/plugins/alphabetize.js module.exports = (on, config) => {   // starts the SMTP server at localhost:7777   const port = 7777   const mailServer = ms.init(port)   console.log('mail service server at port %d', port)    // [receiver email]: email text   let lastEmail = {}    // process all emails   mailServer.bind((addr, id, email) => {     console.log('--- email to %due south ---', email.headers.to)     panel.log(electronic mail.body)     console.log('--- end ---')     // shop the email by the receiver electronic mail     lastEmail[email.headers.to] = email.html || e-mail.body   }) }        

This works, merely nosotros likewise need to laissez passer the final email to the test when needed. Permit's add a job:

          // cypress/plugins/alphabetize.js module.exports = (on, config) => {   ...   // [receiver email]: electronic mail text   allow lastEmail = {}        on('task', {     getLastEmail(email) {       // cy.job cannot return undefined       // thus we render null as a fallback       return lastEmail[e-mail] || null     },   }) }        

We can also add a chore to delete all emails, since we want to start from a make clean slate before each test:

          // cypress/plugins/index.js module.exports = (on, config) => {   ...   on('task', {     resetEmails(email) {       console.log('reset all emails')       if (e-mail) {         delete lastEmail[email]       } else {         lastEmail = {}       }       return nil     },          getLastEmail(email) {       // cy.task cannot render undefined       // thus we return cipher equally a fallback       return lastEmail[email] || cypher     },   }) }        

Let's add together the following commands to our test to catch the email the server should receive:

          // cypress/integration/spec.js ...  // once nosotros accept waited for the ajax call once, // we can immediately become information technology again using cy.become(<alias>) cy.go('@register').its('response.body').should('deep.equal', {   // the response from the API should only   // include name and electronic mail   proper noun: 'Joe Bravo',   e-mail: '[e-mail protected]', })  // by now the SMTP server has probably received the email cy.chore('getLastEmail', '[email protected]').then((email) => {   cy.log(electronic mail) })        

We log the received electronic mail examination in the Command Log to observe

The received email text

Tip: we assumed the server has received the expected email past the fourth dimension we called cy.task('getLastEmail', '[email protected]'). Of course, due to the async nature of the application flow, it is non guaranteed. If the above test is flaky due to the delay in the email arrival, use cypress-recurse to retry the task command until the email arrives (or the exam times out).

          // telephone call the task every second for up to 20 seconds // until information technology returns a cord result recurse(   () => cy.job('getLastEmail', '[email protected]'),   Cypress._.isString,   {     log: false,     filibuster: 1000,     timeout: 20000,   }, ).then(electronic mail => ...)        

Inbound the confirmation code

Let'southward parse the received email text and enter the code into the confirmation page.

          // cypress/integration/spec.js ... // by now the SMTP server has probably received the email cy.job('getLastEmail', '[email protected]')   .then(cy.wrap)   // Tip: modernistic browsers supports named groups   .invoke('lucifer', /code is (?<code>\w+)/)   // the confirmation code   .its('groups.code')   .should('be.a', 'cord')   .then((code) => {     cy.get('#confirmation_code').type(code)     cy.become('button[type=submit]').click()     cy.get('[data-cy=incorrect-lawmaking]').should('not.be')     cy.become('[information-cy=confirmed-code]').should('be.visible')   })        

By wrapping the value yielded from the cy.task using the cy.wrap, we print the value to the Control Log without using an extra cy.log command. The entire test shows that our application is emailing the confirmation code that works.

The code from the email works

Sending HTML emails

In the previous examination we have confirmed the registration flow using the apparently text email. But we can ship and test a rich HTML email also. Let's create a stylish HTML email and confirm that the users with HTML email clients can meet and use the confirmation code.

Writing impenetrable cantankerous-platform HTML emails is hard, thus I used Maizzle to produce a confirmation email that should work on most email clients. The built HTML file emails/confirmation-lawmaking.html looks nice, in my humble opinion:

Confirmation HTML electronic mail

The "Ostend registration" link points at localhost:3000/confirm URL. Our registration API function can send both the manifestly and the rich HTML versions.

          // pages/api/register.js const confirmationEmailPath = join(   procedure.cwd(), // should be the root folder of the project   'emails',   'confirmation-code.html', ) const confirmationEmailHTML = readFileSync(confirmationEmailPath, 'utf-8')  ...  const info = await emailer.sendMail({   from: '"Registration system" <[email protected]>',   to: email,   discipline: 'Confirmation code 1️⃣2️⃣3️⃣',   text: 'Your confirmation code is 654agc',   html: confirmationEmailHTML, })        

Now that nosotros are sending both the plain and the rich HTML emails, let'due south examination the HTML version.

Testing HTML emails

On the SMTP side, we should store both the plain and the HTML text

          // cypress/plugins/index.js let lastEmail = {}  // process all emails mailServer.bind((addr, id, electronic mail) => {   console.log('--- email to %south ---', e-mail.headers.to)   console.log(e-mail.body)   console.log('--- end ---')   // store the email by the receiver email   lastEmail[email.headers.to] = {     body: email.torso,     html: email.html,   } })        

We tin can update the manifestly email test to grab but the body belongings from the object at present returned by the getLastEmail chore

          // cypress/integration/spec.js // past now the SMTP server has probably received the email cy.task('getLastEmail', '[email protected]')   .its('body') // cheque the plain email text   .and then(cy.wrap)   // Tip: modern browsers supports named groups   .invoke('match', /code is (?<lawmaking>\west+)/)        

To test the HTML email, I will clone the full exam we wrote to test the plainly email version. We tin can always refactor the common exam lawmaking later. The test is almost identical right now:

          // cypress/integration/spec.js it('sends an HTML e-mail', () => {   cy.visit('/')   cy.get('#name').type('Joe Bravo')   cy.get('#e-mail').type('[e-mail protected]')   cy.get('#company_size').select('3')    cy.intercept('Post', '/api/annals').as('register')   cy.get('button[blazon=submit]').click()    cy.log('**redirects to /confirm**')   cy.location('pathname').should('equal', '/confirm')    cy.log('**register API telephone call**')   cy.wait('@register').its('request.body').should('deep.equal', {     proper noun: 'Joe Bravo',     electronic mail: '[e-mail protected]',     companySize: '3',   })   // once we have waited for the ajax telephone call one time,   // nosotros tin can immediately get information technology again using cy.get(<alias>)   cy.go('@register').its('response.torso').should('deep.equal', {     // the response from the API should just     // include name and e-mail     proper name: 'Joe Bravo',     email: '[email protected]',   })    // by at present the SMTP server has probably received the email   cy.task('getLastEmail', '[email protected]')     .its('html') // check the HTML email text     // what practise we practice at present? })        

At present we take to bank check the HTML email. But rather than simply search its text, why don't we really test it? Subsequently all, the HTML electronic mail is meant to exist displayed by a browser ... and Cypress is testing inside ane. We can load the received HTML in place of the site!

          // by now the SMTP server has probably received the email cy.task('getLastEmail', '[electronic mail protected]')   .its('html') // check the HTML email text   // what do we practise now?   .then((html) => {     cy.document().invoke('write', html)   })  // by now the HTML e-mail should be displayed in the app iframe // permit'southward ostend the confirmation code and the link cy.contains('654agc')   .should('be.visible')   // I have added small await to make sure the video shows the email   // otherwise it passes way too chop-chop!   .expect(2000) cy.contains('Ostend registration').click() cy.location('pathname').should('equal', '/confirm') // we take already tested the confirmation page in other tests        

The test confirms the code is displayed in the email, and the test actually clicks on the "Ostend ..." link, and checks that the folio /confirm loads.

The complete HTML examination flow

My favorite part most the above examination is the time-traveling debugging experience. I tin see the received email, the confirmation lawmaking it contains, the link clicked - merely past hovering over the test commands.

Hover over the test commands to see test steps

Tip: if you are recording the results to Cypress Dashboard you can apply cy.screenshot commands at major test steps to record the images and make them easily shareable with the squad.

          cy.screenshot('i-registration') cy.become('button[type=submit]').click() ... cy.screenshot('2-the-electronic mail') cy.contains('654agc')   .should('be.visible') ... cy.get('[data-cy=incorrect-lawmaking]').should('not.exist') cy.get('[information-cy=confirmed-code]').should('be.visible') cy.screenshot('3-confirmed')        
The screenshots show the email and the app

What to test side by side

We wrote tests for the manifestly and rich HTML emails. I would extend the HTML e-mail tests to make sure the emails are attainable, work across multiple viewports, even bring the visual testing plugins to make certain the emails never take manner problems.

This post showed how to use the low-level SMTP utilities to ship and receive emails. If yous are using third party services to transport emails, like Mailosaur, SendGrid, or others, and then see if a Cypress plugin exists that lets the Test Runner admission the received email.

Equally e'er, happy testing!

johnsonshre1956.blogspot.com

Source: https://www.cypress.io/blog/2021/05/11/testing-html-emails-using-cypress/

0 Response to "Is the Usps Running Again in Cypress"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel