
* enable PGH, bump pyright and fix all #type: ignore * relock poetry file * ignore incompatible override * fix varop tests * ignore missing imports * fix * fix stuff * fix tests * rechange tests * relock with poetry 2.0
205 lines
5.6 KiB
Python
205 lines
5.6 KiB
Python
"""Integration tests for event exception handlers."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import time
|
|
from typing import Generator, Type
|
|
|
|
import pytest
|
|
from selenium.webdriver.common.by import By
|
|
from selenium.webdriver.remote.webdriver import WebDriver
|
|
from selenium.webdriver.support import expected_conditions as EC
|
|
from selenium.webdriver.support.ui import WebDriverWait
|
|
|
|
from reflex.testing import AppHarness, AppHarnessProd
|
|
|
|
pytestmark = [pytest.mark.ignore_console_error]
|
|
|
|
|
|
def TestApp():
|
|
"""A test app for event exception handler integration."""
|
|
import reflex as rx
|
|
|
|
class TestAppConfig(rx.Config):
|
|
"""Config for the TestApp app."""
|
|
|
|
pass
|
|
|
|
class TestAppState(rx.State):
|
|
"""State for the TestApp app."""
|
|
|
|
react_error: bool = False
|
|
|
|
def divide_by_number(self, number: int):
|
|
"""Divide by number and print the result.
|
|
|
|
Args:
|
|
number: number to divide by
|
|
|
|
"""
|
|
print(1 / number)
|
|
|
|
app = rx.App(_state=rx.State)
|
|
|
|
@app.add_page
|
|
def index():
|
|
return rx.vstack(
|
|
rx.button(
|
|
"induce_frontend_error",
|
|
on_click=rx.call_script("induce_frontend_error()"),
|
|
id="induce-frontend-error-btn",
|
|
),
|
|
rx.button(
|
|
"induce_backend_error",
|
|
on_click=lambda: TestAppState.divide_by_number(0), # pyright: ignore [reportCallIssue]
|
|
id="induce-backend-error-btn",
|
|
),
|
|
rx.button(
|
|
"induce_react_error",
|
|
on_click=TestAppState.set_react_error(True), # pyright: ignore [reportAttributeAccessIssue]
|
|
id="induce-react-error-btn",
|
|
),
|
|
rx.box(
|
|
rx.cond(
|
|
TestAppState.react_error,
|
|
rx.Var.create({"invalid": "cannot have object as child"}),
|
|
"",
|
|
),
|
|
),
|
|
)
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def test_app(
|
|
app_harness_env: Type[AppHarness], tmp_path_factory
|
|
) -> Generator[AppHarness, None, None]:
|
|
"""Start TestApp app at tmp_path via AppHarness.
|
|
|
|
Args:
|
|
app_harness_env: either AppHarness (dev) or AppHarnessProd (prod)
|
|
tmp_path_factory: pytest tmp_path_factory fixture
|
|
|
|
Yields:
|
|
running AppHarness instance
|
|
|
|
"""
|
|
with app_harness_env.create(
|
|
root=tmp_path_factory.mktemp("test_app"),
|
|
app_name=f"testapp_{app_harness_env.__name__.lower()}",
|
|
app_source=TestApp,
|
|
) as harness:
|
|
yield harness
|
|
|
|
|
|
@pytest.fixture
|
|
def driver(test_app: AppHarness) -> Generator[WebDriver, None, None]:
|
|
"""Get an instance of the browser open to the test_app app.
|
|
|
|
Args:
|
|
test_app: harness for TestApp app
|
|
|
|
Yields:
|
|
WebDriver instance.
|
|
|
|
"""
|
|
assert test_app.app_instance is not None, "app is not running"
|
|
driver = test_app.frontend()
|
|
try:
|
|
yield driver
|
|
finally:
|
|
driver.quit()
|
|
|
|
|
|
def test_frontend_exception_handler_during_runtime(
|
|
driver: WebDriver,
|
|
capsys,
|
|
):
|
|
"""Test calling frontend exception handler during runtime.
|
|
|
|
We send an event containing a call to a non-existent function in the frontend.
|
|
This should trigger the default frontend exception handler.
|
|
|
|
Args:
|
|
driver: WebDriver instance.
|
|
capsys: pytest fixture for capturing stdout and stderr.
|
|
|
|
"""
|
|
reset_button = WebDriverWait(driver, 20).until(
|
|
EC.element_to_be_clickable((By.ID, "induce-frontend-error-btn"))
|
|
)
|
|
|
|
reset_button.click()
|
|
|
|
# Wait for the error to be logged
|
|
time.sleep(2)
|
|
|
|
captured_default_handler_output = capsys.readouterr()
|
|
assert (
|
|
"induce_frontend_error" in captured_default_handler_output.out
|
|
and "ReferenceError" in captured_default_handler_output.out
|
|
)
|
|
|
|
|
|
def test_backend_exception_handler_during_runtime(
|
|
driver: WebDriver,
|
|
capsys,
|
|
):
|
|
"""Test calling backend exception handler during runtime.
|
|
|
|
We invoke TestAppState.divide_by_zero to induce backend error.
|
|
This should trigger the default backend exception handler.
|
|
|
|
Args:
|
|
driver: WebDriver instance.
|
|
capsys: pytest fixture for capturing stdout and stderr.
|
|
|
|
"""
|
|
reset_button = WebDriverWait(driver, 20).until(
|
|
EC.element_to_be_clickable((By.ID, "induce-backend-error-btn"))
|
|
)
|
|
|
|
reset_button.click()
|
|
|
|
# Wait for the error to be logged
|
|
time.sleep(2)
|
|
|
|
captured_default_handler_output = capsys.readouterr()
|
|
assert (
|
|
"divide_by_number" in captured_default_handler_output.out
|
|
and "ZeroDivisionError" in captured_default_handler_output.out
|
|
)
|
|
|
|
|
|
def test_frontend_exception_handler_with_react(
|
|
test_app: AppHarness,
|
|
driver: WebDriver,
|
|
capsys,
|
|
):
|
|
"""Test calling frontend exception handler during runtime.
|
|
|
|
Render an object as a react child, which is invalid.
|
|
|
|
Args:
|
|
test_app: harness for TestApp app
|
|
driver: WebDriver instance.
|
|
capsys: pytest fixture for capturing stdout and stderr.
|
|
|
|
"""
|
|
reset_button = WebDriverWait(driver, 20).until(
|
|
EC.element_to_be_clickable((By.ID, "induce-react-error-btn"))
|
|
)
|
|
|
|
reset_button.click()
|
|
|
|
# Wait for the error to be logged
|
|
time.sleep(2)
|
|
|
|
captured_default_handler_output = capsys.readouterr()
|
|
if isinstance(test_app, AppHarnessProd):
|
|
assert "Error: Minified React error #31" in captured_default_handler_output.out
|
|
else:
|
|
assert (
|
|
"Error: Objects are not valid as a React child (found: object with keys \n{invalid})"
|
|
in captured_default_handler_output.out
|
|
)
|