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 folder | What 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:
- Extend
BasePage(or a domain base class that extends it). - Export a
readonly locatorsobject typedas constwith all selectors used by the page — tests must not hardcode selectors inline. - Implement a
navigateToFeature()method that routes to the correct URL and waits for the page to be ready. - Use helper methods (
this.form,this.wait,this.tabs,AgGridHelper) for all interactions — no rawpage.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.
| Method | Selector pattern | Use 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 match | Searchable 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.
| Method | Purpose |
|---|---|
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.
| Method | Purpose |
|---|---|
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.
| Method | Purpose |
|---|---|
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:
| Fixture | POM | What it does before use() |
|---|---|---|
loginPage | LoginPage | No pre-actions (provides a bare LoginPage) |
estimatesPage | FinancialEstimatesPage | Logs in; navigates to estimates |
changeOrdersPage | ChangeOrdersPage | Logs in; navigates to change orders |
clientsAgreementsPage | ClientsAgreementsPage | Logs in |
projectCreationPage | ProjectCreationPage | Logs in; navigates to projects |
projectTeamPage | ProjectTeamPage | Logs in; navigates to team |
portfolioPage | PortfolioPage | Logs in |
projectDeliverablesPage | ProjectDeliverablesPage | Logs in |
To add a fixture for a new page object:
- Import the POM at the top of
tests/fixtures/auth.fixture.ts. - Add the type to
PaceFixtures. - 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
- Create
tests/page-objects/features/{domain}/{FeatureName}Page.tsextendingBasePage(or the domain base). - Export
LOCATORS as const. - Implement
navigateToFeature()and all interaction methods. - 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.