Skip to main content
SeleniumDecoded

Mocha and Jest

Set up Selenium tests with Mocha and Jest, the most popular JavaScript testing frameworks.

Selenium 3 & 4 Stable

Mocha and Jest are the two most popular JavaScript testing frameworks. Both integrate well with Selenium for end-to-end testing.

Mocha Setup

Installation

Terminal window
npm init -y
npm install selenium-webdriver mocha chai --save-dev

Project Structure

project/
├── package.json
├── test/
│ ├── setup.js # Global hooks
│ ├── login.test.js
│ └── search.test.js
└── .mocharc.js # Mocha config

Configuration

.mocharc.js
module.exports = {
timeout: 30000, // 30 second timeout
slow: 10000, // Mark tests > 10s as slow
reporter: 'spec',
require: ['test/setup.js'],
parallel: false, // Disable for Selenium
exit: true
};

Basic Test Structure

test/setup.js
const { Builder } = require('selenium-webdriver');
let driver;
before(async function() {
driver = await new Builder().forBrowser('chrome').build();
});
after(async function() {
await driver.quit();
});
// Export driver for tests
module.exports = { getDriver: () => driver };
test/login.test.js
const { By, until } = require('selenium-webdriver');
const { expect } = require('chai');
const { getDriver } = require('./setup');
describe('Login Tests', function() {
let driver;
before(function() {
driver = getDriver();
});
it('should login with valid credentials', async function() {
await driver.get('https://example.com/login');
await driver.findElement(By.id('username')).sendKeys('testuser');
await driver.findElement(By.id('password')).sendKeys('testpass');
await driver.findElement(By.id('login-btn')).click();
await driver.wait(until.urlContains('/dashboard'), 5000);
const url = await driver.getCurrentUrl();
expect(url).to.include('/dashboard');
});
it('should show error for invalid credentials', async function() {
await driver.get('https://example.com/login');
await driver.findElement(By.id('username')).sendKeys('invalid');
await driver.findElement(By.id('password')).sendKeys('wrong');
await driver.findElement(By.id('login-btn')).click();
const error = await driver.wait(
until.elementLocated(By.css('.error-message')),
5000
);
const text = await error.getText();
expect(text).to.include('Invalid');
});
});

Running Mocha Tests

Terminal window
# Run all tests
npx mocha
# Run specific file
npx mocha test/login.test.js
# Run with grep pattern
npx mocha --grep "login"
# Generate HTML report
npm install mochawesome --save-dev
npx mocha --reporter mochawesome

Jest Setup

Installation

Terminal window
npm init -y
npm install selenium-webdriver jest --save-dev

Project Structure

project/
├── package.json
├── jest.config.js
├── __tests__/
│ ├── setup.js
│ ├── login.test.js
│ └── search.test.js
└── jest.setup.js

Configuration

jest.config.js
module.exports = {
testTimeout: 30000,
setupFilesAfterEnv: ['./jest.setup.js'],
testEnvironment: 'node',
verbose: true,
maxWorkers: 1, // Run sequentially for Selenium
testMatch: ['**/__tests__/**/*.test.js']
};
jest.setup.js
const { Builder } = require('selenium-webdriver');
global.driver = null;
beforeAll(async () => {
global.driver = await new Builder().forBrowser('chrome').build();
});
afterAll(async () => {
if (global.driver) {
await global.driver.quit();
}
});

Basic Test Structure

__tests__/login.test.js
const { By, until } = require('selenium-webdriver');
describe('Login Tests', () => {
beforeEach(async () => {
await driver.get('https://example.com/login');
});
test('should login with valid credentials', async () => {
await driver.findElement(By.id('username')).sendKeys('testuser');
await driver.findElement(By.id('password')).sendKeys('testpass');
await driver.findElement(By.id('login-btn')).click();
await driver.wait(until.urlContains('/dashboard'), 5000);
const url = await driver.getCurrentUrl();
expect(url).toContain('/dashboard');
});
test('should show error for invalid credentials', async () => {
await driver.findElement(By.id('username')).sendKeys('invalid');
await driver.findElement(By.id('password')).sendKeys('wrong');
await driver.findElement(By.id('login-btn')).click();
const error = await driver.wait(
until.elementLocated(By.css('.error-message')),
5000
);
const text = await error.getText();
expect(text).toContain('Invalid');
});
});

Running Jest Tests

