test_dynamic_routes.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. """Integration tests for dynamic route page behavior."""
  2. import time
  3. from contextlib import contextmanager
  4. from typing import Generator
  5. from urllib.parse import urlsplit
  6. import pytest
  7. from selenium.webdriver.common.by import By
  8. from reflex.testing import AppHarness
  9. def DynamicRoute():
  10. """App for testing dynamic routes."""
  11. import reflex as rx
  12. class DynamicState(rx.State):
  13. order: list[str] = []
  14. page_id: str = ""
  15. def on_load(self):
  16. self.order.append(self.page_id or "no page id")
  17. @rx.var
  18. def next_page(self) -> str:
  19. try:
  20. return str(int(self.page_id) + 1)
  21. except ValueError:
  22. return "0"
  23. @rx.var
  24. def token(self) -> str:
  25. return self.get_token()
  26. def index():
  27. return rx.fragment(
  28. rx.input(value=DynamicState.token, is_read_only=True, id="token"),
  29. rx.input(value=DynamicState.page_id, is_read_only=True, id="page_id"),
  30. rx.link("index", href="/", id="link_index"),
  31. rx.link("page_X", href="/static/x", id="link_page_x"),
  32. rx.link(
  33. "next", href="/page/" + DynamicState.next_page, id="link_page_next" # type: ignore
  34. ),
  35. rx.list(
  36. rx.foreach(DynamicState.order, lambda i: rx.list_item(rx.text(i))), # type: ignore
  37. ),
  38. )
  39. app = rx.App(state=DynamicState)
  40. app.add_page(index)
  41. app.add_page(index, route="/page/[page_id]", on_load=DynamicState.on_load) # type: ignore
  42. app.add_page(index, route="/static/x", on_load=DynamicState.on_load) # type: ignore
  43. app.compile()
  44. @pytest.fixture(scope="session")
  45. def dynamic_route(tmp_path_factory) -> Generator[AppHarness, None, None]:
  46. """Start DynamicRoute app at tmp_path via AppHarness.
  47. Args:
  48. tmp_path_factory: pytest tmp_path_factory fixture
  49. Yields:
  50. running AppHarness instance
  51. """
  52. with AppHarness.create(
  53. root=tmp_path_factory.mktemp("dynamic_route"),
  54. app_source=DynamicRoute, # type: ignore
  55. ) as harness:
  56. yield harness
  57. @pytest.fixture
  58. def driver(dynamic_route: AppHarness):
  59. """Get an instance of the browser open to the dynamic_route app.
  60. Args:
  61. dynamic_route: harness for DynamicRoute app
  62. Yields:
  63. WebDriver instance.
  64. """
  65. assert dynamic_route.app_instance is not None, "app is not running"
  66. driver = dynamic_route.frontend()
  67. try:
  68. assert dynamic_route.poll_for_clients()
  69. yield driver
  70. finally:
  71. driver.quit()
  72. @contextmanager
  73. def poll_for_navigation(driver, timeout: int = 5) -> Generator[None, None, None]:
  74. """Wait for driver url to change.
  75. Use as a contextmanager, and apply the navigation event inside the context
  76. block, polling will occur after the context block exits.
  77. Args:
  78. driver: WebDriver instance.
  79. timeout: Time to wait for url to change.
  80. Yields:
  81. None
  82. """
  83. prev_url = driver.current_url
  84. yield
  85. AppHarness._poll_for(lambda: prev_url != driver.current_url, timeout=timeout)
  86. def test_on_load_navigate(dynamic_route: AppHarness, driver):
  87. """Click links to navigate between dynamic pages with on_load event.
  88. Args:
  89. dynamic_route: harness for DynamicRoute app.
  90. driver: WebDriver instance.
  91. """
  92. assert dynamic_route.app_instance is not None
  93. token_input = driver.find_element(By.ID, "token")
  94. link = driver.find_element(By.ID, "link_page_next")
  95. assert token_input
  96. assert link
  97. # wait for the backend connection to send the token
  98. token = dynamic_route.poll_for_value(token_input)
  99. assert token is not None
  100. # click the link a few times
  101. for ix in range(10):
  102. # wait for navigation, then assert on url
  103. with poll_for_navigation(driver):
  104. link.click()
  105. assert urlsplit(driver.current_url).path == f"/page/{ix}/"
  106. link = driver.find_element(By.ID, "link_page_next")
  107. page_id_input = driver.find_element(By.ID, "page_id")
  108. assert link
  109. assert page_id_input
  110. assert dynamic_route.poll_for_value(page_id_input) == str(ix)
  111. # look up the backend state and assert that `on_load` was called for all
  112. # navigation events
  113. backend_state = dynamic_route.app_instance.state_manager.states[token]
  114. time.sleep(0.2)
  115. assert backend_state.order == [str(ix) for ix in range(10)]
  116. def test_on_load_navigate_non_dynamic(dynamic_route: AppHarness, driver):
  117. """Click links to navigate between static pages with on_load event.
  118. Args:
  119. dynamic_route: harness for DynamicRoute app.
  120. driver: WebDriver instance.
  121. """
  122. assert dynamic_route.app_instance is not None
  123. token_input = driver.find_element(By.ID, "token")
  124. link = driver.find_element(By.ID, "link_page_x")
  125. assert token_input
  126. assert link
  127. # wait for the backend connection to send the token
  128. token = dynamic_route.poll_for_value(token_input)
  129. assert token is not None
  130. with poll_for_navigation(driver):
  131. link.click()
  132. assert urlsplit(driver.current_url).path == "/static/x/"
  133. # look up the backend state and assert that `on_load` was called once
  134. backend_state = dynamic_route.app_instance.state_manager.states[token]
  135. time.sleep(0.2)
  136. assert backend_state.order == ["no page id"]
  137. # go back to the index and navigate back to the static route
  138. link = driver.find_element(By.ID, "link_index")
  139. with poll_for_navigation(driver):
  140. link.click()
  141. assert urlsplit(driver.current_url).path == "/"
  142. link = driver.find_element(By.ID, "link_page_x")
  143. with poll_for_navigation(driver):
  144. link.click()
  145. assert urlsplit(driver.current_url).path == "/static/x/"
  146. time.sleep(0.2)
  147. assert backend_state.order == ["no page id", "no page id"]