testing

Jak funguje End-to-End testování?

testing

Jak funguje End-to-End testování?

Jest a Puppeteer jsou velmi populární javascriptové frameworky, které se často využívají na End-to-End testování. Co je to a jaké jsou další známé typy testování?

 

Testování je nezbytnou součástí procesu vyvíjení softwaru. Může drasticky snížit cenu vašeho projektu a zvýšit produktivitu vašeho vývojářského týmu. Dnes rozlišujeme tyto tři hlavní druhy testů:

  • Unit testy – S unit testy testujeme malé izolované části našeho kódu.
  • Integrační testy představují způsob testování, ve kterém kombinujeme jednotlivé části našeho kódu, které pak testujeme společně jako jednu ucelenou skupinu.
  • End-to-End (E2E) testování je definováno jako testování kompletní funkcionality naší aplikace.

Při End-to-End testování se velmi často využívají již zmíněné nástroje Jest a Puppeteer:

  • Jest: Je plně vybavený testovací framework, který je vyvíjen produktů. Nepotřebuje v podstatě žádnou konfiguraci, čili funguje téměř ihned po instalaci.
  • Puppeteer: Knihovna pro Node.js vytvořená Googlem, která poskytuje praktickou API, pomocí které můžeme ovládat Headless Chrome.

Náš tutoriál vás naučí:

1. Jak integrovat Puppeteer a Jest

2. Testování formulářů

3. Testování frontendu

4. Jak udělat screenshot

5. Emulování mobilních zařízení

6. Jak zachytit requestu

7. Jak targetovat nově otevřenou stránku v headless prohlížeči

8. A jak debugovat vaše testy

Project Setup

Jako první krok si budete muset stáhnout projekt, který jsme připravili GitHub Starter Project. Pokud nemáte zájem programovat během tutoriálu, můžete si stáhnout finální projekt GitHub Final Project.

 

Postup po stažení projektu:

1.) cd do repozitáře

cd /E2E_Testing_with_Puppeteer_Starter

2.) install dependencies

npm install

3.) rozjeďte projekt

npm run dev-server

 

Výborně nyní naše aplikace běží na http://localhost:8080. Po otevření byste měli vidět něco takového:

testing puppeteer jest

 

Další věc, kterou musíme udělat, je nainstalovat všechny nezbytné nástroje.

npm install puppeteer jest jest-puppeteer

Také budeme potřebovat nainstalovat jest-cli, abychom mohli spouštět jeden test separátně od ostatních.

npm install -g jest-cli

První pohled na Puppeteer

Nejprve spustíme Puppeteer samostatně, abyste mohli vidět jak funguje bez Jest. V root directory projektu najdete puppeteer_firts_try.js soubor, který obsahuje základní instrukce pro Puppeteer.

Do terminálu zadejte:

node puppeteer_firts_try.js

V puppeteer_firts_try.js:

const puppeteer = require('puppeteer');
(async function main(){
  try {
    const browser = await puppeteer.launch({ headless: false });
    const page = await browser.newPage();
    await page.goto('http://localhost:8080', {waitUntil: 'domcontentloaded'});
    await new Promise(resolve => setTimeout(resolve, 5000));
    console.log('done'); await browser.close();
  }
  catch (e) {
    console.log('Err', e);
  }
})();

Jak můžete již pravděpodobně vidět Puppeteer spoléhá výhradně na promises, takže ho budeme vždy používat s async / Await. S puppeteer.launch() na řádku 5 spouštíme novou instanci Chromia, v argumentech specifikujeme headless: false, což znamená, že prohlížeč se nespustí v headless módu (v podstatě bez grafického uživatelského rozhraní). Na další řádek otevíráme novou stránku a potom na řádku 7 navigujeme na http://localhost:8080waitUntil: 'domcontentloaded' argument na řádku 7 specifikuje, že náš kód bude čekat dokud DOM content nebude načten. Řádek 9 jen způsobí, že aplikace se zastaví na 5 sekund, takže budete moci vidět, co se děje. A na řádku 11 zavíráme prohlížeč.

Integrace Puppeteer s Jest

Nyní integrujeme Puppeteer s Jest. Ale proč to vlastně potřebujeme? Děláme to, proto že Pupeteer sám o sobě není testovací framework, je to nástroj, který nám umožňuje kontrolovat Headless Chrome. Takže abychom si usnadnili naši práci, zkombinujeme Puppeteer s Jest, který nám poskytuje velmi užitečné testovací utilities.

 

Jest Konfigurace

