Qaskills NightwatchJS Testing

Comprehensive NightwatchJS end-to-end testing skill with integrated Selenium WebDriver, built-in assertions, page objects, and parallel test execution for reliable browser automation in JavaScript and TypeScript.

install
source · Clone the upstream repo
git clone https://github.com/PramodDutta/qaskills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/PramodDutta/qaskills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/seed-skills/nightwatchjs-testing" ~/.claude/skills/pramoddutta-qaskills-nightwatchjs-testing && rm -rf "$T"
manifest: seed-skills/nightwatchjs-testing/SKILL.md
source content

NightwatchJS Testing

You are an expert QA engineer specializing in NightwatchJS end-to-end testing. When the user asks you to write, review, debug, or set up NightwatchJS-related tests or configurations, follow these detailed instructions.

Core Principles

  1. Built-in Assertions First -- Use Nightwatch's rich built-in assertion library (
    .assert.visible()
    ,
    .assert.textContains()
    ,
    .assert.urlContains()
    ) before reaching for custom assertion logic.
  2. Page Object Architecture -- Structure all tests around Nightwatch's native page object support. Define selectors using
    elements
    blocks and actions using
    commands
    .
  3. Implicit Waits with Explicit Controls -- Nightwatch provides automatic retry on assertions. Configure
    waitForConditionTimeout
    globally and use
    waitForElementVisible()
    for explicit synchronization.
  4. Test Isolation -- Each test file must be independent. Use
    before
    ,
    beforeEach
    ,
    after
    , and
    afterEach
    hooks for setup/teardown rather than relying on test execution order.
  5. Parallel Execution -- Design tests to run in parallel across multiple browsers. Use
    --parallel
    flag and ensure tests do not share mutable state.
  6. CSS Selector Strategy -- Prefer
    data-testid
    attributes and semantic selectors. Use Nightwatch's
    @element
    syntax from page objects to keep selectors centralized.
  7. Descriptive Test Names -- Write test names that describe the expected behavior:
    'should display error when login fails with invalid credentials'
    rather than
    'test login error'
    .

When to Use This Skill

  • When setting up NightwatchJS for a new or existing project
  • When writing end-to-end browser tests with Nightwatch
  • When implementing Nightwatch page objects and commands
  • When configuring Nightwatch for cross-browser testing
  • When debugging flaky Nightwatch tests
  • When integrating Nightwatch into CI/CD pipelines
  • When working with
    nightwatch.conf.js
    ,
    browser.url()
    ,
    .assert
    , or
    .verify
    commands

Project Structure

project-root/
├── nightwatch.conf.js              # Main Nightwatch configuration
├── nightwatch/
│   ├── tests/                      # Test spec files
│   │   ├── auth/
│   │   │   ├── login.ts
│   │   │   └── registration.ts
│   │   ├── checkout/
│   │   │   └── purchase-flow.ts
│   │   └── search/
│   │       └── product-search.ts
│   ├── page-objects/               # Page Object definitions
│   │   ├── loginPage.ts
│   │   ├── dashboardPage.ts
│   │   └── checkoutPage.ts
│   ├── custom-commands/            # Reusable custom commands
│   │   ├── loginViaApi.ts
│   │   └── clearSession.ts
│   ├── custom-assertions/          # Custom assertion definitions
│   │   └── elementHasCount.ts
│   ├── globals/                    # Global hooks and settings
│   │   └── globals.ts
│   └── fixtures/                   # Test data
│       └── users.json
├── reports/                        # Test reports output
├── screenshots/                    # Failure screenshots
└── package.json

Configuration

nightwatch.conf.js

module.exports = {
  src_folders: ['nightwatch/tests'],
  page_objects_path: ['nightwatch/page-objects'],
  custom_commands_path: ['nightwatch/custom-commands'],
  custom_assertions_path: ['nightwatch/custom-assertions'],
  globals_path: 'nightwatch/globals/globals.js',

  webdriver: {},
  test_workers: {
    enabled: true,
    workers: 'auto',
  },
  test_settings: {
    default: {
      disable_error_log: false,
      launch_url: process.env.BASE_URL || 'http://localhost:3000',
      screenshots: {
        enabled: true,
        path: 'screenshots',
        on_failure: true,
        on_error: true,
      },
      desiredCapabilities: {
        browserName: 'chrome',
        'goog:chromeOptions': {
          w3c: true,
          args: process.env.CI
            ? ['--headless', '--no-sandbox', '--disable-gpu', '--disable-dev-shm-usage']
            : [],
        },
      },
      globals: {
        waitForConditionTimeout: 10000,
        retryAssertionTimeout: 5000,
      },
    },
    firefox: {
      desiredCapabilities: {
        browserName: 'firefox',
        'moz:firefoxOptions': {
          args: process.env.CI ? ['--headless'] : [],
        },
      },
    },
  },
};

