Next-Gen App & Browser Testing Cloud
Trusted by 2 Mn+ QAs & Devs to accelerate their release cycles

Discover how to leverage the Node.js test runner for streamlined, automated testing. This detailed guide explores setup, built-in features, mocks, and more.

Bonnie
January 11, 2026
The JavaScript test runner or Node.js test runner helps automate the testing process for websites and web applications by enabling a range of testing techniques, including unit, integration, and end-to-end testing.
The Node.js test runner automates running tests and offers feedback on the results to help identify and resolve bugs during the stages of software development effectively.
In this blog, we look at how to leverage Node.js test runner for automated testing while exploring methods such as mocking and parallel testing alongside test hooks.
Programming languages like Python, Ruby, and Go had a built-in test runner, but JavaScript did not have one. All test runners in the JavaScript ecosystem, such as Mocha, Jest, and Jasmine, were built as third-party packages.
All this changed when Node.js released an experimental built-in test runner in Node.js version 18 and made the test runner stable in Node.js version 20.
The test runner offers several features, such as:
The native test runner in Node.js provides several advantages for JavaScript automation testing. Outlined below are some of the benefits of utilizing this test runner:
Note: Run Node.js tests across 3000+ real environments. Try TestMu AI Today!
In this part of the blog post, you will learn how to start using the Node.js built-in test runner and run your testing script successfully.
To get started, follow these instructions;
npm init -y
"scripts": {
"test": "node --test TestRunner/tests/"
},
npm install selenium-webdriver
npm install chromedriver
const { Builder, By, Key, until } = require("selenium-webdriver");
const assert = require("assert").strict;
const { test } = require("node:test");
{
let driver;
test("Setup WebDriver for Google Search Test Suite", async (t) => {
driver = await new Builder().forBrowser("chrome").build();
});
test("Navigate to Google and verify title", async (t) => {
await driver.get("http://www.google.com");
const title = await driver.getTitle();
assert.strictEqual(title, "Google");
});
test("Cleanup after Google Search Test Suite", async (t) => {
await driver.quit();
});
}