Terminal window
# Run all tests
npx jest
# Run specific file
npx jest __tests__/login.test.js
# Run with pattern
npx jest --testNamePattern="login"
# Watch mode (re-run on changes)
npx jest --watch
# Coverage report
npx jest --coverage

Fresh Driver Per Test

For better test isolation, create a new driver for each test:

// Mocha - Fresh driver per test
describe('Isolated Tests', function() {
let driver;
beforeEach(async function() {
driver = await new Builder().forBrowser('chrome').build();
});
afterEach(async function() {
if (driver) {
await driver.quit();
}
});
it('test one', async function() {
await driver.get('https://example.com');
// Test runs with fresh browser
});
it('test two', async function() {
await driver.get('https://example.com/other');
// Completely isolated from test one
});
});
// Jest - Fresh driver per test
describe('Isolated Tests', () => {
let driver;
beforeEach(async () => {
driver = await new Builder().forBrowser('chrome').build();
});
afterEach(async () => {
if (driver) {
await driver.quit();
}
});
test('test one', async () => {
await driver.get('https://example.com');
});
test('test two', async () => {
await driver.get('https://example.com/other');
});
});

Page Object Pattern

pages/LoginPage.js
const { By, until } = require('selenium-webdriver');
class LoginPage {
constructor(driver) {
this.driver = driver;
this.url = 'https://example.com/login';
// Locators
this.usernameInput = By.id('username');
this.passwordInput = By.id('password');
this.loginButton = By.id('login-btn');
this.errorMessage = By.css('.error-message');
}
async navigate() {
await this.driver.get(this.url);
}
async login(username, password) {
await this.driver.findElement(this.usernameInput).sendKeys(username);
await this.driver.findElement(this.passwordInput).sendKeys(password);
await this.driver.findElement(this.loginButton).click();
}
async getErrorMessage() {
const element = await this.driver.wait(
until.elementLocated(this.errorMessage),
5000
);
return element.getText();
}
}
module.exports = LoginPage;
// __tests__/login.test.js (using Page Object)
const LoginPage = require('../pages/LoginPage');
describe('Login with Page Object', () => {
let loginPage;
beforeEach(async () => {
loginPage = new LoginPage(driver);
await loginPage.navigate();
});
test('should login successfully', async () => {
await loginPage.login('testuser', 'testpass');
const url = await driver.getCurrentUrl();
expect(url).toContain('/dashboard');
});
test('should show error', async () => {
await loginPage.login('invalid', 'wrong');
const error = await loginPage.getErrorMessage();
expect(error).toContain('Invalid');
});
});

Screenshots on Failure

// Mocha with screenshot on failure
const fs = require('fs');
const path = require('path');
afterEach(async function() {
if (this.currentTest.state === 'failed') {
const screenshot = await driver.takeScreenshot();
const filename = `${this.currentTest.title.replace(/\s+/g, '_')}.png`;
const filepath = path.join('screenshots', filename);
fs.mkdirSync('screenshots', { recursive: true });
fs.writeFileSync(filepath, screenshot, 'base64');
console.log(`Screenshot saved: ${filepath}`);
}
});
// Jest with screenshot on failure (custom reporter or afterEach)
afterEach(async () => {
const testState = expect.getState();
if (testState.currentTestName && !testState.assertionCalls) {
// Test may have failed - capture screenshot
const screenshot = await driver.takeScreenshot();
const filename = `${testState.currentTestName.replace(/\s+/g, '_')}.png`;
fs.mkdirSync('screenshots', { recursive: true });
fs.writeFileSync(`screenshots/${filename}`, screenshot, 'base64');
}
});

Comparison

FeatureMochaJest
SetupManual with ChaiBuilt-in assertions
MockingRequires SinonBuilt-in
AsyncWorks wellWorks well
Parallel—parallel flag—maxWorkers
Watch Mode—watch—watch (better)
CoverageRequires IstanbulBuilt-in
SnapshotRequires pluginBuilt-in
CommunityEstablishedGrowing fast

Best Practices

  1. Use explicit waits - Don’t rely on implicit waits
  2. Isolate tests - Fresh driver or clean state per test
  3. Page Objects - Abstract page interactions
  4. Screenshot on failure - Capture evidence
  5. Meaningful names - Describe what the test verifies
  6. Sequential execution - Avoid parallel for Selenium

Next Steps