test_client_storage.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768
  1. """Integration tests for client side storage."""
  2. from __future__ import annotations
  3. import time
  4. from collections.abc import Generator
  5. import pytest
  6. from selenium.webdriver import Firefox
  7. from selenium.webdriver.common.by import By
  8. from selenium.webdriver.remote.webdriver import WebDriver
  9. from reflex.state import (
  10. State,
  11. StateManagerDisk,
  12. StateManagerMemory,
  13. StateManagerRedis,
  14. _substate_key,
  15. )
  16. from reflex.testing import AppHarness
  17. from . import utils
  18. def ClientSide():
  19. """App for testing client-side state."""
  20. import reflex as rx
  21. class ClientSideState(rx.State):
  22. state_var: str = ""
  23. input_value: str = ""
  24. @rx.event
  25. def set_state_var(self, value: str):
  26. self.state_var = value
  27. @rx.event
  28. def set_input_value(self, value: str):
  29. self.input_value = value
  30. class ClientSideSubState(ClientSideState):
  31. # cookies with default settings
  32. c1: str = rx.Cookie()
  33. c2: str = rx.Cookie("c2 default")
  34. # cookies with custom settings
  35. c3: str = rx.Cookie(max_age=2) # expires after 2 second
  36. c4: str = rx.Cookie(same_site="strict")
  37. c5: str = rx.Cookie(path="/foo/") # only accessible on `/foo/`
  38. c6: str = rx.Cookie(name="c6")
  39. c7: str = rx.Cookie("c7 default")
  40. # local storage with default settings
  41. l1: str = rx.LocalStorage()
  42. l2: str = rx.LocalStorage("l2 default")
  43. # local storage with custom settings
  44. l3: str = rx.LocalStorage(name="l3")
  45. l4: str = rx.LocalStorage("l4 default")
  46. # Sync'd local storage
  47. l5: str = rx.LocalStorage(sync=True)
  48. l6: str = rx.LocalStorage(sync=True, name="l6")
  49. # Session storage
  50. s1: str = rx.SessionStorage()
  51. s2: str = rx.SessionStorage("s2 default")
  52. s3: str = rx.SessionStorage(name="s3")
  53. def set_l6(self, my_param: str):
  54. self.l6 = my_param
  55. @rx.event
  56. def set_var(self):
  57. setattr(self, self.state_var, self.input_value)
  58. self.state_var = self.input_value = ""
  59. class ClientSideSubSubState(ClientSideSubState):
  60. c1s: str = rx.Cookie()
  61. l1s: str = rx.LocalStorage()
  62. s1s: str = rx.SessionStorage()
  63. @rx.event
  64. def set_var(self):
  65. setattr(self, self.state_var, self.input_value)
  66. self.state_var = self.input_value = ""
  67. def index():
  68. return rx.fragment(
  69. rx.input(
  70. value=ClientSideState.router.session.client_token,
  71. read_only=True,
  72. id="token",
  73. ),
  74. rx.input(
  75. placeholder="state var",
  76. value=ClientSideState.state_var,
  77. on_change=ClientSideState.setvar("state_var"),
  78. id="state_var",
  79. ),
  80. rx.input(
  81. placeholder="input value",
  82. value=ClientSideState.input_value,
  83. on_change=ClientSideState.setvar("input_value"),
  84. id="input_value",
  85. ),
  86. rx.button(
  87. "Set ClientSideSubState",
  88. on_click=ClientSideSubState.set_var,
  89. id="set_sub_state",
  90. ),
  91. rx.button(
  92. "Set ClientSideSubSubState",
  93. on_click=ClientSideSubSubState.set_var,
  94. id="set_sub_sub_state",
  95. ),
  96. rx.box(ClientSideSubState.c1, id="c1"),
  97. rx.box(ClientSideSubState.c2, id="c2"),
  98. rx.box(ClientSideSubState.c3, id="c3"),
  99. rx.box(ClientSideSubState.c4, id="c4"),
  100. rx.box(ClientSideSubState.c5, id="c5"),
  101. rx.box(ClientSideSubState.c6, id="c6"),
  102. rx.box(ClientSideSubState.c7, id="c7"),
  103. rx.box(ClientSideSubState.l1, id="l1"),
  104. rx.box(ClientSideSubState.l2, id="l2"),
  105. rx.box(ClientSideSubState.l3, id="l3"),
  106. rx.box(ClientSideSubState.l4, id="l4"),
  107. rx.box(ClientSideSubState.l5, id="l5"),
  108. rx.box(ClientSideSubState.l6, id="l6"),
  109. rx.box(ClientSideSubState.s1, id="s1"),
  110. rx.box(ClientSideSubState.s2, id="s2"),
  111. rx.box(ClientSideSubState.s3, id="s3"),
  112. rx.box(ClientSideSubSubState.c1s, id="c1s"),
  113. rx.box(ClientSideSubSubState.l1s, id="l1s"),
  114. rx.box(ClientSideSubSubState.s1s, id="s1s"),
  115. )
  116. app = rx.App()
  117. app.add_page(index)
  118. app.add_page(index, route="/foo")
  119. @pytest.fixture(scope="module")
  120. def client_side(tmp_path_factory) -> Generator[AppHarness, None, None]:
  121. """Start ClientSide app at tmp_path via AppHarness.
  122. Args:
  123. tmp_path_factory: pytest tmp_path_factory fixture
  124. Yields:
  125. running AppHarness instance
  126. """
  127. with AppHarness.create(
  128. root=tmp_path_factory.mktemp("client_side"),
  129. app_source=ClientSide,
  130. ) as harness:
  131. yield harness
  132. @pytest.fixture
  133. def driver(client_side: AppHarness) -> Generator[WebDriver, None, None]:
  134. """Get an instance of the browser open to the client_side app.
  135. Args:
  136. client_side: harness for ClientSide app
  137. Yields:
  138. WebDriver instance.
  139. """
  140. assert client_side.app_instance is not None, "app is not running"
  141. driver = client_side.frontend()
  142. try:
  143. yield driver
  144. finally:
  145. driver.quit()
  146. @pytest.fixture()
  147. def local_storage(driver: WebDriver) -> Generator[utils.LocalStorage, None, None]:
  148. """Get an instance of the local storage helper.
  149. Args:
  150. driver: WebDriver instance.
  151. Yields:
  152. Local storage helper.
  153. """
  154. ls = utils.LocalStorage(driver)
  155. yield ls
  156. ls.clear()
  157. @pytest.fixture()
  158. def session_storage(driver: WebDriver) -> Generator[utils.SessionStorage, None, None]:
  159. """Get an instance of the session storage helper.
  160. Args:
  161. driver: WebDriver instance.
  162. Yields:
  163. Session storage helper.
  164. """
  165. ss = utils.SessionStorage(driver)
  166. yield ss
  167. ss.clear()
  168. @pytest.fixture(autouse=True)
  169. def delete_all_cookies(driver: WebDriver) -> Generator[None, None, None]:
  170. """Delete all cookies after each test.
  171. Args:
  172. driver: WebDriver instance.
  173. Yields:
  174. None
  175. """
  176. yield
  177. driver.delete_all_cookies()
  178. def cookie_info_map(driver: WebDriver) -> dict[str, dict[str, str]]:
  179. """Get a map of cookie names to cookie info.
  180. Args:
  181. driver: WebDriver instance.
  182. Returns:
  183. A map of cookie names to cookie info.
  184. """
  185. return {cookie_info["name"]: cookie_info for cookie_info in driver.get_cookies()}
  186. @pytest.mark.asyncio
  187. async def test_client_side_state(
  188. client_side: AppHarness,
  189. driver: WebDriver,
  190. local_storage: utils.LocalStorage,
  191. session_storage: utils.SessionStorage,
  192. ):
  193. """Test client side state.
  194. Args:
  195. client_side: harness for ClientSide app.
  196. driver: WebDriver instance.
  197. local_storage: Local storage helper.
  198. session_storage: Session storage helper.
  199. """
  200. app = client_side.app_instance
  201. assert app is not None
  202. assert client_side.frontend_url is not None
  203. def poll_for_token():
  204. token_input = driver.find_element(By.ID, "token")
  205. assert token_input
  206. # wait for the backend connection to send the token
  207. token = client_side.poll_for_value(token_input)
  208. assert token is not None
  209. return token
  210. def set_sub(var: str, value: str):
  211. # Get a reference to the cookie manipulation form.
  212. state_var_input = driver.find_element(By.ID, "state_var")
  213. input_value_input = driver.find_element(By.ID, "input_value")
  214. set_sub_state_button = driver.find_element(By.ID, "set_sub_state")
  215. AppHarness._poll_for(lambda: state_var_input.get_attribute("value") == "")
  216. AppHarness._poll_for(lambda: input_value_input.get_attribute("value") == "")
  217. # Set the values.
  218. state_var_input.send_keys(var)
  219. input_value_input.send_keys(value)
  220. set_sub_state_button.click()
  221. def set_sub_sub(var: str, value: str):
  222. # Get a reference to the cookie manipulation form.
  223. state_var_input = driver.find_element(By.ID, "state_var")
  224. input_value_input = driver.find_element(By.ID, "input_value")
  225. set_sub_sub_state_button = driver.find_element(By.ID, "set_sub_sub_state")
  226. AppHarness._poll_for(lambda: state_var_input.get_attribute("value") == "")
  227. AppHarness._poll_for(lambda: input_value_input.get_attribute("value") == "")
  228. # Set the values.
  229. state_var_input.send_keys(var)
  230. input_value_input.send_keys(value)
  231. set_sub_sub_state_button.click()
  232. token = poll_for_token()
  233. # get a reference to all cookie and local storage elements
  234. c1 = driver.find_element(By.ID, "c1")
  235. c2 = driver.find_element(By.ID, "c2")
  236. c3 = driver.find_element(By.ID, "c3")
  237. c4 = driver.find_element(By.ID, "c4")
  238. c5 = driver.find_element(By.ID, "c5")
  239. c6 = driver.find_element(By.ID, "c6")
  240. c7 = driver.find_element(By.ID, "c7")
  241. l1 = driver.find_element(By.ID, "l1")
  242. l2 = driver.find_element(By.ID, "l2")
  243. l3 = driver.find_element(By.ID, "l3")
  244. l4 = driver.find_element(By.ID, "l4")
  245. s1 = driver.find_element(By.ID, "s1")
  246. s2 = driver.find_element(By.ID, "s2")
  247. s3 = driver.find_element(By.ID, "s3")
  248. c1s = driver.find_element(By.ID, "c1s")
  249. l1s = driver.find_element(By.ID, "l1s")
  250. s1s = driver.find_element(By.ID, "s1s")
  251. # assert on defaults where present
  252. assert c1.text == ""
  253. assert c2.text == "c2 default"
  254. assert c3.text == ""
  255. assert c4.text == ""
  256. assert c5.text == ""
  257. assert c6.text == ""
  258. assert c7.text == "c7 default"
  259. assert l1.text == ""
  260. assert l2.text == "l2 default"
  261. assert l3.text == ""
  262. assert l4.text == "l4 default"
  263. assert s1.text == ""
  264. assert s2.text == "s2 default"
  265. assert s3.text == ""
  266. assert c1s.text == ""
  267. assert l1s.text == ""
  268. assert s1s.text == ""
  269. # no cookies should be set yet!
  270. assert not driver.get_cookies()
  271. local_storage_items = local_storage.items()
  272. local_storage_items.pop("last_compiled_time", None)
  273. local_storage_items.pop("theme", None)
  274. assert not local_storage_items
  275. # set some cookies and local storage values
  276. set_sub("c1", "c1 value")
  277. set_sub("c2", "c2 value")
  278. set_sub("c4", "c4 value")
  279. set_sub("c5", "c5 value")
  280. set_sub("c6", "c6 throwaway value")
  281. set_sub("c6", "c6 value")
  282. set_sub("c7", "c7 value")
  283. set_sub("l1", "l1 value")
  284. set_sub("l2", "l2 value")
  285. set_sub("l3", "l3 value")
  286. set_sub("l4", "l4 value")
  287. set_sub("s1", "s1 value")
  288. set_sub("s2", "s2 value")
  289. set_sub("s3", "s3 value")
  290. set_sub_sub("c1s", "c1s value")
  291. set_sub_sub("l1s", "l1s value")
  292. set_sub_sub("s1s", "s1s value")
  293. state_name = client_side.get_full_state_name(["_client_side_state"])
  294. sub_state_name = client_side.get_full_state_name(
  295. ["_client_side_state", "_client_side_sub_state"]
  296. )
  297. sub_sub_state_name = client_side.get_full_state_name(
  298. ["_client_side_state", "_client_side_sub_state", "_client_side_sub_sub_state"]
  299. )
  300. exp_cookies = {
  301. f"{sub_state_name}.c1": {
  302. "domain": "localhost",
  303. "httpOnly": False,
  304. "name": f"{sub_state_name}.c1",
  305. "path": "/",
  306. "sameSite": "Lax",
  307. "secure": False,
  308. "value": "c1%20value",
  309. },
  310. f"{sub_state_name}.c2": {
  311. "domain": "localhost",
  312. "httpOnly": False,
  313. "name": f"{sub_state_name}.c2",
  314. "path": "/",
  315. "sameSite": "Lax",
  316. "secure": False,
  317. "value": "c2%20value",
  318. },
  319. f"{sub_state_name}.c4": {
  320. "domain": "localhost",
  321. "httpOnly": False,
  322. "name": f"{sub_state_name}.c4",
  323. "path": "/",
  324. "sameSite": "Strict",
  325. "secure": False,
  326. "value": "c4%20value",
  327. },
  328. "c6": {
  329. "domain": "localhost",
  330. "httpOnly": False,
  331. "name": "c6",
  332. "path": "/",
  333. "sameSite": "Lax",
  334. "secure": False,
  335. "value": "c6%20value",
  336. },
  337. f"{sub_state_name}.c7": {
  338. "domain": "localhost",
  339. "httpOnly": False,
  340. "name": f"{sub_state_name}.c7",
  341. "path": "/",
  342. "sameSite": "Lax",
  343. "secure": False,
  344. "value": "c7%20value",
  345. },
  346. f"{sub_sub_state_name}.c1s": {
  347. "domain": "localhost",
  348. "httpOnly": False,
  349. "name": f"{sub_sub_state_name}.c1s",
  350. "path": "/",
  351. "sameSite": "Lax",
  352. "secure": False,
  353. "value": "c1s%20value",
  354. },
  355. }
  356. AppHarness._poll_for(
  357. lambda: all(cookie_key in cookie_info_map(driver) for cookie_key in exp_cookies)
  358. )
  359. cookies = cookie_info_map(driver)
  360. for exp_cookie_key, exp_cookie_data in exp_cookies.items():
  361. assert cookies.pop(exp_cookie_key) == exp_cookie_data
  362. # assert all cookies have been popped for this page
  363. assert not cookies
  364. # Test cookie with expiry by itself to avoid timing flakiness
  365. set_sub("c3", "c3 value")
  366. AppHarness._poll_for(lambda: f"{sub_state_name}.c3" in cookie_info_map(driver))
  367. c3_cookie = cookie_info_map(driver)[f"{sub_state_name}.c3"]
  368. assert c3_cookie.pop("expiry") is not None
  369. assert c3_cookie == {
  370. "domain": "localhost",
  371. "httpOnly": False,
  372. "name": f"{sub_state_name}.c3",
  373. "path": "/",
  374. "sameSite": "Lax",
  375. "secure": False,
  376. "value": "c3%20value",
  377. }
  378. time.sleep(2) # wait for c3 to expire
  379. if not isinstance(driver, Firefox):
  380. # Note: Firefox does not remove expired cookies Bug 576347
  381. assert f"{sub_state_name}.c3" not in cookie_info_map(driver)
  382. local_storage_items = local_storage.items()
  383. local_storage_items.pop("last_compiled_time", None)
  384. local_storage_items.pop("theme", None)
  385. assert local_storage_items.pop(f"{sub_state_name}.l1") == "l1 value"
  386. assert local_storage_items.pop(f"{sub_state_name}.l2") == "l2 value"
  387. assert local_storage_items.pop("l3") == "l3 value"
  388. assert local_storage_items.pop(f"{sub_state_name}.l4") == "l4 value"
  389. assert local_storage_items.pop(f"{sub_sub_state_name}.l1s") == "l1s value"
  390. assert not local_storage_items
  391. session_storage_items = session_storage.items()
  392. session_storage_items.pop("token", None)
  393. assert session_storage_items.pop(f"{sub_state_name}.s1") == "s1 value"
  394. assert session_storage_items.pop(f"{sub_state_name}.s2") == "s2 value"
  395. assert session_storage_items.pop("s3") == "s3 value"
  396. assert session_storage_items.pop(f"{sub_sub_state_name}.s1s") == "s1s value"
  397. assert not session_storage_items
  398. assert c1.text == "c1 value"
  399. assert c2.text == "c2 value"
  400. assert c3.text == "c3 value"
  401. assert c4.text == "c4 value"
  402. assert c5.text == "c5 value"
  403. assert c6.text == "c6 value"
  404. assert c7.text == "c7 value"
  405. assert l1.text == "l1 value"
  406. assert l2.text == "l2 value"
  407. assert l3.text == "l3 value"
  408. assert l4.text == "l4 value"
  409. assert s1.text == "s1 value"
  410. assert s2.text == "s2 value"
  411. assert s3.text == "s3 value"
  412. assert c1s.text == "c1s value"
  413. assert l1s.text == "l1s value"
  414. assert s1s.text == "s1s value"
  415. # navigate to the /foo route
  416. with utils.poll_for_navigation(driver):
  417. driver.get(client_side.frontend_url + "/foo")
  418. # get new references to all cookie and local storage elements
  419. c1 = driver.find_element(By.ID, "c1")
  420. c2 = driver.find_element(By.ID, "c2")
  421. c3 = driver.find_element(By.ID, "c3")
  422. c4 = driver.find_element(By.ID, "c4")
  423. c5 = driver.find_element(By.ID, "c5")
  424. c6 = driver.find_element(By.ID, "c6")
  425. c7 = driver.find_element(By.ID, "c7")
  426. l1 = driver.find_element(By.ID, "l1")
  427. l2 = driver.find_element(By.ID, "l2")
  428. l3 = driver.find_element(By.ID, "l3")
  429. l4 = driver.find_element(By.ID, "l4")
  430. s1 = driver.find_element(By.ID, "s1")
  431. s2 = driver.find_element(By.ID, "s2")
  432. s3 = driver.find_element(By.ID, "s3")
  433. c1s = driver.find_element(By.ID, "c1s")
  434. l1s = driver.find_element(By.ID, "l1s")
  435. s1s = driver.find_element(By.ID, "s1s")
  436. assert c1.text == "c1 value"
  437. assert c2.text == "c2 value"
  438. assert c3.text == "" # cookie expired so value removed from state
  439. assert c4.text == "c4 value"
  440. assert c5.text == "c5 value"
  441. assert c6.text == "c6 value"
  442. assert c7.text == "c7 value"
  443. assert l1.text == "l1 value"
  444. assert l2.text == "l2 value"
  445. assert l3.text == "l3 value"
  446. assert l4.text == "l4 value"
  447. assert s1.text == "s1 value"
  448. assert s2.text == "s2 value"
  449. assert s3.text == "s3 value"
  450. assert c1s.text == "c1s value"
  451. assert l1s.text == "l1s value"
  452. assert s1s.text == "s1s value"
  453. # reset the backend state to force refresh from client storage
  454. async with client_side.modify_state(f"{token}_{state_name}") as state:
  455. state.reset()
  456. driver.refresh()
  457. # wait for the backend connection to send the token (again)
  458. token_input = driver.find_element(By.ID, "token")
  459. assert token_input
  460. token = client_side.poll_for_value(token_input)
  461. assert token is not None
  462. # get new references to all cookie and local storage elements (again)
  463. c1 = driver.find_element(By.ID, "c1")
  464. c2 = driver.find_element(By.ID, "c2")
  465. c3 = driver.find_element(By.ID, "c3")
  466. c4 = driver.find_element(By.ID, "c4")
  467. c5 = driver.find_element(By.ID, "c5")
  468. c6 = driver.find_element(By.ID, "c6")
  469. c7 = driver.find_element(By.ID, "c7")
  470. l1 = driver.find_element(By.ID, "l1")
  471. l2 = driver.find_element(By.ID, "l2")
  472. l3 = driver.find_element(By.ID, "l3")
  473. l4 = driver.find_element(By.ID, "l4")
  474. s1 = driver.find_element(By.ID, "s1")
  475. s2 = driver.find_element(By.ID, "s2")
  476. s3 = driver.find_element(By.ID, "s3")
  477. c1s = driver.find_element(By.ID, "c1s")
  478. l1s = driver.find_element(By.ID, "l1s")
  479. s1s = driver.find_element(By.ID, "s1s")
  480. assert c1.text == "c1 value"
  481. assert c2.text == "c2 value"
  482. assert c3.text == "" # temporary cookie expired after reset state!
  483. assert c4.text == "c4 value"
  484. assert c5.text == "c5 value"
  485. assert c6.text == "c6 value"
  486. assert c7.text == "c7 value"
  487. assert l1.text == "l1 value"
  488. assert l2.text == "l2 value"
  489. assert l3.text == "l3 value"
  490. assert l4.text == "l4 value"
  491. assert s1.text == "s1 value"
  492. assert s2.text == "s2 value"
  493. assert s3.text == "s3 value"
  494. assert c1s.text == "c1s value"
  495. assert l1s.text == "l1s value"
  496. assert s1s.text == "s1s value"
  497. # make sure c5 cookie shows up on the `/foo` route
  498. AppHarness._poll_for(lambda: f"{sub_state_name}.c5" in cookie_info_map(driver))
  499. assert cookie_info_map(driver)[f"{sub_state_name}.c5"] == {
  500. "domain": "localhost",
  501. "httpOnly": False,
  502. "name": f"{sub_state_name}.c5",
  503. "path": "/foo/",
  504. "sameSite": "Lax",
  505. "secure": False,
  506. "value": "c5%20value",
  507. }
  508. # Open a new tab to check that sync'd local storage is working
  509. main_tab = driver.window_handles[0]
  510. driver.switch_to.new_window("window")
  511. driver.get(client_side.frontend_url)
  512. # New tab should have a different state token.
  513. assert poll_for_token() != token
  514. # Set values and check them in the new tab.
  515. set_sub("l5", "l5 value")
  516. set_sub("l6", "l6 value")
  517. l5 = driver.find_element(By.ID, "l5")
  518. l6 = driver.find_element(By.ID, "l6")
  519. assert AppHarness._poll_for(lambda: l6.text == "l6 value")
  520. assert l5.text == "l5 value"
  521. # Set session storage values in the new tab
  522. set_sub("s1", "other tab s1")
  523. s1 = driver.find_element(By.ID, "s1")
  524. s2 = driver.find_element(By.ID, "s2")
  525. s3 = driver.find_element(By.ID, "s3")
  526. assert AppHarness._poll_for(lambda: s1.text == "other tab s1")
  527. assert s2.text == "s2 default"
  528. assert s3.text == ""
  529. # Switch back to main window.
  530. driver.switch_to.window(main_tab)
  531. # The values should have updated automatically.
  532. l5 = driver.find_element(By.ID, "l5")
  533. l6 = driver.find_element(By.ID, "l6")
  534. assert AppHarness._poll_for(lambda: l6.text == "l6 value")
  535. assert l5.text == "l5 value"
  536. s1 = driver.find_element(By.ID, "s1")
  537. s2 = driver.find_element(By.ID, "s2")
  538. s3 = driver.find_element(By.ID, "s3")
  539. assert AppHarness._poll_for(lambda: s1.text == "s1 value")
  540. assert s2.text == "s2 value"
  541. assert s3.text == "s3 value"
  542. # Simulate state expiration
  543. if isinstance(client_side.state_manager, StateManagerRedis):
  544. await client_side.state_manager.redis.delete(
  545. _substate_key(token, State.get_full_name())
  546. )
  547. await client_side.state_manager.redis.delete(_substate_key(token, state_name))
  548. await client_side.state_manager.redis.delete(
  549. _substate_key(token, sub_state_name)
  550. )
  551. await client_side.state_manager.redis.delete(
  552. _substate_key(token, sub_sub_state_name)
  553. )
  554. elif isinstance(client_side.state_manager, (StateManagerMemory, StateManagerDisk)):
  555. del client_side.state_manager.states[token]
  556. if isinstance(client_side.state_manager, StateManagerDisk):
  557. client_side.state_manager.token_expiration = 0
  558. client_side.state_manager._purge_expired_states()
  559. # Ensure the state is gone (not hydrated)
  560. async def poll_for_not_hydrated():
  561. state = await client_side.get_state(_substate_key(token or "", state_name))
  562. return not state.is_hydrated
  563. assert await AppHarness._poll_for_async(poll_for_not_hydrated)
  564. # Trigger event to get a new instance of the state since the old was expired.
  565. set_sub("c1", "c1 post expire")
  566. # get new references to all cookie and local storage elements (again)
  567. c1 = driver.find_element(By.ID, "c1")
  568. c2 = driver.find_element(By.ID, "c2")
  569. c3 = driver.find_element(By.ID, "c3")
  570. c4 = driver.find_element(By.ID, "c4")
  571. c5 = driver.find_element(By.ID, "c5")
  572. c6 = driver.find_element(By.ID, "c6")
  573. c7 = driver.find_element(By.ID, "c7")
  574. l1 = driver.find_element(By.ID, "l1")
  575. l2 = driver.find_element(By.ID, "l2")
  576. l3 = driver.find_element(By.ID, "l3")
  577. l4 = driver.find_element(By.ID, "l4")
  578. s1 = driver.find_element(By.ID, "s1")
  579. s2 = driver.find_element(By.ID, "s2")
  580. s3 = driver.find_element(By.ID, "s3")
  581. c1s = driver.find_element(By.ID, "c1s")
  582. l1s = driver.find_element(By.ID, "l1s")
  583. s1s = driver.find_element(By.ID, "s1s")
  584. assert c1.text == "c1 post expire"
  585. assert c2.text == "c2 value"
  586. assert c3.text == "" # temporary cookie expired after reset state!
  587. assert c4.text == "c4 value"
  588. assert c5.text == "c5 value"
  589. assert c6.text == "c6 value"
  590. assert c7.text == "c7 value"
  591. assert l1.text == "l1 value"
  592. assert l2.text == "l2 value"
  593. assert l3.text == "l3 value"
  594. assert l4.text == "l4 value"
  595. assert s1.text == "s1 value"
  596. assert s2.text == "s2 value"
  597. assert s3.text == "s3 value"
  598. assert c1s.text == "c1s value"
  599. assert l1s.text == "l1s value"
  600. assert s1s.text == "s1s value"
  601. # Get the backend state and ensure the values are still set
  602. async def get_sub_state():
  603. root_state = await client_side.get_state(
  604. _substate_key(token or "", sub_state_name)
  605. )
  606. state = root_state.substates[client_side.get_state_name("_client_side_state")]
  607. sub_state = state.substates[
  608. client_side.get_state_name("_client_side_sub_state")
  609. ]
  610. return sub_state
  611. async def poll_for_c1_set():
  612. sub_state = await get_sub_state()
  613. return sub_state.c1 == "c1 post expire"
  614. assert await AppHarness._poll_for_async(poll_for_c1_set)
  615. sub_state = await get_sub_state()
  616. assert sub_state.c1 == "c1 post expire"
  617. assert sub_state.c2 == "c2 value"
  618. assert sub_state.c3 == ""
  619. assert sub_state.c4 == "c4 value"
  620. assert sub_state.c5 == "c5 value"
  621. assert sub_state.c6 == "c6 value"
  622. assert sub_state.c7 == "c7 value"
  623. assert sub_state.l1 == "l1 value"
  624. assert sub_state.l2 == "l2 value"
  625. assert sub_state.l3 == "l3 value"
  626. assert sub_state.l4 == "l4 value"
  627. assert sub_state.s1 == "s1 value"
  628. assert sub_state.s2 == "s2 value"
  629. assert sub_state.s3 == "s3 value"
  630. sub_sub_state = sub_state.substates[
  631. client_side.get_state_name("_client_side_sub_sub_state")
  632. ]
  633. assert sub_sub_state.c1s == "c1s value"
  634. assert sub_sub_state.l1s == "l1s value"
  635. assert sub_sub_state.s1s == "s1s value"
  636. # clear the cookie jar and local storage, ensure state reset to default
  637. driver.delete_all_cookies()
  638. local_storage.clear()
  639. # refresh the page to trigger re-hydrate
  640. driver.refresh()
  641. # wait for the backend connection to send the token (again)
  642. token_input = driver.find_element(By.ID, "token")
  643. assert token_input
  644. token = client_side.poll_for_value(token_input)
  645. assert token is not None
  646. # all values should be back to their defaults
  647. c1 = driver.find_element(By.ID, "c1")
  648. c2 = driver.find_element(By.ID, "c2")
  649. c3 = driver.find_element(By.ID, "c3")
  650. c4 = driver.find_element(By.ID, "c4")
  651. c5 = driver.find_element(By.ID, "c5")
  652. c6 = driver.find_element(By.ID, "c6")
  653. c7 = driver.find_element(By.ID, "c7")
  654. l1 = driver.find_element(By.ID, "l1")
  655. l2 = driver.find_element(By.ID, "l2")
  656. l3 = driver.find_element(By.ID, "l3")
  657. l4 = driver.find_element(By.ID, "l4")
  658. c1s = driver.find_element(By.ID, "c1s")
  659. l1s = driver.find_element(By.ID, "l1s")
  660. # assert on defaults where present
  661. assert c1.text == ""
  662. assert c2.text == "c2 default"
  663. assert c3.text == ""
  664. assert c4.text == ""
  665. assert c5.text == ""
  666. assert c6.text == ""
  667. assert c7.text == "c7 default"
  668. assert l1.text == ""
  669. assert l2.text == "l2 default"
  670. assert l3.text == ""
  671. assert l4.text == "l4 default"
  672. assert c1s.text == ""
  673. assert l1s.text == ""