feat(skills): create html-template skill for E2E and ARIA attributes

- Create comprehensive html-template skill with 3 reference files
  - E2E Testing Attributes (data-what, data-which patterns)
  - ARIA Accessibility Attributes (roles, properties, states, WCAG)
  - Combined Patterns (real-world examples with both)
- Move E2E attribute guidance from command to skill
- Add extensive ARIA accessibility documentation
- Update angular-template skill with cross-references
- Remove dev-add-e2e-attrs command (functionality now in skill)

The new skill provides 3,322 lines of comprehensive documentation
covering both testing and accessibility best practices for HTML
templates, with practical examples for forms, navigation, tables,
dialogs, and more.

Benefits:
- Clean separation: Angular syntax vs HTML attributes
- Reusable: html-template works with any HTML context
- Comprehensive: E2E + ARIA in one place
- Integrated: Works seamlessly with angular-template skill
This commit is contained in:
Lorenz Hilpert
2025-10-29 13:21:29 +01:00
parent f87d3a35d9
commit bf30ec1213
6 changed files with 3325 additions and 198 deletions

View File

@@ -1,197 +0,0 @@
# /dev:add-e2e-attrs - Add E2E Test Attributes
Add required E2E test attributes (`data-what`, `data-which`, dynamic `data-*`) to component templates for QA automation.
## Parameters
- `component-path`: Path to component directory or HTML template file
## Required E2E Attributes
### Core Attributes (Required)
1. **`data-what`**: Semantic description of element's purpose
- Example: `data-what="submit-button"`, `data-what="search-input"`
2. **`data-which`**: Unique identifier for the specific instance
- Example: `data-which="primary"`, `data-which="customer-{{ customerId }}"`
### Dynamic Attributes (Contextual)
3. **`data-*`**: Additional context based on state/data
- Example: `data-status="active"`, `data-index="0"`
## Tasks
### 1. Analyze Component Template
- Read component HTML template
- Identify interactive elements that need E2E attributes:
- Buttons (`button`, `ui-button`)
- Inputs (`input`, `textarea`, `select`)
- Links (`a`, `routerLink`)
- Custom interactive components
- Form elements
- Clickable elements (`(click)` handlers)
### 2. Add Missing Attributes
**Buttons:**
```html
<!-- BEFORE -->
<button (click)="submit()">Submit</button>
<!-- AFTER -->
<button
(click)="submit()"
data-what="submit-button"
data-which="form-primary">
Submit
</button>
```
**Inputs:**
```html
<!-- BEFORE -->
<input [(ngModel)]="searchTerm" placeholder="Search..." />
<!-- AFTER -->
<input
[(ngModel)]="searchTerm"
placeholder="Search..."
data-what="search-input"
data-which="main-search" />
```
**Dynamic Lists:**
```html
<!-- BEFORE -->
@for (item of items; track item.id) {
<li (click)="selectItem(item)">{{ item.name }}</li>
}
<!-- AFTER -->
@for (item of items; track item.id) {
<li
(click)="selectItem(item)"
data-what="list-item"
[attr.data-which]="item.id"
[attr.data-status]="item.status">
{{ item.name }}
</li>
}
```
**Links:**
```html
<!-- BEFORE -->
<a routerLink="/orders/{{ orderId }}">View Order</a>
<!-- AFTER -->
<a
[routerLink]="['/orders', orderId]"
data-what="order-link"
[attr.data-which]="orderId">
View Order
</a>
```
**Custom Components:**
```html
<!-- BEFORE -->
<ui-button (click)="save()">Save</ui-button>
<!-- AFTER -->
<ui-button
(click)="save()"
data-what="save-button"
data-which="order-form">
Save
</ui-button>
```
### 3. Naming Conventions
**`data-what` Guidelines:**
- Use kebab-case
- Be descriptive but concise
- Common patterns:
- `*-button` (submit-button, cancel-button, delete-button)
- `*-input` (email-input, search-input, quantity-input)
- `*-link` (product-link, order-link, customer-link)
- `*-item` (list-item, menu-item, card-item)
- `*-dialog` (confirm-dialog, error-dialog)
- `*-dropdown` (status-dropdown, category-dropdown)
**`data-which` Guidelines:**
- Unique identifier for the instance
- Use dynamic binding for list items: `[attr.data-which]="item.id"`
- Static for unique elements: `data-which="primary"`
- Combine with context: `data-which="customer-{{ customerId }}-edit"`
### 4. Scan for Coverage
Check template coverage:
```bash
# Count interactive elements
grep -E '(click)=|routerLink|button|input|select|textarea' [template-file]
# Count elements with data-what
grep -c 'data-what=' [template-file]
# List elements missing E2E attributes
grep -E '(click)=|button' [template-file] | grep -v 'data-what='
```
### 5. Validate Attributes
- No duplicates in `data-which` within same view
- All interactive elements have both `data-what` and `data-which`
- Dynamic attributes use proper Angular binding: `[attr.data-*]`
- Attributes don't contain sensitive data (passwords, tokens)
### 6. Update Component Tests
Add E2E attribute selectors to tests:
```typescript
// Use E2E attributes for element selection
const submitButton = fixture.nativeElement.querySelector('[data-what="submit-button"][data-which="primary"]');
expect(submitButton).toBeTruthy();
```
### 7. Document Attributes
Add comment block at top of template:
```html
<!--
E2E Test Attributes:
- data-what="submit-button" data-which="primary" - Main form submission
- data-what="cancel-button" data-which="primary" - Cancel action
- data-what="search-input" data-which="main" - Product search field
-->
```
## Output
Provide summary:
- Template analyzed: [path]
- Interactive elements found: [count]
- Attributes added: [count]
- Coverage: [percentage]% (elements with E2E attrs / total interactive elements)
- List of added attributes with descriptions
- Validation status: ✅/❌
## Common Patterns by Component Type
**Form Components:**
- `data-what="[field]-input" data-which="[form-name]"`
- `data-what="submit-button" data-which="[form-name]"`
- `data-what="cancel-button" data-which="[form-name]"`
**List/Table Components:**
- `data-what="list-item" [attr.data-which]="item.id"`
- `data-what="edit-button" [attr.data-which]="item.id"`
- `data-what="delete-button" [attr.data-which]="item.id"`
**Navigation Components:**
- `data-what="nav-link" data-which="[destination]"`
- `data-what="breadcrumb" data-which="[level]"`
**Dialog Components:**
- `data-what="confirm-button" data-which="dialog"`
- `data-what="close-button" data-which="dialog"`
## References
- CLAUDE.md Code Quality section (E2E Testing Requirements)
- docs/guidelines/testing.md
- QA team E2E test documentation (if available)

