ScreenshotNeo

BlogHow-to

How to Automate React Applications with Selenium

Automate a React app with Selenium WebDriver in JavaScript. Set up a browser session, synchronize with rendered UI changes, and troubleshoot flaky tests.

By the ScreenshotNeo team4 October 20268 min read

Automate a React application with Selenium by driving a real browser through WebDriver, locating controls in the rendered DOM, and waiting for the specific UI state each action should produce. In a Node.js project, install selenium-webdriver, create a browser session, navigate to your app, interact with user-visible elements, assert the result, and always close the session.

The key React-specific challenge is timing: Selenium’s navigation wait does not guarantee that client-side rendering or a later UI update has finished. Use explicit waits for observable conditions such as a result becoming visible. The examples below use JavaScript, Chrome, and Node.js; selectors and expected results must match your application.

1. Install Selenium and prepare the app

Start your React app using its normal development or test-server command, then keep it running while the Selenium script runs. For example, if it serves at http://localhost:3000, use that URL in the script. Your app should expose stable, user-facing controls and outcomes for the test to locate.

npm install selenium-webdriver

Selenium’s JavaScript API documentation currently specifies Node.js 22 or newer. Check the official API page for current runtime and browser setup details before adopting these steps; package and runtime requirements can change.

Selenium WebDriver drives browsers through browser automation APIs and can run locally or against a remote machine. React’s client APIs render the interface into a browser DOM node. Selenium tests should therefore exercise the rendered interface rather than depend on React component internals. See the React client DOM APIs and Selenium’s WebDriver overview.

2. Write a browser test around the rendered UI

This runnable script expects the app to have a button matching [data-testid="save"] and a status element matching [role="status"] that becomes visible after saving. Change those selectors and the assertion to match the actual interface. The example uses Node’s built-in assertion module and Selenium’s explicit wait.

// save-test.js
const assert = require('node:assert/strict');
const { Builder, Browser, By, until } = require('selenium-webdriver');

