Test JS logic embedded in HTML using two-layer strategy - Python unit tests + Playwright browser integration tests
Scanned 5/27/2026
Install via CLI
openskills install liaohch3/claude-tap---
name: js-in-html-testing
description: Test JS logic embedded in HTML using two-layer strategy - Python unit tests + Playwright browser integration tests
user_invocable: false
---
# JS-in-HTML Two-Layer Testing Strategy
For JavaScript logic embedded in HTML files (e.g., diff navigation in viewer.html), use a two-layer testing approach.
## Layer 1: Python Unit Tests (fast algorithm verification)
Replicate core JS algorithms in Python and verify correctness via pytest.
Best for: pure computation logic, state decisions, matching algorithms — anything that doesn't depend on the DOM.
**Example**: `tests/test_diff_matching.py`
```python
# Replicate JS findPrevSameModel / findNextSameModel
def find_diff_parent_by_prefix(entries, idx):
...
def find_next_by_prefix(entries, idx):
...
# Replicate JS updateNavButtons state computation
def compute_nav_button_states(entries, cur_idx):
prev_idx = find_diff_parent_by_prefix(entries, cur_idx)
...
return (prev_enabled, next_enabled)
```
Advantages: fast (0.02s), no browser dependency, integrates with existing pytest setup.
## Layer 2: Playwright Browser Integration Tests (DOM interaction verification)
Generate HTML with test data embedded, open it in real Chromium via Playwright, and verify DOM state and user interactions.
Best for: button disabled states, overlay show/hide, keyboard events, click navigation, etc.
**Example**: `tests/test_nav_browser.py`
### Building test HTML
```python
def _build_test_html():
from claude_tap.viewer import VIEWER_SCRIPT_ANCHOR, _read_viewer_template
template = _read_viewer_template()
records = [json.dumps(e) for e in TEST_ENTRIES]
data_js = "const EMBEDDED_TRACE_DATA = [\n" + ",\n".join(records) + "\n];\n"
# Inject data into template
return template.replace(
VIEWER_SCRIPT_ANCHOR,
f"<script>\n{data_js}</script>\n{VIEWER_SCRIPT_ANCHOR}",
1,
)
```
### Playwright fixture
```python
@pytest.fixture(scope="module")
def browser_page(html_file):
from playwright.sync_api import sync_playwright
pw = sync_playwright().start()
browser = pw.chromium.launch(headless=True)
page = browser.new_page()
page.goto(f"file://{html_file}")
page.wait_for_selector(".sidebar-item", timeout=5000)
yield page
browser.close()
pw.stop()
```
### Verifying DOM state
```python
def _get_nav_state(page):
return page.evaluate("""() => {
const overlay = document.querySelector('.diff-overlay');
if (!overlay) return { overlayExists: false };
return {
overlayExists: true,
prevDisabled: overlay.querySelector('.diff-nav-prev')?.disabled,
nextDisabled: overlay.querySelector('.diff-nav-next')?.disabled,
title: overlay.querySelector('.diff-title')?.textContent,
};
}""")
```
### Simulating user interaction
```python
# Click buttons
page.click(".diff-nav-next")
# Keyboard navigation
page.keyboard.press("ArrowRight")
# Call internal JS functions
page.evaluate("selectEntry(2)")
page.evaluate("showDiff()")
```
## Notes
- Trace data may contain `</script>` text (e.g., when Claude discusses code), which breaks `<script>` block parsing. Escape it: `line.replace("</script>", '</scr" + "ipt>')`
- Test data must match the viewer's expected JSONL format (including `turn`, `duration_ms`, `request_id`, `request.path`, etc.)
- Playwright requires `uv pip install playwright`
## Running
```bash
# Fast unit tests
uv run pytest tests/test_diff_matching.py -v
# Browser integration tests
uv run pytest tests/test_nav_browser.py -v
# All (excluding slow e2e)
uv run pytest tests/ --ignore=tests/test_e2e.py -v
```
No comments yet. Be the first to comment!