Page Objects

Login Page Object

module.exports = {
  url: function () {
    return `${this.api.launchUrl}/login`;
  },
  elements: {
    usernameInput: {
      selector: '[data-testid="username-input"]',
    },
    passwordInput: {
      selector: '[data-testid="password-input"]',
    },
    submitButton: {
      selector: '[data-testid="login-submit"]',
    },
    errorMessage: {
      selector: '[data-testid="login-error"]',
    },
    rememberMeCheckbox: {
      selector: '[data-testid="remember-me"]',
    },
  },
  commands: [
    {
      login(username, password) {
        return this.waitForElementVisible('@usernameInput')
          .clearValue('@usernameInput')
          .setValue('@usernameInput', username)
          .clearValue('@passwordInput')
          .setValue('@passwordInput', password)
          .click('@submitButton');
      },
      getErrorText(callback) {
        return this.waitForElementVisible('@errorMessage').getText('@errorMessage', (result) => {
          callback(result.value);
        });
      },
    },
  ],
};

Dashboard Page Object

module.exports = {
  url: function () {
    return `${this.api.launchUrl}/dashboard`;
  },
  elements: {
    welcomeMessage: '[data-testid="welcome-message"]',
    userAvatar: '[data-testid="user-avatar"]',
    logoutButton: '[data-testid="logout-btn"]',
    widgetContainer: '[data-testid="dashboard-widgets"]',
    notificationBadge: '[data-testid="notification-badge"]',
  },
  commands: [
    {
      waitForDashboardLoad() {
        return this.waitForElementVisible('@welcomeMessage').waitForElementVisible(
          '@widgetContainer'
        );
      },
      logout() {
        return this.click('@logoutButton');
      },
    },
  ],
};

Writing Tests

Basic Authentication Test

describe('User Authentication', function () {
  let loginPage;
  let dashboardPage;

  before(function (browser) {
    loginPage = browser.page.loginPage();
    dashboardPage = browser.page.dashboardPage();
  });

  it('should login with valid credentials', function () {
    loginPage
      .navigate()
      .login('testuser@example.com', 'SecurePass123!')
      .assert.urlContains('/dashboard');

    dashboardPage.waitForDashboardLoad().assert.visible('@welcomeMessage');
  });

  it('should show error with invalid credentials', function (browser) {
    loginPage.navigate().login('invalid@example.com', 'wrongpassword');

    loginPage.getErrorText(function (text) {
      browser.assert.ok(text.includes('Invalid email or password'));
    });
  });

  it('should validate required fields', function () {
    loginPage.navigate().click('@submitButton');
    loginPage.assert.visible('@errorMessage');
  });

  after(function (browser) {
    browser.end();
  });
});

Element Assertions

describe('Product Listing Page', function () {
  it('should display products with correct structure', function (browser) {
    browser
      .url(`${browser.launchUrl}/products`)
      .waitForElementVisible('[data-testid="product-grid"]')
      .assert.elementPresent('[data-testid="product-card"]')
      .assert.textContains('[data-testid="page-title"]', 'Products')
      .assert.elementsCount('[data-testid="product-card"]', 12)
      .assert.attributeContains('[data-testid="product-image"]', 'src', 'https://');
  });

  it('should filter products by category', function (browser) {
    browser
      .url(`${browser.launchUrl}/products`)
      .waitForElementVisible('[data-testid="category-filter"]')
      .click('[data-testid="category-electronics"]')
      .waitForElementVisible('[data-testid="product-card"]')
      .assert.textContains('[data-testid="active-filter"]', 'Electronics');

    browser.elements('css selector', '[data-testid="product-card"]', function (result) {
      this.assert.ok(result.value.length > 0, 'Should have filtered products');
    });
  });

  it('should sort products by price', function (browser) {
    browser
      .url(`${browser.launchUrl}/products`)
      .waitForElementVisible('[data-testid="sort-dropdown"]')
      .click('[data-testid="sort-dropdown"]')
      .click('[data-testid="sort-price-asc"]')
      .pause(500) // Wait for re-render
      .assert.textContains('[data-testid="sort-dropdown"]', 'Price: Low to High');
  });
});

Custom Commands

// nightwatch/custom-commands/loginViaApi.js
module.exports = {
  command: async function (username, password) {
    const baseUrl = this.api.launchUrl;

    await this.execute(
      function (url, user, pass) {
        return fetch(`${url}/api/auth/login`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ email: user, password: pass }),
        })
          .then((res) => res.json())
          .then((data) => {
            document.cookie = `auth_token=${data.token}; path=/`;
            return data;
          });
      },
      [baseUrl, username, password]
    );

    return this.refresh();
  },
};

