Stack-Agnostic End-to-End Testing with Jest and Puppeteer @ 18F

2e055eb589fb86174fd268748b0fcd30?s=47 Andrew Dunkman
January 28, 2019
31

Stack-Agnostic End-to-End Testing with Jest and Puppeteer @ 18F

For web applications, why does our testing stack change when moving from language to language, when our users are using the same browsers to access them? The Chrome team has done quite a bit of work to make testing in a real browser within reach — and this talk covers how to use it for stable and fast end-to-end tests, executable in any environment.

2e055eb589fb86174fd268748b0fcd30?s=128

Andrew Dunkman

January 28, 2019
Tweet

Transcript

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

  2. 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
  3. 1. What are those!? Stack-Agnostic End-to-End Testing with Jest and

    Puppeteer
  4. 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
  5. agnostic: a person who claims neither faith nor disbelief in

    God. Stack-agnostic 1. What are those!?
  6. 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!?
  7. 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!?
  8. 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!?
  9. “… 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!?
  10. 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!?
  11. 2. Why Jest and Puppeteer? Stack-Agnostic End-to-End Testing with Jest

    and Puppeteer
  12. 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
  13. Test runner output 2. Why Jest and Puppeteer?

  14. Test failure output 2. Why Jest and Puppeteer?

  15. 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?
  16. 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)
  17. 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?
  18. 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?
  19. • 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?
  20. 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
  21. 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?
  22. Captured screenshot 2. Why Jest and Puppeteer?

  23. 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?
  24. 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?
  25. const dimensions = await page.$eval('html', el => { return {

    width: el.clientWidth, height: el.clientHeight }; }); Accessing page properties (better!) 2. Why Jest and Puppeteer?
  26. 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?
  27. 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?
  28. 3. Using Jest with Puppeteer Stack-Agnostic End-to-End Testing with Jest

    and Puppeteer
  29. 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
  30. 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
  31. // 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
  32. 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
  33. Thank you! Stack-Agnostic End-to-End Testing with Jest and Puppeteer Andrew

    Dunkman
  34. Appendix Stack-Agnostic End-to-End Testing with Jest and Puppeteer

  35. 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
  36. 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