Vytvořte jest.config.js soubor v root directory projektu a vložte tam tento kód:


module.exports = {
    preset: "jest-puppeteer",
    globals: {
      URL: "http://localhost:8080"
    },
    testMatch: [
      "**/test/**/*.test.js"
    ],
    verbose: true
}

Na řádku 2 specifikujemejest-puppeteer preset, který nám umožní použít Jest s Pupeteer. V globals deklarujeme proměnné, které budou dostupné ve všech testech. A v testMatch jednoduše říkáme, ve kterých složkách má Jest hledat soubory.

 

Konfigurace pre for jest-puppeteer preset

Vytvořte jest-puppeteer.config.js soubor v root directory našeho projektu a použijte tento kód:


module.exports = {
    launch: {
        headless: process.env.HEADLESS !== 'false',
        slowMo: process.env.SLOWMO ? process.env.SLOWMO : 0,
        devtools: true
    }
}

V launch objektu specifikujeme argumenty pro instanci Chromia, která bude spuštěna dříve než poběží naše testy a bude dostupná ve všech našich testovacích souborech, takže zde můžete zadat všechny argumenty, které byste normálně dali do puppeteer.launch(). Na řádku 3 specifikujeme, zda by měl Puppeteer spustit prohlížeč v headless módě nebo ne. A na řádku 4 mu říkáme, aby běžel v slowmo, což zpomalí Puppeteer o milisekundy, které specifikujeme, takže budeme moci pozorovat co vlastně dělá. Obě možnosti, které zde definujeme jsou skvělé pro ladění.

Psaní našich testů

Testing Frontend

Jelikož máme naše testovací prostředí připraveno, můžeme začít s psaním prvních testů. Bude lepší, pokud začneme s něčím jednoduchým. V src/test/ najdete soubor se jménem frontend.test.js, do kterého budete potřebovat vložit tento kód:


const timeout = process.env.SLOWMO ? 30000 : 10000;

beforeAll(async () => {
    await page.goto(URL, {waitUntil: 'domcontentloaded'});
});

describe('Test header and title of the page', () => {
    test('Title of the page', async () => {
        const title = await page.title();
        expect(title).toBe('E2E Puppeteer Testing');
        
    }, timeout);
});

A teď tak můžete zadat toto do vašeho terminálu:

npm run test

A měli byste vidět něco takového:

touch4it testovanie

 

Pojďme analyzovat tento kód po jednotlivých řádcích. Na prvním řádku nastavujeme timeout proměnnou, kterou později používáme na to, abychom specifikovali timeout pro naše testy (nezapomeňte, že specifikujeme tento timeout v milisekundách). Jak můžete vidět, pokud Puppeteer běží v slowmo, zvýšíme náš timeout z 10000 ms na 30000ms. Toto zajistí, že naše testy neselžou kvůli timeoutu. Na řádku 3 používáme beforeAll, tato funkce provede nějaký kód předtím než poběží všechny testy v tomto souboru. Této funkci dáváme jako parametr async callback, ve kterém navigujeme na URL, kterou jsme specifikovali předtím než globální proměnnou. Ale odkud jsme vzali page proměnnou? Ta je dostupná také ve všech testovacích souborech díky jest-puppeteer preset. Na řádku 7 používáme describe, které nám umožňuje seskupovat testy, a v něm už pak píšeme naše samotné testy. Tento test je docela jednoduchý, na řádku 9 získáváme title stránky a pak používáme Assertion knihovnu expect, která je vestavěná v Jest, na to abychom ověřili, zda jsme dostali správný výsledek.

 

Zkusme teď přidat další test do tďchto souboru. Vložte tento kód hned pod náš první test v describe bloku:


test('Header of the page', async () => {
        const h1Handle = await page.$('.learn_header');
        const html = await page.evaluate(h1Handle => h1Handle.innerHTML, h1Handle);

        expect(html).toBe("What will you learn");
    }, timeout);

Na druhém řádku používáme page.$() Funkci, Kotri nám umožňuje vybrat HTML element pomocí normálního CSS selektoru, a nakonec nám tato funkce vrátí ElementHandle, kterou později použijeme k tomu, abychom získali innerHTML tohoto elementu. Na řádku 3 pak používáme page.evaluate(), která vyhodnotí funkci v kontextu stránky, a tím pádem získáme přístup k innerHTML naše ElementHandle.

Form Tests

Nyní, když už máme nějaké základní testy za námi, zkusíme napsat test pro jednoduchý formulář, který jsme připravili:

