mcpbeat

NightwatchJS Testing

pramoddutta/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.

4k tokens
context cost
the whole folder, loaded on every use
1
files
instructions only
0
copies elsewhere
how many repositories repackaged it
195
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/PramodDutta/qaskills --skill NightwatchJS Testing

The instruction itself

19 sections, as written by the author

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

  • Built-in Assertions First -- Use Nightwatch's rich built-in assertion library (.assert.visible(), .assert.textContains(), .assert.urlContains()) before reaching for custom assertion logic.
  • Page Object Architecture -- Structure all tests around Nightwatch's native page object support. Define selectors using elements blocks and actions using commands.
  • Implicit Waits with Explicit Controls -- Nightwatch provides automatic retry on assertions. Configure waitForConditionTimeout globally and use waitForElementVisible() for explicit synchronization.
  • 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.
  • Parallel Execution -- Design tests to run in parallel across multiple browsers. Use --parallel flag and ensure tests do not share mutable state.
  • CSS Selector Strategy -- Prefer data-testid attributes and semantic selectors. Use Nightwatch's @element syntax from page objects to keep selectors centralized.
  • 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('[email protected]', 'SecurePass123!')
      .assert.urlContains('/dashboard');

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

  it('should show error with invalid credentials', function (browser) {
    loginPage.navigate().login('[email protected]', '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('[email protected]', '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

  • Use Nightwatch's @element syntax from page objects to keep selectors centralized and maintainable. Reference elements as '@usernameInput' rather than repeating raw selectors.
  • Configure waitForConditionTimeout globally in the globals section rather than adding explicit waits to every command.
  • Enable test_workers for parallel execution. Set workers: 'auto' to use all available CPU cores for maximum throughput.
  • Use assert for hard assertions (fail immediately) and verify for soft assertions (continue execution and report at the end).
  • Capture screenshots on failure via the screenshots configuration. Enable both on_failure and on_error for comprehensive visual debugging.
  • Implement custom commands for repetitive multi-step operations like API-based login, clearing sessions, or seeding test data.
  • Leverage Nightwatch's built-in Selenium manager -- modern Nightwatch auto-manages browser drivers, eliminating manual driver installation.
  • Use --env flag for multi-browser runs: npx nightwatch --env chrome,firefox to execute tests across browsers in a single command.
  • 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

  • Using browser.pause() for synchronization -- Arbitrary sleeps make tests slow and unreliable. Use waitForElementVisible() or waitForElementPresent() instead.
  • Writing tests that depend on execution order -- Tests should be fully independent. Use before/beforeEach hooks for setup, not prior test results.
  • Hardcoding selectors in test files -- Duplicating selectors across tests means changing one selector requires updating dozens of files. Use page objects.
  • Not using page object commands -- Putting multi-step interactions directly in tests creates duplication. Encapsulate sequences like login flows in page object commands.
  • Ignoring test worker compatibility -- Tests that share global variables or browser state fail when run in parallel. Ensure full isolation.
  • 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.
  • Skipping browser.end() in teardown -- Not closing the browser session causes resource leaks and can make subsequent tests fail.
  • Nesting describes too deeply -- More than two levels of nesting makes tests hard to read and maintain. Keep the hierarchy flat.
  • 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

How to use it

Copy the folder

Take pramoddutta/nightwatchjs testing from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

The agent identifies a skill by the name field in its header. Two skills with the same name cannot sit side by side — one of them will be ignored.

Install what it needs

The instructions reference npm, npx. Without those the skill loads but fails at the first command.