Jump to content

Connect SuperML | Leeroopedia MCP: Equip your AI agents with best practices, code verification, and debugging knowledge. Powered by Leeroo — building Organizational Superintelligence. Contact us at founders@leeroo.com.

Implementation:DevExpress Testcafe Page Object Pattern

From Leeroopedia
Revision as of 11:12, 16 February 2026 by Admin (talk | contribs) (Auto-imported from implementations/DevExpress_Testcafe_Page_Object_Pattern.md)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Knowledge Sources
Domains Testing, Web_Automation, Software_Design
Last Updated 2026-02-12 04:00 GMT

Overview

Concrete implementation of the Page Object Model pattern in TestCafe using ES6 classes with Selector properties, demonstrating component composition and singleton export.

Description

TestCafe implements the Page Object Model through ES6 class-based patterns, as demonstrated in the official examples. Page classes define Selector properties in the constructor to represent page elements, with optional methods for common interactions. Smaller component classes (like Feature) can be composed into larger page objects. Page objects are typically exported as singleton instances for import in test files, though factory patterns can also be used for pages requiring parameterization.

Usage

Create a class with Selector properties representing page elements. Export as singleton or factory. Import in test files and use properties directly in TestController actions.

Code Reference

Source Location

  • Repository: testcafe
  • File: examples/basic/page-model.js
  • Lines: 1-38

Signature

// Component class pattern
class Component {
  constructor(selectorOrElement) {
    this.element = Selector(selectorOrElement);
    // Define child selectors
  }
}

// Page class pattern
class Page {
  constructor() {
    // Define selectors as properties
    this.element1 = Selector('#id');
    this.element2 = Selector('.class');

    // Compose smaller components
    this.component = new Component('#root');
  }

  // Optional: methods for complex interactions
  async performAction(testController) {
    await testController.click(this.element1);
  }
}

// Singleton export
export default new Page();

// Or factory export
export function createPage() {
  return new Page();
}

Import

// In test files
import page from './page-models/my-page';
// Or
import { createPage } from './page-models/my-page';

test('Use page object', async t => {
  await t.click(page.element1);
});

I/O Contract

Inputs

Name Type Required Description
constructor params varies No Optional parameters for page object initialization

Outputs

Name Type Description
page object object Instance with Selector properties and optional methods

Usage Examples

Basic Page Object

// page-models/login-page.js
import { Selector } from 'testcafe';

class LoginPage {
  constructor() {
    this.usernameInput = Selector('#username');
    this.passwordInput = Selector('#password');
    this.submitButton = Selector('#submit-button');
    this.errorMessage = Selector('.error-message');
  }
}

export default new LoginPage();
// tests/login.test.js
import { Selector } from 'testcafe';
import loginPage from '../page-models/login-page';

fixture`Login Tests`
  .page`https://example.com/login`;

test('Login with valid credentials', async t => {
  await t
    .typeText(loginPage.usernameInput, 'user@example.com')
    .typeText(loginPage.passwordInput, 'password123')
    .click(loginPage.submitButton)
    .expect(Selector('.dashboard').exists).ok();
});

test('Show error on invalid credentials', async t => {
  await t
    .typeText(loginPage.usernameInput, 'invalid@example.com')
    .typeText(loginPage.passwordInput, 'wrongpassword')
    .click(loginPage.submitButton)
    .expect(loginPage.errorMessage.visible).ok();
});

Component Composition

// page-models/components/navigation.js
import { Selector } from 'testcafe';

export class Navigation {
  constructor() {
    this.homeLink = Selector('nav a').withText('Home');
    this.aboutLink = Selector('nav a').withText('About');
    this.contactLink = Selector('nav a').withText('Contact');
    this.userMenu = Selector('#user-menu');
    this.logoutButton = Selector('#logout');
  }
}
// page-models/dashboard-page.js
import { Selector } from 'testcafe';
import { Navigation } from './components/navigation';

class DashboardPage {
  constructor() {
    this.nav = new Navigation();
    this.welcomeMessage = Selector('.welcome');
    this.statsPanel = Selector('.stats-panel');
    this.recentActivity = Selector('.recent-activity');
  }
}

export default new DashboardPage();
// tests/dashboard.test.js
import dashboardPage from '../page-models/dashboard-page';

fixture`Dashboard Tests`
  .page`https://example.com/dashboard`;

test('Navigate to About page', async t => {
  await t
    .click(dashboardPage.nav.aboutLink)
    .expect(Selector('h1').textContent).contains('About');
});

TestCafe Official Example

// From examples/basic/page-model.js
import { Selector } from 'testcafe';

class Feature {
  constructor(text) {
    this.label = Selector('label').withText(text);
    this.checkbox = this.label.find('input[type=checkbox]');
  }
}

class Page {
  constructor() {
    this.nameInput = Selector('#developer-name');
    this.triedTestCafeCheckbox = Selector('#tried-test-cafe');
    this.populateButton = Selector('#populate');
    this.submitButton = Selector('#submit-button');
    this.results = Selector('.result-content');
    this.commentsTextArea = Selector('#comments');

    this.featureList = [
      new Feature('Support for testing on remote devices'),
      new Feature('Re-using existing JavaScript code for testing'),
      new Feature('Running tests in background and/or in parallel'),
      new Feature('Easy embedding into a Continuous integration system'),
      new Feature('Advanced traffic and markup analysis')
    ];
  }
}

