e2e_toggle_persistence_test.py 11 KB

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