# 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.**
