Skip to content
📚 4 min read

Nightwatch Guide

Nightwatch.js is an integrated framework for automated testing of web applications and websites using Node.js. It provides a complete end-to-end testing solution.

Key Features

  • End-to-end testing framework
  • Built-in test runner
  • Selenium WebDriver integration
  • Page Object Model support
  • CSS/Xpath selectors
  • Cloud testing integration
  • Parallel test execution
  • Visual regression testing

Getting Started

bash
# Install Nightwatch
npm install nightwatch

# Install browser drivers
npm install chromedriver geckodriver

Core Concepts

Configuration

javascript
// nightwatch.conf.js
module.exports = {
  src_folders: ['tests'],
  webdriver: {
    start_process: true,
    server_path: require('chromedriver').path,
    port: 9515,
  },
  test_settings: {
    default: {
      desiredCapabilities: {
        browserName: 'chrome',
        chromeOptions: {
          args: ['--headless', '--no-sandbox'],
        },
      },
    },
  },
};

Basic Test Structure

javascript
module.exports = {
  'Basic test': (browser) => {
    browser
      .url('https://example.com')
      .waitForElementVisible('body')
      .assert.titleContains('Example')
      .end();
  },
};

Element Commands

javascript
module.exports = {
  'Element interactions': (browser) => {
    // Basic interactions
    browser
      .click('#button')
      .setValue('input[type=text]', 'Hello')
      .clearValue('input[type=text]')
      .submitForm('#form');

    // Complex interactions
    browser
      .moveToElement('#menu', 10, 10)
      .mouseButtonClick('left')
      .doubleClick('#item')
      .dragAndDrop('#source', '#target');
  },
};

Advanced Features

Page Objects

javascript
// pages/login.js
const commands = {
  login(email, password) {
    return this.setValue('@email', email)
      .setValue('@password', password)
      .click('@submit');
  },

  assertError() {
    return this.assert.visible('@errorMessage');
  },
};

module.exports = {
  url: '/login',
  commands: [commands],
  elements: {
    email: {
      selector: '#email',
    },
    password: {
      selector: '#password',
    },
    submit: {
      selector: 'button[type=submit]',
    },
    errorMessage: {
      selector: '.error-message',
    },
  },
};

// tests/login.js
module.exports = {
  'Login test': (browser) => {
    const login = browser.page.login();

    login
      .navigate()
      .login('user@example.com', 'password')
      .assert.urlContains('/dashboard');
  },
};

Custom Commands

javascript
// commands/customWait.js
module.exports = class CustomWait {
  async command(selector, callback) {
    await this.api.waitForElementVisible(selector);
    if (callback) {
      callback.call(this);
    }
    return this;
  }
};

// Usage
module.exports = {
  'Custom command test': (browser) => {
    browser.customWait('#element', function () {
      console.log('Element is visible');
    });
  },
};

Assertions

javascript
module.exports = {
  'Assertion examples': (browser) => {
    // Element assertions
    browser.assert
      .visible('#element')
      .assert.elementPresent('.item')
      .assert.containsText('#message', 'Success')
      .assert.attributeContains('#link', 'href', 'example.com')
      .assert.cssProperty('#button', 'color', 'rgb(255, 0, 0)');

    // Value assertions
    browser.assert
      .value('input[type=text]', 'Expected value')
      .assert.valueContains('input[type=text]', 'partial')
      .assert.urlContains('/dashboard')
      .assert.title('Dashboard');

    // Custom assertions
    browser.expect.element('#count').text.to.equal('5');
    browser.expect.element('#status').to.be.enabled;
    browser.expect.element('#form').to.be.an('form');
  },
};

Testing Patterns

Visual Regression

javascript
module.exports = {
  'Visual regression': (browser) => {
    browser.saveScreenshot('tests/screenshots/baseline.png');

    // Compare with baseline
    browser.assert.screenshotEquals(
      'tests/screenshots/baseline.png',
      'tests/screenshots/current.png',
      'Homepage should match baseline'
    );

    // Element screenshot
    browser.saveElementScreenshot('#component', 'component.png');
  },
};

