Testing javascript applications with Selenium, Async/Await, and Jest
Tags: node, javascript, testing, actionherojs
Published: 2019-11-03 18:18:31

*Originally [posted](https://blog.evantahler.com/testing-javascript-applications-with-selenium-async-await-and-jest-7580ed074f2b) Mar 27, 2018*

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](https://www.actionherojs.com/), 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](https://github.com/request/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](https://jestjs.io/). 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](https://github.com/alexeyraspopov/jest-webdriver?source=post_page-----7580ed074f2b----------------------)… 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:


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

# if you don't have homebrew: https://brew.sh/
brew 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:


```js
/**
 * @jest-environment jest-environment-webdriver
 */
```

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](https://github.com/alexeyraspopov/jest-webdriver/tree/master/packages/jest-environment-webdriver)), 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*
```js
/**
 * @jest-environment jest-environment-webdriver
 */

const url = 'https://www.actionherojs.com'

describe('www.actionherojs.com#index', () => {
  test('it renders', async () => {
    await browser.get(url)
    const title = await browser.findElement(by.tagName('h2')).getText()
    expect(title).toContain('reusable, scalable, and quick')
  })

  test('loads the latest version number from GitHub', async () => {
    const foundAndLoadedCheck = async () => {
      await until.elementLocated(by.id('latestRelease'))
      const value = await browser.findElement(by.id('latestRelease')).getText()
      return value !== '~'
    }

    await browser.wait(foundAndLoadedCheck, 3000)
    const latestRelease = await browser.findElement(by.id('latestRelease')).getText()
    expect(latestRelease).toEqual('v18.1.3')
  })

  describe('save a screenshot from the browser', () => {
    test('save a picture', async () => {
      // files saved in ./reports/screenshots by default
      await browser.get(url)
      await browser.takeScreenshot()
    })
  })
})
```

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

```
jest __tests__/integration/simple.js
 PASS  __tests__/integration/simple.js
  www.actionherojs.com#index
    ✓ it renders (770ms)
    ✓ loads the latest version number from GitHub (267ms)
    save a screenshot from the browser
      ✓ save a picture (784ms)
Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        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](https://selenium.dev/selenium/docs/api/javascript/)). 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!