Introduction to Nightwatch.js
Nightwatch.js is a powerful Node.js-based end-to-end testing framework that provides a simple yet effective syntax for writing browser automation (as discussed in BDD: From Requirements to Automation) tests. Built on WebDriver and supporting modern protocols like WebDriver BiDi and Chrome DevTools, Nightwatch offers an excellent solution for JavaScript developers.
Setup and Configuration
// nightwatch.conf.js
module.exports = {
src_folders: ['tests'],
page_objects_path: ['page_objects'],
custom_commands_path: ['custom_commands'],
custom_assertions_path: ['custom_assertions'],
webdriver: {
start_process: true,
server_path: require('chromedriver').path,
port: 9515
},
test_settings: {
default: {
launch_url: 'https://example.com',
desiredCapabilities: {
browserName: 'chrome',
chromeOptions: {
args: ['--headless', '--no-sandbox']
}
}
},
chrome: {
desiredCapabilities: {
browserName: 'chrome'
}
},
firefox: {
desiredCapabilities: {
browserName: 'firefox'
},
webdriver: {
server_path: require('geckodriver').path
}
}
}
};
Page Object Pattern
// page_objects/loginPage.js
module.exports = {
url: 'https://example.com/login',
elements: {
usernameInput: {
selector: '#username'
},
passwordInput: {
selector: '#password'
},
loginButton: {
selector: 'button[type="submit"]'
},
errorMessage: {
selector: '.error-message'
}
},
commands: [{
login(username, password) {
return this
.navigate()
.waitForElementVisible('@usernameInput')
.setValue('@usernameInput', username)
.setValue('@passwordInput', password)
.click('@loginButton');
},
verifyLoginSuccess() {
return this
.assert.urlContains('/dashboard')
.assert.visible('.welcome-message');
},
verifyLoginError(expectedMessage) {
return this
.assert.visible('@errorMessage')
.assert.containsText('@errorMessage', expectedMessage);
}
}]
};
Custom Commands
// custom_commands/loginAs.js
module.exports = class LoginAs {
async command(userType) {
const users = {
admin: { username: 'admin@example.com', password: 'admin123' },
user: { username: 'user@example.com', password: 'user123' },
guest: { username: 'guest@example.com', password: 'guest123' }
};
const credentials = users[userType];
const loginPage = this.api.page.loginPage();
await loginPage.login(credentials.username, credentials.password);
return this;
}
};
// custom_commands/waitForAjax.js
module.exports = class WaitForAjax {
async command(timeout = 5000) {
await this.api.executeAsync(function(done) {
if (window.jQuery) {
const checkAjax = () => {
if (jQuery.active === 0) {
done(true);
} else {
setTimeout(checkAjax, 100);
}
};
checkAjax();
} else {
done(true);
}
});
return this;
}
};
Custom Assertions
// custom_assertions/elementCountEquals.js
exports.assertion = function(selector, expectedCount) {
this.message = `Testing if element <${selector}> count equals ${expectedCount}`;
this.expected = expectedCount;
this.pass = value => value === expectedCount;
this.value = result => result.value.length;
this.command = callback => {
return this.api.elements('css selector', selector, callback);
};
};
// Usage in tests
browser.assert.elementCountEquals('.product-item', 10);
Parallel Execution
// nightwatch.conf.js - Parallel configuration
module.exports = {
test_workers: {
enabled: true,
workers: 4
},
test_settings: {
default: {
desiredCapabilities: {
browserName: 'chrome',
'goog:chromeOptions': {
args: ['--headless']
}
}
}
}
};
// Run tests in parallel
// npx nightwatch --workers=4
Best Practices
1. Organized Test Structure
// tests/e2e/shopping/addToCart.test.js
describe('Shopping Cart', function() {
before(browser => {
browser.page.loginPage().login('user@example.com', 'pass123');
});
it('should add product to cart', function(browser) {
const productPage = browser.page.productPage();
productPage
.navigate()
.waitForElementVisible('@productList')
.clickProduct('Laptop Pro')
.clickAddToCart()
.assert.containsText('@cartCount', '1');
});
it('should update quantity', function(browser) {
const cartPage = browser.page.cartPage();
cartPage
.navigate()
.setQuantity('Laptop Pro', 3)
.assert.containsText('@cartTotal', '$2,999.97');
});
after(browser => browser.end());
});
2. Data-Driven Testing
// tests/data-driven/loginValidation.test.js
const testData = require('../data/loginTestData.json');
describe('Login Validation', function() {
testData.forEach(data => {
it(`should handle login with ${data.scenario}`, function(browser) {
const loginPage = browser.page.loginPage();
loginPage
.navigate()
.login(data.username, data.password);
if (data.shouldSucceed) {
loginPage.verifyLoginSuccess();
} else {
loginPage.verifyLoginError(data.expectedError);
}
});
});
});
3. API Testing Integration
// tests/api/userManagement.test.js
describe('User Management API', function() {
it('should create user via API and verify in UI', async function(browser) {
// Create user via API
const response = await fetch('https://api.example.com/users', {
method: 'POST',
body: JSON.stringify({
username: 'newuser@example.com',
password: 'pass123'
})
});
const user = await response.json();
// Verify in UI
browser
.loginAs('admin')
.page.usersPage()
.searchUser(user.username)
.assert.visible(`[data-user-id="${user.id}"]`);
});
});
Feature Comparison
Feature | Nightwatch.js | Cypress | Playwright |
---|---|---|---|
Language | JavaScript | JavaScript | JavaScript, TypeScript, Python |
WebDriver | Yes (W3C) | No (Own protocol) | No (Own protocol) |
Cross-browser | Excellent | Limited | Excellent |
Parallel Testing | Built-in | Paid (Dashboard) | Built-in |
Page Objects | Built-in | Manual | Manual |
Custom Commands | Built-in | Built-in | Fixtures |
Assertions | Built-in + Custom | Chai + Custom | Expect API |
Time Travel Debug | No | Yes | Yes |
Network Stubbing | Limited | Excellent | Excellent |
Video Recording | Plugin | Built-in | Built-in |
Setup Complexity | Low | Very Low | Low |
Practical Use Case: E-Commerce Testing
// tests/e2e/completePurchase.test.js
describe('Complete Purchase Flow', function() {
let orderId;
before(browser => {
browser.maximizeWindow();
});
it('should complete guest checkout', function(browser) {
const homePage = browser.page.homePage();
const productPage = browser.page.productPage();
const cartPage = browser.page.cartPage();
const checkoutPage = browser.page.checkoutPage();
// Browse and add products
homePage
.navigate()
.searchFor('Laptop');
productPage
.clickProduct('Laptop Pro 15')
.clickAddToCart()
.assert.containsText('@addedMessage', 'Added to cart');
// Proceed to checkout
cartPage
.navigate()
.proceedToCheckout();
// Fill checkout form
checkoutPage
.fillGuestInfo({
email: 'guest@example.com',
firstName: 'John',
lastName: 'Doe',
address: '123 Main St',
city: 'San Francisco',
state: 'CA',
zip: '94105'
})
.selectShipping('standard')
.fillPaymentInfo({
cardNumber: '4532123456789010',
expiry: '12/25',
cvv: '123',
name: 'John Doe'
})
.placeOrder();
// Verify order
checkoutPage
.assert.visible('@confirmationMessage')
.getText('@orderNumber', result => {
orderId = result.value;
console.log('Order created:', orderId);
});
});
it('should verify order in account', function(browser) {
const ordersPage = browser.page.ordersPage();
browser
.loginAs('admin')
.pause(1000);
ordersPage
.navigate()
.searchOrder(orderId)
.assert.visible(`[data-order-id="${orderId}"]`)
.assert.containsText(`[data-order-id="${orderId}"] .status`, 'Confirmed');
});
after(browser => browser.end());
});
CI/CD Integration
# .github/workflows/nightwatch.yml
name: Nightwatch Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
browser: [chrome, firefox]
node: [16, 18]
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
- name: Install dependencies
run: npm ci
- name: Run Nightwatch tests
run: npx nightwatch --env ${{ matrix.browser }} --workers=4
- name: Upload test reports
if: always()
uses: actions/upload-artifact@v3
with:
name: nightwatch-reports-${{ matrix.browser }}
path: tests_output/
Conclusion
Nightwatch.js provides a mature, well-documented solution for end-to-end testing with excellent WebDriver support and built-in features like page objects and parallel execution. Its straightforward syntax and extensive customization options make it ideal for teams familiar with Node.js looking for reliable browser automation (as discussed in Cucumber BDD Automation: Complete Guide to Behavior-Driven Development Testing).
Key advantages:
- WebDriver Standards: Full W3C WebDriver compliance
- Page Objects: Built-in pattern support
- Customization: Easy custom commands and assertions
- Parallel Execution: Native support without additional costs
- Cross-browser: Excellent multi-browser support
Best suited for teams prioritizing WebDriver compatibility, page object patterns, and straightforward Node.js-based automation (as discussed in Cypress Deep Dive: Architecture, Debugging, and Network Stubbing Mastery) without the complexity of modern alternatives like Playwright or Cypress.