View File

@@ -15,6 +15,8 @@ Guide for modern Angular 20+ template patterns: control flow, lazy loading, proj
- Designing reusable components with `ng-content`
- Template performance optimization
**Related Skill:** For E2E testing attributes (`data-what`, `data-which`) and ARIA accessibility attributes, see the **[html-template](../html-template/SKILL.md)** skill. Both skills work together when writing Angular templates.
## Control Flow (Angular 17+)
### @if / @else if / @else
@@ -211,7 +213,7 @@ Groups elements without DOM footprint:
1. **Use signals:** `isExpanded = signal(false)`
2. **Prefer control flow over directives:** Use `@if` not `*ngIf`
3. **Keep expressions simple:** Use `computed()` for complex logic
4. **E2E attributes:** Always add `[attr.data-what]` and `[attr.data-which]`
4. **Testing & Accessibility:** Always add E2E and ARIA attributes (see **[html-template](../html-template/SKILL.md)** skill)
5. **Track expressions:** Required in `@for`, use unique IDs
## Migration

View File

@@ -0,0 +1,291 @@
# HTML Template - Testing & Accessibility Attributes
This skill should be used when writing or reviewing HTML templates to ensure proper testing and accessibility attributes are included.
## When to Use This Skill
Use this skill when:
- Writing or modifying Angular component templates
- Creating any HTML templates or markup
- Reviewing code for testing and accessibility compliance
- Adding interactive elements (buttons, inputs, links, etc.)
- Implementing forms, lists, navigation, or dialogs
**Works seamlessly with:** `angular-template` skill for complete Angular template guidance.
## Overview
This skill provides comprehensive guidance for two critical HTML attribute categories:
### 1. E2E Testing Attributes
Enable automated end-to-end testing by providing stable selectors for QA automation:
- **`data-what`**: Semantic description of element's purpose
- **`data-which`**: Unique identifier for specific instances
- **`data-*`**: Additional contextual information
### 2. ARIA Accessibility Attributes
Ensure web applications are accessible to all users, including those using assistive technologies:
- **Roles**: Define element purpose (button, navigation, dialog, etc.)
- **Properties**: Provide additional context (aria-label, aria-describedby)
- **States**: Indicate dynamic states (aria-expanded, aria-disabled)
- **Live Regions**: Announce dynamic content changes
## Why Both Are Essential
- **E2E Attributes**: Enable reliable automated testing without brittle CSS or XPath selectors
- **ARIA Attributes**: Ensure compliance with WCAG standards and improve user experience for people with disabilities
- **Together**: Create robust, testable, and accessible web applications
## Quick Reference
### Button Example
```html
<button
type="button"
(click)="onSubmit()"
data-what="submit-button"
data-which="registration-form"
aria-label="Submit registration form">
Submit
</button>
```
### Input Example
```html
<input
type="text"
[(ngModel)]="email"
data-what="email-input"
data-which="registration-form"
aria-label="Email address"
aria-describedby="email-hint"
aria-required="true" />
<span id="email-hint">We'll never share your email</span>
```
### Dynamic List Example
```html
@for (item of items; track item.id) {
<li
(click)="selectItem(item)"
data-what="list-item"
[attr.data-which]="item.id"
[attr.data-status]="item.status"
[attr.aria-label]="'Select ' + item.name"
role="button"
tabindex="0">
{{ item.name }}
</li>
}
```
### Link Example
```html
<a
[routerLink]="['/orders', orderId]"
data-what="order-link"
[attr.data-which]="orderId"
[attr.aria-label]="'View order ' + orderNumber">
View Order #{{ orderNumber }}
</a>
```
### Dialog Example
```html
<div
class="dialog"
data-what="confirmation-dialog"
data-which="delete-item"
role="dialog"
aria-modal="true"
aria-labelledby="dialog-title"
aria-describedby="dialog-description">
<h2 id="dialog-title">Confirm Deletion</h2>
<p id="dialog-description">Are you sure you want to delete this item?</p>
<button
(click)="confirm()"
data-what="confirm-button"
data-which="delete-dialog"
aria-label="Confirm deletion">
Delete
</button>
<button
(click)="cancel()"
data-what="cancel-button"
data-which="delete-dialog"
aria-label="Cancel deletion">
Cancel
</button>
</div>
```
## Common Patterns by Element Type
### Interactive Elements That Need Attributes
**Required attributes for:**
- Buttons (`<button>`, `<ui-button>`, custom button components)
- Form inputs (`<input>`, `<textarea>`, `<select>`)
- Links (`<a>`, `[routerLink]`)
- Clickable elements (elements with `(click)` handlers)
- Custom interactive components
- List items in dynamic lists
- Navigation items
- Dialog/modal controls
### Naming Conventions
**E2E `data-what` patterns:**
- `*-button` (submit-button, cancel-button, delete-button)
- `*-input` (email-input, search-input, quantity-input)
- `*-link` (product-link, order-link, customer-link)
- `*-item` (list-item, menu-item, card-item)
- `*-dialog` (confirm-dialog, error-dialog, info-dialog)
- `*-dropdown` (status-dropdown, category-dropdown)
**E2E `data-which` guidelines:**
- Use unique identifiers: `data-which="primary"`, `data-which="customer-list"`
- Bind dynamically for lists: `[attr.data-which]="item.id"`
- Combine with context: `data-which="customer-{{ customerId }}-edit"`
**ARIA role patterns:**
- Interactive elements: `button`, `link`, `menuitem`
- Structural: `navigation`, `main`, `complementary`, `contentinfo`
- Widget: `dialog`, `alertdialog`, `tooltip`, `tablist`, `tab`
- Landmark: `banner`, `search`, `form`, `region`
## Best Practices
### E2E Attributes
1. ✅ Add to ALL interactive elements
2. ✅ Use kebab-case for `data-what` values
3. ✅ Ensure `data-which` is unique within the view
4. ✅ Use Angular binding for dynamic values: `[attr.data-*]`
5. ✅ Avoid including sensitive data in attributes
6. ✅ Document complex attribute patterns in template comments
### ARIA Attributes
1. ✅ Use semantic HTML first (use `<button>` instead of `<div role="button">`)
2. ✅ Provide text alternatives for all interactive elements
3. ✅ Ensure proper keyboard navigation (tabindex, focus management)
4. ✅ Use `aria-label` when visual label is missing
5. ✅ Use `aria-labelledby` to reference existing visible labels
6. ✅ Keep ARIA attributes in sync with visual states
7. ✅ Test with screen readers (NVDA, JAWS, VoiceOver)
### Combined Best Practices
1. ✅ Add both E2E and ARIA attributes to every interactive element
2. ✅ Keep attributes close together in the HTML for readability
3. ✅ Update tests to use `data-what` and `data-which` selectors
4. ✅ Validate coverage: all interactive elements should have both types
5. ✅ Review with QA and accessibility teams
## Detailed References
For comprehensive guides, examples, and patterns, see:
- **[E2E Testing Attributes](references/e2e-attributes.md)** - Complete E2E attribute patterns and conventions
- **[ARIA Accessibility Attributes](references/aria-attributes.md)** - Comprehensive ARIA guidance and WCAG compliance
- **[Combined Patterns](references/combined-patterns.md)** - Real-world examples with both attribute types
## Project-Specific Links
- **Testing Guidelines**: `docs/guidelines/testing.md` - Project testing standards including E2E attributes
- **CLAUDE.md**: Project conventions and requirements
- **Angular Template Skill**: `.claude/skills/angular-template` - For Angular-specific syntax
## Validation Checklist
Before considering template complete:
- [ ] All buttons have `data-what`, `data-which`, and `aria-label`
- [ ] All inputs have `data-what`, `data-which`, and appropriate ARIA attributes
- [ ] All links have `data-what`, `data-which`, and descriptive ARIA labels
- [ ] Dynamic lists use `[attr.data-*]` bindings with unique identifiers
- [ ] Dialogs have proper ARIA roles and relationships
- [ ] Forms have proper field associations and error announcements
- [ ] Interactive elements are keyboard accessible (tabindex where needed)
- [ ] No duplicate `data-which` values within the same view
- [ ] Screen reader testing completed (if applicable)
## Example: Complete Form
```html
<form
data-what="registration-form"
data-which="user-signup"
role="form"
aria-labelledby="form-title">
<h2 id="form-title">User Registration</h2>
<div class="form-field">
<label for="username-input">Username</label>
<input
id="username-input"
type="text"
[(ngModel)]="username"
data-what="username-input"
data-which="registration-form"
aria-required="true"
aria-describedby="username-hint" />
<span id="username-hint">Must be at least 3 characters</span>
</div>
<div class="form-field">
<label for="email-input">Email</label>
<input
id="email-input"
type="email"
[(ngModel)]="email"
data-what="email-input"
data-which="registration-form"
aria-required="true"
[attr.aria-invalid]="emailError ? 'true' : null"
aria-describedby="email-error" />
@if (emailError) {
<span
id="email-error"
role="alert"
aria-live="polite">
{{ emailError }}
</span>
}
</div>
<div class="form-actions">
<button
type="submit"
(click)="onSubmit()"
data-what="submit-button"
data-which="registration-form"
[attr.aria-disabled]="!isValid"
aria-label="Submit registration form">
Register
</button>
<button
type="button"
(click)="onCancel()"
data-what="cancel-button"
data-which="registration-form"
aria-label="Cancel registration">
Cancel
</button>
</div>
</form>
```
## Remember
- **Always use both E2E and ARIA attributes together**
- **E2E attributes enable automated testing** - your QA team relies on them
- **ARIA attributes enable accessibility** - legal requirement and right thing to do
- **Test with real users and assistive technologies** - automated checks aren't enough
- **Keep attributes up-to-date** - maintain as code changes
---
**This skill works automatically with Angular templates. Both E2E and ARIA attributes should be added to every interactive element.**

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,842 @@
# E2E Testing Attributes - Complete Reference
This reference provides comprehensive guidance for adding E2E (End-to-End) testing attributes to HTML templates for reliable automated testing.
## Table of Contents
- [Overview](#overview)
- [Core Attribute Types](#core-attribute-types)
- [Why E2E Attributes?](#why-e2e-attributes)
- [Naming Conventions](#naming-conventions)
- [Patterns by Element Type](#patterns-by-element-type)
- [Patterns by Component Type](#patterns-by-component-type)
- [Dynamic Attributes](#dynamic-attributes)
- [Best Practices](#best-practices)
- [Validation](#validation)
- [Testing Integration](#testing-integration)
## Overview
E2E testing attributes provide stable, semantic selectors for automated testing. They enable QA automation without relying on brittle CSS classes, IDs, or XPath selectors that frequently break when styling changes.
## Core Attribute Types
### 1. `data-what` (Required)
**Purpose**: Semantic description of the element's purpose or type
**Format**: kebab-case string
**Examples**:
- `data-what="submit-button"`
- `data-what="search-input"`
- `data-what="product-link"`
- `data-what="list-item"`
**Guidelines**:
- Describes WHAT the element is or does
- Should be consistent across similar elements
- Use descriptive, semantic names
- Keep it concise but clear
### 2. `data-which` (Required)
**Purpose**: Unique identifier for the specific instance of this element type
**Format**: kebab-case string or dynamic binding
**Examples**:
- `data-which="primary"` (static)
- `data-which="customer-form"` (static)
- `[attr.data-which]="item.id"` (dynamic)
- `[attr.data-which]="'customer-' + customerId"` (dynamic with context)
**Guidelines**:
- Identifies WHICH specific instance of this element type
- Must be unique within the same view/component
- Use dynamic binding for list items: `[attr.data-which]="item.id"`
- Can combine multiple identifiers: `data-which="customer-123-edit"`
### 3. `data-*` (Contextual)
**Purpose**: Additional contextual information about state, status, or data
**Format**: Custom attributes with kebab-case names
**Examples**:
- `data-status="active"`
- `data-index="0"`
- `data-role="admin"`
- `[attr.data-count]="items.length"`
**Guidelines**:
- Use for additional context that helps testing
- Avoid sensitive data (passwords, tokens, PII)
- Use Angular binding for dynamic values: `[attr.data-*]`
- Keep attribute names semantic and clear
## Why E2E Attributes?
### Problems with Traditional Selectors
**CSS Classes (Bad)**:
```html
<!-- Brittle - breaks when styling changes -->
<button class="btn btn-primary submit">Submit</button>
```
```javascript
// Test breaks when class names change
await page.click('.btn-primary.submit');
```
**XPath (Bad)**:
```javascript
// Brittle - breaks when structure changes
await page.click('//div[@class="form"]/button[2]');
```
**IDs (Better, but limited)**:
```html
<!-- IDs must be unique across entire page -->
<button id="submit-btn">Submit</button>
```
### Benefits of E2E Attributes
**Stable, Semantic Selectors (Good)**:
```html
<button
class="btn btn-primary"
data-what="submit-button"
data-which="registration-form">
Submit
</button>
```
```javascript
// Stable - survives styling and structure changes
await page.click('[data-what="submit-button"][data-which="registration-form"]');
```
**Advantages**:
- ✅ Decoupled from styling (CSS classes can change freely)
- ✅ Semantic and self-documenting
- ✅ Consistent across the application
- ✅ Easy to read and maintain
- ✅ Survives refactoring and restructuring
- ✅ QA and developers speak the same language
## Naming Conventions
### Common `data-what` Patterns
| Pattern | Use Case | Examples |
|---------|----------|----------|
| `*-button` | All button elements | `submit-button`, `cancel-button`, `delete-button`, `save-button` |
| `*-input` | Text inputs and textareas | `email-input`, `search-input`, `quantity-input`, `password-input` |
| `*-select` | Dropdown/select elements | `status-select`, `category-select`, `country-select` |
| `*-checkbox` | Checkbox inputs | `terms-checkbox`, `subscribe-checkbox`, `remember-checkbox` |
| `*-radio` | Radio button inputs | `payment-radio`, `shipping-radio` |
| `*-link` | Navigation links | `product-link`, `order-link`, `customer-link`, `home-link` |
| `*-item` | List/grid items | `list-item`, `menu-item`, `card-item`, `row-item` |
| `*-dialog` | Modals and dialogs | `confirm-dialog`, `error-dialog`, `info-dialog` |
| `*-dropdown` | Dropdown menus | `actions-dropdown`, `filter-dropdown` |
| `*-toggle` | Toggle switches | `theme-toggle`, `notifications-toggle` |
| `*-tab` | Tab navigation | `profile-tab`, `settings-tab` |
| `*-badge` | Status badges | `status-badge`, `count-badge` |
| `*-icon` | Interactive icons | `close-icon`, `menu-icon`, `search-icon` |
### `data-which` Naming Guidelines
**Static unique identifiers** (single instance):
- `data-which="primary"` - Primary action button
- `data-which="secondary"` - Secondary action button
- `data-which="main-search"` - Main search input
- `data-which="customer-form"` - Customer form context
**Dynamic identifiers** (multiple instances):
- `[attr.data-which]="item.id"` - List item by ID
- `[attr.data-which]="'product-' + product.id"` - Product item
- `[attr.data-which]="index"` - By array index (use sparingly)
**Contextual identifiers** (combine context):
- `data-which="customer-{{ customerId }}-edit"` - Edit button for specific customer
- `data-which="order-{{ orderId }}-cancel"` - Cancel button for specific order
## Patterns by Element Type
### Buttons
```html
<!-- Submit Button -->
<button
type="submit"
(click)="onSubmit()"
data-what="submit-button"
data-which="registration-form">
Submit
</button>
<!-- Cancel Button -->
<button
type="button"
(click)="onCancel()"
data-what="cancel-button"
data-which="registration-form">
Cancel
</button>
<!-- Delete Button with Confirmation -->
<button
(click)="onDelete(item)"
data-what="delete-button"
[attr.data-which]="item.id"
[attr.data-status]="item.canDelete ? 'enabled' : 'disabled'">
Delete
</button>
<!-- Icon Button -->
<button
(click)="toggleMenu()"
data-what="menu-button"
data-which="main-nav"
aria-label="Toggle menu">
<i class="icon-menu"></i>
</button>
<!-- Custom Button Component -->
<ui-button
(click)="save()"
data-what="save-button"
data-which="order-form">
Save Order
</ui-button>
```
### Inputs
```html
<!-- Text Input -->
<input
type="text"
[(ngModel)]="email"
placeholder="Email address"
data-what="email-input"
data-which="registration-form" />
<!-- Textarea -->
<textarea
[(ngModel)]="comments"
data-what="comments-textarea"
data-which="feedback-form"
rows="4"></textarea>
<!-- Number Input with State -->
<input
type="number"
[(ngModel)]="quantity"
data-what="quantity-input"
data-which="order-form"
[attr.data-min]="minQuantity"
[attr.data-max]="maxQuantity" />
<!-- Search Input -->
<input
type="search"
[(ngModel)]="searchTerm"
(input)="onSearch()"
placeholder="Search products..."
data-what="search-input"
data-which="product-catalog" />
<!-- Password Input -->
<input
type="password"
[(ngModel)]="password"
data-what="password-input"
data-which="login-form" />
```
### Select/Dropdown
```html
<!-- Basic Select -->
<select
[(ngModel)]="selectedStatus"
data-what="status-select"
data-which="order-filter">
<option value="">All Statuses</option>
<option value="pending">Pending</option>
<option value="completed">Completed</option>
</select>
<!-- Custom Dropdown Component -->
<ui-dropdown
[(value)]="selectedCategory"
data-what="category-dropdown"
data-which="product-filter">
</ui-dropdown>
```
### Checkboxes and Radios
```html
<!-- Checkbox -->
<label>
<input
type="checkbox"
[(ngModel)]="agreedToTerms"
data-what="terms-checkbox"
data-which="registration-form" />
I agree to the terms
</label>
<!-- Radio Group -->
<div data-what="payment-radio-group" data-which="checkout-form">
<label>
<input
type="radio"
name="payment"
value="credit"
[(ngModel)]="paymentMethod"
data-what="payment-radio"
data-which="credit-card" />
Credit Card
</label>
<label>
<input
type="radio"
name="payment"
value="paypal"
[(ngModel)]="paymentMethod"
data-what="payment-radio"
data-which="paypal" />
PayPal
</label>
</div>
```
### Links
```html
<!-- Static Link -->
<a
routerLink="/about"
data-what="nav-link"
data-which="about">
About Us
</a>
<!-- Dynamic Link with ID -->
<a
[routerLink]="['/products', product.id]"
data-what="product-link"
[attr.data-which]="product.id">
{{ product.name }}
</a>
<!-- External Link -->
<a
href="https://example.com"
target="_blank"
data-what="external-link"
data-which="documentation">
Documentation
</a>
<!-- Action Link (not navigation) -->
<a
(click)="downloadReport()"
data-what="download-link"
data-which="sales-report">
Download Report
</a>
```
### Lists and Tables
```html
<!-- Dynamic List with @for -->
<ul data-what="product-list" data-which="catalog">
@for (product of products; track product.id) {
<li
(click)="selectProduct(product)"
data-what="list-item"
[attr.data-which]="product.id"
[attr.data-status]="product.stock > 0 ? 'in-stock' : 'out-of-stock'">
{{ product.name }}
</li>
}
</ul>
<!-- Table Row -->
<table data-what="orders-table" data-which="customer-orders">
<tbody>
@for (order of orders; track order.id) {
<tr
data-what="table-row"
[attr.data-which]="order.id">
<td>{{ order.id }}</td>
<td>{{ order.date }}</td>
<td>
<button
data-what="view-button"
[attr.data-which]="order.id">
View
</button>
</td>
</tr>
}
</tbody>
</table>
```
### Dialogs and Modals
```html
<!-- Confirmation Dialog -->
<div
*ngIf="showDialog"
data-what="confirmation-dialog"
data-which="delete-item">
<h2>Confirm Deletion</h2>
<p>Are you sure you want to delete this item?</p>
<button
(click)="confirmDelete()"
data-what="confirm-button"
data-which="delete-dialog">
Delete
</button>
<button
(click)="cancelDelete()"
data-what="cancel-button"
data-which="delete-dialog">
Cancel
</button>
</div>
<!-- Info Dialog with Close -->
<div
data-what="info-dialog"
data-which="welcome-message">
<button
(click)="closeDialog()"
data-what="close-button"
data-which="dialog">
×
</button>
<div data-what="dialog-content" data-which="welcome">
<h2>Welcome!</h2>
<p>Thank you for joining us.</p>
</div>
</div>
```
## Patterns by Component Type
### Form Components
```html
<form data-what="user-form" data-which="registration">
<!-- Field inputs -->
<input
data-what="username-input"
data-which="registration-form"
type="text" />
<input
data-what="email-input"
data-which="registration-form"
type="email" />
<!-- Action buttons -->
<button
data-what="submit-button"
data-which="registration-form"
type="submit">
Submit
</button>
<button
data-what="cancel-button"
data-which="registration-form"
type="button">
Cancel
</button>
</form>
```
### List/Table Components
```html
<!-- Each item needs unique data-which -->
@for (item of items; track item.id) {
<div
data-what="list-item"
[attr.data-which]="item.id">
<span data-what="item-name" [attr.data-which]="item.id">
{{ item.name }}
</span>
<button
data-what="edit-button"
[attr.data-which]="item.id">
Edit
</button>
<button
data-what="delete-button"
[attr.data-which]="item.id">
Delete
</button>
</div>
}
```
### Navigation Components
```html
<nav data-what="main-navigation" data-which="header">
<a
routerLink="/dashboard"
data-what="nav-link"
data-which="dashboard">
Dashboard
</a>
<a
routerLink="/orders"
data-what="nav-link"
data-which="orders">
Orders
</a>
<a
routerLink="/customers"
data-what="nav-link"
data-which="customers">
Customers
</a>
</nav>
<!-- Breadcrumbs -->
<nav data-what="breadcrumb" data-which="page-navigation">
@for (crumb of breadcrumbs; track $index) {
<a
[routerLink]="crumb.url"
data-what="breadcrumb-link"
[attr.data-which]="crumb.id">
{{ crumb.label }}
</a>
}
</nav>
```
### Dialog/Modal Components
```html
<!-- All dialog buttons need clear identifiers -->
<div data-what="modal" data-which="user-settings">
<button
data-what="close-button"
data-which="modal">
Close
</button>
<button
data-what="save-button"
data-which="modal">
Save Changes
</button>
<button
data-what="reset-button"
data-which="modal">
Reset to Defaults
</button>
</div>
```
## Dynamic Attributes
### Using Angular Binding
When values need to be dynamic, use Angular's attribute binding:
```html
<!-- Static (simple values) -->
<button data-what="submit-button" data-which="form">
<!-- Dynamic (from component properties) -->
<button
data-what="submit-button"
[attr.data-which]="formId">
<!-- Dynamic (from loop variables) -->
@for (item of items; track item.id) {
<div
data-what="list-item"
[attr.data-which]="item.id"
[attr.data-status]="item.status"
[attr.data-index]="$index">
</div>
}
<!-- Dynamic (computed values) -->
<button
data-what="action-button"
[attr.data-which]="'customer-' + customerId + '-' + action">
</button>
```
### Loop Variables
Angular's `@for` provides special variables:
```html
@for (item of items; track item.id; let idx = $index; let isFirst = $first) {
<div
data-what="list-item"
[attr.data-which]="item.id"
[attr.data-index]="idx"
[attr.data-first]="isFirst">
{{ item.name }}
</div>
}
```
## Best Practices
### Do's ✅
1. **Add to ALL interactive elements**
- Buttons, inputs, links, clickable elements
- Custom components that handle user interaction
- Form controls and navigation items
2. **Use consistent naming**
- Follow the naming patterns (e.g., `*-button`, `*-input`)
- Use kebab-case consistently
- Be descriptive but concise
3. **Ensure uniqueness**
- `data-which` must be unique within the view
- Use item IDs for list items: `[attr.data-which]="item.id"`
- Combine context when needed: `data-which="form-primary-submit"`
4. **Use Angular binding for dynamic values**
- `[attr.data-which]="item.id"`
- `data-which="{{ item.id }}"` ❌ (avoid interpolation)
5. **Document complex patterns**
- Add comments for non-obvious attribute choices
- Document the expected test selectors
6. **Keep attributes updated**
- Update when element purpose changes
- Remove when elements are removed
- Maintain consistency across refactoring
### Don'ts ❌
1. **Don't include sensitive data**
-`data-which="password-{{ userPassword }}"`
-`data-token="{{ authToken }}"`
-`data-ssn="{{ socialSecurity }}"`
2. **Don't use generic values**
-`data-what="button"` (too generic)
-`data-what="submit-button"` (specific)
3. **Don't duplicate `data-which` in the same view**
- ❌ Two buttons with `data-which="primary"`
-`data-which="form-primary"` and `data-which="dialog-primary"`
4. **Don't rely only on index for lists**
-`[attr.data-which]="$index"` (changes when list reorders)
-`[attr.data-which]="item.id"` (stable identifier)
5. **Don't forget about custom components**
- Custom components need attributes too
- Attributes should be on the component tag, not just internal elements
## Validation
### Coverage Check
Ensure all interactive elements have E2E attributes:
```bash
# Count interactive elements
grep -E '\(click\)|routerLink|button|input|select|textarea' component.html | wc -l
# Count elements with data-what
grep -c 'data-what=' component.html
# Find elements missing E2E attributes
grep -E '\(click\)|button' component.html | grep -v 'data-what='
```
### Uniqueness Check
Verify no duplicate `data-which` values in the same template:
```typescript
// In component tests
it('should have unique data-which attributes', () => {
const elements = fixture.nativeElement.querySelectorAll('[data-which]');
const dataWhichValues = Array.from(elements).map(
(el: any) => el.getAttribute('data-which')
);
const uniqueValues = new Set(dataWhichValues);
expect(dataWhichValues.length).toBe(uniqueValues.size);
});
```
### Validation Checklist
- [ ] All buttons have `data-what` and `data-which`
- [ ] All inputs have `data-what` and `data-which`
- [ ] All links have `data-what` and `data-which`
- [ ] All clickable elements have attributes
- [ ] Dynamic lists use `[attr.data-which]="item.id"`
- [ ] No duplicate `data-which` values in the same view
- [ ] No sensitive data in attributes
- [ ] Custom components have attributes
- [ ] Attributes use kebab-case
- [ ] Coverage: 100% of interactive elements
## Testing Integration
### Using E2E Attributes in Tests
**Unit Tests (Angular Testing Utilities)**:
```typescript
import { ComponentFixture, TestBed } from '@angular/core/testing';
describe('MyComponent', () => {
let fixture: ComponentFixture<MyComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [MyComponent],
}).compileComponents();
fixture = TestBed.createComponent(MyComponent);
fixture.detectChanges();
});
it('should have submit button with E2E attributes', () => {
const button = fixture.nativeElement.querySelector(
'[data-what="submit-button"][data-which="registration-form"]'
);
expect(button).toBeTruthy();
expect(button.textContent).toContain('Submit');
});
it('should have unique data-which for list items', () => {
const items = fixture.nativeElement.querySelectorAll('[data-what="list-item"]');
const dataWhichValues = Array.from(items).map(
(item: any) => item.getAttribute('data-which')
);
// All should have unique IDs
const uniqueValues = new Set(dataWhichValues);
expect(dataWhichValues.length).toBe(uniqueValues.size);
});
});
```
**E2E Tests (Playwright)**:
```typescript
import { test, expect } from '@playwright/test';
test('user registration flow', async ({ page }) => {
await page.goto('/register');
// Fill form using E2E attributes
await page.fill(
'[data-what="username-input"][data-which="registration-form"]',
'johndoe'
);
await page.fill(
'[data-what="email-input"][data-which="registration-form"]',
'john@example.com'
);
// Click submit using E2E attributes
await page.click(
'[data-what="submit-button"][data-which="registration-form"]'
);
// Verify success
await expect(page.locator('[data-what="success-message"]')).toBeVisible();
});
```
**E2E Tests (Cypress)**:
```typescript
describe('Order Management', () => {
it('should edit an order', () => {
cy.visit('/orders');
// Find specific order by ID using data-which
cy.get('[data-what="list-item"][data-which="order-123"]')
.should('be.visible');
// Click edit button for that specific order
cy.get('[data-what="edit-button"][data-which="order-123"]')
.click();
// Update quantity
cy.get('[data-what="quantity-input"][data-which="order-form"]')
.clear()
.type('5');
// Save changes
cy.get('[data-what="save-button"][data-which="order-form"]')
.click();
});
});
```
## Documentation in Templates
Add comment blocks to document E2E attributes:
```html
<!--
E2E Test Attributes:
- data-what="submit-button" data-which="registration-form" - Main form submission
- data-what="cancel-button" data-which="registration-form" - Cancel registration
- data-what="username-input" data-which="registration-form" - Username field
- data-what="email-input" data-which="registration-form" - Email field
- data-what="password-input" data-which="registration-form" - Password field
-->
<form data-what="registration-form" data-which="user-signup">
<!-- Form content -->
</form>
```
## Related Documentation
- **[ARIA Accessibility Attributes](aria-attributes.md)** - Accessibility guidance
- **[Combined Patterns](combined-patterns.md)** - Examples with E2E + ARIA together
- **Testing Guidelines**: `docs/guidelines/testing.md` - Project testing standards
- **CLAUDE.md**: Project code quality requirements
## Summary
E2E testing attributes are essential for:
- ✅ Stable, maintainable automated tests
- ✅ Clear communication between developers and QA
- ✅ Tests that survive styling and structural changes
- ✅ Self-documenting code that expresses intent
- ✅ Reliable CI/CD pipelines
**Always add `data-what` and `data-which` to every interactive element.**