name: "NightwatchJS Testing" description: "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." version: 1.0.0 author: thetestingacademy license: MIT tags: [nightwatch, nightwatchjs, selenium, e2e, browser-testing, page-objects, automation] testingTypes: [e2e, integration, visual] frameworks: [nightwatchjs] languages: [javascript, typescript] domains: [web] agents: [claude-code, cursor, github-copilot, windsurf, codex, aider, continue, cline, zed, bolt]
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
elementsblocks and actions usingcommands. - Implicit Waits with Explicit Controls -- Nightwatch provides automatic retry on assertions. Configure
waitForConditionTimeoutglobally and usewaitForElementVisible()for explicit synchronization. - Test Isolation -- Each test file must be independent. Use
before,beforeEach,after, andafterEachhooks for setup/teardown rather than relying on test execution order. - Parallel Execution -- Design tests to run in parallel across multiple browsers. Use
--parallelflag and ensure tests do not share mutable state. - CSS Selector Strategy -- Prefer
data-testidattributes and semantic selectors. Use Nightwatch's@elementsyntax 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.verifycommands
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
- Use Nightwatch's
@elementsyntax from page objects to keep selectors centralized and maintainable. Reference elements as'@usernameInput'rather than repeating raw selectors. - Configure
waitForConditionTimeoutglobally in theglobalssection rather than adding explicit waits to every command. - Enable
test_workersfor parallel execution. Setworkers: 'auto'to use all available CPU cores for maximum throughput. - Use
assertfor hard assertions (fail immediately) andverifyfor soft assertions (continue execution and report at the end). - Capture screenshots on failure via the
screenshotsconfiguration. Enable bothon_failureandon_errorfor 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
--envflag for multi-browser runs:npx nightwatch --env chrome,firefoxto 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.
- Set
retryAssertionTimeoutin 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. UsewaitForElementVisible()orwaitForElementPresent()instead. - Writing tests that depend on execution order -- Tests should be fully independent. Use
before/beforeEachhooks 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
.verifywhen.assertis needed -- Soft assertions can mask failures. Use.assertfor critical checks and.verifyonly 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.
- 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