Get started

Page Object Model (POM)

Introduction

The Page Object Model (POM) is a pattern where you put selectors and page actions in separate classes (page objects) and use them in tests. This keeps tests readable and makes it easier to update selectors in one place when the UI changes.

CSTesting supports POM by:

  • Scaffolding a pages/ and tests/ structure with sample code via npx cstesting init
  • Letting you pass the browser into page object classes so they call browser.goto(), browser.click(), browser.locator(), etc.

This guide describes how to use POM with CSTesting.

You will learn

  • What the Page Object Model is and why to use it
  • How to scaffold the structure with cstesting init
  • How to write a page object and use it in tests
  • How to add more page objects (e.g. login, dashboard)

Why use Page Objects?

  • Single place for selectors — If a button's selector changes, you update it in the page object, not in every test.
  • Readable tests — Tests call loginPage.typeUsername('admin') instead of browser.type('[name="userName"]', 'admin').
  • Reuse — The same page object can be used in many tests and suites.
  • Clear structurepages/ holds page classes; tests/ holds test files.

Scaffold the structure

From your project root, run:

npx cstesting init
# or
npx cst init

This creates:

  • pages/ — folder for page object classes
  • tests/ — folder for test files
  • pages/HomePage.js — sample page object for example.com
  • tests/home.test.js — sample test that uses HomePage

Existing files are not overwritten. You can add more page objects and tests as needed.

Run the sample tests:

npx cstesting tests/
# or
npx cstesting tests/home.test.js

Structure

your-project/
  pages/
    HomePage.js      # Page object for the home page
    LoginPage.js     # Page object for the login page
    ...
  tests/
    home.test.js     # Tests using HomePage
    login.test.js    # Tests using LoginPage
    ...
  • Page objects — Classes that receive the browser and expose methods (e.g. goto(), clickSubmit(), getHeadingText()). They use this.browser to call goto, click, type, locator, waitForLoad, etc.
  • Tests — Create the browser (e.g. in beforeAll), instantiate page objects with that browser, then call page object methods and use expect() for assertions.

Example: HomePage (scaffolded)

The scaffolded pages/HomePage.js looks like this:

/**
 * Page Object: Home page (example.com).
 * Centralizes selectors and page actions — use in tests for maintainability.
 */

class HomePage {
  constructor(browser) {
    this.browser = browser;
  }

  /** Page URL */
  get url() {
    return 'https://example.com';
  }

  /** Navigate to the home page */
  async goto() {
    await this.browser.goto(this.url);
    await this.browser.waitForLoad();
  }

  /** Get the main heading text */
  async getHeadingText() {
    const text = await this.browser.evaluate(
      "document.querySelector('h1') ? document.querySelector('h1').textContent : ''"
    );
    return text;
  }

  /** Click the "More information..." link */
  async clickMoreInfo() {
    const link = this.browser.locator('a');
    await link.click();
    await this.browser.waitForLoad();
  }

  /** Get page title */
  async getTitle() {
    return this.browser.evaluate('document.title');
  }
}

module.exports = HomePage;

tests/home.test.js uses it:

const path = require('path');
const cstesting = (() => {
  try { return require('cstesting'); } catch { return require(path.join(__dirname, '..')); }
})();
const { describe, it, expect, beforeAll, afterAll } = cstesting;
const HomePage = require('../pages/HomePage');

describe('Home page (POM)', () => {
  let browser;
  let homePage;

  beforeAll(async () => {
    browser = await cstesting.createBrowser({ headless: true });
    homePage = new HomePage(browser);
  });

  afterAll(async () => {
    if (browser) await browser.close();
  });

  it('should open home page and show Example Domain heading', async () => {
    await homePage.goto();
    const heading = await homePage.getHeadingText();
    expect(heading).toContain('Example Domain');
  });

  it('should have correct page title', async () => {
    await homePage.goto();
    const title = await homePage.getTitle();
    expect(title).toContain('Example');
  });

  it('should navigate when clicking More information link', async () => {
    await homePage.goto();
    await homePage.clickMoreInfo();
    const html = await browser.content();
    expect(html.length).toBeGreaterThan(0);
  });
});

Example: LoginPage (custom)

You can add more page objects for other screens. Example pages/LoginPage.js:

class LoginPage {
  constructor(browser) {
    this.browser = browser;
  }

  get url() {
    return 'https://example.com/login';
  }

  async goto() {
    await this.browser.goto(this.url);
    await this.browser.waitForLoad();
  }

  async typeUsername(value) {
    await this.browser.type('[name="userName"]', value);
  }

  async typePassword(value) {
    await this.browser.type('[name="password"]', value);
  }

  async submit() {
    await this.browser.click('[name="submit"]');
  }
}

module.exports = LoginPage;

tests/login.test.js:

const { describe, it, expect, beforeAll, afterAll, createBrowser } = require('cstesting');
const LoginPage = require('../pages/LoginPage');

describe('Login (POM)', () => {
  let browser;
  let loginPage;

  beforeAll(async () => {
    browser = await createBrowser({ headless: true });
    loginPage = new LoginPage(browser);
  });

  afterAll(async () => {
    if (browser) await browser.close();
  });

  it('logs in with valid credentials', async () => {
    await loginPage.goto();
    await loginPage.typeUsername('mercury');
    await loginPage.typePassword('secret');
    await loginPage.submit();
    const url = await browser.url();
    expect(url).not.toContain('/login');
  });
});

What you can do inside a page object

Page objects receive the browser (or a tab handle). You can use any browser API:

MethodUse in page object
this.browser.goto(url)Navigate
this.browser.click(selector)Click
this.browser.type(selector, text)Type into input
this.browser.locator(selector)Get locator: .click(), .type(text), .check(), .select(option)
this.browser.getByAttribute(attr, value)Locate by attribute
this.browser.select(selector, option)Select dropdown option
this.browser.check(selector) / uncheck(selector)Checkbox / radio
this.browser.waitForSelector(selector, { timeout })Wait for element
this.browser.waitForLoad()Wait for load event
this.browser.content()Get HTML string
this.browser.url()Get current URL
this.browser.evaluate(expression)Run JavaScript in the page

Keep selectors inside the page object; expose methods to the tests (e.g. clickSubmit(), getHeadingText()). Tests should not need to know CSS selectors.


Best practices

  1. One class per page (or major section) — e.g. HomePage, LoginPage, DashboardPage.
  2. Constructor takes browserconstructor(browser) { this.browser = browser; } so the same browser is reused across page objects if needed.
  3. Expose actions and getters, hide selectors — Tests call loginPage.typeUsername('x'), not browser.type('[name="userName"]', 'x').
  4. Optional: expose URL — A url getter or goto() method keeps navigation in one place.
  5. Use locators when it helps — e.g. this.browser.locator('button.primary').click() inside the page object to keep selectors out of tests.
  6. Reuse in multiple tests — Create the browser once in beforeAll, create the page object once, then use it in each test (and navigate or reset in beforeEach if needed).

Summary

StepAction
ScaffoldRun npx cstesting init to create pages/ and tests/ with sample HomePage and home.test.js.
Page objectCreate a class that takes browser in the constructor and uses this.browser for actions. Export the class.
TestIn beforeAll, create the browser and the page object (new HomePage(browser)). In tests, call page object methods and use expect().
Runnpx cstesting tests/ or npx cstesting tests/home.test.js.

The Page Object Model in CSTesting is plain JavaScript classes and the standard browser API — no extra framework. You can use the same pattern in TypeScript by giving the browser parameter a type (e.g. BrowserApi from cstesting).