How to use Selenium in NodeJS (A Detailed Guide)

Selenium in Node.js helps teams build JavaScript-based browser tests. Learn how to set up the project, common commands, waits, and debugging techniques.

Written by Sarthak Sharma Sarthak Sharma
Reviewed by Vinayak Mirani Vinayak Mirani
Last updated: 6 August 2026 19 min read

Key Takeaways

  • Selenium with Node.js combines JavaScript and WebDriver to automate end-to-end, regression, cross-browser testing, and dynamic page validation.
  • Use explicit waits and consistent async/await handling to prevent timing issues, stale interactions, and commands running before the browser is ready.
  • Keep tests focused, isolate test data, use stable locators and page objects, and capture failure evidence to maintain a dependable suite.

Selenium lets you control a browser through code, while Node.js gives you a familiar JavaScript environment for building and running those scripts. Used together, they can support tasks such as UI testing, regression testing, form automation, and browser-based data collection.

The basic setup is simple, but reliable test automation depends on more than opening a page and clicking an element. You need to choose stable locators, wait for dynamic content correctly, manage browser sessions, and structure tests so they remain easy to maintain as the application grows.

By the end of this guide, you will understand how to set up Selenium with Node.js, write your first browser test, work with elements and waits, connect Selenium with test frameworks, and avoid common issues that make tests slow or flaky.

Key use cases for Selenium in Node.js

Selenium with Node.js is a good fit when you need to control a real browser through JavaScript. It can open pages, find elements, enter data, click controls, and verify what appears after each action. This makes it useful for several browser-based testing and automation tasks.

Selenium with Node.js is useful when you need to control a browser through JavaScript. It can open pages, locate elements, enter data, click controls, and verify the result of each action. These capabilities support several browser-based testing and automation use cases.

1. End-to-End Testing

You can use Selenium with Node.js for end-to-end testing of critical workflows such as login, checkout, account creation, and order management.

End-to-end testing checks whether the frontend, backend, browser, and connected services work together as expected. It is most useful for workflows where a failure could directly affect users, transactions, or business operations.

2. Regression Testing

Selenium also supports regression testing after code changes, dependency updates, or new releases. You can rerun important browser tests to confirm that existing features still work as expected.

For example, a change to the authentication service might affect login, account settings, and checkout. Regression testing helps identify these unexpected effects before the application reaches production.

3. Cross-Browser Testing

You can use Selenium for cross-browser testing across Chrome, Firefox, Edge, Safari, and other supported browsers. The same Node.js test suite can run on different browser and operating system combinations.

Cross-browser testing helps you find differences in page rendering, JavaScript behavior, CSS support, and browser controls. It becomes important when your users access the application through several browsers or browser versions.

4. Repetitive Browser Automation

Selenium can handle repetitive browser automation tasks that follow a fixed sequence. Examples include submitting forms, downloading reports, capturing screenshots, and checking content after a website update.

Repetitive browser automation is useful when the task depends on browser interaction and no suitable API is available. When an API can perform the same task, it is usually faster and less likely to break after a UI change.

5. Testing Dynamic Web Applications

Selenium is useful for testing dynamic web applications where elements appear after an API response, animation, page update, or user action. The test can wait for a specific condition before it tries to interact with an element.

Testing dynamic web applications with Selenium helps you validate autocomplete fields, dynamic tables, modal windows, single-page navigation, and asynchronously loaded content. Condition-based waits are more reliable for these interfaces than fixed delays.

6. Web Scraping

Selenium can support web scraping when a page requires JavaScript execution or browser interaction before its content becomes available. It can open menus, apply filters, move through paginated results, and extract dynamically loaded data.

Web scraping with Selenium is best suited to interactive or JavaScript-heavy pages. For static pages, an HTTP client and HTML parser usually require fewer resources and complete the task faster.

Setting up Selenium with Node.js

Setting up Selenium with Node.js is a straightforward process. This section details the essential steps: installing Node.js, the Selenium WebDriver, and the browser driver, followed by configuring the environment for seamless execution.

Prerequisites

Here are the prerequisites for getting started with Selenium and NodeJs:

  1. Install Node.js: Ensure that Node.js is installed on your machine. You can verify the installation by running node -v in your terminal.
  2. Install Selenium WebDriver: Use npm to install the Selenium WebDriver package.
  3. Install Browser Driver: Depending on the browser you intend to use (for example, Chrome), install the corresponding driver.

Step-by-Step Setup

This section provides a step-by-step guide to setting up your Node.js environment for Selenium testing, covering project initialization, package installation, and crucial path configuration.

1. Initialize a Node.js project:

mkdir selenium-nodejs-project

cd selenium-nodejs-project

npm init -y

2. Install Selenium WebDriver:

