--- 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"\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 `` text (e.g., when Claude discusses code), which breaks `", '')` - 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 ```