export default new Page();
// Using the official example
import page from '../page-model';

fixture`Page Model Example`
  .page`http://devexpress.github.io/testcafe/example`;

test('Fill form using page model', async t => {
  await t
    .typeText(page.nameInput, 'Peter Parker')
    .click(page.triedTestCafeCheckbox)
    .click(page.featureList[0].checkbox)
    .click(page.featureList[1].checkbox)
    .click(page.submitButton)
    .expect(page.results.textContent).contains('Peter Parker');
});

Page Object with Methods

// page-models/registration-page.js
import { Selector } from 'testcafe';

class RegistrationPage {
  constructor() {
    this.firstNameInput = Selector('#first-name');
    this.lastNameInput = Selector('#last-name');
    this.emailInput = Selector('#email');
    this.passwordInput = Selector('#password');
    this.confirmPasswordInput = Selector('#confirm-password');
    this.termsCheckbox = Selector('#terms');
    this.submitButton = Selector('button[type="submit"]');
  }

  async fillBasicInfo(t, firstName, lastName, email) {
    await t
      .typeText(this.firstNameInput, firstName)
      .typeText(this.lastNameInput, lastName)
      .typeText(this.emailInput, email);
  }

  async fillPassword(t, password) {
    await t
      .typeText(this.passwordInput, password)
      .typeText(this.confirmPasswordInput, password);
  }

  async acceptTerms(t) {
    await t.click(this.termsCheckbox);
  }

  async submit(t) {
    await t.click(this.submitButton);
  }

  async completeRegistration(t, userData) {
    await this.fillBasicInfo(t, userData.firstName, userData.lastName, userData.email);
    await this.fillPassword(t, userData.password);
    await this.acceptTerms(t);
    await this.submit(t);
  }
}

export default new RegistrationPage();
// tests/registration.test.js
import registrationPage from '../page-models/registration-page';

fixture`Registration Tests`
  .page`https://example.com/register`;

test('Complete registration', async t => {
  await registrationPage.completeRegistration(t, {
    firstName: 'John',
    lastName: 'Doe',
    email: 'john.doe@example.com',
    password: 'SecurePass123'
  });

  await t.expect(Selector('.success-message').visible).ok();
});

Parameterized Page Objects

// page-models/product-page.js
import { Selector } from 'testcafe';

class ProductPage {
  constructor(productId) {
    this.productId = productId;
    this.title = Selector('.product-title');
    this.price = Selector('.product-price');
    this.addToCartButton = Selector('#add-to-cart');
    this.quantityInput = Selector('#quantity');
    this.reviewsSection = Selector('.reviews');
  }

  get url() {
    return `https://example.com/products/${this.productId}`;
  }
}

// Factory function instead of singleton
export function createProductPage(productId) {
  return new ProductPage(productId);
}
// tests/product.test.js
import { createProductPage } from '../page-models/product-page';

fixture`Product Tests`;

test('View product details', async t => {
  const productPage = createProductPage(12345);

  await t
    .navigateTo(productPage.url)
    .expect(productPage.title.textContent).ok()
    .expect(productPage.price.textContent).match(/\$\d+\.\d{2}/);
});

Multi-level Component Hierarchy

// page-models/components/form-field.js
import { Selector } from 'testcafe';

export class FormField {
  constructor(fieldName) {
    this.container = Selector(`[data-field="${fieldName}"]`);
    this.label = this.container.find('label');
    this.input = this.container.find('input, textarea, select');
    this.errorMessage = this.container.find('.error');
  }
}
// page-models/components/contact-form.js
import { Selector } from 'testcafe';
import { FormField } from './form-field';

export class ContactForm {
  constructor() {
    this.nameField = new FormField('name');
    this.emailField = new FormField('email');
    this.messageField = new FormField('message');
    this.submitButton = Selector('button[type="submit"]');
  }
}
// page-models/contact-page.js
import { Selector } from 'testcafe';
import { ContactForm } from './components/contact-form';

class ContactPage {
  constructor() {
    this.heading = Selector('h1');
    this.form = new ContactForm();
    this.successMessage = Selector('.success-message');
  }
}

export default new ContactPage();
// tests/contact.test.js
import contactPage from '../page-models/contact-page';

fixture`Contact Tests`
  .page`https://example.com/contact`;

test('Submit contact form', async t => {
  await t
    .typeText(contactPage.form.nameField.input, 'Jane Doe')
    .typeText(contactPage.form.emailField.input, 'jane@example.com')
    .typeText(contactPage.form.messageField.input, 'Hello!')
    .click(contactPage.form.submitButton)
    .expect(contactPage.successMessage.visible).ok();
});

Related Pages

Implements Principle

Page Connections

Double-click a node to navigate. Hold to expand connections.
Principle
Implementation
Heuristic
Environment