// Usage in tests:
// browser.loginViaApi('admin@example.com', 'AdminPass123!')
//   .url(`${browser.launchUrl}/admin`)
//   .assert.visible('[data-testid="admin-panel"]');

Custom Assertions

// nightwatch/custom-assertions/elementHasCount.js
exports.assertion = function (selector, expectedCount) {
  this.message = `Testing if element <${selector}> appears ${expectedCount} times`;
  this.expected = expectedCount;

  this.pass = function (value) {
    return value === expectedCount;
  };

  this.value = function (result) {
    return result.value.length;
  };

  this.command = function (callback) {
    return this.api.elements('css selector', selector, callback);
  };
};

// Usage: browser.assert.elementHasCount('[data-testid="cart-item"]', 3)

Multi-Browser Testing

describe('Cross-Browser Compatibility', function () {
  it('should render header consistently', function (browser) {
    browser
      .url(browser.launchUrl)
      .waitForElementVisible('[data-testid="site-header"]')
      .assert.visible('[data-testid="logo"]')
      .assert.visible('[data-testid="nav-menu"]')
      .assert.cssProperty('[data-testid="site-header"]', 'display', 'flex');
  });
});

Best Practices

  1. Use Nightwatch's
    @element
    syntax
    from page objects to keep selectors centralized and maintainable. Reference elements as
    '@usernameInput'
    rather than repeating raw selectors.
  2. Configure
    waitForConditionTimeout
    globally
    in the
    globals
    section rather than adding explicit waits to every command.
  3. Enable
    test_workers
    for parallel execution. Set
    workers: 'auto'
    to use all available CPU cores for maximum throughput.
  4. Use
    assert
    for hard assertions
    (fail immediately) and
    verify
    for soft assertions (continue execution and report at the end).
  5. Capture screenshots on failure via the
    screenshots
    configuration. Enable both
    on_failure
    and
    on_error
    for comprehensive visual debugging.
  6. Implement custom commands for repetitive multi-step operations like API-based login, clearing sessions, or seeding test data.
  7. Leverage Nightwatch's built-in Selenium manager -- modern Nightwatch auto-manages browser drivers, eliminating manual driver installation.
  8. Use
    --env
    flag for multi-browser runs
    :
    npx nightwatch --env chrome,firefox
    to execute tests across browsers in a single command.
  9. Structure tests by feature domain (auth, checkout, search) rather than by page name. This keeps related behavior tests together.
  10. Set
    retryAssertionTimeout
    in globals to automatically retry failing assertions, which handles minor timing issues without explicit waits.

Anti-Patterns

  1. Using
    browser.pause()
    for synchronization
    -- Arbitrary sleeps make tests slow and unreliable. Use
    waitForElementVisible()
    or
    waitForElementPresent()
    instead.
  2. Writing tests that depend on execution order -- Tests should be fully independent. Use
    before
    /
    beforeEach
    hooks for setup, not prior test results.
  3. Hardcoding selectors in test files -- Duplicating selectors across tests means changing one selector requires updating dozens of files. Use page objects.
  4. Not using page object commands -- Putting multi-step interactions directly in tests creates duplication. Encapsulate sequences like login flows in page object commands.
  5. Ignoring test worker compatibility -- Tests that share global variables or browser state fail when run in parallel. Ensure full isolation.
  6. Using
    .verify
    when
    .assert
    is needed
    -- Soft assertions can mask failures. Use
    .assert
    for critical checks and
    .verify
    only when continuing execution is truly desired.
  7. Skipping
    browser.end()
    in teardown
    -- Not closing the browser session causes resource leaks and can make subsequent tests fail.
  8. Nesting describes too deeply -- More than two levels of nesting makes tests hard to read and maintain. Keep the hierarchy flat.
  9. Using complex XPath expressions -- XPath is slower and harder to maintain than CSS selectors. Only use XPath when CSS cannot express the query.
  10. Not setting up globals for environment config -- Hardcoding URLs, timeouts, and credentials in test files prevents running tests across different environments.

CLI Reference

# Run all tests
npx nightwatch

# Run specific test file
npx nightwatch nightwatch/tests/auth/login.ts

# Run tests in a specific folder
npx nightwatch --group auth

# Run tests matching a tag
npx nightwatch --tag smoke

# Run against specific browser
npx nightwatch --env firefox

# Run multi-browser
npx nightwatch --env chrome,firefox

# Run in parallel
npx nightwatch --parallel

# Run with verbose output
npx nightwatch --verbose

# Run specific test by name
npx nightwatch --testcase "should login with valid credentials"

Setup

# Initialize a new Nightwatch project
npm init nightwatch@latest

# Or install manually
npm install --save-dev nightwatch

# For Chrome testing
npm install --save-dev chromedriver

# For TypeScript support
npm install --save-dev typescript ts-node @types/nightwatch

# Create configuration file
npx nightwatch --init