e2e_toggle_persistence_test.py 11 KB

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