test_form_submit.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. """Integration tests for forms."""
  2. import functools
  3. import time
  4. from collections.abc import Generator
  5. import pytest
  6. from selenium.webdriver.common.by import By
  7. from selenium.webdriver.common.keys import Keys
  8. from reflex.testing import AppHarness
  9. from reflex.utils import format
  10. def FormSubmit(form_component):
  11. """App with a form using on_submit.
  12. Args:
  13. form_component: The str name of the form component to use.
  14. """
  15. import reflex as rx
  16. class FormState(rx.State):
  17. form_data: dict = {}
  18. var_options: list[str] = ["option3", "option4"]
  19. def form_submit(self, form_data: dict):
  20. self.form_data = form_data
  21. app = rx.App()
  22. @app.add_page
  23. def index():
  24. return rx.vstack(
  25. rx.input(
  26. value=FormState.router.session.client_token,
  27. is_read_only=True,
  28. id="token",
  29. ),
  30. eval(form_component)(
  31. rx.vstack(
  32. rx.input(id="name_input"),
  33. rx.checkbox(id="bool_input"),
  34. rx.switch(id="bool_input2"),
  35. rx.checkbox(id="bool_input3"),
  36. rx.switch(id="bool_input4"),
  37. rx.slider(id="slider_input", default_value=[50], width="100%"),
  38. rx.radio(["option1", "option2"], id="radio_input"),
  39. rx.radio(FormState.var_options, id="radio_input_var"),
  40. rx.select(
  41. ["option1", "option2"],
  42. name="select_input",
  43. default_value="option1",
  44. ),
  45. rx.select(FormState.var_options, id="select_input_var"),
  46. rx.text_area(id="text_area_input"),
  47. rx.input(
  48. id="debounce_input",
  49. debounce_timeout=0,
  50. on_change=rx.console_log,
  51. ),
  52. rx.button("Submit", type_="submit"),
  53. ),
  54. on_submit=FormState.form_submit,
  55. custom_attrs={"action": "/invalid"},
  56. ),
  57. rx.spacer(),
  58. height="100vh",
  59. )
  60. def FormSubmitName(form_component):
  61. """App with a form using on_submit.
  62. Args:
  63. form_component: The str name of the form component to use.
  64. """
  65. import reflex as rx
  66. class FormState(rx.State):
  67. form_data: dict = {}
  68. val: str = "foo"
  69. options: list[str] = ["option1", "option2"]
  70. def form_submit(self, form_data: dict):
  71. self.form_data = form_data
  72. app = rx.App()
  73. @app.add_page
  74. def index():
  75. return rx.vstack(
  76. rx.input(
  77. value=FormState.router.session.client_token,
  78. is_read_only=True,
  79. id="token",
  80. ),
  81. eval(form_component)(
  82. rx.vstack(
  83. rx.input(name="name_input"),
  84. rx.checkbox(name="bool_input"),
  85. rx.switch(name="bool_input2"),
  86. rx.checkbox(name="bool_input3"),
  87. rx.switch(name="bool_input4"),
  88. rx.slider(name="slider_input", default_value=[50], width="100%"),
  89. rx.radio(FormState.options, name="radio_input"),
  90. rx.select(
  91. FormState.options,
  92. name="select_input",
  93. default_value=FormState.options[0],
  94. ),
  95. rx.text_area(name="text_area_input"),
  96. rx.input(
  97. name="debounce_input",
  98. debounce_timeout=0,
  99. on_change=rx.console_log,
  100. ),
  101. rx.button("Submit", type_="submit"),
  102. rx.icon_button(rx.icon(tag="plus")),
  103. ),
  104. on_submit=FormState.form_submit,
  105. custom_attrs={"action": "/invalid"},
  106. ),
  107. rx.spacer(),
  108. height="100vh",
  109. )
  110. @pytest.fixture(
  111. scope="module",
  112. params=[
  113. functools.partial(FormSubmit, form_component="rx.form.root"),
  114. functools.partial(FormSubmitName, form_component="rx.form.root"),
  115. functools.partial(FormSubmit, form_component="rx.el.form"),
  116. functools.partial(FormSubmitName, form_component="rx.el.form"),
  117. ],
  118. ids=[
  119. "id-radix",
  120. "name-radix",
  121. "id-html",
  122. "name-html",
  123. ],
  124. )
  125. def form_submit(request, tmp_path_factory) -> Generator[AppHarness, None, None]:
  126. """Start FormSubmit app at tmp_path via AppHarness.
  127. Args:
  128. request: pytest request fixture
  129. tmp_path_factory: pytest tmp_path_factory fixture
  130. Yields:
  131. running AppHarness instance
  132. """
  133. param_id = request._pyfuncitem.callspec.id.replace("-", "_")
  134. with AppHarness.create(
  135. root=tmp_path_factory.mktemp("form_submit"),
  136. app_source=request.param,
  137. app_name=request.param.func.__name__ + f"_{param_id}",
  138. ) as harness:
  139. assert harness.app_instance is not None, "app is not running"
  140. yield harness
  141. @pytest.fixture
  142. def driver(form_submit: AppHarness):
  143. """GEt an instance of the browser open to the form_submit app.
  144. Args:
  145. form_submit: harness for ServerSideEvent app
  146. Yields:
  147. WebDriver instance.
  148. """
  149. driver = form_submit.frontend()
  150. try:
  151. yield driver
  152. finally:
  153. driver.quit()
  154. @pytest.mark.asyncio
  155. async def test_submit(driver, form_submit: AppHarness):
  156. """Fill a form with various different output, submit it to backend and verify
  157. the output.
  158. Args:
  159. driver: selenium WebDriver open to the app
  160. form_submit: harness for FormSubmit app
  161. """
  162. assert form_submit.app_instance is not None, "app is not running"
  163. by = By.ID if form_submit.app_source is FormSubmit else By.NAME
  164. # get a reference to the connected client
  165. token_input = driver.find_element(By.ID, "token")
  166. assert token_input
  167. # wait for the backend connection to send the token
  168. token = form_submit.poll_for_value(token_input)
  169. assert token
  170. name_input = driver.find_element(by, "name_input")
  171. name_input.send_keys("foo")
  172. checkbox_input = driver.find_element(By.XPATH, "//button[@role='checkbox']")
  173. checkbox_input.click()
  174. switch_input = driver.find_element(By.XPATH, "//button[@role='switch']")
  175. switch_input.click()
  176. radio_buttons = driver.find_elements(By.XPATH, "//button[@role='radio']")
  177. radio_buttons[1].click()
  178. textarea_input = driver.find_element(By.TAG_NAME, "textarea")
  179. textarea_input.send_keys("Some", Keys.ENTER, "Text")
  180. debounce_input = driver.find_element(by, "debounce_input")
  181. debounce_input.send_keys("bar baz")
  182. time.sleep(1)
  183. prev_url = driver.current_url
  184. submit_input = driver.find_element(By.CLASS_NAME, "rt-Button")
  185. submit_input.click()
  186. state_name = form_submit.get_state_name("_form_state")
  187. full_state_name = form_submit.get_full_state_name(["_form_state"])
  188. async def get_form_data():
  189. return (
  190. (await form_submit.get_state(f"{token}_{full_state_name}"))
  191. .substates[state_name]
  192. .form_data
  193. )
  194. # wait for the form data to arrive at the backend
  195. form_data = await AppHarness._poll_for_async(get_form_data)
  196. assert isinstance(form_data, dict)
  197. form_data = format.collect_form_dict_names(form_data)
  198. print(form_data)
  199. assert form_data["name_input"] == "foo"
  200. assert form_data["bool_input"]
  201. assert form_data["bool_input2"]
  202. assert not form_data.get("bool_input3", False)
  203. assert not form_data.get("bool_input4", False)
  204. assert form_data["slider_input"] == "50"
  205. assert form_data["radio_input"] == "option2"
  206. assert form_data["select_input"] == "option1"
  207. assert form_data["text_area_input"] == "Some\nText"
  208. assert form_data["debounce_input"] == "bar baz"
  209. # submitting the form should NOT change the url (preventDefault on_submit event)
  210. assert driver.current_url == prev_url