e2e_comprehensive_test.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. #!/usr/bin/env python3
  2. """Comprehensive end-to-end test for Bambuddy application."""
  3. import os
  4. import time
  5. from playwright.sync_api import expect, sync_playwright
  6. BASE_URL = os.environ.get("BAMBUDDY_URL", "http://localhost:8000")
  7. def test_navigation_and_sidebar(page):
  8. """Test sidebar navigation and all main pages."""
  9. print("\n=== Testing Navigation & Sidebar ===")
  10. # Go to home page
  11. page.goto(BASE_URL)
  12. page.wait_for_load_state("networkidle")
  13. # Take initial screenshot
  14. page.screenshot(path="/tmp/bambuddy_home.png", full_page=True)
  15. print("✓ Home page loaded")
  16. # Check sidebar is visible
  17. sidebar = page.locator("nav").first
  18. expect(sidebar).to_be_visible()
  19. print("✓ Sidebar is visible")
  20. # Test navigation to each main page
  21. nav_items = [
  22. ("Printers", "/"),
  23. ("Archives", "/archives"),
  24. ("Queue", "/queue"),
  25. ("Statistics", "/stats"),
  26. ("Profiles", "/profiles"),
  27. ("Maintenance", "/maintenance"),
  28. ("Settings", "/settings"),
  29. ]
  30. for name, _path in nav_items:
  31. # Click on nav item by text
  32. nav_link = page.locator(f'nav >> text="{name}"').first
  33. if nav_link.is_visible():
  34. nav_link.click()
  35. page.wait_for_load_state("networkidle")
  36. time.sleep(0.5) # Brief wait for animations
  37. print(f"✓ Navigated to {name}")
  38. else:
  39. print(f"⚠ Nav item '{name}' not visible")
  40. return True
  41. def test_printers_page(page):
  42. """Test Printers page functionality."""
  43. print("\n=== Testing Printers Page ===")
  44. page.goto(BASE_URL)
  45. page.wait_for_load_state("networkidle")
  46. time.sleep(1)
  47. # Check for printer cards or "Add Printer" button
  48. page_content = page.content()
  49. # Look for printer-related elements
  50. if "Add Printer" in page_content or "printer" in page_content.lower():
  51. print("✓ Printers page content detected")
  52. # Check for AMS display if printers are connected
  53. ams_elements = page.locator("text=/AMS-[A-Z]/").all()
  54. if ams_elements:
  55. print(f"✓ Found {len(ams_elements)} AMS unit(s) displayed")
  56. # Take screenshot
  57. page.screenshot(path="/tmp/bambuddy_printers.png", full_page=True)
  58. print("✓ Printers page screenshot saved")
  59. return True
  60. def test_archives_page(page):
  61. """Test Archives page functionality."""
  62. print("\n=== Testing Archives Page ===")
  63. page.goto(f"{BASE_URL}/archives")
  64. page.wait_for_load_state("networkidle")
  65. time.sleep(1)
  66. # Check for search input
  67. search_input = page.locator('input[placeholder*="Search"]').first
  68. if search_input.is_visible():
  69. print("✓ Search input found")
  70. # Test search functionality
  71. search_input.fill("test")
  72. time.sleep(0.5)
  73. search_input.clear()
  74. # Check for upload button
  75. upload_btn = page.locator('text="Upload"').first
  76. if upload_btn.is_visible():
  77. print("✓ Upload button found")
  78. # Take screenshot
  79. page.screenshot(path="/tmp/bambuddy_archives.png", full_page=True)
  80. print("✓ Archives page screenshot saved")
  81. return True
  82. def test_queue_page(page):
  83. """Test Queue page functionality."""
  84. print("\n=== Testing Queue Page ===")
  85. page.goto(f"{BASE_URL}/queue")
  86. page.wait_for_load_state("networkidle")
  87. time.sleep(1)
  88. # Check for queue-related content
  89. page_content = page.content()
  90. if "Queue" in page_content or "queue" in page_content.lower():
  91. print("✓ Queue page content detected")
  92. # Take screenshot
  93. page.screenshot(path="/tmp/bambuddy_queue.png", full_page=True)
  94. print("✓ Queue page screenshot saved")
  95. return True
  96. def test_statistics_page(page):
  97. """Test Statistics page functionality."""
  98. print("\n=== Testing Statistics Page ===")
  99. page.goto(f"{BASE_URL}/stats")
  100. page.wait_for_load_state("networkidle")
  101. time.sleep(1)
  102. # Check for statistics widgets
  103. page_content = page.content()
  104. stats_keywords = ["Total", "Success", "Failed", "Prints", "Filament", "Time"]
  105. found_stats = [kw for kw in stats_keywords if kw in page_content]
  106. if found_stats:
  107. print(f"✓ Statistics found: {', '.join(found_stats)}")
  108. # Take screenshot
  109. page.screenshot(path="/tmp/bambuddy_statistics.png", full_page=True)
  110. print("✓ Statistics page screenshot saved")
  111. return True
  112. def test_settings_page(page):
  113. """Test Settings page functionality."""
  114. print("\n=== Testing Settings Page ===")
  115. page.goto(f"{BASE_URL}/settings")
  116. page.wait_for_load_state("networkidle")
  117. time.sleep(1)
  118. # Check for settings sections
  119. settings_sections = [
  120. "Spoolman",
  121. "Notifications",
  122. "Smart Plugs",
  123. "General",
  124. ]
  125. page_content = page.content()
  126. for section in settings_sections:
  127. if section in page_content:
  128. print(f"✓ Settings section found: {section}")
  129. # Take screenshot
  130. page.screenshot(path="/tmp/bambuddy_settings.png", full_page=True)
  131. print("✓ Settings page screenshot saved")
  132. return True
  133. def test_keyboard_shortcuts(page):
  134. """Test keyboard shortcuts functionality."""
  135. print("\n=== Testing Keyboard Shortcuts ===")
  136. page.goto(BASE_URL)
  137. page.wait_for_load_state("networkidle")
  138. time.sleep(0.5)
  139. # Press '?' to open shortcuts modal
  140. page.keyboard.press("?")
  141. time.sleep(0.5)
  142. # Check if modal opened
  143. modal = page.locator('text="Keyboard Shortcuts"').first
  144. if modal.is_visible():
  145. print("✓ Keyboard shortcuts modal opened with '?'")
  146. page.screenshot(path="/tmp/bambuddy_shortcuts_modal.png")
  147. # Close with Escape
  148. page.keyboard.press("Escape")
  149. time.sleep(0.3)
  150. if not modal.is_visible():
  151. print("✓ Modal closed with Escape")
  152. else:
  153. print("⚠ Keyboard shortcuts modal did not open")
  154. # Test number key navigation
  155. # Press '2' to go to Archives
  156. page.keyboard.press("2")
  157. page.wait_for_load_state("networkidle")
  158. time.sleep(0.5)
  159. current_url = page.url
  160. if "/archives" in current_url:
  161. print("✓ Hotkey '2' navigated to Archives")
  162. else:
  163. print(f"⚠ Hotkey '2' navigation - current URL: {current_url}")
  164. # Press '1' to go back to Printers
  165. page.keyboard.press("1")
  166. page.wait_for_load_state("networkidle")
  167. time.sleep(0.5)
  168. current_url = page.url
  169. if current_url == BASE_URL + "/" or current_url == BASE_URL:
  170. print("✓ Hotkey '1' navigated to Printers")
  171. return True
  172. def test_theme_toggle(page):
  173. """Test theme toggle functionality."""
  174. print("\n=== Testing Theme Toggle ===")
  175. page.goto(f"{BASE_URL}/settings")
  176. page.wait_for_load_state("networkidle")
  177. time.sleep(1)
  178. # Check if dark theme is applied (should be default)
  179. html = page.locator("html")
  180. classes = html.get_attribute("class") or ""
  181. if "dark" in classes:
  182. print("✓ Dark theme is active (default)")
  183. else:
  184. print("ℹ Dark theme class not found on HTML element")
  185. # Look for theme-related UI elements
  186. page_content = page.content()
  187. if "theme" in page_content.lower() or "dark" in page_content.lower():
  188. print("✓ Theme-related content found on page")
  189. return True
  190. def test_responsive_design(page):
  191. """Test responsive design at different viewport sizes."""
  192. print("\n=== Testing Responsive Design ===")
  193. viewports = [
  194. ("Desktop", 1920, 1080),
  195. ("Tablet", 768, 1024),
  196. ("Mobile", 375, 667),
  197. ]
  198. for name, width, height in viewports:
  199. page.set_viewport_size({"width": width, "height": height})
  200. page.goto(BASE_URL)
  201. page.wait_for_load_state("networkidle")
  202. time.sleep(0.5)
  203. page.screenshot(path=f"/tmp/bambuddy_{name.lower()}.png", full_page=True)
  204. print(f"✓ {name} viewport ({width}x{height}) screenshot saved")
  205. # Reset to desktop
  206. page.set_viewport_size({"width": 1920, "height": 1080})
  207. return True
  208. def test_external_links_sidebar(page):
  209. """Test external links in sidebar."""
  210. print("\n=== Testing External Links in Sidebar ===")
  211. page.goto(BASE_URL)
  212. page.wait_for_load_state("networkidle")
  213. time.sleep(0.5)
  214. # Look for external link indicators
  215. external_links = page.locator('nav a[target="_blank"], nav >> text=/Spoolman|SpoolEase/i').all()
  216. if external_links:
  217. print(f"✓ Found {len(external_links)} external link(s) in sidebar")
  218. else:
  219. print("ℹ No external links configured in sidebar")
  220. return True
  221. def test_api_health(page):
  222. """Test basic API endpoints."""
  223. print("\n=== Testing API Health ===")
  224. # Test printers endpoint
  225. response = page.request.get(f"{BASE_URL}/api/v1/printers/")
  226. if response.ok:
  227. data = response.json()
  228. print(f"✓ GET /api/v1/printers/ - {len(data)} printer(s)")
  229. else:
  230. print(f"⚠ GET /api/v1/printers/ - Status: {response.status}")
  231. # Test archives endpoint
  232. response = page.request.get(f"{BASE_URL}/api/v1/archives/")
  233. if response.ok:
  234. data = response.json()
  235. print(f"✓ GET /api/v1/archives/ - {len(data)} archive(s)")
  236. else:
  237. print(f"⚠ GET /api/v1/archives/ - Status: {response.status}")
  238. # Test settings endpoint
  239. response = page.request.get(f"{BASE_URL}/api/v1/settings/")
  240. if response.ok:
  241. print("✓ GET /api/v1/settings/ - OK")
  242. else:
  243. print(f"⚠ GET /api/v1/settings/ - Status: {response.status}")
  244. return True
  245. def run_comprehensive_test():
  246. """Run all comprehensive tests."""
  247. print("=" * 60)
  248. print("BAMBUDDY COMPREHENSIVE E2E TEST")
  249. print("=" * 60)
  250. print(f"Target: {BASE_URL}")
  251. results = {}
  252. with sync_playwright() as p:
  253. browser = p.chromium.launch(headless=True)
  254. context = browser.new_context(viewport={"width": 1920, "height": 1080})
  255. page = context.new_page()
  256. # Enable console logging
  257. page.on("console", lambda msg: print(f" [Browser] {msg.text}") if msg.type == "error" else None)
  258. tests = [
  259. ("API Health", test_api_health),
  260. ("Navigation & Sidebar", test_navigation_and_sidebar),
  261. ("Printers Page", test_printers_page),
  262. ("Archives Page", test_archives_page),
  263. ("Queue Page", test_queue_page),
  264. ("Statistics Page", test_statistics_page),
  265. ("Settings Page", test_settings_page),
  266. ("Keyboard Shortcuts", test_keyboard_shortcuts),
  267. ("Theme Toggle", test_theme_toggle),
  268. ("External Links", test_external_links_sidebar),
  269. ("Responsive Design", test_responsive_design),
  270. ]
  271. for test_name, test_func in tests:
  272. try:
  273. results[test_name] = test_func(page)
  274. except Exception as e:
  275. print(f"\n❌ {test_name} FAILED: {e}")
  276. results[test_name] = False
  277. page.screenshot(path=f"/tmp/bambuddy_error_{test_name.lower().replace(' ', '_')}.png")
  278. browser.close()
  279. # Summary
  280. print("\n" + "=" * 60)
  281. print("TEST SUMMARY")
  282. print("=" * 60)
  283. passed = sum(1 for v in results.values() if v)
  284. total = len(results)
  285. for test_name, passed_test in results.items():
  286. status = "✓ PASS" if passed_test else "❌ FAIL"
  287. print(f" {status} - {test_name}")
  288. print(f"\nTotal: {passed}/{total} tests passed")
  289. print("Screenshots saved to /tmp/bambuddy_*.png")
  290. return all(results.values())
  291. if __name__ == "__main__":
  292. success = run_comprehensive_test()
  293. exit(0 if success else 1)