|
| 1 | +#!/usr/bin/env python3 |
| 2 | +""" |
| 3 | +Test mobile sidebar scrolling and swipe gesture improvements. |
| 4 | +Verifies: |
| 5 | +1. Sidebar has proper scrollable height |
| 6 | +2. Scroll indicators (has-scroll class) are applied |
| 7 | +3. Interactive elements exist and are accessible |
| 8 | +""" |
| 9 | +import os |
| 10 | +from playwright.sync_api import sync_playwright |
| 11 | + |
| 12 | + |
| 13 | +URL = os.environ.get("WEBAPP_URL", "http://127.0.0.1:5000/ballot_lens") |
| 14 | + |
| 15 | + |
| 16 | +def test_mobile_sidebar_scrollability(page): |
| 17 | + """Test that sidebar is scrollable on mobile viewport.""" |
| 18 | + # Set mobile viewport |
| 19 | + page.set_viewport_size({"width": 375, "height": 667}) |
| 20 | + |
| 21 | + # Open sidebar if it exists |
| 22 | + sidebar_selector = ".sidebar-left, #sidebar" |
| 23 | + page.wait_for_selector(sidebar_selector, timeout=10000, state="attached") |
| 24 | + |
| 25 | + # Check if sidebar has scrollable content |
| 26 | + scroll_info = page.eval_on_selector( |
| 27 | + sidebar_selector, |
| 28 | + """el => ({ |
| 29 | + scrollHeight: el.scrollHeight, |
| 30 | + clientHeight: el.clientHeight, |
| 31 | + hasScroll: el.scrollHeight > el.clientHeight, |
| 32 | + overflowY: getComputedStyle(el).overflowY, |
| 33 | + classList: Array.from(el.classList) |
| 34 | + })""" |
| 35 | + ) |
| 36 | + |
| 37 | + print(f"Sidebar scroll info: {scroll_info}") |
| 38 | + |
| 39 | + # Verify overflow-y is auto or scroll |
| 40 | + assert scroll_info["overflowY"] in ["auto", "scroll"], \ |
| 41 | + f"Sidebar overflow-y should be auto or scroll, got: {scroll_info['overflowY']}" |
| 42 | + |
| 43 | + # Check if scroll indicators would be applied (has-scroll class) |
| 44 | + # Note: The class is applied by JS, so we just verify the structure is correct |
| 45 | + print(f"✓ Sidebar has overflow-y: {scroll_info['overflowY']}") |
| 46 | + print(f"✓ Scrollable: {scroll_info['hasScroll']}") |
| 47 | + |
| 48 | + return True |
| 49 | + |
| 50 | + |
| 51 | +def test_sidebar_css_properties(page): |
| 52 | + """Test that sidebar has correct CSS properties for mobile.""" |
| 53 | + page.set_viewport_size({"width": 375, "height": 667}) |
| 54 | + |
| 55 | + sidebar_selector = ".sidebar-left, #sidebar" |
| 56 | + page.wait_for_selector(sidebar_selector, timeout=10000, state="attached") |
| 57 | + |
| 58 | + css_props = page.eval_on_selector( |
| 59 | + sidebar_selector, |
| 60 | + """el => { |
| 61 | + const cs = getComputedStyle(el); |
| 62 | + return { |
| 63 | + position: cs.position, |
| 64 | + overflowY: cs.overflowY, |
| 65 | + overflowX: cs.overflowX, |
| 66 | + webkitOverflowScrolling: cs.webkitOverflowScrolling |
| 67 | + }; |
| 68 | + }""" |
| 69 | + ) |
| 70 | + |
| 71 | + print(f"Sidebar CSS properties: {css_props}") |
| 72 | + |
| 73 | + # Verify key CSS properties |
| 74 | + assert css_props["position"] == "fixed", \ |
| 75 | + f"Expected position: fixed, got: {css_props['position']}" |
| 76 | + assert css_props["overflowY"] in ["auto", "scroll"], \ |
| 77 | + f"Expected overflow-y: auto or scroll, got: {css_props['overflowY']}" |
| 78 | + assert css_props["overflowX"] == "hidden", \ |
| 79 | + f"Expected overflow-x: hidden, got: {css_props['overflowX']}" |
| 80 | + |
| 81 | + print("✓ Sidebar CSS properties are correct") |
| 82 | + return True |
| 83 | + |
| 84 | + |
| 85 | +def test_interactive_elements_exist(page): |
| 86 | + """Test that interactive elements in sidebar exist.""" |
| 87 | + page.set_viewport_size({"width": 375, "height": 667}) |
| 88 | + |
| 89 | + # Check for URL input field |
| 90 | + url_input = page.query_selector("#newUrl") |
| 91 | + if url_input: |
| 92 | + print("✓ URL input field found") |
| 93 | + |
| 94 | + # Check for source cards |
| 95 | + source_cards = page.query_selector_all(".source-card") |
| 96 | + print(f"✓ Found {len(source_cards)} source cards") |
| 97 | + |
| 98 | + # Check for control groups |
| 99 | + control_groups = page.query_selector_all(".control-group") |
| 100 | + print(f"✓ Found {len(control_groups)} control groups") |
| 101 | + |
| 102 | + return True |
| 103 | + |
| 104 | + |
| 105 | +def main(): |
| 106 | + """Run all tests.""" |
| 107 | + print(f"Testing mobile sidebar at: {URL}") |
| 108 | + |
| 109 | + with sync_playwright() as p: |
| 110 | + browser = p.chromium.launch(headless=True) |
| 111 | + page = browser.new_page() |
| 112 | + |
| 113 | + try: |
| 114 | + # Load page |
| 115 | + page.goto(URL, wait_until="load", timeout=45000) |
| 116 | + page.wait_for_selector("body", timeout=15000) |
| 117 | + |
| 118 | + # Run tests |
| 119 | + print("\n=== Test 1: Sidebar Scrollability ===") |
| 120 | + test_mobile_sidebar_scrollability(page) |
| 121 | + |
| 122 | + print("\n=== Test 2: Sidebar CSS Properties ===") |
| 123 | + test_sidebar_css_properties(page) |
| 124 | + |
| 125 | + print("\n=== Test 3: Interactive Elements ===") |
| 126 | + test_interactive_elements_exist(page) |
| 127 | + |
| 128 | + print("\n✅ All tests passed!") |
| 129 | + |
| 130 | + except Exception as e: |
| 131 | + print(f"\n❌ Test failed: {e}") |
| 132 | + raise |
| 133 | + finally: |
| 134 | + browser.close() |
| 135 | + |
| 136 | + |
| 137 | +if __name__ == "__main__": |
| 138 | + main() |
0 commit comments