npm install selenium-webdriver

3. Install the browser driver (for example, ChromeDriver):

npm install chromedriver

4. Add WebDriver to System PATH

Locate the WebDriver: Note the directory where you downloaded the WebDriver (for example, /Users/YOUR_USER/Downloads/).

Edit Bash Profile:

Open your terminal and run:

vim ~/.bash_profile

Add this line, replacing the path with your WebDriver’s location:

export PATH=$PATH:/Users/YOUR_USER/Downloads/

Save and Refresh:

  • Press Esc, type :wq, and hit Enter to save.
  • Run:
source ~/.bash_profile

5. Create a JavaScript file (for example, test.js) and import Selenium:

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

Writing your First Selenium Test in Node.js

Start with a basic Selenium test that launches Chrome and opens a web page.

Example: Opening a Browser and Navigating to a Web Page

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




(async function example() {

    const driver = await new Builder()

        .forBrowser('chrome')

        .build();




    try {

        await driver.get('https://www.example.com');

    } finally {

        await driver.quit();

    }

})();

Explanation

This Node.js script uses Selenium WebDriver to launch Chrome, navigate to a web page, and close the browser session after the test finishes.

1. Importing the Required Module

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

This line imports the Builder class from the selenium-webdriver package.

Builder is used to configure and create a WebDriver instance for a specific browser. The original example also imported By, but it is not required here because the script does not locate or interact with any page elements.

2. Creating an Asynchronous Function

(async function example() {

    // Test code

})();

The test runs inside an async Immediately Invoked Function Expression, or async IIFE. The function runs as soon as it is defined.

Selenium WebDriver methods are asynchronous because browser actions take time to complete. The async keyword allows you to use await, which ensures that each browser operation finishes before the script moves to the next one.

3. Creating a WebDriver Instance

const driver = await new Builder()

    .forBrowser('chrome')

    .build();

This code creates a WebDriver session for Chrome.

  • new Builder() creates a WebDriver builder.
  • .forBrowser(‘chrome’) specifies Chrome as the browser.
  • .build() starts the WebDriver session and returns the driver object.

You can replace ‘chrome’ with another supported browser, such as ‘firefox’, when the required browser and driver configuration are available.

4. Navigating to a URL

await driver.get('https://www.example.com');

The get() method instructs the browser to open the specified URL. The await keyword pauses the function until Selenium completes the navigation according to the configured page-load strategy.

Some applications load additional content through JavaScript after the initial navigation completes. In those cases, you may still need an explicit wait before interacting with a dynamic element.

5. Closing the Browser

try {

    await driver.get('https://www.example.com');

} finally {

    await driver.quit();

}

The try…finally block ensures that driver.quit() runs even when navigation fails or another error occurs during the test.

The quit() method closes all browser windows associated with the WebDriver session and releases the related resources. This prevents unused browser processes from remaining active after test execution.

Advanced Features of using Selenium in Node.js

This section explores advanced Selenium techniques in Node.js, covering element location strategies, interaction methods, handling dynamic content with waits, and optimizing test execution with headless mode.

Locating Elements

Selenium provides several locators to identify web elements:

  • By.id: Locates an element by its ID.
  • By.name: Finds an element by its name attribute.
  • By.xpath: Uses an XPath expression.

Example Code:

let element = await driver.findElement(By.id('username'));

Interacting with Web Elements

Selenium allows you to interact with elements like filling forms, clicking buttons, and handling pop-ups.

Example Code:

await driver.findElement(By.name('q')).sendKeys('Selenium', Key.RETURN);

Handling Waits

Dynamic content often requires waits to ensure elements are loaded before interacting.

  • Implicit Wait: Waits for a set amount of time.
  • Explicit Wait: Waits for a specific condition.

Example of Explicit Wait:

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

await driver.wait(until.elementLocated(By.id('result')), 10000);

Running Tests in Headless Mode

Running tests in headless mode speeds up execution by bypassing the GUI.

Configuration:

const chrome = require('selenium-webdriver/chrome');

const options = new chrome.Options().headless();

let driver = new Builder().forBrowser('chrome').setChromeOptions(options).build();

Integrating Selenium with Test Frameworks

Popular test frameworks like Mocha and Jasmine can be combined with Selenium to create robust test suites. These frameworks provide test case management, reporting, and assertions, complementing Selenium’s capabilities.

Mocha

Mocha is a feature-rich JavaScript test framework running on Node.js, making asynchronous testing simple and fun. To integrate Selenium with Mocha:

Install Mocha: Ensure Mocha is installed in your project.

npm install --save-dev mocha

Set Up a Test Script: Create a test file (for example, test.js) and require necessary modules.

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

const assert = require('assert');

