Skip to content

data-testid Policy (Canonical)

This is the single normative rule for data-testid usage in the Open Learning Hub monorepo. App-specific docs link here instead of restating policy.

Purpose

Stable data-testid values let Playwright and Testing Library target components and containers without brittle CSS or text selectors. User-visible copy stays in i18n; never use translation strings as test hooks.

Scope

  • LMS (apps/lms) and CMS (apps/cms) custom UI under src/components/** and src/app/**.
  • Shared package @open-learning-hub/ui: primitives use shadcn data-slot; composites that accept testId / dataTestId are documented below.
  • Widget renderers (packages/widget-renderers): widget-{type}-* prefixes per 130-assessment-widgets.mdc.

Does not replace 055-data-listing-patterns.mdc (table/search/pagination hooks) or 058-search-input-ux-policy.mdc (search aria-label / placeholder).

Three layers (every user-facing surface)

Layer Required ID Example
Page / section shell {area}-page or documented alias admin-tenants-page, learn-dashboard
Primary container {area}-{feature} admin-users-table, workarea-preview
Interactive control {area}-{control} or row/menu patterns sign-in-submit, admin-user-row-actions-{id}

Thin page.tsx files may omit a shell ID when a child component owns the full contract (e.g. TenantsManageradmin-tenants-page).

Naming

  • kebab-case, coarse → fine: site-header, main-nav-link-courses, theme-option-dark.
  • Dynamic suffix for lists: admin-user-row-actions-{id}, learn-dashboard-enrollment-{slug}.
  • Area prefixes (CMS authoring; LMS may use the same where helpful):
Prefix Use
shared- Cross-app chrome (header, theme toggle)
auth-{form}- Auth forms (auth-login-form-email)
workarea- / sidebar- / editor- CMS workarea
admin-{entity}-{form\|list\|row}- Admin CRUD
widget-{type}- Widget internals

Row menus: apps/cms/docs/reference/menu_actions.md — LMS uses the same trigger/item pattern via AdminRowActionsMenu.

Search inputs: apps/cms/docs/reference/searching.md ({surface}-search, {surface}-search-clear).

What must have a test id

  • All interactive controls: button / Button, a / Link, input / Input / Textarea, Switch, Select + trigger/items, DropdownMenuItem, dialog confirm/cancel.
  • Exported custom component roots get one stable id or documented delegation to a child that owns the contract.
  • ConfirmDialog (@open-learning-hub/ui/confirm-dialog): pass testId; cancel/confirm become ${testId}-cancel / ${testId}-confirm.

Exemptions (do not flag in audit)

Category Examples
@open-learning-hub/ui / app components/ui/* primitives Use wrapper testids or data-slot
Providers / adapters ThemeProvider, session-provider, widget-host-adapter
Pure presentational icons GateStateIcon, decorative SVGs
Thin server wrappers MainNavMainNavView (leaf owns IDs)
DnD-only drop zones Unless e2e targets them

Prop patterns

  • data-testid on DOM nodes.
  • dataTestId, testId, dataTestIdPrefix, testIdPrefix, rootTestId, triggerTestId on shared components (forward to DOM or children).

Audit tooling

Use npm run audit:testids from the repo root (report-only). Canonical script usage and flags (--json, --write-registry) are documented in Root Scripts Reference.

Not part of blocking npm run check until a future enforcement phase.

Testing

Future work (out of current scope)

  • LMS Playwright specs for learner flows (learn-dashboard, catalog, enroll, course entry).
  • Promote audit:testids to blocking CI in npm run check.
  • Vitest catalogue for widget-* parity between CMS editor and packages/widget-renderers.