touch4it testing form

 

Přejmenujte form.test.js.example v src/test na form.test.js a vložte tento kód do describe bloku, který tam už je:


test('Submit form with valid data', async () => {
        await page.click('[href="/login"]');
        
        await page.waitForSelector('form');
        await page.type('#name', 'Rick');

        await page.type('#password','szechuanSauce');
        await page.type('#repeat_password','szechuanSauce');

        await page.click('[type="submit"]');
        await page.waitForSelector('.success');
        const html = await page.$eval('.success', el => el.innerHTML);

        expect(html).toBe('Successfully signed up!');
    }, timeout);

 

První věc kterou zde děláme je taková, že klikneme na Login link v navigaci. Používáme na to page.click() funkci, která bere jeden argument CSS selektor. Jelikož jsme navigovat na jinou URL používáme page.waitForSelector(), na to abychom počkali, dokud DOM objeví náš formulář, takže budeme s ním moci interagovat. Potom používáme page.type() metodu, abychom vyplnil náš formulář. Tato metoda bere dva argumenty CSS selektor a text, který chceme napsat. Potom klikáme na submit button a čekáme až se objeví zpráva o úspěšném podání formuláře, kterou získáme pomocí page.$eval().

Pokud nyní zadáte npm run test, měli byste vidět tři úspěšně testy.

Braní screenshotů na mobilních a desktopových zařízeních

Formulář a frontend je otestován, takže můžeme obrátit naši pozornost na braní screenshotů a emulovaných mobilních zařízení.

Přejmenujte screenshots.test.js.example v src/test na screenshots.test.js a vložte tento kód do describe bloku, který tam už je:

test('Take screenshot of home page', async () => { await page.setViewport({ width: 1920, height: 1080 }); await page.screenshot({ path: './src/test/screenshots/home.jpg', fullpage: true, type: 'jpeg' }); }, timeout);

V tomto kódu nejprve nastavíme viewport naší stránky s page.setViewport() a potom uděláme screenshot s funkcí page.screenshot(), jejíž poskytujeme nějaké argumenty, abychom specifikovali kde a v jakém formátu uložit screenshot.

 

Poté, co zadáte npm run test, měli byste být schopni najít obrázek s názvem home.jpg v test/screenshots složce. Nyní zkusme udělat screenshot, během toho jak emulovat mobilní zařízení. Nejprve přidejte tento kód na vrch našeho souboru:

const devices = require('puppeteer/DeviceDescriptors');

 

A pak přidejte tento test do describe bloku:


test('Emulate Mobile Device And take screenshot', async () => {
    await page.goto(`${URL}/login`, {waitUntil: 'domcontentloaded'})
    const iPhonex = devices['iPhone X'];
    await page.emulate(iPhonex);
    await page.setViewport({ width: 375, height: 812, isMobile: true});
    await page.screenshot({
        path: './src/test/screenshots/home-mobile.jpg',
        fullpage: true,
        type: 'jpeg'
    });
}, timeout);

Tento kód je podobný tomu předchozímu, rozdíl je v tom, že nyní importujeme zařízení z puppeteer/DeviceDescriptors. Pak vybíráme IPhone X na řádku 3 z objektu devices. Na další řádek emulovat toto zařízení s page.emulate(). A pak jednoduše uděláme screenshot přesně stejným způsobem jako v předchozím testu.

Jak zachytit request a targetovanie nově otevřených stránek

Nyní se podíváme na trochu pokročilejší vlastnosti, které Puppeteer poskytuje, jako je například zachycování requestu. A také vám ukážeme, jak Targett nově otevřenou stránku v headless prohlížeči.

Úvodem přejmenujte general.test.js.example v src/test na general.test.js a zkopírujte tam tento kód:


   test('Intercept Request', async () => {
        await page.setRequestInterception(true);
        page.on('request', interceptedRequest => {
            if (interceptedRequest.url().endsWith('.png')) {
                interceptedRequest.abort();
            } else {
                interceptedRequest.continue();
            }
        });
        await page.reload({waitUntil: 'networkidle0'});
        // await jestPuppeteer.debug();
        await page.setRequestInterception(false);
    }, timeout);

 

