ScreenshotNeo

BlogGuides

Jest Tutorial for Selenium JavaScript Testing

Use Jest to organize and assert Selenium browser tests in JavaScript. Install the tools, automate a form, manage sessions, and troubleshoot common failures.

By the ScreenshotNeo team4 October 20269 min read

Jest runs and checks the tests; Selenium launches and controls the browser. Use Jest’s test cases, assertions, and lifecycle hooks around asynchronous commands from the selenium-webdriver package. This tutorial builds a working form test, explains session cleanup and configuration, and covers common setup failures.

1. How Jest and Selenium work together

Jest is the test runner: it discovers test files, executes test functions, provides assertions such as expect, and runs setup and cleanup hooks. Selenium WebDriver is the browser automation layer: it opens a browser, navigates to pages, finds elements, and interacts with them. Selenium’s documentation lists Jest as an option for organizing Selenium tests; its JavaScript walkthrough uses Mocha, so the Jest wiring below is the tutorial’s integration pattern.

Selenium describes WebDriver as browser automation that can run locally or on a remote machine. For a local test, Selenium’s JavaScript binding uses Selenium Manager to handle browser driver installation in the documented default workflow. You generally do not need to download and configure a driver executable manually.

2. Requirements and installation

  1. Install a supported Node.js version. The current Selenium JavaScript API page lists Node.js 22 as the minimum and Node 22, 24, and 26 as supported release lines, with support-end dates shown on that page. Treat its table as the current compatibility guidance, not a permanent guarantee.
  2. Use an existing JavaScript project or create one with npm init -y.
  3. Install Jest and Selenium WebDriver:
npm install --save-dev jest
npm install selenium-webdriver

Jest is installed as a development dependency. The Selenium binding can be classified according to your project’s packaging conventions; the Selenium API page documents npm install selenium-webdriver.

Add a test script to package.json:

{
  "scripts": {
    "test": "jest"
  }
}

Keep any existing package fields and scripts when editing your project’s file. Jest’s getting-started guide uses a package script to invoke the test runner.

3. Write a complete browser test

Create web-form.test.js in your project. This CommonJS example opens the Selenium demonstration form, enters text, submits it, checks the result, and closes the browser after the suite.

const { Builder, Browser, By } = require('selenium-webdriver');

describe('web form', () => {
  let driver;

  beforeAll(async () => {
    driver = await new Builder().forBrowser(Browser.CHROME).build();
  });

  afterAll(async () => {
    if (driver) {
      await driver.quit();
    }
  });

  test('submits a value and displays the response', async () => {
    await driver.get('https://www.selenium.dev/selenium/web/web-form.html');
    await driver.findElement(By.name('my-text')).sendKeys('Selenium');
    await driver.findElement(By.css('button')).click();

    const message = await driver.findElement(By.id('message')).getText();
    expect(message).toBe('Received!');
  });
});

Run it with:

npm test -- --runInBand

--runInBand runs tests in one process, which can make an initial browser test easier to diagnose and avoids parallel suites competing for local browser resources. Once your suite is stable, decide whether Jest’s normal worker behavior suits your tests and environment.

The code follows the documented Selenium first-script flow and Jest’s asynchronous test pattern. Selenium commands return promises, so await navigation, element interactions, and reads before making assertions. Assert something observable on the page rather than only asserting that a command did not throw.

4. Choose browser session scope

The example creates one browser session per test file using beforeAll and closes it in afterAll. This reduces browser startup work when a file contains several related tests, but those tests share browser state: cookies, local storage, open tabs, and current page can carry over.

Pattern Use it when Tradeoff
beforeAll / afterAll Tests intentionally share a session or setup is expensive. Tests may depend on order or leave state for later tests.
beforeEach / afterEach Each test should start from a fresh browser session. Repeated browser startup can increase suite time and resource use.

For per-test isolation, move browser creation and cleanup into these hooks:

let driver;

beforeEach(async () => {
  driver = await new Builder().forBrowser(Browser.CHROME).build();
});

afterEach(async () => {
  if (driver) {
    await driver.quit();
    driver = undefined;
  }
});

Choose one lifecycle model for a given driver. Do not quit a shared driver after each test if the suite expects to reuse it. In either model, cleanup must run after failures; Jest awaits asynchronous hooks, and the guard handles a session that failed to initialize.

5. Locators, waits, and reliable assertions

Locate the element you intend to test

Selenium supports locator strategies such as names, CSS selectors, and IDs. Prefer selectors tied to stable application attributes or accessible semantics when your application controls the markup. Broad selectors such as button can become ambiguous as a page changes. If a locator finds no element or the wrong one, inspect the rendered page and narrow the selector.

Await every browser operation

Navigation, finding elements, typing, clicking, and reading values are asynchronous. Missing await can make an assertion run before the browser has completed the preceding action. Awaiting a click only means the click command completed; if the application updates later, wait for the resulting condition rather than assuming the update is immediate.

Use an explicit wait when an element or state appears asynchronously. Selenium’s JavaScript API includes wait facilities; keep the condition tied to the page behavior under test and choose a timeout appropriate to your application and environment. Avoid arbitrary sleeps as the default: they can waste time on fast runs and still be too short on slow ones.

const { until } = require('selenium-webdriver');

await driver.wait(until.elementLocated(By.id('message')), 10000);
const message = await driver.findElement(By.id('message')).getText();
expect(message).toBe('Received!');

This waits for the element to be located. If your application renders the element before its text is updated, wait for the relevant text or application state as well. A timeout should fail the test clearly rather than letting a later lookup fail with less context.

