Skip to main content

Page objects, helpers & fixtures

This page is the developer reference for the three infrastructure layers that sit between raw Playwright and the test spec files: page objects (POMs), utility helpers, and test fixtures.


BasePage

Every feature POM extends BasePage (tests/page-objects/BasePage.ts), which exposes four wired-up helper instances and a set of shared navigation methods.

export class BasePage {
readonly page: Page;
readonly wait: WaitHelper;
readonly tabs: TabHelper;
readonly form: FormHelper;

constructor(page: Page) { ... }

async navigateTo(url: string): Promise<void> { ... }
async clickButtonByText(text: string): Promise<void> { ... }
async clickButtonById(id: string): Promise<void> { ... }
async navigateViaSidebar(menuGroup: string, menuItem: string): Promise<void> { ... }
async searchProject(projectId: string, projectName: string): Promise<void> { ... }
async navigateToDashboard(): Promise<void> { ... }
}

navigateTo waits for DOM content loaded, network idle (with a soft catch for persistent WebSocket connections), and the block-UI loader to disappear before returning. Always use it instead of bare page.goto.

navigateViaSidebar handles accordion menus that may be open or collapsed — it checks visibility first and self-heals by toggling the accordion if the target link is not yet visible.


Feature POMs

Feature POMs live at tests/page-objects/features/{domain}/{FeatureName}Page.ts.

Naming and domain conventions

Domain folderWhat goes there
projects/Project creation, project list operations
project/Project-internal pages (team, deliverables, WBS)
financial-planning/Estimates, budgets, change orders, forecasts
portfolio/Portfolio dashboard

Structure contract

Every feature POM must:

  1. Extend BasePage (or a domain base class that extends it).
  2. Export a readonly locators object typed as const with all selectors used by the page — tests must not hardcode selectors inline.
  3. Implement a navigateToFeature() method that routes to the correct URL and waits for the page to be ready.
  4. Use helper methods (this.form, this.wait, this.tabs, AgGridHelper) for all interactions — no raw page.locator('[name=...]') in test files.

Locator export pattern

export const MY_PAGE_LOCATORS = {
createBtn: '#global-create-icon',
drawer: '#global-create-FROM_SCRATCH-drawer',
} as const;

export class MyFeaturePage extends BasePage {
readonly locators = MY_PAGE_LOCATORS;
...
}

When a child POM needs to extend a parent's locators, spread the exported constant — do not use super.locators in a class property initializer (TypeScript does not allow it):

import { PARENT_LOCATORS } from './ParentPage';

export const CHILD_LOCATORS = {
...PARENT_LOCATORS,
childBtn: '#child-btn',
} as const;

The [NEW: ClassName.method()] contract

When the Generator agent writes a spec that references a method not yet on the POM (e.g., step method: ProjectTeamPage.addMember()), it creates a stub implementation and marks it [NEW: ClassName.method()] in the output summary. The Reviewer then validates that the stub follows the same style as surrounding methods. This annotation is for human review only — it does not appear in the committed code.


Helper classes

FormHelper (tests/utils/form.helper.ts)

Accessed via this.form on any POM.

MethodSelector patternUse for
fillInput(field, value)[name="{field}"]Text and date inputs
selectDropdown(field, text)[data-testid="input-form-select-{field}"]Single-line option dropdowns
searchAndSelectDropdown(field, search, text)types to filter, partial matchSearchable dropdowns; options with subtitles
getInputValue(field)[name="{field}"]Read value for assertions
getDropdownValue(field)Read selected dropdown label
isFieldReadOnly(field)Conditional logic
hasError(field) / getErrorMessage(field)Validation error assertions
toggleCheckbox(field) / isChecked(field)Checkbox fields

Use searchAndSelectDropdown instead of selectDropdown whenever option text is multi-line (subtitle present) or the dropdown supports type-to-filter.

WaitHelper (tests/utils/wait.helper.ts)

Accessed via this.wait.