Write Test Cases: Utilize Mocha’s describe and it functions to structure your tests.

describe('Selenium with Mocha', function() {

    let driver;



    before(async function() {

        driver = await new Builder().forBrowser('chrome').build();

    });



    after(async function() {

        await driver.quit();

    });



    it('should open a webpage', async function() {

        await driver.get('https://example.com');

        const title = await driver.getTitle();

        assert.strictEqual(title, 'Example Domain');

    });

});

Jasmine

Jasmine is a behavior-driven development framework for testing JavaScript code. To integrate Selenium with Jasmine:

Install Jasmine: Add Jasmine to your project.

npm install --save-dev jasmine

Initialize Jasmine: Set up Jasmine configuration.

npx jasmine init

Write Test Specifications: Create a spec file (for example, spec.js) and include Selenium WebDriver.

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



describe('Selenium with Jasmine', function() {

    let driver;



    beforeAll(async function() {

        driver = await new Builder().forBrowser('chrome').build();

    });



    afterAll(async function() {

        await driver.quit();

    });



    it('should open a webpage', async function() {

        await driver.get('https://example.com');

        const title = await driver.getTitle();

        expect(title).toBe('Example Domain');

    });

});

Common Challenges and Solutions for Integrating Selenium in Node.js

A Selenium test can work locally and still fail when another developer or CI server runs it. The cause is often not the application itself. Browser versions, asynchronous execution, page behavior, and test state can all affect the result. The following challenges are common in Node.js test suites.

1. Browser and WebDriver Version Mismatch

A browser may fail to start when the installed browser version is not compatible with the driver available in the test environment. You may see session creation errors, unsupported capability messages, or failures immediately after build() runs.

Check the browser, Selenium WebDriver, and driver versions used on each machine. Keep dependency versions controlled through package-lock.json, and make sure local and CI environments install the same package versions.

Avoid depending on a driver that exists only on one developer’s system path. The browser setup should be reproducible wherever the test suite runs.

2. Elements Are Not Ready for Interaction

Modern pages often render in stages. An element may exist in the DOM but still be hidden, disabled, covered by another element, or waiting for data.

This can produce errors such as:

  • NoSuchElementError
  • StaleElementReferenceError
  • ElementClickInterceptedError
  • Timeout errors

Wait for the condition required by the next action. For example, finding an element is not enough when the test needs to click it. The element must also be visible and available for interaction.

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



const submitButton = await driver.wait(

    until.elementLocated(By.id('submit-order')),

    10000

);



await driver.wait(

    until.elementIsVisible(submitButton),

    5000

);



await driver.wait(

    until.elementIsEnabled(submitButton),

    5000

);



await submitButton.click();

This example first waits for the button to appear in the DOM. It then confirms that the button is visible and enabled before clicking it. Each wait reflects a condition required by the next browser action.

Avoid fixed delays such as sleep(5000). A fixed delay may be longer than necessary on a fast run and still too short on a slow run. Condition-based waits respond to the actual page state.

3. Asynchronous Commands Run in the Wrong Order

Selenium WebDriver methods return promises. Missing an await can cause the next command to run before the previous browser action finishes.

For example, a test may attempt to locate an element before navigation completes:

driver.get('https://example.com/login');

const form = await driver.findElement(By.id('login-form'));

The navigation should be awaited:

await driver.get('https://example.com/login');

const form = await driver.findElement(By.id('login-form'));

Use await consistently for navigation, element lookup, user actions, waits, and browser cleanup. Also return or await asynchronous operations inside test hooks so the test framework knows when setup and teardown have finished.

4. Tests Affect One Another

A test may pass when executed alone but fail when the full suite runs. This usually means it depends on state left behind by another test.

Shared browser sessions, reused accounts, existing cookies, and modified test data can all create order-dependent results. One test may leave a user signed in, delete data needed by another test, or change a setting that remains active in the next case.

Reset the required state before each test. Create test data through a controlled setup process and remove it afterward when necessary. Tests should not depend on a specific execution order.

5. Local and CI Environments Behave Differently

Tests that pass on a developer machine may fail in CI because the environment has different resources, permissions, display settings, network access, or browser configuration.

Check whether the CI environment can start the selected browser and access the application under test. Confirm that environment variables, credentials, test URLs, and required files are available during the run.

Do not solve CI-only failures by increasing every timeout. First identify whether the delay comes from application loading, test infrastructure, network access, or an incorrect environment configuration.

Best Practices for using Selenium with Node.js

Reliable Selenium tests depend on how the suite is designed, not only on whether individual commands work. The following practices help keep the code readable and useful as the application and test suite grow.

1. Organize UI Actions with Page Objects

