e2e_toggle_persistence_test.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. #!/usr/bin/env python3
  2. """E2E tests for toggle persistence - critical regression prevention.
  3. These tests verify that toggle settings (auto_off, notification events, etc.)
  4. are properly persisted to the database and survive page reloads.
  5. """
  6. import os
  7. import time
  8. from playwright.sync_api import expect, sync_playwright
  9. BASE_URL = os.environ.get("BAMBUDDY_URL", "http://localhost:8000")
  10. def test_smart_plug_auto_off_toggle_persistence(page):
  11. """CRITICAL: Test that auto_off toggle persists after page reload.
  12. This tests the regression where auto_off toggle wasn't being saved.
  13. """
  14. print("\n=== Testing Smart Plug Auto-Off Toggle Persistence ===")
  15. # Navigate to Settings page
  16. page.goto(f"{BASE_URL}/settings")
  17. page.wait_for_load_state("networkidle")
  18. time.sleep(1)
  19. # Look for Smart Plugs section
  20. smart_plugs_section = page.locator('text="Smart Plugs"').first
  21. if not smart_plugs_section.is_visible():
  22. print("⚠ No Smart Plugs section found - skipping test")
  23. return True
  24. # Find an Automation Settings toggle to expand
  25. automation_toggle = page.locator('text="Automation Settings"').first
  26. if not automation_toggle.is_visible():
  27. print("⚠ No Automation Settings found - skipping test")
  28. return True
  29. automation_toggle.click()
  30. time.sleep(0.5)
  31. # Find Auto Off toggle
  32. auto_off_label = page.locator('text="Auto Off"').first
  33. if not auto_off_label.is_visible():
  34. print("⚠ Auto Off label not found - skipping test")
  35. return True
  36. # Find the toggle switch near the Auto Off label
  37. # The toggle is a sibling element
  38. auto_off_section = auto_off_label.locator("..").first
  39. toggle = auto_off_section.locator('button[role="switch"]').first
  40. if not toggle.is_visible():
  41. # Try finding any toggle in the section
  42. toggle = page.locator('button[role="switch"]').nth(1) # Skip first (main enabled toggle)
  43. if not toggle.is_visible():
  44. print("⚠ Auto Off toggle not found - skipping test")
  45. return True
  46. # Get initial state
  47. initial_state = toggle.get_attribute("aria-checked")
  48. print(f"✓ Initial auto_off state: {initial_state}")
  49. # Click to toggle
  50. toggle.click()
  51. time.sleep(1) # Wait for API call
  52. # Verify toggle changed
  53. new_state = toggle.get_attribute("aria-checked")
  54. assert new_state != initial_state, "Toggle should have changed state"
  55. print(f"✓ Toggled auto_off to: {new_state}")
  56. # Reload the page
  57. page.reload()
  58. page.wait_for_load_state("networkidle")
  59. time.sleep(1)
  60. # Expand automation settings again
  61. automation_toggle = page.locator('text="Automation Settings"').first
  62. automation_toggle.click()
  63. time.sleep(0.5)
  64. # Find toggle again and verify state persisted
  65. auto_off_section = page.locator('text="Auto Off"').first.locator("..").first
  66. toggle = auto_off_section.locator('button[role="switch"]').first
  67. if not toggle.is_visible():
  68. toggle = page.locator('button[role="switch"]').nth(1)
  69. persisted_state = toggle.get_attribute("aria-checked")
  70. assert persisted_state == new_state, (
  71. f"State should persist after reload. Expected {new_state}, got {persisted_state}"
  72. )
  73. print(f"✓ Toggle state persisted after reload: {persisted_state}")
  74. # Restore original state
  75. if persisted_state != initial_state:
  76. toggle.click()
  77. time.sleep(1)
  78. print("✓ Restored original toggle state")
  79. return True
  80. def test_notification_event_toggle_persistence(page):
  81. """CRITICAL: Test that notification event toggles persist after page reload.
  82. This tests the regression where notification event toggles weren't being saved.
  83. """
  84. print("\n=== Testing Notification Event Toggle Persistence ===")
  85. # Navigate to Settings page
  86. page.goto(f"{BASE_URL}/settings")
  87. page.wait_for_load_state("networkidle")
  88. time.sleep(1)
  89. # Look for Notifications section
  90. notifications_section = page.locator('text="Notifications"').first
  91. if not notifications_section.is_visible():
  92. print("⚠ No Notifications section found - skipping test")
  93. return True
  94. # Find Event Settings toggle to expand
  95. event_settings = page.locator('text="Event Settings"').first
  96. if not event_settings.is_visible():
  97. print("⚠ No Event Settings found - skipping test")
  98. return True
  99. event_settings.click()
  100. time.sleep(0.5)
  101. # Find Print Stopped toggle (this was a regression point)
  102. stopped_label = page.locator('text="Print Stopped"').first
  103. if not stopped_label.is_visible():
  104. print("⚠ Print Stopped label not found - skipping test")
  105. return True
  106. # Find the toggle switch
  107. stopped_section = stopped_label.locator("..").first
  108. toggle = stopped_section.locator('button[role="switch"]').first
  109. if not toggle.is_visible():
  110. print("⚠ Print Stopped toggle not found - skipping test")
  111. return True
  112. # Get initial state
  113. initial_state = toggle.get_attribute("aria-checked")
  114. print(f"✓ Initial on_print_stopped state: {initial_state}")
  115. # Click to toggle
  116. toggle.click()
  117. time.sleep(1) # Wait for API call
  118. # Verify toggle changed
  119. new_state = toggle.get_attribute("aria-checked")
  120. assert new_state != initial_state, "Toggle should have changed state"
  121. print(f"✓ Toggled on_print_stopped to: {new_state}")
  122. # Reload the page
  123. page.reload()
  124. page.wait_for_load_state("networkidle")
  125. time.sleep(1)
  126. # Expand event settings again
  127. event_settings = page.locator('text="Event Settings"').first
  128. event_settings.click()
  129. time.sleep(0.5)
  130. # Find toggle again and verify state persisted
  131. stopped_section = page.locator('text="Print Stopped"').first.locator("..").first
  132. toggle = stopped_section.locator('button[role="switch"]').first
  133. if toggle.is_visible():
  134. persisted_state = toggle.get_attribute("aria-checked")
  135. assert persisted_state == new_state, (
  136. f"State should persist after reload. Expected {new_state}, got {persisted_state}"
  137. )
  138. print(f"✓ Toggle state persisted after reload: {persisted_state}")
  139. # Restore original state
  140. if persisted_state != initial_state:
  141. toggle.click()
  142. time.sleep(1)
  143. print("✓ Restored original toggle state")
  144. return True
  145. def test_ams_alarm_toggle_persistence(page):
  146. """Test that AMS alarm toggles persist after page reload."""
  147. print("\n=== Testing AMS Alarm Toggle Persistence ===")
  148. # Navigate to Settings page
  149. page.goto(f"{BASE_URL}/settings")
  150. page.wait_for_load_state("networkidle")
  151. time.sleep(1)
  152. # Look for Event Settings in notification provider
  153. event_settings = page.locator('text="Event Settings"').first
  154. if not event_settings.is_visible():
  155. print("⚠ No Event Settings found - skipping test")
  156. return True
  157. event_settings.click()
  158. time.sleep(0.5)
  159. # Look for AMS Humidity High toggle
  160. ams_humidity_label = page.locator('text="AMS Humidity High"').first
  161. if not ams_humidity_label.is_visible():
  162. print("⚠ AMS Humidity High label not found - skipping test")
  163. return True
  164. print("✓ AMS Alarm toggles section found")
  165. # Find and test the toggle
  166. ams_section = ams_humidity_label.locator("..").first
  167. toggle = ams_section.locator('button[role="switch"]').first
  168. if toggle.is_visible():
  169. initial_state = toggle.get_attribute("aria-checked")
  170. print(f"✓ Initial AMS humidity alarm state: {initial_state}")
  171. toggle.click()
  172. time.sleep(1)
  173. new_state = toggle.get_attribute("aria-checked")
  174. print(f"✓ Toggled AMS humidity alarm to: {new_state}")
  175. # Reload and verify
  176. page.reload()
  177. page.wait_for_load_state("networkidle")
  178. time.sleep(1)
  179. event_settings = page.locator('text="Event Settings"').first
  180. event_settings.click()
  181. time.sleep(0.5)
  182. ams_section = page.locator('text="AMS Humidity High"').first.locator("..").first
  183. toggle = ams_section.locator('button[role="switch"]').first
  184. if toggle.is_visible():
  185. persisted_state = toggle.get_attribute("aria-checked")
  186. print(f"✓ AMS alarm state after reload: {persisted_state}")
  187. # Restore
  188. if persisted_state != initial_state:
  189. toggle.click()
  190. time.sleep(1)
  191. return True
  192. def test_smart_plug_power_off_confirmation(page):
  193. """Test that power off shows confirmation dialog."""
  194. print("\n=== Testing Smart Plug Power Off Confirmation ===")
  195. page.goto(f"{BASE_URL}/settings")
  196. page.wait_for_load_state("networkidle")
  197. time.sleep(1)
  198. # Find an Off button
  199. off_button = page.locator('button:has-text("Off")').first
  200. if not off_button.is_visible():
  201. print("⚠ No Off button found - skipping test")
  202. return True
  203. off_button.click()
  204. time.sleep(0.5)
  205. # Look for confirmation dialog
  206. confirm_dialog = page.locator("text=/Turn Off|Confirm|cut power/i").first
  207. if confirm_dialog.is_visible():
  208. print("✓ Confirmation dialog appeared")
  209. # Close dialog by clicking Cancel or outside
  210. cancel_btn = page.locator('button:has-text("Cancel")').first
  211. if cancel_btn.is_visible():
  212. cancel_btn.click()
  213. print("✓ Cancelled power off")
  214. else:
  215. print("⚠ No confirmation dialog found")
  216. return True
  217. def run_all_toggle_tests():
  218. """Run all toggle persistence tests."""
  219. print("=" * 60)
  220. print("Bambuddy Toggle Persistence E2E Tests")
  221. print("=" * 60)
  222. with sync_playwright() as p:
  223. browser = p.chromium.launch(headless=True)
  224. context = browser.new_context(viewport={"width": 1280, "height": 720})
  225. page = context.new_page()
  226. tests = [
  227. ("Smart Plug Auto-Off Toggle", test_smart_plug_auto_off_toggle_persistence),
  228. ("Notification Event Toggle", test_notification_event_toggle_persistence),
  229. ("AMS Alarm Toggle", test_ams_alarm_toggle_persistence),
  230. ("Power Off Confirmation", test_smart_plug_power_off_confirmation),
  231. ]
  232. results = []
  233. for name, test_func in tests:
  234. try:
  235. result = test_func(page)
  236. results.append((name, "PASS" if result else "FAIL"))
  237. except Exception as e:
  238. print(f"✗ Test failed with error: {e}")
  239. results.append((name, f"ERROR: {e}"))
  240. browser.close()
  241. # Print summary
  242. print("\n" + "=" * 60)
  243. print("Test Results Summary")
  244. print("=" * 60)
  245. for name, result in results:
  246. status = "✓" if result == "PASS" else "✗"
  247. print(f"{status} {name}: {result}")
  248. passed = sum(1 for _, r in results if r == "PASS")
  249. total = len(results)
  250. print(f"\nTotal: {passed}/{total} passed")
  251. return passed == total
  252. if __name__ == "__main__":
  253. import sys
  254. success = run_all_toggle_tests()
  255. sys.exit(0 if success else 1)