Zde na prvním řádku nastavujeme page.setRequestInterception() na true, což nám umožňuje zachytit každý odcházející request. Na řádcích 3-9 mluvíme aby Puppeteer přerušil každou odcházející request, který konči s '.png'. Takže díky tomuto kódu se na stránce nenačte obrázek, který je na domovské stránce naší aplikace, vlastně se toto stane, až poté co znovu načteme stránku, protože obrázek již byl načten, předtím než jsme nastavili zachycování requestu. Potom na řádku 10 znovu načteme stránku page.reload(), takže budeme moci vidět, že se obrázek nezobrazuje. Ale jak vlastně, protože Puppeteer testy jsou tak rychlé? Na to využijeme kód na další řádek, který je momentálně zakomentovaný, ale k tomuto se vrátím až později v skecii o debugování. A na řádku 12 nastavujeme page.setRequestInterception() na false, což je velmi důležité! Protože kebyze to neuděláme tak by zachytávání requestu zůstalo zapnuté po zbytek našeho testování a to může způsobit mnoho problémů, a být velmi obtížné na ladění.

 

Přidejme nyní náš poslední test, se kterým vám ukážeme jak můžete Targett nově otevřené stránky v headless prohlížeči. Přidejte tento test do describe bloku:


   test('Target newly opened page', async () => {
        const newPagePromise = new Promise(resolve => browser.once('targetcreated', target => resolve(target.page())));
        await page.click('.repo_link');

        const newPage = await newPagePromise;
        const title = await newPage.title();
        await newPage.close();

        expect(title).toBe('GitHub - Zovi343/E2E_Testing_with_Puppeteer_Final');
    }, timeout);

 

Na řádku 2 vytváříme nový Promise, ve kterém posloucháme s browser.on('targetcreated'), zda nový target (page) je nebo není vytvořen. Znovu máme přístup k globální proměnné browser, díky jest-puppeteer preset. Potom klikáme na link na naší domovské stránce, který otevírá novou stránku, konkrétně: GitHub Starter Project. Na 7 řádku očekáváme Promise, který jsme vytvořili na řádku 2 a tento Promise vrací nově otevřenou stránku. Takže v závěru jsme schopni získat title stránky a učinit naše assertions.

Debugování vašich testů

Mnohdy budou vaše testy selhávat a často je velmi obtížné takové testy debugovat a zjistit, co se vlastně děje pouze z terminálu. To je hlavní důvod, proč vám chceme ukázat různé metody, pomocí kterých můžete debugovat vaše testy:

 

Headless a SlowMo argumenty

Takže pro ladění budete chtít spustit Puppeteer v headless módě a také ho zpomalit, takže budete schopni vidět, co se vlastně děje. Jelikož jsme nastavili tyto dvě možnosti v jest-puppeteer.config.js, vše co teď musíme udělat je poskytnout dvě environmentální proměnné, když spouštíte testy z terminálu.

Do terminálu zadejte:

HEADLESS="false" SLOWMO=100 npm run test

 

Running single Test Seperately

Někdy potřebujete rozjet pouze jeden test, aniž se spouštěly další. Abychom mohli toto udělat použijeme jest-cli, kterou jsme nainstalovali na začátku. Vraťme se nyní zpět k zachycování requestu, protože to jsme předtím nebyli zcela schopni vidět a pozorovat.

Do terminálu zadejte:

HEADLESS="false" SLOWMO=100 jest -t 'Intercept Request'

Sice jsme už teď mohli vidět, že se obrázek nezobrazil (přesně jak jsme předpokládali), avšak bylo to stále docela rychlé, ne? Pojďme to napravit s jestPuppeteer.debug().

 

jestPuppeteer.debug()

jestPuppeteer.debug() zastaví naše testy, abychom mohli zjistit, co se děje v prohlížeči. Abyste znovu spustili testy, musíte stisknout Enter. Takže nyní můžete odkomentovat řádek 11 z testu o zachycování requestu a zadat předchozí příkaz do terminálu. A budete moci jasně vidět, že obrázek není zobrazen na domovské stránce, protože request něj byla zachycena a přerušena.

Bonus Puppeteer Recorder

Nakonec bychom vám rádi doporučil jednu Chrome extension, která se vám může sejít, když píšete testy s Puppeteer. Jmenuje se Puppeteer Recorder a umožňuje vám nahrávat vaše interakce s prohlížečem a následně vygenerovat z toho Puppeteer script.

Závěr

V tomto článku jsme se zabývali dvěma velmi populárními Framework - Jest a Puppeteer. Naučili jsme se, že když tyto dva nástroje zkombinujeme, získáme velmi robustní testovací prostředí. Naučili jste se jako integrovat Puppeteer a Jest, jak psát testy pro různé příležitosti, jako debugovat vaše testy a mnoho dalšího.