Testing javascript applications with Selenium, Async/Await, and Jest

javascript node.js 
↞ See all posts

The last time I used Selenium, in 2015, I hated it. It was slow, brittle, and difficult to get working. These days, it can actually be pleasant!

Recently, in the ActionHero project, we found that we really needed a "full browser" integration test… something that we couldn’t mock or accomplish with even a robust tool like request. We needed to ensure that our HTTP and WebSocket libraries properly shared session & fingerprint information, which required cookies, headers, and 2 "full" protocols in the test… so we needed a real browser :/

We recently switched ActionHero’s test suite from mocha to Jest. Jest is an awesome test framework for javascript projects (and react, and other things that compile to javascript). It supports parallel testing, watching & retrying, mocking, snapshotting… all the tools I was missing coming from Rails, the gold-standard for TDD frameworks. It turns out that some wonderful person has already done the heavy lifting to make a full-featured integration between Selenium and Jest… and it’s actually simple to use!


What follows is a step-by-step guide to writing a "full-browser" test in Jest on OSX, complete with saving off photos of the page.

First, you’ll need to install a few things into your node.js project:

1npm install --save-dev jest jest-environment-webdriver

if you don't have homebrew: https://brew.sh/

1brew install chromedriver

chromedriver is a version of the Chrome browser which is able to be "machine controlled" by selenium in our tests. Note that we do not need to install anything else like the selenium server.

Jest already has support for multiple "renderers". This is how it handles testing compiled-to-javascript files, like JSX. This means that we can signal to Jest in a given test file that it should use selenium. Jest uses magic comments for this:

1/** 2 * @jest-environment jest-environment-webdriver 3 */

The default is to use chromedriver, which is what we’ll be using, but you can also test with Firefox, Safari, and other browsers. Using jest-environment-webdriver means that we get a few new global variables we can use in our tests, specifically browser, until, and by(full list here), which we will use in our test.

From here on out, you can use normal Jest commands to start your server in before blocks, configure whatever you need… and control your browser in the test. We can continue to use the normal Jest/Jasmine assertions. In this example, we’ll be testing www.actionherojs.com for a few things, but you’ll probably be testing localhost.

File Location: __tests__/integration/test.js

1/** 2 * @jest-environment jest-environment-webdriver 3 */ 4 5const url = "https://www.actionherojs.com"; 6 7describe("www.actionherojs.com#index", () => { 8 test("it renders", async () => { 9 await browser.get(url); 10 const title = await browser.findElement(by.tagName("h2")).getText(); 11 expect(title).toContain("reusable, scalable, and quick"); 12 }); 13 14 test("loads the latest version number from GitHub", async () => { 15 const foundAndLoadedCheck = async () => { 16 await until.elementLocated(by.id("latestRelease")); 17 const value = await browser.findElement(by.id("latestRelease")).getText(); 18 return value !== "~"; 19 }; 20 21 await browser.wait(foundAndLoadedCheck, 3000); 22 const latestRelease = await browser 23 .findElement(by.id("latestRelease")) 24 .getText(); 25 expect(latestRelease).toEqual("v18.1.3"); 26 }); 27 28 describe("save a screenshot from the browser", () => { 29 test("save a picture", async () => { 30 // files saved in ./reports/screenshots by default 31 await browser.get(url); 32 await browser.takeScreenshot(); 33 }); 34 }); 35});

Your test can now be run via the normal jest command*.* That’s it!

1jest __tests__/integration/simple.js 2 PASS __tests__/integration/simple.js 3 www.actionherojs.com#index 4 ✓ it renders (770ms) 5 ✓ loads the latest version number from GitHub (267ms) 6 save a screenshot from the browser 7 ✓ save a picture (784ms) 8Test Suites: 1 passed, 1 total 9Tests: 3 passed, 3 total 10Snapshots: 0 total 11Time: 3.204s, estimated 6s

Note that there is no need to start or stop the chromedriver or selenium server (this handled for you).

Selenium is very powerful (full api docs here). You can type input, scroll the page, get and set cookies, etc. If you do find that you need a "full" integration test, this is a very painless way to do it!

Hi, I'm Evan

I write about Technology, Software, and Startups. I use my Product Management, Software Engineering, and Leadership skills to build teams that create world-class digital products.

Get in touch