Slide 1

Slide 1 text

Stack-Agnostic End-to-End Testing with Jest and Puppeteer Andrew Dunkman

Slide 2

Slide 2 text

1 2 3 What is… all of that? Why use Jest, and why use Puppeteer? Using Jest and Puppeteer together. Outline Stack-Agnostic End-to-End Testing with Jest and Puppeteer

Slide 3

Slide 3 text

1. What are those!? Stack-Agnostic End-to-End Testing with Jest and Puppeteer

Slide 4

Slide 4 text

1 2 3 What are those!? Stack-Agnostic End-to-End Testing with Jest and Puppeteer Stack-Agnostic End-to-End Testing Jest and Puppeteer

Slide 5

Slide 5 text

agnostic: a person who claims neither faith nor disbelief in God. Stack-agnostic 1. What are those!?

Slide 6

Slide 6 text

GREEK gnōstikos GREEK gnōstos known LATIN gignōskein know ENGLISH gnostic late 16th century ENGLISH agnostic mid 19th century ENGLISH a- not’ Stack-agnostic 1. What are those!?

Slide 7

Slide 7 text

stack-agnostic: denoting or relating to hardware or software that is compatible with many types of platforms or operating systems. (without knowledge of the platform) Stack-agnostic 1. What are those!?

Slide 8

Slide 8 text

Tests the integration of system components, including the interaction between frontend and backend code. From human to electrons and back. End-to-end test 1. What are those!?

Slide 9

Slide 9 text

“… when engineers are provided with ready-to-use tools, they end up writing more tests, which in turn results in more stable and healthy code bases.” Includes assertion tooling (expect) and mocking (faked functions) without configuration. Ready-to-use, familiar Node.js testing framework supported by Facebook. Jest 1. What are those!?

Slide 10

Slide 10 text

Thin Node.js wrapper around headless chromium, supported by Google. Chromium is the base of Chrome (and Microsoft Edge soon). Uses DevTools protocol (no extra code executing in browser environment). Use JavaScript to control a real, invisible browser. Puppeteer 1. What are those!?

Slide 11

Slide 11 text

2. Why Jest and Puppeteer? Stack-Agnostic End-to-End Testing with Jest and Puppeteer

Slide 12

Slide 12 text

1 2 3 4 5 Why Jest (Node.js test runner)? 2. Why Jest and Puppeteer? Automatic parallelization Console messages buffered Sandboxed with global state reset Supported by a major organization Lifecycle hooks

Slide 13

Slide 13 text

Test runner output 2. Why Jest and Puppeteer?

Slide 14

Slide 14 text

Test failure output 2. Why Jest and Puppeteer?

Slide 15

Slide 15 text

test('using async/await for Promises', async () => { const results = await thing.fetch(); expect(results).toEqual({}); }); Using the async keyword for test execution 2. Why Jest and Puppeteer?

Slide 16

Slide 16 text

expect().toBe(1) expect().toEqual({}) expect().toBeNull() expect().toBeUndefined() expect().toBeTruthy() expect().toBeGreaterThan(1) expect().toBeGreaterThanOrEqualTo(1) expect(0.2 + 0.2 + 0.2).toBeCloseTo(0.6, 2) Using expectations 2. Why Jest and Puppeteer? expect('hello').toMatch(/hello/) expect([]).toContain('hello') expect(fn).toHaveBeenCalled() expect(fn).toHaveBeenCalledWith() expect(fn).toThrow(err)

Slide 17

Slide 17 text

All expectations can be negated by using expect().not. expect(4).not.toBe(1) expect(1).not.toBeGreaterThan(6) Negating expectations 2. Why Jest and Puppeteer?

Slide 18

Slide 18 text

Custom assertions expect() can be extended with custom assertions. expect.extend({ toBeBear(obj) { return { message: () => `expected ${obj} to have pr…`, pass: obj.bearable === 'yes', }; } }); 2. Why Jest and Puppeteer?

Slide 19

Slide 19 text

● Built-in support for faking setTimeout and setInterval. ● Code coverage reports. ● Mock functions with jest.fn(() => {}). ● Mocked dependencies with jest.mock('module'). ● Repeated test setup and teardown with beforeEach and afterEach. ● Scoped tests with describe(). Other Jest features not covered 2. Why Jest and Puppeteer?

