E2E Selector Contract¶
Purpose: - Define stable selector attributes for e2e tests so UI refactors do not break workflow tests. - Separate accessibility assertions from product navigation assertions. - Ensure new pages follow the contract from day one.
Selector Strategy¶
data-testid for product structure¶
Every product page must declare these stable identifiers:
| Element | Attribute | Convention | Example |
|---|---|---|---|
| Page root | data-testid="{page}-page" |
kebab-case page name | data-testid="allocations-page" |
| Page heading | data-testid="page-heading" |
Same on every page | data-testid="page-heading" |
| Status tabs bar | data-testid="status-tabs" |
Same on every workbench | data-testid="status-tabs" |
| Data table | data-testid="data-table" |
Same on every workbench | data-testid="data-table" |
| Table row | data-testid="row-{id}" |
Entity ID | data-testid="row-alloc-123" |
| Primary row action | data-testid="row-open-{id}" |
Entity ID | data-testid="row-open-alloc-123" |
| More actions trigger | data-testid="more-actions-{id}" |
Entity ID | data-testid="more-actions-alloc-123" |
| Detail drawer | data-testid="detail-drawer" |
Same on every page with drawer | data-testid="detail-drawer" |
| Empty state | data-testid="empty-state" |
Same on every page | data-testid="empty-state" |
| Launch wizard step | data-testid="wizard-step-{key}" |
Step key | data-testid="wizard-step-hardware" |
| Filter bar | data-testid="filter-bar" |
Same on every workbench | data-testid="filter-bar" |
role + name for accessibility assertions¶
Keep accessible role checks for true a11y assertions, but do NOT use exact heading text for page navigation tests:
// GOOD: accessibility test
await expect(page.getByRole("heading", { level: 1 })).toBeVisible();
// BAD: navigation test using exact copy
await expect(page.getByRole("heading", { name: "Billing", exact: true })).toBeVisible();
// GOOD: navigation test using stable selector
await expect(page.getByTestId("billing-page")).toBeVisible();
URL for page identification¶
For navigation smoke tests, assert the URL rather than heading copy:
// GOOD
await page.goto("/billing");
await expect(page).toHaveURL(/\/billing/);
await expect(page.getByTestId("billing-page")).toBeVisible();
// BAD
await page.goto("/billing");
await expect(page.getByRole("heading", { name: "Billing & Usage" })).toBeVisible();
Shared E2E Helpers¶
All e2e tests should import helpers from e2e/helpers.ts:
seedSession(context, role)¶
Seeds session storage with auth tokens. Replaces the inline
context.addInitScript(...) pattern duplicated in every test file.
gotoProductPage(page, slug)¶
Navigates to a page and waits for the data-testid="{slug}-page" element.
Does not assert heading text.
selectStatusFilter(page, value)¶
Clicks a status tab by value in the data-testid="status-tabs" container.
expectPageLoaded(page, slug)¶
Asserts the page is loaded: URL matches, page root testid visible, no generic error state visible.
Rules¶
- New pages must add
data-testid="{page}-page"to their root element. - New workbench pages must add
data-testid="status-tabs"anddata-testid="data-table"to their filter and table elements. - Do not assert on exact heading copy in navigation tests. Use testid or URL instead. Heading copy assertions belong in accessibility-specific tests only.
- Do not use
getByRole("button")for product controls that may change role. UsegetByTestIdfor status tabs, view toggles, and wizard steps. - Keep an accessibility smoke suite that intentionally checks headings, roles, and labels. These tests are expected to break on copy changes and should be updated as part of copy change PRs.
Page Registry¶
| Page | Route | data-testid |
Status |
|---|---|---|---|
| Allocations | /allocations |
allocations-page |
To add |
| Workloads | /workloads |
workloads-page |
To add |
| Billing | /billing |
billing-page |
To add |
| Launch | /launch |
launch-page |
To add |
| Marketplace | /marketplace |
marketplace-page |
To add |
| Storage | /storage |
storage-page |
To add |
| Notifications | /notifications |
notifications-page |
To add |
| SSH Keys | /settings/ssh-keys |
ssh-keys-page |
To add |
| Profile | /settings/profile |
profile-page |
To add |
| Admin Overview | /admin/overview |
admin-overview-page |
To add |
| Admin Nodes | /admin/nodes |
admin-nodes-page |
Exists |
| Admin Allocations | /admin/allocations |
admin-allocations-page |
To add |
| Admin Audit Logs | /admin/audit-logs |
admin-audit-logs-page |
To add |
| Admin Users | /admin/users |
admin-users-page |
To add |
| Admin SKUs | /admin/skus |
admin-skus-page |
To add |
| Admin OS Images | /admin/os-images |
admin-os-images-page |
To add |
| Admin Payments | /admin/payments/sessions |
admin-payments-page |
To add |
| Ops Attention | /ops/attention |
ops-attention-page |
Future |
| Ops Telemetry | /ops/telemetry |
ops-telemetry-page |
Future |