Skip to content

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:

import { seedSession, gotoProductPage, selectStatusFilter, expectPageLoaded } from "./helpers";

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

  1. New pages must add data-testid="{page}-page" to their root element.
  2. New workbench pages must add data-testid="status-tabs" and data-testid="data-table" to their filter and table elements.
  3. Do not assert on exact heading copy in navigation tests. Use testid or URL instead. Heading copy assertions belong in accessibility-specific tests only.
  4. Do not use getByRole("button") for product controls that may change role. Use getByTestId for status tabs, view toggles, and wizard steps.
  5. 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