Na webu yachtingu máme netradiční tří krokovou registraci, která sice ihned v prvním kroku vytvoří uživatelský účet, ale ten není aktivní, dokud uživatel neklikne na odkaz v emailu a nevyplní další údaje, například heslo.

Jde vlastně o ten samý problém, jak automaticky testovat funkci “zapomenuté heslo”. Uživatel na webu vyplní email, na který mu dojde typicky jednorázový odkaz na obnovu hesla - stránku s formulářem, kam zadá heslo nové.

Jenže jak ten krok s emailem otestovat, jde to vůbec? A jak moc náročné to bude?

Mohli jsme jít tou nejjednodušší variantou a nechat naši registraci na, v lepším případě, manuálním testování a mít jednoho testovacího uživatele, kterého použijeme k testování zbytku. Jenže některé akce v našem systému mohou být provedeny pouze párkrát a vzhledem k tomu, že tyhle testy běží několikrát denně a samozřejmě mohou kdykoliv selhat a nechat tak účet v náhodném stavu, tak se tahle vcelku jednoduchá cesta začíná komplikovat - uvést účet do čistého úvodního stavu v našem případě není nejjednodušší, navíc to přidává nechtěnou komplexitu, čas potřebný k exekuci...

Druhou variantou je žádný email pro vybrané, testovací uživatele vůbec neposílat a rovnou jim vytvořit aktivní účet s nějakým výchozím heslem. Tohle bohužel také není správné řešení, protože zavádět výjimku jenom pro potřeby testu je špatná věc, vytvářející falešný pocit bezpečí.

Další a také “tou” variantou, kterou jsme se vydali my, je pozorování emailové schránky a reagování na nově přijaté zprávy.

Cypress běží v nodu, node ovládá browser a v browseru běží testy. Díky nově přidanému příkazu cy.task můžete mezi těmito dvěma kontexty přepínat. To znamená, že z browseru se kdykoliv můžete přepnout do nodu, prakticky cokoliv asynchronně pustit, výsledek si poslat zpět do prohlížeče a pokračovat v exekuci testů:

// cypress/integration/example.test.js

it('sends email with link and then continues to that link', () => {
  cy.visit('http://localhost:3000/')
  cy.task('mail', {}, { timeout: 60 * 1000 })
    .then((link) => cy.visit(link))
  cy.get('div').contains('welcome')
})

Důležitý je řádek s cy.task, který “pozastaví” exekuci v browseru, přepne do prostředí nodu a tam něco vykoná. V našem případě se připojíme do emailu a začneme naslouchat na všechny nově příchozí, nepřečtené zprávy (většinou se na mail čeká 10-12 vteřin):

// cypress/support/mail-watch.js

const notifier = require('mail-notifier')

const watch = (options, timeout) =>
  new Promise((resolve, reject) => {
    const imap = {
      user: process.env.MAIL_USER,
      password: process.env.MAIL_PASS,
      host: process.env.MAIL_HOST,
      port: 993,
      tls: true,
      tlsOptions: { rejectUnauthorized: false },
    }
    const n = notifier(imap)
    n.on('mail', (mail) => {
      resolve(mail)
      n.stop()
    })
    n.on('error', reject)
    n.start()

    setTimeout(() => {
      n.stop()
      reject(new Error('Email not received (timeout)'))
    }, timeout)
  })

module.exports = watch

TIP: V opravdovém testu se on('mail') událost může vyvolat několikrát a je tak dobré kód doplnit o kontrolu například předmětu nebo odesílatele/příjemce a teprve vyhovující zprávu poslat do resolve callbacku. Skrz argument options lze poslat cokoliv, co jde serializovat do JSONu.

Aby to celé fungovalo, musíme zaregistrovat task se jménem mail:

// cypress/plugins/index.js

const watch = require('../support/mail-watch')

module.exports = (on, config) => {
  on('task', {
    mail: async (options) => {
      const { text } = await watch(options, 50 * 1000)
      return text
    },
  })
}

TIP: V reálném kódu bysme registrovali více popisné a hlavně specializované tasky, např. watchForForgottenPasswordEmail. Stejně tak obsah emailů je typicky v html a kromě linku samotného obsahuje další informace. My k získání linku používáme cheerio.

Závěr

S volbou cypressu jakožto hlavního nástroje pro automatizované testování našeho webu jsem stále nesmírně spokojen. Jednoduchá integrace, interaktivní vývoj, dobrá dokumentace a stabilní exekuce jsou jedny z mnoha předností, které na cypressu oceňuji a pravidelně nám usnadňují život.