aria-testing 0.1.2
Accessibility-focused DOM testing library for tdom
Requires Python: >=3.14

# aria-testing

Accessibility-focused DOM testing library for tdom, built with modern Python 3.14+.

📚 **[Full Documentation](https://t-strings.github.io/aria-testing/)** | 📦 *
*[PyPI Package](https://pypi.org/project/aria-testing/)** | 🐙 *
*[GitHub Repository](https://github.com/t-strings/aria-testing)**

## Overview

`aria-testing` is a Python DOM testing library that provides accessibility-focused query functions for tdom. It follows
the DOM Testing Library philosophy: **"The more your tests resemble the way your software is used, the more confidence
they can give you."**

## Features

- ✨ **Modern Python 3.14+** - Uses structural pattern matching, PEP 695 generics, and modern type hints
- 🎯 **Accessibility-first** - Query by role, label text, and semantic attributes
- ⚡ **High performance** - Optimized traversal with caching and early-exit strategies
- 🔒 **Type-safe** - Full type annotations with strict type checking
- 🧪 **Well-tested** - 144 tests, 100% passing, comprehensive coverage

## Installation

```bash
uv add --dev aria-testing
```

## Quick Start

```python
from tdom.processor import html
from aria_testing import get_by_role, get_by_text, get_by_label_text

# Create a tdom structure
document = html(t"""<div>
    <h1>Welcome</h1>
    <nav>
        <a href="/home">Home</a>
    </nav>
    <form>
        <label>Email
            <input type="email" />
        </label>
        <button>Submit</button>
    </form>
</div>""")

# Find elements using accessibility patterns
heading = get_by_role(document, "heading", level=1)
nav = get_by_role(document, "navigation")
link = get_by_role(document, "link", name="Home")
email_input = get_by_label_text(document, "Email")
button = get_by_text(document, "Submit")
```

## Query Types

Queries are prioritized by accessibility best practices:

### 1. **By Role** ⭐ (Most Recommended)

Find elements by their ARIA role - mirrors how screen readers interact with your app.

```python
button = get_by_role(document, "button")
heading = get_by_role(document, "heading", level=1)
link = get_by_role(document, "link", name="Home")
```

### 2. **By Label Text** ⭐

Find form elements by their associated label - how users identify form fields.

```python
username = get_by_label_text(document, "Username")
email = get_by_label_text(document, "Email Address")
```

### 3. **By Text**

Find elements by their text content.

```python
button = get_by_text(document, "Click me")
heading = get_by_text(document, "Welcome")
```

### 4. **By Test ID**

Find elements by `data-testid` attribute - useful when semantic queries aren't possible.

```python
component = get_by_test_id(document, "user-menu")
```

### 5. **By Tag Name**

Find elements by HTML tag name - use sparingly, prefer semantic queries.

```python
all_links = get_all_by_tag_name(document, "a")
```

### 6. **By ID** & **By Class**

Find elements by HTML attributes - available but less recommended.

```python
element = get_by_id(document, "main-content")
buttons = get_all_by_class(document, "btn-primary")
```

## Query Variants

Each query type comes in **four variants** for different use cases:

| Variant          | Returns               | Not Found      | Multiple Found |
|------------------|-----------------------|----------------|----------------|
| `get_by_*`       | Single element        | ❌ Raises error | ❌ Raises error |
| `query_by_*`     | Element or None       | ✅ Returns None | ❌ Raises error |
| `get_all_by_*`   | List of elements      | ❌ Raises error | ✅ Returns all  |
| `query_all_by_*` | List (possibly empty) | ✅ Returns `[]` | ✅ Returns all  |

### When to Use Each

- **`get_by_*`**: When element MUST exist and be unique (most common)
- **`query_by_*`**: When checking if element exists
- **`get_all_by_*`**: When multiple elements MUST exist
- **`query_all_by_*`**: When finding zero or more elements

## Assertion Helpers

Frozen dataclass-based assertion helpers for deferred execution in dynamic systems. Create assertions up front, apply them later when the DOM is available.

```python
from aria_testing import GetByRole, GetAllByRole

# Define assertion early (no DOM needed yet)
assert_button = GetByRole(role="button").text_content("Save")

# Apply later when container becomes available
def verify_component(container):
    assert_button(container)  # Raises AssertionError if fails
```

**Use Cases:**
- Component testing frameworks that render components dynamically
- Story-based testing where assertions are defined separately from execution
- Test fixtures that verify DOM structure after setup
- Reusable assertion sets applied across multiple test scenarios

**Key Features:**
- Immutable frozen dataclasses
- Fluent API: `.not_()`, `.text_content()`, `.with_attribute()`
- List operations: `.count()`, `.nth()`
- Type-safe with full IDE support

📚 **[See full documentation →](https://t-strings.github.io/aria-testing/assertion-helpers.html)**

## Modern Python Features

Built with cutting-edge Python 3.14+ features:

- **Structural pattern matching** (`match`/`case`) for clean conditionals
- **PEP 695 generic functions** for type-safe query factories
- **Modern type hints** (`X | Y` unions, built-in generics)
- **Keyword-only arguments** for clear, readable APIs
- **Walrus operator** (`:=`) for concise, performant code

## Performance

aria-testing is highly optimized for speed with multiple performance strategies:

### Optimization Techniques

- **Two-level caching** - Element list and role computation caching
- **Early-exit strategies** - Stops searching after finding matches
- **Iterative traversal** - Non-recursive DOM traversal for large trees
- **String interning** - Fast identity-based comparisons for common roles
- **Set-based class matching** - O(1) instead of O(n) for class queries

### Benchmark Results

*Measured on December 10, 2024 - Apple M-series CPU, Python 3.14*

**Test Suite Performance**:

- 154 tests complete in **0.08 seconds** ⚡

**Query Performance** (200-element DOM, 100 iterations per query):

| Query Type    | Without Caching | With Caching | Speedup          |
|---------------|-----------------|--------------|------------------|
| Role queries  | 4.3μs           | 1.8μs        | **2.4x faster**  |
| Text queries  | 13.6μs          | 11.2μs       | **1.2x faster**  |
| Class queries | 3.2μs           | 0.7μs        | **4.3x faster**  |
| Tag queries   | 3.5μs           | 3.1μs        | **1.1x faster**  |
| **Average**   | **5.8μs**       | **3.7μs**    | **1.55x faster** |

**Cache Efficiency**:

- Element list cache: **99.8% hit rate**
- Role cache: **99.5% hit rate**

*Run benchmarks yourself*:

```bash
just benchmark        # General performance
just benchmark-cache  # Caching comparison
```

### Performance Tips

1. **Reuse containers** - Query the same DOM multiple times to benefit from caching
2. **Use appropriate queries** - `query_all_*` gets full caching benefits
3. **Let caching work** - Caches auto-clear between pytest tests

See `CACHING_IMPLEMENTATION.md` for detailed performance analysis.

## Requirements

- Python 3.14+
- tdom

## Documentation

📚 **[Read the full documentation on GitHub Pages →](https://t-strings.github.io/aria-testing/)**

The documentation includes:

- Complete API reference for all query functions
- Detailed guides on accessibility testing patterns
- Performance optimization techniques
- Type safety and modern Python features
- Advanced usage examples and best practices

## License

MIT