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
npm init -ynpm install selenium-webdriver mocha chai --save-devProject Structure
project/├── package.json├── test/│ ├── setup.js # Global hooks│ ├── login.test.js│ └── search.test.js└── .mocharc.js # Mocha configConfiguration
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
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 testsmodule.exports = { getDriver: () => driver };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
# Run all testsnpx mocha
# Run specific filenpx mocha test/login.test.js
# Run with grep patternnpx mocha --grep "login"
# Generate HTML reportnpm install mochawesome --save-devnpx mocha --reporter mochawesomeJest Setup
Installation
npm init -ynpm install selenium-webdriver jest --save-devProject Structure
project/├── package.json├── jest.config.js├── __tests__/│ ├── setup.js│ ├── login.test.js│ └── search.test.js└── jest.setup.jsConfiguration
module.exports = { testTimeout: 30000, setupFilesAfterEnv: ['./jest.setup.js'], testEnvironment: 'node', verbose: true, maxWorkers: 1, // Run sequentially for Selenium testMatch: ['**/__tests__/**/*.test.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
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
# Run all testsnpx jest
# Run specific filenpx jest __tests__/login.test.js
# Run with patternnpx jest --testNamePattern="login"
# Watch mode (re-run on changes)npx jest --watch
# Coverage reportnpx jest --coverageFresh Driver Per Test
For better test isolation, create a new driver for each test:
// Mocha - Fresh driver per testdescribe('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 testdescribe('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
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 failureconst 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
| Feature | Mocha | Jest |
|---|---|---|
| Setup | Manual with Chai | Built-in assertions |
| Mocking | Requires Sinon | Built-in |
| Async | Works well | Works well |
| Parallel | —parallel flag | —maxWorkers |
| Watch Mode | —watch | —watch (better) |
| Coverage | Requires Istanbul | Built-in |
| Snapshot | Requires plugin | Built-in |
| Community | Established | Growing fast |
Best Practices
- Use explicit waits - Don’t rely on implicit waits
- Isolate tests - Fresh driver or clean state per test
- Page Objects - Abstract page interactions
- Screenshot on failure - Capture evidence
- Meaningful names - Describe what the test verifies
- Sequential execution - Avoid parallel for Selenium
Next Steps
- Page Object Model - Structure your tests
- Parallel Execution - Scale tests
- Selenium Grid - Distributed testing