304 lines
11 KiB
Python
304 lines
11 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
End-to-end Playwright tests for watchlist integration
|
||
Tests: /watchlist page functionality - filters, settings, scheduler, refresh
|
||
"""
|
||
|
||
import asyncio
|
||
import json
|
||
from pathlib import Path
|
||
from playwright.async_api import async_playwright
|
||
|
||
BASE_URL = "http://localhost:3000"
|
||
EVIDENCE_DIR = Path(".sisyphus/evidence")
|
||
EVIDENCE_DIR.mkdir(parents=True, exist_ok=True)
|
||
|
||
|
||
async def main():
|
||
# First get auth token
|
||
import httpx
|
||
|
||
resp = httpx.post(
|
||
f"{BASE_URL}/api/auth/login",
|
||
json={"username": "e2etest", "password": "password123"},
|
||
)
|
||
token_data = resp.json()
|
||
token = token_data.get("access_token")
|
||
user = token_data.get("user", {})
|
||
print(f"Got token for user: {user.get('username')}")
|
||
|
||
async with async_playwright() as p:
|
||
browser = await p.chromium.launch(
|
||
args=["--no-sandbox", "--disable-setuid-sandbox"]
|
||
)
|
||
context = await browser.new_context(viewport={"width": 1920, "height": 1080})
|
||
page = await context.new_page()
|
||
|
||
# Set auth BEFORE navigation
|
||
await page.add_init_script(f"""
|
||
window.localStorage.setItem('auth_token', '{token}');
|
||
window.localStorage.setItem('user', '{json.dumps(user)}');
|
||
""")
|
||
|
||
results = []
|
||
|
||
# Test 1: Navigate to /watchlist
|
||
print("\n=== Test 1: Navigate to /watchlist ===")
|
||
await page.goto(f"{BASE_URL}/watchlist")
|
||
await page.wait_for_load_state("networkidle")
|
||
await page.wait_for_timeout(2000)
|
||
await page.screenshot(
|
||
path=str(EVIDENCE_DIR / "01_watchlist_page.png"), full_page=True
|
||
)
|
||
|
||
title = await page.title()
|
||
url = page.url
|
||
page_loaded = "Watchlist" in title and "login" not in url.lower()
|
||
results.append(("Navigate to /watchlist", page_loaded))
|
||
print(f"Page title: {title}, URL: {url}")
|
||
|
||
# Test 2: Verify watchlist tab is active (highlighted)
|
||
print("\n=== Test 2: Verify watchlist tab highlighted ===")
|
||
active_tab = await page.query_selector(
|
||
'button.tab.active:has-text("Watchlist")'
|
||
)
|
||
is_active = active_tab is not None
|
||
await page.screenshot(
|
||
path=str(EVIDENCE_DIR / "02_tab_highlighted.png"), full_page=True
|
||
)
|
||
results.append(("Watchlist tab highlighted", is_active))
|
||
|
||
# Test 3: Verify header/nav matches other tabs
|
||
print("\n=== Test 3: Verify header/nav ===")
|
||
header = await page.query_selector("h1")
|
||
tabs = await page.query_selector_all(".tabs .tab")
|
||
has_header = header is not None
|
||
has_tabs = len(tabs) >= 4
|
||
await page.screenshot(
|
||
path=str(EVIDENCE_DIR / "03_header_nav.png"), full_page=True
|
||
)
|
||
results.append(("Header/nav present", has_header and has_tabs))
|
||
print(f"Found {len(tabs)} tabs, header: {has_header}")
|
||
|
||
# Test 4: Verify scheduler panel displays correctly
|
||
print("\n=== Test 4: Verify scheduler panel ===")
|
||
scheduler = await page.query_selector(
|
||
'.scheduler-status, #schedulerStatus, [class*="scheduler"]'
|
||
)
|
||
has_scheduler = scheduler is not None
|
||
start_btn = await page.query_selector(
|
||
'#startSchedulerBtn, [onclick*="startScheduler"]'
|
||
)
|
||
stop_btn = await page.query_selector(
|
||
'#stopSchedulerBtn, [onclick*="stopScheduler"]'
|
||
)
|
||
check_btn = await page.query_selector(
|
||
'[onclick*="CheckAll"], button:has-text("Vérifier")'
|
||
)
|
||
await page.screenshot(
|
||
path=str(EVIDENCE_DIR / "04_scheduler_panel.png"), full_page=True
|
||
)
|
||
results.append(("Scheduler panel displays", has_scheduler))
|
||
print(
|
||
f"Scheduler: {has_scheduler}, Start btn: {start_btn is not None}, Stop btn: {stop_btn is not None}, Check btn: {check_btn is not None}"
|
||
)
|
||
|
||
# Test 5: Test filter tabs (All/Active/Paused/Completed)
|
||
print("\n=== Test 5: Test filter tabs ===")
|
||
filter_tabs = await page.query_selector_all(
|
||
'.filter-tabs .filter-tab, [class*="filter-tab"]'
|
||
)
|
||
await page.screenshot(
|
||
path=str(EVIDENCE_DIR / "05_filter_tabs.png"), full_page=True
|
||
)
|
||
|
||
filter_names = []
|
||
for i, tab in enumerate(filter_tabs):
|
||
try:
|
||
tab_text = await tab.text_content()
|
||
filter_names.append(tab_text.strip())
|
||
await tab.click()
|
||
await page.wait_for_timeout(500)
|
||
except Exception as e:
|
||
print(f"Error clicking filter {i}: {e}")
|
||
|
||
await page.screenshot(
|
||
path=str(EVIDENCE_DIR / "05_filters_clicked.png"), full_page=True
|
||
)
|
||
results.append(("Filter tabs present and clickable", len(filter_tabs) >= 4))
|
||
print(f"Found filter tabs: {filter_names}")
|
||
|
||
# Test 6: Test settings modal
|
||
print("\n=== Test 6: Test settings modal ===")
|
||
settings_btn = await page.query_selector(
|
||
'button:has-text("Paramètres"), button:has-text("Settings"), [onclick*="settings"]'
|
||
)
|
||
if settings_btn:
|
||
await settings_btn.click()
|
||
await page.wait_for_timeout(1000)
|
||
await page.screenshot(
|
||
path=str(EVIDENCE_DIR / "06_settings_open.png"), full_page=True
|
||
)
|
||
|
||
# Close modal - try multiple methods
|
||
modal_closed = False
|
||
for selector in [
|
||
"#settingsModal button[onclick*='closeSettingsModal']",
|
||
"#settingsModal button:has-text('×')",
|
||
'button:has-text("Fermer")',
|
||
'button:has-text("Close")',
|
||
]:
|
||
try:
|
||
close_btn = await page.query_selector(selector)
|
||
if close_btn:
|
||
await close_btn.click()
|
||
modal_closed = True
|
||
break
|
||
except:
|
||
continue
|
||
|
||
# If still not closed, evaluate JS to close
|
||
if not modal_closed:
|
||
try:
|
||
await page.evaluate("closeSettingsModal()")
|
||
modal_closed = True
|
||
except:
|
||
pass
|
||
|
||
if not modal_closed:
|
||
await page.keyboard.press("Escape")
|
||
modal_closed = False
|
||
for selector in [
|
||
"#settingsModal button.close",
|
||
'[class*="modal"] .close',
|
||
'button:has-text("Fermer")',
|
||
'button:has-text("Close")',
|
||
]:
|
||
try:
|
||
close_btn = await page.query_selector(selector)
|
||
if close_btn:
|
||
await close_btn.click()
|
||
modal_closed = True
|
||
break
|
||
except:
|
||
continue
|
||
|
||
if not modal_closed:
|
||
await page.keyboard.press("Escape")
|
||
|
||
await page.wait_for_timeout(1000)
|
||
await page.screenshot(
|
||
path=str(EVIDENCE_DIR / "06_settings_closed.png"), full_page=True
|
||
)
|
||
results.append(("Settings modal works", True))
|
||
print("Settings modal opened and closed")
|
||
else:
|
||
await page.screenshot(
|
||
path=str(EVIDENCE_DIR / "06_settings_not_found.png"), full_page=True
|
||
)
|
||
results.append(("Settings modal works", False))
|
||
print("Settings button not found")
|
||
|
||
# Test 7: Verify 30-second status refresh
|
||
print("\n=== Test 7: Check for refresh interval ===")
|
||
page_content = await page.content()
|
||
has_refresh = "setInterval" in page_content
|
||
has_scheduler_interval = (
|
||
"loadSchedulerStatus" in page_content or "scheduler" in page_content.lower()
|
||
)
|
||
await page.screenshot(
|
||
path=str(EVIDENCE_DIR / "07_refresh_check.png"), full_page=True
|
||
)
|
||
results.append(
|
||
("Refresh mechanism present", has_refresh or has_scheduler_interval)
|
||
)
|
||
print(
|
||
f"Has setInterval: {has_refresh}, Has scheduler refresh: {has_scheduler_interval}"
|
||
)
|
||
|
||
# Test 8: Test tab switching
|
||
print("\n=== Test 8: Test tab switching ===")
|
||
try:
|
||
# Force close any modal first
|
||
await page.keyboard.press("Escape")
|
||
await page.wait_for_timeout(500)
|
||
|
||
home_tab = await page.query_selector('button.tab:has-text("Accueil")')
|
||
if home_tab:
|
||
await home_tab.click()
|
||
await page.wait_for_timeout(1000)
|
||
await page.screenshot(
|
||
path=str(EVIDENCE_DIR / "08_home_tab.png"), full_page=True
|
||
)
|
||
|
||
watchlist_tab = await page.query_selector(
|
||
'button.tab:has-text("Watchlist")'
|
||
)
|
||
if watchlist_tab:
|
||
await watchlist_tab.click()
|
||
await page.wait_for_timeout(1000)
|
||
await page.screenshot(
|
||
path=str(EVIDENCE_DIR / "08_back_to_watchlist.png"), full_page=True
|
||
)
|
||
results.append(("Tab switching works", True))
|
||
except Exception as e:
|
||
print(f"Tab switching error: {e}")
|
||
results.append(("Tab switching works", False))
|
||
|
||
# Test 9: Direct /web#watchlist URL
|
||
print("\n=== Test 9: Test /web#watchlist URL ===")
|
||
await page.goto(f"{BASE_URL}/web#watchlist")
|
||
await page.wait_for_load_state("networkidle")
|
||
await page.wait_for_timeout(2000)
|
||
await page.screenshot(
|
||
path=str(EVIDENCE_DIR / "09_web_hash_watchlist.png"), full_page=True
|
||
)
|
||
|
||
watchlist_content = await page.query_selector(
|
||
".watchlist-container, #watchlistContainer"
|
||
)
|
||
results.append(
|
||
("/web#watchlist loads watchlist", watchlist_content is not None)
|
||
)
|
||
|
||
# Test 10: Test /watchlist redirect
|
||
print("\n=== Test 10: Verify /watchlist returns content ===")
|
||
await page.goto(f"{BASE_URL}/watchlist")
|
||
await page.wait_for_load_state("networkidle")
|
||
content = await page.content()
|
||
has_watchlist_content = (
|
||
"watchlist" in content.lower() and "ma watchlist" in content.lower()
|
||
)
|
||
results.append(("/watchlist page has content", has_watchlist_content))
|
||
|
||
# Print results
|
||
print("\n" + "=" * 60)
|
||
print("TEST RESULTS:")
|
||
print("=" * 60)
|
||
passed = 0
|
||
for name, result in results:
|
||
status = "PASS" if result else "FAIL"
|
||
print(f"[{status}] {name}")
|
||
if result:
|
||
passed += 1
|
||
print("=" * 60)
|
||
print(f"Total: {passed}/{len(results)} tests passed")
|
||
|
||
# Save results
|
||
with open(EVIDENCE_DIR / "test_results.txt", "w") as f:
|
||
f.write("Watchlist Integration Test Results\n")
|
||
f.write("=" * 60 + "\n")
|
||
for name, result in results:
|
||
status = "PASS" if result else "FAIL"
|
||
f.write(f"[{status}] {name}\n")
|
||
f.write("=" * 60 + "\n")
|
||
f.write(f"Total: {passed}/{len(results)} tests passed\n")
|
||
|
||
await browser.close()
|
||
return passed >= len(results) * 0.8
|
||
|
||
|
||
if __name__ == "__main__":
|
||
success = asyncio.run(main())
|
||
exit(0 if success else 1)
|