test_exception_handlers.py 4.0 KB

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