E2E Selectors
Use stable data-* attributes for Playwright E2E tests.
Why
- •CSS classes change (Tailwind refactors)
- •Text changes (i18n translations)
- •DOM structure changes (component rewrites)
- •Portals render dropdowns under
document.body, breaking scoped selectors
Rules
Naming
- •Use
kebab-casefor attribute names - •Use short, explicit ids:
edit,delete,view,create,submit - •Prefix by feature when needed:
storage-<id>,product-<id>
Standard Attributes
| Attribute | Purpose | Example |
|---|---|---|
data-nav-id | Sidebar nav item | data-nav-id="storage" |
data-menu-id | Action menu instance | data-menu-id="storage-<uuid>" |
data-action-id | Action inside menu | data-action-id="edit" |
data-target-id | Entity id for menu | data-target-id="<uuid>" |
data-modal-id | Modal instance | data-modal-id="create-storage" |
data-modal-confirm | Modal confirm button | data-modal-confirm |
Patterns
Sidebar Navigation
tsx
// Component
<button data-nav-id="storage">Storage</button>
// Playwright
await page.locator('[data-nav-id="storage"]').click();
Action Menu (with Portal)
tsx
// Component uses ActiveMenu with menuId prop
<ActiveMenu menuId={`storage-${item.id}`} items={[
{ id: 'edit', label: 'Edit', ... },
{ id: 'delete', label: 'Delete', ... }
]} />
// Playwright - open menu
await page.locator('[data-menu-trigger][data-menu-id="storage-<id>"]').click();
// Playwright - click action (works even with portal!)
await page.locator('[data-menu-action][data-menu-id="storage-<id>"][data-action-id="delete"]').click();
Modal Confirm
tsx
// Playwright
await page.locator('[data-modal-confirm][data-modal-id="create-storage"]').click();
When to Add Selectors
Add data-* when element is:
- •Part of critical flow (create/edit/delete, navigation, submit)
- •Hard to select reliably (portals, hover-only, icons without text, i18n)
Don't add everywhere—only where E2E needs stability.
Checklist (PR Review)
- • Selectors don't depend on Tailwind classes
- • Selectors don't depend on translated text
- • Portaled UI has stable
data-menu-id - • Actions have stable
data-action-id - • Values are deterministic (no random strings)