CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-nightwatch

Easy to use Node.js based end-to-end testing solution for web applications using the W3C WebDriver API.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

page-object-model.mddocs/

Page Object Model

Structured page object pattern for organizing elements, sections, and custom commands into reusable, maintainable test components.

Capabilities

Page Object Definition

Define page objects with elements, sections, and custom commands for organized test structure.

/**
 * Page object definition interface
 */
interface PageObjectModel {
  // Page URL (string or function)
  url: string | (() => string);
  
  // Element definitions
  elements: Record<string, ElementProperties>;
  
  // Section definitions for nested components
  sections: Record<string, SectionProperties>;
  
  // Custom command definitions
  commands: Record<string, Function>;
  
  // Custom properties
  props?: Record<string, any> | (() => Record<string, any>);
}

/**
 * Element properties definition
 */
interface ElementProperties {
  selector: string;
  locateStrategy?: LocateStrategy;
  index?: number;
  abortOnFailure?: boolean;
  timeout?: number;
  retryInterval?: number;
  suppressNotFoundErrors?: boolean;
}

/**
 * Section properties definition
 */
interface SectionProperties {
  selector: string;
  locateStrategy?: LocateStrategy;
  elements?: Record<string, ElementProperties>;
  sections?: Record<string, SectionProperties>;
  commands?: Record<string, Function>;
  props?: Record<string, any> | (() => Record<string, any>);
}

/**
 * Locate strategy options
 */
type LocateStrategy = 'css selector' | 'xpath' | 'tag name' | 'class name' | 'id' | 'name' | 'link text' | 'partial link text';

Usage Examples:

// Define a login page object
const loginPage = {
  url: 'https://example.com/login',
  
  elements: {
    usernameInput: '#username',
    passwordInput: '#password',
    submitButton: 'button[type="submit"]',
    errorMessage: {
      selector: '.error-message',
      suppressNotFoundErrors: true
    }
  },
  
  sections: {
    header: {
      selector: '#header',
      elements: {
        logo: '.logo',
        userMenu: '.user-menu'
      }
    }
  },
  
  commands: {
    login: function(username, password) {
      return this
        .setValue('@usernameInput', username)
        .setValue('@passwordInput', password)
        .click('@submitButton');
    }
  }
};

module.exports = loginPage;

Enhanced Page Object Instance

Runtime page object instances with navigation and element access capabilities.

/**
 * Enhanced page object instance interface
 */
interface PageObjectInstance {
  // Page object metadata
  name: string;
  
  // Nightwatch client and API access
  client: NightwatchClient;
  api: NightwatchAPI;
  
  // Element access function
  element: (selector: string) => Element;
  
  // Element instances map (using @ syntax)
  elements: Record<string, ElementInstance>;
  
  // Section instances map
  section: Record<string, SectionInstance>;
  
  // Custom properties
  props: Record<string, any>;
  
  // Navigation method
  navigate(url?: string): Promise<PageObjectInstance>;
}

/**
 * Element instance with page object context
 */
interface ElementInstance {
  selector: string;
  locateStrategy: LocateStrategy;
  
  // Standard element methods
  click(): Promise<ElementInstance>;
  setValue(value: string): Promise<ElementInstance>;
  getText(): Promise<string>;
  isVisible(): Promise<boolean>;
  
  // Page object specific methods
  waitForElementVisible(timeout?: number): Promise<ElementInstance>;
  assert: {
    visible(): Promise<ElementInstance>;
    present(): Promise<ElementInstance>;
    textContains(text: string): Promise<ElementInstance>;
  };
}

/**
 * Section instance with nested elements and sections
 */
interface SectionInstance {
  selector: string;
  elements: Record<string, ElementInstance>;
  sections: Record<string, SectionInstance>;
  
  // Section-specific commands
  [commandName: string]: Function;
}

Usage Examples:

// Use page object in test
module.exports = {
  'Login Test': function(browser) {
    const loginPage = browser.page.loginPage();
    
    loginPage
      .navigate()
      .login('testuser', 'password123')
      .assert.visible('@errorMessage'); // Using @ syntax for elements
  }
};

Page Object Navigation

Navigate to page URLs with support for dynamic URL generation.

