test_form_submit.py 8.1 KB

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