Slide 20

Slide 20 text

1 2 3 4 5 Why Puppeteer (programmable browser)? 2. Why Jest and Puppeteer? It just works Up-to-date Real browser environment without hacks Supported by a major organization Fast enough to have a slow motion mode

Slide 21

Slide 21 text

const puppeteer = require('puppeteer'); (async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('https://www.dunkman.me'); await page.screenshot({path: 'dunkman.me.png'}); await browser.close(); })(); The browser just works 2. Why Jest and Puppeteer?

Slide 22

Slide 22 text

Captured screenshot 2. Why Jest and Puppeteer?

Slide 23

Slide 23 text

page.$('#main-content'); page.$$('p'); page.evaluate(() => document.title); page.$eval('#search', el => el.value); page.$$eval('input', els => els.map(el => el.value)); Inspecting page elements 2. Why Jest and Puppeteer?

Slide 24

Slide 24 text

const dimensions = await page.evaluate(() => { const el = document.querySelector('html'); return { width: el.clientWidth, height: el.clientHeight }; }); Accessing page properties 2. Why Jest and Puppeteer?

Slide 25

Slide 25 text

const dimensions = await page.$eval('html', el => { return { width: el.clientWidth, height: el.clientHeight }; }); Accessing page properties (better!) 2. Why Jest and Puppeteer?

Slide 26

Slide 26 text

page.click('#my-button'); page.tap('#my-button'); page.hover('#my-button'); page.focus('#my-input'); page.type('#my-input', 'hello there'); page.select('select#colors', 'red'); Interacting with the page 2. Why Jest and Puppeteer?

Slide 27

Slide 27 text

Be sure to wait for events before initiating those events — which can be counter-intuitive. await Promise.all([ page.waitForNavigation(), page.click(link) ]); Avoid race condition when navigating 2. Why Jest and Puppeteer?

Slide 28

Slide 28 text

3. Using Jest with Puppeteer Stack-Agnostic End-to-End Testing with Jest and Puppeteer

Slide 29

Slide 29 text

Install jest, puppeteer, and the lifecycle hooks to boot the browser when jest starts using npm. npm install --save-dev jest jest-puppeteer puppeteer Installation 2. Using Jest with Puppeteer

Slide 30

Slide 30 text

Configure Jest to call into jest-puppeteer when starting by adding configuration to the package.json. { "name": "jest-puppeteer-example", "devDependencies": {...}, "jest": { "preset": "jest-puppeteer" } } Configuration 2. Using Jest with Puppeteer

Slide 31

Slide 31 text

// mytest.test.js test('i installed it correctly', async () => { await page.goto('https://www.example.com'); const heading = await page .$eval('h1', h1 => h1.innerText); expect(heading).toBe("Example Domain"); }); Writing your first test 2. Using Jest with Puppeteer

Slide 32

Slide 32 text

Debugging tips ● Disable headless mode with { headless: false, devtools: true }. ● Increase Jest’s timeout with jest.setTimeout(100000); ● Pause execution with await jestPuppeteer.debug(); 2. Using Jest with Puppeteer

Slide 33

Slide 33 text

Thank you! Stack-Agnostic End-to-End Testing with Jest and Puppeteer Andrew Dunkman

Slide 34

Slide 34 text

Appendix Stack-Agnostic End-to-End Testing with Jest and Puppeteer

Slide 35

Slide 35 text

Replacing setTimeout and setInterval with fake timers helps keep tests fast and useful. jest.useFakeTimers(); test('waits 1 second', () => { … expect(setTimeout).toHaveBeenCalledTimes(1); expect(setTimeout).toHaveBeenLastCalledWith( expect.any(Function), 1000); }); Fake timers Appendix

Slide 36

Slide 36 text

When using fake timers, time is not advanced. Instead, advance by a number of milliseconds or run all timers. jest.advanceTimersByTime(1000); jest.runAllTimers(); If your timer functions schedule timer functions, running all timers may not be practical. Instead, only run timers currently pending. jest.runOnlyPendingTimers(); Fake timers Appendix