/**
 * Navigate to page URL
 * @param url - Optional URL override
 * @returns Promise resolving with page object instance
 */
pageObject.navigate(url?: string): Promise<PageObjectInstance>;

/**
 * Dynamic URL generation function
 * @param params - URL parameters
 * @returns Generated URL string
 */
type UrlFunction = (params?: Record<string, any>) => string;

Usage Examples:

// Static URL page object
const homePage = {
  url: 'https://example.com',
  elements: {
    welcomeMessage: '#welcome'
  }
};

// Dynamic URL page object
const userProfilePage = {
  url: function(userId) {
    return `https://example.com/users/${userId}`;
  },
  elements: {
    profileName: '#profile-name',
    profileEmail: '#profile-email'
  }
};

// Usage in tests
module.exports = {
  'Navigate to pages': function(browser) {
    // Navigate to static URL
    const home = browser.page.homePage();
    home.navigate();
    
    // Navigate to dynamic URL
    const profile = browser.page.userProfilePage();
    profile.navigate('123'); // Navigates to /users/123
  }
};

Element Access Patterns

Access page object elements using various syntax patterns.

/**
 * Element access using @ syntax (recommended)
 */
pageObject.click('@elementName');
pageObject.setValue('@inputElement', 'value');

/**
 * Element access using elements map
 */
pageObject.elements.elementName.click();
pageObject.elements.inputElement.setValue('value');

/**
 * Direct selector access
 */
pageObject.click('css selector', '#direct-selector');

Usage Examples:

// Page object definition
const formPage = {
  elements: {
    firstName: '#first-name',
    lastName: '#last-name',
    submitBtn: 'button[type="submit"]'
  },
  
  commands: {
    fillForm: function(first, last) {
      return this
        .setValue('@firstName', first)    // @ syntax
        .setValue('@lastName', last)
        .click('@submitBtn');
    }
  }
};

// Test usage
module.exports = {
  'Form Test': function(browser) {
    const form = browser.page.formPage();
    
    // Using @ syntax
    form.setValue('@firstName', 'John');
    form.setValue('@lastName', 'Doe');
    form.click('@submitBtn');
    
    // Using elements map
    form.elements.firstName.setValue('Jane');
    form.elements.lastName.setValue('Smith');
    
    // Using custom command
    form.fillForm('Bob', 'Johnson');
  }
};

Section Definitions

Organize related elements into reusable sections within page objects.

/**
 * Section definition with nested elements
 */
interface SectionDefinition {
  selector: string;
  elements: Record<string, ElementProperties>;
  sections?: Record<string, SectionDefinition>;
  commands?: Record<string, Function>;
}

Usage Examples:

// Page object with sections
const dashboardPage = {
  url: '/dashboard',
  
  sections: {
    navigation: {
      selector: '#nav-bar',
      elements: {
        homeLink: 'a[href="/"]',
        profileLink: 'a[href="/profile"]',
        settingsLink: 'a[href="/settings"]'
      },
      commands: {
        navigateToProfile: function() {
          return this.click('@profileLink');
        }
      }
    },
    
    sidebar: {
      selector: '.sidebar',
      elements: {
        searchBox: 'input[type="search"]',
        filterButton: '.filter-btn'
      },
      
      sections: {
        filters: {
          selector: '.filters-panel',
          elements: {
            categoryFilter: '#category',
            dateFilter: '#date-range'
          }
        }
      }
    }
  }
};

// Test usage
module.exports = {
  'Dashboard Test': function(browser) {
    const dashboard = browser.page.dashboardPage();
    
    dashboard.navigate();
    
    // Access section elements
    dashboard.section.navigation.click('@profileLink');
    dashboard.section.sidebar.setValue('@searchBox', 'test query');
    
    // Access nested section elements
    dashboard.section.sidebar.section.filters.click('@categoryFilter');
    
    // Use section commands
    dashboard.section.navigation.navigateToProfile();
  }
};

Custom Commands

Define reusable command functions for common page interactions.

/**
 * Custom command function signature
 * @param this - Page object or section instance
 * @param args - Command arguments
 * @returns Promise or page object instance for chaining
 */
type CustomCommand = (this: PageObjectInstance | SectionInstance, ...args: any[]) => any;