API Testing

javascript
const axios = require('axios');

module.exports = {
  'API integration': async (browser) => {
    // Create test data
    const response = await axios.post('https://api.example.com/users', {
      name: 'Test User',
      email: 'test@example.com',
    });

    // Verify in UI
    browser
      .url(`/users/${response.data.id}`)
      .waitForElementVisible('.user-profile')
      .assert.containsText('.user-name', 'Test User');

    // Cleanup
    await axios.delete(`https://api.example.com/users/${response.data.id}`);
  },
};

Mobile Testing

javascript
module.exports = {
  'Mobile tests': (browser) => {
    // Set mobile viewport
    browser.resizeWindow(375, 812);

    // Touch actions
    browser
      .touchMove('#slider', 100, 0)
      .touchDown('#button')
      .touchUp('#button');
  },
};

Best Practices

1. Element Selection

javascript
module.exports = {
  'Selector best practices': (browser) => {
    // ❌ Avoid
    browser.click('button:nth-child(2)');
    browser.click('div.btn-class');

    // ✅ Prefer
    browser.click('[data-testid="submit"]');
    browser.click('#login-button');
    browser.click('button[aria-label="Submit"]');
  },
};

2. Waiting Strategies

javascript
module.exports = {
  'Wait strategies': (browser) => {
    // ❌ Avoid
    browser.pause(5000);

    // ✅ Prefer
    browser
      .waitForElementVisible('#element')
      .waitForElementNotPresent('.loader')
      .waitUntil(() => {
        return browser.expect.element('#status').text.to.contain('Ready');
      });
  },
};

3. Error Handling

javascript
module.exports = {
  beforeEach: (browser) => {
    browser.windowMaximize();
  },

  afterEach: (browser, done) => {
    if (browser.currentTest.results.failed > 0) {
      browser
        .saveScreenshot(`tests/screenshots/error-${Date.now()}.png`)
        .end(done);
    } else {
      browser.end(done);
    }
  },

  'Error handling': (browser) => {
    try {
      browser.click('#non-existent');
    } catch (error) {
      console.error('Test failed:', error);
      browser.saveScreenshot('error.png');
      throw error;
    }
  },
};

4. Test Organization

javascript
// globals.js
module.exports = {
  beforeEach: (browser) => {
    browser.windowMaximize().deleteCookies().url('https://example.com');
  },

  afterEach: (browser) => {
    browser.end();
  },
};

// test-groups/admin.js
module.exports = {
  '@tags': ['admin', 'smoke'],

  before: (browser) => {
    // Admin setup
    browser.page.login().login('admin@example.com', 'admin123');
  },

  'Admin dashboard': (browser) => {
    browser.assert
      .visible('#admin-panel')
      .assert.containsText('#user-count', '5');
  },
};

5. Configuration Best Practices

javascript
// nightwatch.conf.js
const chromedriver = require('chromedriver');
const geckodriver = require('geckodriver');

module.exports = {
  src_folders: ['tests'],
  page_objects_path: ['pages'],
  custom_commands_path: ['commands'],
  custom_assertions_path: ['assertions'],
  globals_path: 'globals.js',

  webdriver: {
    start_process: true,
    server_path: chromedriver.path,
    port: 9515,
  },

  test_settings: {
    default: {
      screenshots: {
        enabled: true,
        on_failure: true,
        path: 'tests/screenshots',
      },
      desiredCapabilities: {
        browserName: 'chrome',
        chromeOptions: {
          args: ['--headless'],
        },
        javascriptEnabled: true,
        acceptSslCerts: true,
      },
    },

    firefox: {
      webdriver: {
        server_path: geckodriver.path,
        port: 4444,
      },
      desiredCapabilities: {
        browserName: 'firefox',
        'moz:firefoxOptions': {
          args: ['-headless'],
        },
      },
    },
  },
};