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.mdsource 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
- Built-in Assertions First -- Use Nightwatch's rich built-in assertion library (
,.assert.visible()
,.assert.textContains()
) before reaching for custom assertion logic..assert.urlContains() - Page Object Architecture -- Structure all tests around Nightwatch's native page object support. Define selectors using
blocks and actions usingelements
.commands - Implicit Waits with Explicit Controls -- Nightwatch provides automatic retry on assertions. Configure
globally and usewaitForConditionTimeout
for explicit synchronization.waitForElementVisible() - Test Isolation -- Each test file must be independent. Use
,before
,beforeEach
, andafter
hooks for setup/teardown rather than relying on test execution order.afterEach - Parallel Execution -- Design tests to run in parallel across multiple browsers. Use
flag and ensure tests do not share mutable state.--parallel - CSS Selector Strategy -- Prefer
attributes and semantic selectors. Use Nightwatch'sdata-testid
syntax from page objects to keep selectors centralized.@element - Descriptive Test Names -- Write test names that describe the expected behavior:
rather than'should display error when login fails with invalid credentials'
.'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()
, or.assert
commands.verify
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
syntax from page objects to keep selectors centralized and maintainable. Reference elements as@element
rather than repeating raw selectors.'@usernameInput' - Configure
globally in thewaitForConditionTimeout
section rather than adding explicit waits to every command.globals - Enable
for parallel execution. Settest_workers
to use all available CPU cores for maximum throughput.workers: 'auto' - Use
for hard assertions (fail immediately) andassert
for soft assertions (continue execution and report at the end).verify - Capture screenshots on failure via the
configuration. Enable bothscreenshots
andon_failure
for comprehensive visual debugging.on_error - 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
flag for multi-browser runs:--env
to execute tests across browsers in a single command.npx nightwatch --env chrome,firefox - Structure tests by feature domain (auth, checkout, search) rather than by page name. This keeps related behavior tests together.
- Set
in globals to automatically retry failing assertions, which handles minor timing issues without explicit waits.retryAssertionTimeout
Anti-Patterns
- Using
for synchronization -- Arbitrary sleeps make tests slow and unreliable. Usebrowser.pause()
orwaitForElementVisible()
instead.waitForElementPresent() - Writing tests that depend on execution order -- Tests should be fully independent. Use
/before
hooks for setup, not prior test results.beforeEach - 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
when.verify
is needed -- Soft assertions can mask failures. Use.assert
for critical checks and.assert
only when continuing execution is truly desired..verify - Skipping
in teardown -- Not closing the browser session causes resource leaks and can make subsequent tests fail.browser.end() - 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