6. Run and debug the suite

  1. Check node --version against the Selenium API’s current supported versions.
  2. Confirm dependencies installed successfully with npm ls jest selenium-webdriver.
  3. Run one file: npx jest web-form.test.js --runInBand.
  4. Confirm the requested browser is installed and can start in the environment.
  5. Inspect the failing locator against the actual page structure.
  6. Check each navigation, interaction, and read for a missing await; add an explicit wait for delayed page state.

For headless or remote execution, configure the browser and Selenium environment for that target. The local example deliberately uses Selenium’s default local browser setup; browser-specific arguments and remote endpoints depend on your execution environment.

7. Configuration choices and edge cases

  • CommonJS and ES modules: The sample uses require, matching Selenium’s quick-start style. If your project uses ES modules, use the import syntax and Jest configuration already established for that project.
  • Browser choice: Browser.CHROME selects Chrome. Use a browser supported by your installed Selenium binding and available in the environment. Browser availability and configuration can differ across developer machines and CI.
  • Local and remote browsers: WebDriver can control a local browser or a browser on a remote machine. Remote execution requires the appropriate remote Selenium endpoint and capabilities for your infrastructure; the local Builder example does not configure those.
  • Test state: Tests that share a driver can inherit navigation, cookies, and form state. Reset state deliberately or create a fresh session where isolation matters.
  • Failures during startup: If build() rejects, the driver variable remains unset and the guarded cleanup does not call quit(). Report setup failures separately from page assertion failures when diagnosing a suite.
  • Slow or unavailable pages: Navigation and element operations can fail when the page is unreachable, slow, or changed. Verify the target URL and use waits that reflect the expected behavior.
  • Test timeouts: Browser startup and remote environments can take longer than ordinary unit tests. If Jest’s timeout is reached, identify which browser operation is stalled before increasing the limit globally.
  • Parallel tests: Multiple sessions consume browser and machine resources. Start with serial execution when debugging, then increase concurrency only if the environment supports it and tests do not share mutable state.

8. Common errors and fixes

Symptom Likely cause Fix
SyntaxError around require or import The sample module syntax does not match the project configuration. Use the syntax and module configuration already used by your project; do not mix CommonJS and ES modules unintentionally.
Jest reports no tests found The file name or location does not match Jest’s discovery pattern, or the wrong path was supplied. Use a conventional name such as web-form.test.js and run Jest with that file path.
Browser session cannot start Unsupported Node.js version, unavailable browser, or environment-specific browser startup issue. Check the current Selenium Node support table, install or enable the target browser, and inspect the session startup error before changing driver setup.
Element cannot be located Wrong locator, page not yet in the expected state, or page markup changed. Inspect the live DOM, correct the locator, and wait for delayed content when needed.
Assertion sees empty or stale text The assertion ran before the page finished updating, or it read a different element. Await all commands and wait for the expected UI state before reading and asserting.
Jest exits while browser remains open Cleanup hook is missing, not asynchronous, or does not await driver.quit(). Use afterAll or afterEach, declare it async, and await quit() with a guard for failed setup.
Intermittent failures under parallel execution Resource contention or state shared across tests. Try --runInBand, isolate browser sessions, and remove dependence on test order.

9. Performance, reliability, and cost

Browser tests are heavier than ordinary unit tests because they start and control a real browser. Reduce unnecessary sessions where tests can safely share setup, but preserve isolation for tests whose results depend on clean state. Avoid repeated fixed delays; waiting for the condition under test can keep fast runs moving while allowing slower page updates.

Reliability comes from deterministic fixtures, stable locators, explicit waits for asynchronous UI, observable assertions, and guaranteed session cleanup. External websites can change or become unavailable, so use a controlled application fixture for a production test suite when possible. The Selenium demonstration page is useful for learning the mechanics, not a guarantee that an external dependency will remain unchanged.

Jest and Selenium are open-source tools installed through npm. Running local browser tests has no per-screenshot API charge, though CI, browser infrastructure, and engineering time may have their own costs. Selenium supports remote browser execution, which lets teams run tests on browser infrastructure they manage or provide.

10. Or skip the browser setup

If your task is to capture a page image for a visual check, report, or agent workflow, you can use ScreenshotNeo’s website screenshot API instead of wiring up a Selenium browser session. It is a screenshot service, not a replacement for interactive end-to-end assertions like the form test above.

One GET request returns an image or PDF. This cURL example saves a WebP screenshot:

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://stripe.com \
  -o shot.webp

See the ScreenshotNeo API documentation for the request options. Equivalent Python and Node.js examples:

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://stripe.com'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
const bytes = new Uint8Array(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));

ScreenshotNeo accepts cookie and consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, with the result identified in X-Page-Verdict and X-Billed headers. Its MCP server gives Claude, Cursor, and other MCP clients take_screenshot, get_page_info, and capture_pdf tools. Plans include 1,000 screenshots per month free with no card, then $5 for 3,000 on Starter; yearly billing gives two months free, and every feature is on every plan.

Sign up for ScreenshotNeo’s free 1,000 screenshots per month, with no card required.

11. Frequently asked questions

Does Jest replace Selenium WebDriver?

No. Jest runs tests and checks results; Selenium WebDriver controls the browser.

Does Selenium require manually downloading a driver?

Selenium Manager handles browser driver installation in the documented default setup. Environment-specific browser setup may still be needed.

Can I use Jest with Selenium in CI?

Yes. Configure a supported Node.js runtime and make the target browser available in the CI environment, then run the same Jest command used locally.

Should every test get a new browser?

Use a fresh session when isolation is more important than startup cost. Share a session only when the tests are designed to share state and clean it deliberately.

Official references