async function main() {
  const driver = await new Builder().forBrowser(Browser.CHROME).build();

  try {
    await driver.get('http://localhost:3000');

    const saveButton = await driver.findElement(By.css('[data-testid="save"]'));
    await saveButton.click();

    const status = await driver.wait(
      until.elementLocated(By.css('[role="status"]')),
      10000,
      'Save status did not appear'
    );
    await driver.wait(until.elementIsVisible(status), 5000, 'Save status is not visible');

    const message = await status.getText();
    assert.match(message, /saved/i, `Unexpected save status: ${message}`);
  } finally {
    await driver.quit();
  }
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

Run it with node save-test.js. Selenium’s JavaScript quick start demonstrates the same basic lifecycle: build a session, navigate, inspect the page, and quit in a finally block. Closing the session in finally also runs if a locator, wait, or assertion fails.

Choose selectors that reflect the app’s interface

Use a locator that identifies the control or outcome the test cares about. A test ID such as data-testid can be a practical project convention, but Selenium and React do not require it. CSS selectors, accessible roles and labels, and other locators are application-specific. Prefer selectors that remain meaningful when layout or styling changes; avoid depending on generated class names if those are unstable.

Locate elements after navigation or after the relevant transition. If the app replaces an element during a render, an element reference obtained before that render may become stale. Locate it again after the transition.

3. Synchronize with React updates

A successful driver.get() does not mean every React-driven update is complete. Selenium’s navigation strategy waits for a document readyState (by default, complete), but JavaScript can continue changing the page afterward. A button click may trigger a request, render, route change, or conditional component; wait for the outcome needed by the next step.

Situation Useful condition to wait for
Content is added after a request The result element is located and visible
Submitting a form A success message appears or the error message appears
A control is initially hidden The control becomes visible or enabled before clicking
A route changes A route-specific heading or landmark appears
A list refreshes The expected item appears, disappears, or has updated text

For example, to wait until a submit button is enabled before clicking:

const submit = await driver.findElement(By.css('button[type="submit"]'));
await driver.wait(until.elementIsEnabled(submit), 5000, 'Submit button stayed disabled');
await submit.click();

Timeouts should reflect the application and the CI environment; there is no universal correct value. Include a message that names the missing condition so a timeout points to the failed transition. Selenium’s explicit waits poll a condition until it succeeds or the timeout expires, and its JavaScript API supports customizing timeout, polling interval, ignored exceptions, and timeout message.

Prefer explicit waits over fixed sleeps

A fixed delay such as await driver.sleep(2000) always spends that time even when the app is ready sooner, and can still be too short on a slower run. A condition-specific explicit wait proceeds as soon as the needed state appears.

Selenium also offers implicit waits, which set a global element-location wait. Keep synchronization easy to reason about: use explicit waits for the transition being tested and do not combine implicit and explicit waits. Selenium warns that mixing them can produce unpredictable timing. See the official waiting strategies guide.

4. Run locally or use a remote browser

Local execution is a good starting point for development feedback. Selenium’s JavaScript bindings use Builder to select a browser, and the current API page documents Selenium Manager’s browser-driver setup behavior. Keep browser and package versions aligned with the current Selenium documentation.

When a team needs execution on other machines or platform combinations, Selenium Grid provides remote execution across machines and platforms. Selenium’s JavaScript API also documents connecting to a remote server and reading the remote URL from SELENIUM_REMOTE_URL. Remote setup is not required for a first local script.

// Remote session example; set SELENIUM_REMOTE_URL to your Selenium server URL.
const { Builder, Browser } = require('selenium-webdriver');

const remoteUrl = process.env.SELENIUM_REMOTE_URL;
if (!remoteUrl) throw new Error('Set SELENIUM_REMOTE_URL to the Selenium server URL');

const driver = await new Builder()
  .forBrowser(Browser.CHROME)
  .usingServer(remoteUrl)
  .build();

try {
  await driver.get('http://localhost:3000');
  console.log(await driver.getTitle());
} finally {
  await driver.quit();
}

For a remote browser, ensure the app URL is reachable from the machine running that browser. A localhost address on your test runner may refer to a different machine from the Grid node. The Selenium overview describes Grid’s role; the documentation does not establish general cost or speed advantages over local runs.

5. Troubleshoot common failures

Symptom Likely cause Fix
Cannot find module 'selenium-webdriver' The package is missing from this project or the script runs from another directory. Run npm install selenium-webdriver in the project and execute the script there.
Node version or package installation error The active Node runtime does not meet the current package requirement. Check the current Selenium JavaScript API page and use a supported Node release.
Browser session fails to start The browser is unavailable, incompatible, or its driver setup failed. Confirm the selected browser is installed and consult current Selenium Manager and browser setup instructions.
ECONNREFUSED on navigation The React server is stopped or the URL/port is wrong. Start the app, verify its listening address, and use the URL reachable from the browser host.
NoSuchElementError immediately after navigation or a click The app has not rendered the element yet, or the selector does not match the current DOM. Check the selector in the browser DOM and wait for the expected element or state.
Wait timeout The expected state never arrived, took longer than the chosen timeout, or the test is waiting for the wrong condition. Report the condition in the wait message; inspect app errors, network behavior, selector, and expected state before adjusting the timeout.
Stale element reference A React update replaced the DOM node after Selenium found it. After the update, locate the element again and wait for the new state.
Test passes locally but fails remotely The remote machine may not reach the app URL, or its browser/platform differs. Use a URL reachable from the remote browser and check the remote browser configuration and logs.
Test hangs after failure The session was not closed on every path. Put driver.quit() in a finally block.

6. Keep tests reliable and efficient

  • Wait for the state the next command depends on, rather than waiting for a generic delay.
  • Keep each test focused on a user-visible flow and its outcome.
  • Use clear failure messages and selectors that match the current interface.
  • Close every browser session, including after assertion failures.
  • Develop locally, then use remote execution when machine or platform coverage calls for it.
  • When a test flakes, identify whether the cause is app behavior, an incorrect selector, a missing wait, or the browser environment before increasing timeouts.

Explicit waits can avoid unnecessary fixed delay while remaining bounded by a timeout. Local versus Grid execution depends on where browsers must run and what platform combinations are needed; the cited Selenium documentation does not provide comparative performance or cost figures. Selenium is browser automation and entails browser-session setup and maintenance. For a static visual capture, a screenshot API can avoid managing a browser session in the script.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. A single GET request can return a PNG, JPEG, WebP, or PDF. For current parameters, see the ScreenshotNeo API documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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}`);

ScreenshotNeo accepts cookie or 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 or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response identifies the page verdict and billing status in headers. Its MCP server offers take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The free plan includes 1,000 shots per month without a card; paid plans start at $5 for 3,000 shots. Every feature is on every plan.

Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.

FAQ

Does Selenium need access to React components?

No. Selenium drives the browser and interacts with the rendered DOM. This makes it appropriate for checking user-visible behavior; component-level tests are a separate kind of test.

Does Selenium wait for all React rendering after navigation?

No. The document ready state does not guarantee that later JavaScript-driven UI changes have completed. Wait for the specific result your test needs.

Do I need Selenium Grid to start?

No. Start with a local browser session. Grid is relevant when tests need remote machines or broader platform coverage.

Official references