Usage Examples:

// Page object with custom commands
const loginPage = {
  url: '/login',
  
  elements: {
    username: '#username',
    password: '#password',
    submitButton: '#submit',
    errorMessage: '.error'
  },
  
  commands: {
    // Simple login command
    login: function(user, pass) {
      return this
        .setValue('@username', user)
        .setValue('@password', pass)
        .click('@submitButton');
    },
    
    // Login with validation
    loginAndExpectSuccess: function(user, pass) {
      return this
        .login(user, pass)
        .waitForElementNotVisible('@errorMessage', 2000)
        .assert.urlContains('/dashboard');
    },
    
    // Async custom command
    loginWithDelay: async function(user, pass, delay = 1000) {
      await this.setValue('@username', user);
      await this.pause(delay);
      await this.setValue('@password', pass);
      await this.click('@submitButton');
      return this;
    }
  }
};

// Test usage
module.exports = {
  'Login Commands Test': function(browser) {
    const login = browser.page.loginPage();
    
    login.navigate();
    
    // Use custom commands
    login.login('testuser', 'password123');
    login.loginAndExpectSuccess('validuser', 'validpass');
  }
};

Properties and Dynamic Content

Define dynamic properties and computed values for page objects.

/**
 * Properties definition (object or function)
 */
interface PropertiesDefinition {
  [key: string]: any;
}

type PropertiesFunction = (this: PageObjectInstance) => Record<string, any>;

Usage Examples:

// Page object with static properties
const apiPage = {
  url: '/api/docs',
  
  props: {
    apiBaseUrl: 'https://api.example.com/v1',
    defaultHeaders: {
      'Content-Type': 'application/json',
      'Accept': 'application/json'
    }
  },
  
  elements: {
    endpointList: '.endpoint-list'
  }
};

// Page object with dynamic properties
const userPage = {
  url: function(userId) {
    return `/users/${userId}`;
  },
  
  props: function() {
    return {
      currentUser: this.api.globals.currentUser,
      baseUrl: this.api.launchUrl,
      timestamp: new Date().toISOString()
    };
  },
  
  elements: {
    userProfile: '#user-profile'
  },
  
  commands: {
    verifyUserData: function() {
      const { currentUser } = this.props;
      return this.assert.textContains('#user-name', currentUser.name);
    }
  }
};

// Test usage
module.exports = {
  'Properties Test': function(browser) {
    const userPage = browser.page.userPage();
    
    // Access static properties
    console.log('API Base URL:', userPage.props.apiBaseUrl);
    
    // Access dynamic properties
    const timestamp = userPage.props.timestamp;
    console.log('Page loaded at:', timestamp);
    
    // Use properties in commands
    userPage.verifyUserData();
  }
};

Page Object Organization

Best practices for organizing page objects in test projects.

File Structure:

pages/
├── index.js              // Export all page objects
├── loginPage.js          // Login page object
├── dashboardPage.js      // Dashboard page object
└── components/
    ├── header.js         // Reusable header section
    └── navigation.js     // Reusable navigation section

Usage Examples:

// pages/loginPage.js
module.exports = {
  url: '/login',
  elements: {
    usernameInput: '#username',
    passwordInput: '#password',
    submitButton: '#submit'
  },
  commands: {
    login: function(username, password) {
      return this
        .setValue('@usernameInput', username)
        .setValue('@passwordInput', password)
        .click('@submitButton');
    }
  }
};

// pages/index.js - Export all page objects
module.exports = {
  loginPage: require('./loginPage'),
  dashboardPage: require('./dashboardPage')
};

// nightwatch.conf.js - Configure page objects path
module.exports = {
  page_objects_path: './pages',
  // ... other config
};

// Test file usage
module.exports = {
  'E2E Test': function(browser) {
    const login = browser.page.loginPage();
    const dashboard = browser.page.dashboardPage();
    
    login
      .navigate()
      .login('testuser', 'password');
      
    dashboard
      .navigate()
      .assert.visible('@welcomeMessage');
  }
};

docs

advanced-features.md

assertions-expectations.md

browser-control.md

element-interaction.md

index.md

modern-element-api.md

page-object-model.md

programmatic-api.md

protocol-commands.md

tile.json