Keep locators and page-specific actions outside the test case. A page object can expose methods such as login(), searchForProduct(), or submitOrder() while hiding the locator and WebDriver details.

For example, a login test should describe the behavior being verified:

await loginPage.open();

await loginPage.login('testuser', 'password');

assert.strictEqual(await dashboardPage.isDisplayed(), true);

The page object should contain the selectors and browser actions required to perform the login.

This structure reduces changes across the suite when an element ID, CSS selector, or page layout changes. Avoid creating one large page object for the entire application. Separate objects by page, component, or meaningful user area.

2. Keep Each Test Focused on One Behavior

A test that validates login, profile editing, product search, checkout, and logout in one flow is difficult to diagnose. When it fails, you may not know which behavior caused the problem.

Keep each test focused on a clear result. Use longer end-to-end tests for a small number of critical workflows. Use shorter UI tests for specific behaviors that require browser validation.

Do not repeat the same validation through several nearly identical Selenium tests. Browser tests are expensive to run and maintain. Cover lower-level business rules through API, integration, or unit tests when browser interaction is not required.

3. Use Stable Locators Intended for Testing

Choose locators in Selenium that describe a stable element rather than its current visual position. IDs, accessible names, and dedicated test attributes are usually easier to maintain than deeply nested CSS selectors or absolute XPath expressions.

Avoid selectors that depend on:

  • Element position
  • Generated class names
  • Long DOM paths
  • Text that changes frequently
  • Styling classes used only for presentation

Work with developers to add stable attributes when the interface does not expose a dependable locator. A locator should identify one intended element without depending on unrelated page structure.

4. Keep Test Data Explicit

A test should make it clear which user, account, product, or record it requires. Hidden dependencies on pre-existing data make failures difficult to reproduce.

Create unique test data when tests may run in parallel. For example, generate a distinct email address or order reference for each test instead of reusing one shared value.

Store environment-specific values such as base URLs and credentials outside the test code. Do not hard-code production credentials or sensitive values in test files.

5. Capture Useful Failure Evidence

A failed assertion alone may not explain what happened in the browser. Capture evidence that helps you reconstruct the failure.

Useful artifacts include:

  • Screenshots
  • Browser console logs
  • Current URL
  • Page title
  • Relevant application logs
  • Test input and environment details

The following helper captures a screenshot and saves it with the test name:

const fs = require('fs');

const path = require('path');



async function captureFailureScreenshot(driver, testName) {

    const image = await driver.takeScreenshot();

    const safeName = testName.replace(/[^a-z0-9]/gi, '-').toLowerCase();

    const filePath = path.join('artifacts', `${safeName}.png`);



    fs.mkdirSync('artifacts', { recursive: true });

    fs.writeFileSync(filePath, image, 'base64');

}

Call the helper from the failure path in your test framework. Using the test name in the filename makes it easier to connect the screenshot with the failed test in CI results.

Capture these artifacts when a test fails rather than for every successful step. Name files with the test name and run identifier so they can be matched to the correct failure.

Logs should record meaningful actions such as navigation, submitted input, and expected state. Avoid logging every WebDriver command because excessive output can hide the actual failure.

6. Control Parallel Execution

Parallel execution can reduce total runtime, but increasing the worker count does not always produce faster or more reliable results. Tests may compete for CPU, memory, browser sessions, accounts, or shared application data.

Start with a small number of parallel workers and measure the result. Increase concurrency only when the test environment and application can support it.

Tests running in parallel must use independent data and sessions. Group tests that modify the same shared resource, or run those tests separately when isolation is not possible.

Conclusion

Selenium with Node.js gives you a practical way to automate browser-based testing with JavaScript. Once the basic setup is in place, you can use it for end-to-end testing, regression testing, cross-browser checks, and other workflows that depend on real browser behavior.

The quality of the test suite depends on how you handle waits, asynchronous commands, test data, locators, and browser sessions. Keep tests focused, isolate their state, capture useful failure evidence, and run only the checks needed at each pipeline stage. These choices make Selenium tests easier to debug and maintain as the application grows.

Version History

  1. Aug 06, 2026 Current Version

    Updated selected sections to improve technical accuracy, remove repetitive or generic phrasing, strengthen practical guidance, and keep the article focused on Selenium with Node.js.

    Vinayak Mirani
    Reviewed by Vinayak Mirani Lead Solution Engineer
Tags
Automation Testing Selenium Website Testing
Sarthak Sharma
Sarthak Sharma

Senior Software Development Engineer

Sarthak Sharma is a Senior Software Development Engineer with 9+ years of experience in software testing and customer engineering. He specializes in helping teams adopt effective automation practices and maximize the value of their testing infrastructure.

Need More Browser Coverage?
Access real browsers through a cloud Selenium Grid.