Síla dvou kontextů cypress.io
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 doresolve
callbacku. Skrz argumentoptions
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.