Stack-Agnostic End-to-End Testing with Jest and Puppeteer @ Kansas City Developer Conference

Stack-Agnostic End-to-End Testing with Jest and Puppeteer @ Kansas City Developer Conference

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

July 19, 2019
Tweet

Transcript

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

  2. Outline 1 What are those? 2 Why Jest, and why

    Puppeteer? 3 The union of Jest and Puppeteer.
  3. 1 What are those?

  4. 1 What are those? End-to-End Testing Stack- Agnostic Jest and

    Puppeteer
  5. 1 What are those? agnostic: a person who claims neither

    faith nor disbelief in god Stack-Agnostic
  6. 1 What are those? Stack-Agnostic gnōstos gnōstikos gignōskein gnostic a-

    agnostic ENGLISH ENGLISH ENGLISH LATIN GREEK GREEK known know not’ late 16th century mid 19th century
  7. 1 What are those? 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
  8. 1 What are those? end-to-end test: tests the integration of

    system components, including the interaction between frontend and backend code. (from humans to electrons and back) End-to-End Testing
  9. 1 What are those? End-to-End Testing

  10. 1 What are those? “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.” Jest and Puppeteer
  11. 1 What are those? Ready-to-use, familiar testing framework running in

    Node.js, supported by Facebook. Includes assertion tooling (expect) and mocking (faked functions) without configuration. Jest and Puppeteer
  12. 1 What are those? Thin Node.js wrapper around headless chromium,

    supported by Google. Chromium is the base of Chrome (and soon to be Edge). Uses DevTools protocol (no extra code executing in browser environment). Use JavaScript to control a real, invisible browser. Jest and Puppeteer
  13. 1 What are those?

  14. Outline 1 What are those? 2 Why Jest, and why

    Puppeteer? 3 The union of Jest and Puppeteer.
  15. 2 Why Jest, and why Puppeteer?

  16. 2 Why Jest, and why Puppeteer? ✓ Automatic parallelization ✓

    Console messages buffered ✓ Sandboxed with global state reset ✓ Supported by a major organization ✓ Lifecycle hooks
  17. 2 Why Jest, and why Puppeteer?

  18. 2 Why Jest, and why Puppeteer?

  19. 2 Why Jest, and why Puppeteer?

  20. 2 Why Jest, and why Puppeteer? test('using async/await for promises',

    async () => { const results = await thing.fetch(); expect(results).toEqual({}); });
  21. 2 Why Jest, and why Puppeteer? 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); expect('hello').toMatch(/hello/); expect([]).toContain('hello'); expect(fn).toHaveBeenCalled(); expect(fn).toHaveBeenCalledWith(args); expect(fn).toThrow(error);
  22. 2 Why Jest, and why Puppeteer? 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); expect('hello').toMatch(/hello/); expect([]).toContain('hello'); expect(fn).toHaveBeenCalled(); expect(fn).toHaveBeenCalledWith(args); expect(fn).toThrow(error);
  23. 2 Why Jest, and why Puppeteer? All expectations can be

    negated using expect().not expect(4).not.toBe(1); expect(1).not.toBeGreaterThan(6);
  24. 2 Why Jest, and why Puppeteer? expect() can be extended

    with custom assertions. expect.extend({ toBeBear(obj) { return { message: () => `expected ${obj} to have pr…`, pass: obj.bearable === 'yes', }; } });
  25. 2 Why Jest, and why Puppeteer? ✓ Built-in support for

    faking setTimeout() and setInterval(). ✓ Mock functions with jest.fn(() => {}). ✓ Mocked dependencies with jest.mock('module'). ✓ Repeated test setup and teardown with beforeEach() and afterEach(). ✓ Scoped tests with describe().
  26. 2 Why Jest, and why 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
  27. 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(); })(); 2 Why Jest, and why Puppeteer?
  28. 2 Why Jest, and why Puppeteer?

  29. Inspecting page elements is similar to the DevTools console. page.$('#main-content');

    page.$$('p'); page.evaluate(() => document.title); page.$eval('#search', el => el.value); page.$$eval('input', els => els.map(el => el.value)); 2 Why Jest, and why Puppeteer?
  30. Accessing page properties can be done via page.$eval(). const dimensions

    = await page.$eval('html', el => { return { width: el.clientWidth, height: el.clientHeight, }; }); 2 Why Jest, and why Puppeteer?
  31. Interacting with the page is simple. page.click('#my-button'); page.tap('#my-button'); page.hover('#my-button'); page.focus('#my-input');

    page.type('#my-input', 'ICE was founded in 2003, abolish it'); page.select('select#colors', 'red'); 2 Why Jest, and why Puppeteer?
  32. Waiting for the browser is straightforward with await and avoids

    race conditions. await Promise.all([ page.waitForNavigation(), page.click(link), ]); 2 Why Jest, and why Puppeteer?
  33. 2 Why Jest, and why Puppeteer?

  34. Outline 1 What are those? 2 Why Jest, and why

    Puppeteer? 3 The union of Jest and Puppeteer.
  35. 3 The union of Jest and Puppeteer.

  36. 3 The union of Jest and Puppeteer. Install jest, puppeteer,

    and the lifecycle hooks to boot the browser when jest starts npm install --save-dev jest jest-puppeteer puppeteer
  37. 3 The union of Jest and Puppeteer. Configure Jest to

    load jest-puppeteer when starting by adding to package.json: { "name": "jest-puppeteer-example", "devDependencies": {...}, "jest": { "preset": "jest-puppeteer" } }
  38. 3 The union of Jest and Puppeteer. Writing your first

    test: // 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'); })
  39. 3 The union of Jest and Puppeteer. Let’s take a

    closer look.
  40. 3 The union of Jest and Puppeteer. Launch the browser

    visibly, increase the test runner timeout, or pause execution to aid in debugging. {headless: false, devtools: true} jest.setTimeout(100000); await jestPuppeteer.debug();
  41. 3 The union of Jest and Puppeteer.

  42. Outline 1 What are those? 2 Why Jest, and why

    Puppeteer? 3 The union of Jest and Puppeteer.
  43. Slides are available at dunkman.me/talks Have a thought or question?

    Come up front to chat in a small group.