test_exception_handlers.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. """Integration tests for event exception handlers."""
  2. from __future__ import annotations
  3. import time
  4. from typing import Generator, Type
  5. import pytest
  6. from selenium.webdriver.common.by import By
  7. from selenium.webdriver.remote.webdriver import WebDriver
  8. from selenium.webdriver.support import expected_conditions as EC
  9. from selenium.webdriver.support.ui import WebDriverWait
  10. from reflex.testing import AppHarness
  11. def TestApp():
  12. """A test app for event exception handler integration."""
  13. import reflex as rx
  14. class TestAppConfig(rx.Config):
  15. """Config for the TestApp app."""
  16. pass
  17. class TestAppState(rx.State):
  18. """State for the TestApp app."""
  19. def divide_by_number(self, number: int):
  20. """Divide by number and print the result.
  21. Args:
  22. number: number to divide by
  23. """
  24. print(1 / number)
  25. app = rx.App(state=rx.State)
  26. @app.add_page
  27. def index():
  28. return rx.vstack(
  29. rx.button(
  30. "induce_frontend_error",
  31. on_click=rx.call_script("induce_frontend_error()"),
  32. id="induce-frontend-error-btn",
  33. ),
  34. rx.button(
  35. "induce_backend_error",
  36. on_click=lambda: TestAppState.divide_by_number(0), # type: ignore
  37. id="induce-backend-error-btn",
  38. ),
  39. )
  40. @pytest.fixture(scope="module")
  41. def test_app(
  42. app_harness_env: Type[AppHarness], tmp_path_factory
  43. ) -> Generator[AppHarness, None, None]:
  44. """Start TestApp app at tmp_path via AppHarness.
  45. Args:
  46. app_harness_env: either AppHarness (dev) or AppHarnessProd (prod)
  47. tmp_path_factory: pytest tmp_path_factory fixture
  48. Yields:
  49. running AppHarness instance
  50. """
  51. with app_harness_env.create(
  52. root=tmp_path_factory.mktemp("test_app"),
  53. app_name=f"testapp_{app_harness_env.__name__.lower()}",
  54. app_source=TestApp, # type: ignore
  55. ) as harness:
  56. yield harness
  57. @pytest.fixture
  58. def driver(test_app: AppHarness) -> Generator[WebDriver, None, None]:
  59. """Get an instance of the browser open to the test_app app.
  60. Args:
  61. test_app: harness for TestApp app
  62. Yields:
  63. WebDriver instance.
  64. """
  65. assert test_app.app_instance is not None, "app is not running"
  66. driver = test_app.frontend()
  67. try:
  68. yield driver
  69. finally:
  70. driver.quit()
  71. def test_frontend_exception_handler_during_runtime(
  72. driver: WebDriver,
  73. capsys,
  74. ):
  75. """Test calling frontend exception handler during runtime.
  76. We send an event containing a call to a non-existent function in the frontend.
  77. This should trigger the default frontend exception handler.
  78. Args:
  79. driver: WebDriver instance.
  80. capsys: pytest fixture for capturing stdout and stderr.
  81. """
  82. reset_button = WebDriverWait(driver, 20).until(
  83. EC.element_to_be_clickable((By.ID, "induce-frontend-error-btn"))
  84. )
  85. reset_button.click()
  86. # Wait for the error to be logged
  87. time.sleep(2)
  88. captured_default_handler_output = capsys.readouterr()
  89. assert (
  90. "induce_frontend_error" in captured_default_handler_output.out
  91. and "ReferenceError" in captured_default_handler_output.out
  92. )
  93. def test_backend_exception_handler_during_runtime(
  94. driver: WebDriver,
  95. capsys,
  96. ):
  97. """Test calling backend exception handler during runtime.
  98. We invoke TestAppState.divide_by_zero to induce backend error.
  99. This should trigger the default backend exception handler.
  100. Args:
  101. driver: WebDriver instance.
  102. capsys: pytest fixture for capturing stdout and stderr.
  103. """
  104. reset_button = WebDriverWait(driver, 20).until(
  105. EC.element_to_be_clickable((By.ID, "induce-backend-error-btn"))
  106. )
  107. reset_button.click()
  108. # Wait for the error to be logged
  109. time.sleep(2)
  110. captured_default_handler_output = capsys.readouterr()
  111. assert (
  112. "divide_by_number" in captured_default_handler_output.out
  113. and "ZeroDivisionError" in captured_default_handler_output.out
  114. )