The above code imports the required functions from node:test and node:assert and describes a simple test that navigates to Google and verifies the title.
node tests/started.test.js
In Node.js test runner, you can use describe() and it() blocks to run tests. The describe() block is used to declare a suite that organizes and groups related tests together, while the it() block is used to declare a test.
The benefit of using describe()/it() blocks is that it provides a way to organize your tests into blocks of related functionality or features. This is useful for larger test suites, where you want to keep tests neatly organized and logically grouped.
Inside a describe() block, you can have multiple test() or it() blocks that define specific test cases. You can also nest describe() blocks within each other to create sub-groups of tests for more detailed organization.
You can write tests using describe() and it() blocks, as shown below.
describe("Form Input Test", async () => {
let driver;
await it("Setup WebDriver", async () => {
driver = await new Builder().forBrowser("chrome").build();
});
await it("should input values in a form and check their sum", async () => {
await driver.get("https://www.lambdatest.com/selenium-playground/simple-form-demo");
await driver.findElement(By.id("sum1")).sendKeys(2);
await driver.findElement(By.id("sum2")).sendKeys(3);
await driver.findElement(By.xpath("//button[normalize-space()='Get Sum']")).click();
let sum = await driver.wait(until.elementLocated(By.id("addmessage")),10000);
let sumNo = await sum.getText();
assert.strictEqual(sumNo, "5");
});
await it("Cleanup: Close the browser", async () => {
await driver.quit();
});
});
node tests/describeit.test.jsThe Node.js test runner also allows you to skip tests. You can skip tests if they are flaky, the feature being tested is under active development, the test is dependent on unavailable external dependencies, or tests are on deprecated features.
Individual tests can be skipped by passing the skip option to the test or by calling the test context’s skip() annotation. Annotations to avoid running the test in the built-in test runner consist of indicators, like skip:true, skip;’This test is skipped’, t.skip(), and t.skip(“This test is skipped”) as illustrated in this example.
describe("Expected Values To Be Strictly Equal", async () => {
let driver;
driver = await new Builder().forBrowser("chrome").build();
it("should be strictly equal", async () => {
await driver.get("http://www.google.com");
const title = await driver.getTitle();
assert.strictEqual(title, "Google");
});
it("skip test option", { skip: true }, async (t) => {
await driver.get("http://www.google.com");
const title = await driver.getTitle();
assert.strictEqual(title, "Google");
});
it(
"skip test option with message",
{ skip: "This test is skipped" },
async (t) => {
await driver.get("http://www.google.com");
const title = await driver.getTitle();
assert.strictEqual(title, "Google");
}
);
it("Calling skip() method", async (t) => {
await driver.get("http://www.google.com");
const title = await driver.getTitle();
assert.strictEqual(title, "Google");
t.skip();
});
it("Calling skip() method with message", async (t) => {
await driver.get("http://www.google.com");
const title = await driver.getTitle();
assert.strictEqual(title, "Google");
t.skip("This test is skipped");
});
await it("Cleanup: Close the browser", async () => {
await driver.quit();
});
});
node tests/skipping.test.jsThe Node.js test runner provides different test hooks. Hooks are functions that run immediately before or immediately after a test. The hooks available in the Node.js test runner are before(), beforeEach(), after(), and afterEach().
Here are some examples of how to use these hooks:
before() Hook
The before() hook is used to prepare the testing environment where it runs once before all the tests in a describe block. For example, you can use the before() hook to set up the WebDriver before all your tests are executed.
Below is how to use the before() hook:
describe("Simple Form Demo Title Test", async () => {
let driver;
before(async () => {
driver = await new Builder().forBrowser("chrome").build();
});
await it("should Navigate to Simple Form Demo and verify title", async () => {
await driver.get("http://www.lambdatest.com/selenium-playground/simple-form-demo");
const title = await driver.getTitle();
assert.strictEqual(title, "Selenium Grid Online | Run Selenium Test On Cloud");
});
await it("Cleanup: Close the browser", async () => {
await driver.quit();
});
});
node tests/beforehook.test.js
beforeEach() Hook
The beforeEach() hook runs once before each test, where it is used to isolate tests so they do not affect each other. For example, if you have to visit a specific page URL for a couple of tests, you can use a beforeEach() hook to open the URL page before each test is executed.
Below is how to use the beforeEach() hook:
describe("Simple Form Demo Test", async () => {
let driver;
before(async () => {
driver = await new Builder().forBrowser("chrome").build();
});
beforeEach(async () => {
await driver.get("https://www.lambdatest.com/selenium-playground/simple-form-demo");
});
await it("should Navigate to Simple Form Demo and verify title", async () => {
const title = await driver.getTitle();
assert.strictEqual(title,"Selenium Grid Online | Run Selenium Test On Cloud");
});
await it("Should add values to simple form input fields and check their sum", async () => {
await driver.findElement(By.id("sum1")).sendKeys(2);
await driver.findElement(By.id("sum2")).sendKeys(3);
await driver.findElement(By.xpath("//button[normalize-space()='Get Sum']")).click();
let sum = await driver.wait(until.elementLocated(By.id("addmessage")),10000);
let sumNo = await sum.getText();
assert.strictEqual(sumNo, "5");
});
await it("Cleanup: Close the browser", async () => {
await driver.quit();
});
})
node tests/beforeEachhook.test.js
after() Hook
The after() hook runs once after the execution of all the tests where it is used to perform cleanup actions after all the tests are executed. For example, if you want to close the WebDriver after the tests have been executed, you can use the after() hook.
Below is how to use the after() hook:
describe("Simple Form Demo Test", async () => {
let driver;
before(async () => {
driver = await new Builder().forBrowser("chrome").build();
});
beforeEach(async () => {
await driver.get(
"https://www.lambdatest.com/selenium-playground/simple-form-demo"
);
});
await it("should Navigate to Simple Form Demo and verify title", async () => {
const title = await driver.getTitle();
assert.strictEqual(
title,
"Selenium Grid Online | Run Selenium Test On Cloud"
);
});
await it("Should add values to simple form input fields and check their sum", async () => {
await driver.findElement(By.id("sum1")).sendKeys(2);
await driver.findElement(By.id("sum2")).sendKeys(3);
await driver
.findElement(By.xpath("//button[normalize-space()='Get Sum']"))
.click();
let sum = await driver.wait(
until.elementLocated(By.id("addmessage")),
10000
);
let sumNo = await sum.getText();
assert.strictEqual(sumNo, "5");
});
after(async () => {
await driver.quit();
});
});
node tests/afterhook.test.js
afterEach() Hook
The afterEach() hook is run once after each test. It is used to perform cleanup actions after each test. For example, if you want to clear cookies after each test, you can use an afterEach() hook.
const { Builder, By, Key, until } = require("selenium-webdriver");
const assert = require("assert").strict;
const {
describe,
it,
before,
beforeEach,
after,
afterEach,
} = require("node:test");
describe("Simple Form Demo Test", async () => {
let driver;
before(async () => {
driver = await new Builder().forBrowser("chrome").build();
});
beforeEach(async () => {
await driver.get(
"https://www.lambdatest.com/selenium-playground/simple-form-demo"
);
});
await it("should Navigate to Simple Form Demo and verify title", async () => {
const title = await driver.getTitle();
assert.strictEqual(
title,
"Selenium Grid Online | Run Selenium Test On Cloud"
);
});
await it("Should add values to simple form input fields and check their sum", async () => {
await driver.findElement(By.id("sum1")).sendKeys(2);
await driver.findElement(By.id("sum2")).sendKeys(3);
await driver
.findElement(By.xpath("//button[normalize-space()='Get Sum']"))
.click();
let sum = await driver.wait(
until.elementLocated(By.id("addmessage")),
10000
);
let sumNo = await sum.getText();
assert.strictEqual(sumNo, "5");
});
afterEach(function () {
driver.manage().deleteAllCookies();
});
after(async () => {
await driver.quit();
});
});
node tests/afterEachhook.test.js
The built-in mocking functionality of the Node.js test runner enables you to mock and substitute functions during testing situations where external dependencies or third-party packages are being used. It is particularly handy when those dependencies are still in the development phase.
You can use mocking functionality to create spies and stubs. Below is an example that illustrates using mocking functionalities to validate fetching data from an API:
First, install axios, a promise-based HTTP client for the browser and Node.js, using the below-given command:
npm install axios
Then, create an index.js file and add the following code:
const axios = require("axios");
class MakeRequest {
constructor() {}
static async fetchDataFromAPI(id) {
const { data: todo } = await axios.get(
"https://jsonplaceholder.typicode.com/todos/1"
);
return todo;
}
static slugifyTitle(todo) {
const slug = `${todo.title.replace(/ /g, "-")}${todo.id}`;
return { ...todo, slug };
}
static async addToDB() {
let todo = await this.fetchDataFromAPI();
todo = this.slugifyTitle(todo);
return todo;
}
}
module.exports = MakeRequest;
The code above implements a MakeRequest class, which has three functions fetchDataFromAPI(), slugifyTitle(), and addToDB().
Then, create a mock.test.js file and add the following code:
// Describing the tests related to mocking
describe("Mocking Tests", async () => {
// Resetting mocks before each test
beforeEach(() => mock.restoreAll());
// Testing fetchDataFromAPI function to return a product
it("fetchDataFromAPI should return a product", async (t) => {
// Mocking the fetchDataFromAPI function to return a predefined product
mock
.method(MakeRequest, MakeRequest.fetchDataFromAPI.name)
.mock.mockImplementation(async () => {
return {
userId: 1,
id: 1,
title: "delectus aut autem",
completed: false,
};
});
// Calling the function and asserting the returned product properties
const res = await MakeRequest.fetchDataFromAPI();
assert.strictEqual(res.userId, 1);
assert.strictEqual(res.completed, false);
});
// Testing fetchDataFromAPI function to return a product with a given ID
it("fetchDataFromAPI should return product with given ID", async (t) => {
const id = 3;
// Mocking the fetchDataFromAPI function to return a product with a given ID
mock
.method(MakeRequest, MakeRequest.fetchDataFromAPI.name)
.mock.mockImplementation(async (id) => {
return {
userId: 1,
id: id,
title: "delectus aut autem",
completed: false,
};
});
// Calling the function with the provided ID and asserting the returned ID
const res = await MakeRequest.fetchDataFromAPI(id);
assert.deepEqual(res.id, id);
});
// Testing addToDB function to create a slug based on the title
it("should create a slug based on the title", async (t) => {
// Spy on the slugifyTitle method
const slugSpy = mock.method(MakeRequest, "slugifyTitle");
// Mocking the fetchDataFromAPI function to return a predefined product
mock
.method(MakeRequest, "fetchDataFromAPI")
.mock.mockImplementation(async () => {
return {
userId: 1,
id: 1,
title: "delectus aut autem",
completed: false,
};
});
// Calling the addToDB function
await MakeRequest.addToDB();
// Getting the call information for slugifyTitle method
const call = MakeRequest.slugifyTitle.mock.calls[0];
// Asserting the number of calls made to slugifyTitle and fetchDataFromAPI
assert.deepEqual(slugSpy.mock.calls.length, 1);
assert.deepEqual(MakeRequest.fetchDataFromAPI.mock.callCount(), 1);
// Asserting the created slug based on the title
assert.strictEqual(call.result.slug, `delectus-aut-autem1`);
});
});
In the code above, the fetchDataFromAPI method is mocked from the MakeRequest class.
To prevent the function from making a network request, the mockImplementation() method is used to return a predefined output, which can be tested for specific values.
Finally, mock.method() is used to create a spy to test if the slugifyTitle() function is called. Also, how many times the function is called, and its output is tested based on the title.
Run the tests using the below-given command:
node tests/mock.test.js
Node.js test runner allows you to execute multiple tests in parallel and at the same time instead of running them sequentially.
To run tests in parallel in Node.js test runner, you need to pass the concurrency: true parameter as the second argument to the describe() function.
Below is an example of how to run tests in parallel using Node.js native test runner and Selenium using concurrency parameters.
describe("Ecommerce Site Tests", { concurrency: true }, async () => {
let driver;
before(async () => {
driver = await new Builder().forBrowser("chrome").build();
await driver.get("https://ecommerce-playground.lambdatest.io/");
});
await it("should Navigate to ecommerce site and find an item", async () => {
const title = await driver.getTitle();
assert.strictEqual(title, "Your Store");
});
await it("should Navigate to ecommerce site and add item to cart", async () => {
driver.findElement(By.xpath('//a[normalize-space()="Shop by Category"]')).click();
driver.findElement(By.xpath('//span[normalize-space()="Phone, Tablets & Ipod"]')).click();
driver.findElement(By.xpath('//div[@class="carousel-item active"]//img[@title="iPhone"]')).click();
});
afterEach(function () {
driver.manage().deleteAllCookies();
});
after(async () => {
await driver.quit();
});
});
node tests/parallel.test.js
The above test executions are performed on the local grid. However, to scale your automation testing with Node.js, you can consider using a cloud-based testing approach.
AI-driven test execution platforms like TestMu AI let you run Node.js tests on a scalable automation cloud infrastructure, ensuring compatibility and reliability.
Check out this guide to get started with JavaScript automation with TestMu AI.
In summary, the Node.js test runner offers a lightweight solution for creating and executing automated tests in your web projects. Though it may not include all the functionalities found in popular testing frameworks, its straightforwardness and user-friendly nature make it an excellent option for beginning automated testing.
This blog discusses the features of the Node.js built-in test runner. It covers how to create tests using the describe() function and its syntax while making use of hooks for setup and teardown operations, as well as mocking and running tests in parallel threads concurrently for JavaScript code quality assurance and stability enhancement purposes.
For complex testing situations, you may want to evaluate the default test runner against popular frameworks such as Mocha, Jasmine, or Jest to determine which one best suits your requirements.
Did you find this page helpful?
More Related Hubs
TestMu AI forEnterprise
Get access to solutions built on Enterprise
grade security, privacy, & compliance