MethodPurpose
waitForLoaderToDisappear(timeout?)Waits for .blockUI and spinner overlays to hide
waitForNetworkIdle(timeout?)Waits for no pending network requests
waitForPageReady(timeout?)DOM + network + loader combined
waitForToast(type)Waits for a success/error toast to appear
expectToastAfterAction(type, actionFn)Arms toast listener before the action — prevents race condition

Always use expectToastAfterAction for toast assertions; calling waitForToast after the action risks missing a toast that auto-dismisses in ~3 s.

TabHelper (tests/utils/tab.helper.ts)

Accessed via this.tabs. Uses [data-pace-tab-id="{tabId}"] selectors.

MethodPurpose
clickTab(tabId)Click tab; waits for network idle + loaders
isTabVisible(tabId) / expectTabVisible(tabId)Check tab exists
isTabSelected(tabId)Check which tab is active

AgGridHelper (tests/utils/ag-grid.helper.ts)

Instantiated per-grid by the feature POM, passing the grid root locator. Not wired into BasePage because the root locator differs per page.

MethodPurpose
waitForGridReady()Waits for grid + first row visible
getCellValue(colId, rowIndex)Read cell text
setCellValue(colId, rowIndex, value)Double-click, type, Tab to commit
getRowByText(text)Find row containing text
scrollToRowByText(colId, text)Scroll virtual grid to find row
isCellReadOnly(colId, rowIndex)Check for pace-read-only-cell class
selectCellDropdownOption(colId, rowIndex, text)Select from a grid-cell dropdown (not FormHelper)
getRowCount()Count visible rows
clickActionIcon(rowIndex, selector)Hover row + click action-column icon

Grid action icons (delete, duplicate) live in right-pinned columns and are hidden until the row is hovered — always hover the center row first, then click the icon in the same row-index in the pinned container.


Fixtures (tests/fixtures/)

auth.fixture.ts

The test fixture file that extends Playwright's base.test. Every test file imports { test, expect } from here instead of @playwright/test.

Currently registered fixtures:

FixturePOMWhat it does before use()
loginPageLoginPageNo pre-actions (provides a bare LoginPage)
estimatesPageFinancialEstimatesPageLogs in; navigates to estimates
changeOrdersPageChangeOrdersPageLogs in; navigates to change orders
clientsAgreementsPageClientsAgreementsPageLogs in
projectCreationPageProjectCreationPageLogs in; navigates to projects
projectTeamPageProjectTeamPageLogs in; navigates to team
portfolioPagePortfolioPageLogs in
projectDeliverablesPageProjectDeliverablesPageLogs in

To add a fixture for a new page object:

  1. Import the POM at the top of tests/fixtures/auth.fixture.ts.
  2. Add the type to PaceFixtures.
  3. Implement the fixture body — log in, navigate, call use(featurePage).

data.fixture.ts

Exposes the resolved, typed data object from the test-data manifests. Use this when a test needs raw data values that are not in testConfig.

test-config.ts

Typed shorthand for the most commonly needed runtime values:

testConfig.credentials.username; // TEST_USERNAME (resolved from .env)
testConfig.credentials.password; // TEST_PASSWORD
testConfig.project.id; // project_id from data manifest
testConfig.goldenEstimate.name; // golden estimate name
testConfig.editableEstimate.name; // editable estimate name
testConfig.laborResourceName;
testConfig.taskName;

Adding new infrastructure

New page object

  1. Create tests/page-objects/features/{domain}/{FeatureName}Page.ts extending BasePage (or the domain base).
  2. Export LOCATORS as const.
  3. Implement navigateToFeature() and all interaction methods.
  4. Add a matching fixture in tests/fixtures/auth.fixture.ts.

New helper method

Add to the existing helper class in tests/utils/. Follow the same visibility/ timeout patterns as surrounding methods. Accept generic parameters (field name or locator), not hardcoded selectors. Call waitForLoaderToDisappear() after actions that trigger API calls.