From d6c2182260c0a920070411c5ae278a54b1aff458 Mon Sep 17 00:00:00 2001 From: byron Date: Wed, 23 Oct 2019 14:44:58 -0400 Subject: [PATCH 1/9] add extra wait for callbacks in queue --- dash/testing/browser.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dash/testing/browser.py b/dash/testing/browser.py index 61c76c222f..f1bfe2c2fb 100644 --- a/dash/testing/browser.py +++ b/dash/testing/browser.py @@ -504,7 +504,11 @@ def visit_and_snapshot(self, resource_path, hook_id, assert_check=True): if path != resource_path: logger.warning("we stripped the left '/' in resource_path") self.driver.get("{}/{}".format(self.server_url.rstrip("/"), path)) + + # wait for the hook_id to present and all callbacks get fired self.wait_for_element_by_id(hook_id) + until(lambda: self.redux_state_rqs, timeout=10) + self.percy_snapshot(path) if assert_check: assert not self.driver.find_elements_by_css_selector( From f6732cd0179f01359541e34db622de3d7c833694 Mon Sep 17 00:00:00 2001 From: byron Date: Wed, 23 Oct 2019 17:02:10 -0400 Subject: [PATCH 2/9] also make sure all status are 200 --- dash/testing/browser.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/dash/testing/browser.py b/dash/testing/browser.py index f1bfe2c2fb..117fe407aa 100644 --- a/dash/testing/browser.py +++ b/dash/testing/browser.py @@ -445,7 +445,7 @@ def zoom_in_graph_by_ratio( elem_or_selector, start_fraction=0.5, zoom_box_fraction=0.2, - compare=True + compare=True, ): """Zoom out a graph with a zoom box fraction of component dimension default start at middle with a rectangle of 1/5 of the dimension use @@ -507,7 +507,11 @@ def visit_and_snapshot(self, resource_path, hook_id, assert_check=True): # wait for the hook_id to present and all callbacks get fired self.wait_for_element_by_id(hook_id) - until(lambda: self.redux_state_rqs, timeout=10) + until( + lambda: self.redux_state_rqs + and all((_["status"] == 200 for _ in self.redux_state_rqs)), + timeout=10, + ) self.percy_snapshot(path) if assert_check: From 6266d86d5cc537beeea5c870c186d39e6e78f248 Mon Sep 17 00:00:00 2001 From: byron Date: Wed, 23 Oct 2019 17:13:39 -0400 Subject: [PATCH 3/9] change status to responseTime --- dash/testing/browser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dash/testing/browser.py b/dash/testing/browser.py index 117fe407aa..9d9cc3e36f 100644 --- a/dash/testing/browser.py +++ b/dash/testing/browser.py @@ -509,7 +509,7 @@ def visit_and_snapshot(self, resource_path, hook_id, assert_check=True): self.wait_for_element_by_id(hook_id) until( lambda: self.redux_state_rqs - and all((_["status"] == 200 for _ in self.redux_state_rqs)), + and all((_["responseTime"] for _ in self.redux_state_rqs)), timeout=10, ) From 6b92ad61035913e80fd1809becec7328a4386862 Mon Sep 17 00:00:00 2001 From: byron Date: Wed, 23 Oct 2019 19:44:09 -0400 Subject: [PATCH 4/9] :ok_hand: use get --- dash/testing/browser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dash/testing/browser.py b/dash/testing/browser.py index 9d9cc3e36f..7c935f5a0d 100644 --- a/dash/testing/browser.py +++ b/dash/testing/browser.py @@ -509,7 +509,7 @@ def visit_and_snapshot(self, resource_path, hook_id, assert_check=True): self.wait_for_element_by_id(hook_id) until( lambda: self.redux_state_rqs - and all((_["responseTime"] for _ in self.redux_state_rqs)), + and all((_.get("responseTime") for _ in self.redux_state_rqs)), timeout=10, ) From 61b34fc61b1ba86d68ad8642d766122e6d8ff6ac Mon Sep 17 00:00:00 2001 From: byron Date: Fri, 25 Oct 2019 11:26:33 -0400 Subject: [PATCH 5/9] make it more flexible --- dash/testing/browser.py | 16 +++++++--------- dash/testing/dash_page.py | 12 ++++++++++++ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/dash/testing/browser.py b/dash/testing/browser.py index 9d9cc3e36f..73772ab167 100644 --- a/dash/testing/browser.py +++ b/dash/testing/browser.py @@ -100,7 +100,7 @@ def __exit__(self, exc_type, exc_val, traceback): except percy.errors.Error: logger.exception("percy runner failed to finalize properly") - def percy_snapshot(self, name=""): + def percy_snapshot(self, name="", wait_for_callbacks=False): """percy_snapshot - visual test api shortcut to `percy_runner.snapshot`. It also combines the snapshot `name` with the python version. """ @@ -108,6 +108,8 @@ def percy_snapshot(self, name=""): name, sys.version_info.major, sys.version_info.minor ) logger.info("taking snapshot name => %s", snapshot_name) + if wait_for_callbacks: + until(self._wait_for_callbacks, timeout=10) self.percy_runner.snapshot(name=snapshot_name) def take_snapshot(self, name): @@ -498,7 +500,9 @@ def reset_log_timestamp(self): if entries: self._last_ts = entries[-1]["timestamp"] - def visit_and_snapshot(self, resource_path, hook_id, assert_check=True): + def visit_and_snapshot( + self, resource_path, hook_id, wait_for_callbacks=True, assert_check=True + ): try: path = resource_path.lstrip("/") if path != resource_path: @@ -507,13 +511,7 @@ def visit_and_snapshot(self, resource_path, hook_id, assert_check=True): # wait for the hook_id to present and all callbacks get fired self.wait_for_element_by_id(hook_id) - until( - lambda: self.redux_state_rqs - and all((_["responseTime"] for _ in self.redux_state_rqs)), - timeout=10, - ) - - self.percy_snapshot(path) + self.percy_snapshot(path, wait_for_callbacks=wait_for_callbacks) if assert_check: assert not self.driver.find_elements_by_css_selector( "div.dash-debug-alert" diff --git a/dash/testing/dash_page.py b/dash/testing/dash_page.py index 62b3b95e8b..c63aea8e6d 100644 --- a/dash/testing/dash_page.py +++ b/dash/testing/dash_page.py @@ -1,4 +1,5 @@ from bs4 import BeautifulSoup +import dash.testing.wait as wait class DashPageMixin(object): @@ -36,6 +37,17 @@ def redux_state_rqs(self): "return window.store.getState().requestQueue" ) + def _wait_for_callbacks(self): + if self.driver.execute_script( + "return window.store" + ) and self.driver.execute_script( + "return window.store.getState().requestQueue" + ): + return self.redux_state_rqs and all( + (_["responseTime"] for _ in self.redux_state_rqs) + ) + return True + def get_local_storage(self, store_id="local"): return self.driver.execute_script( "return JSON.parse(window.localStorage.getItem('{}'));".format( From 571d903dad96a384dbff0dc036e34fc8ff1332e3 Mon Sep 17 00:00:00 2001 From: byron Date: Fri, 25 Oct 2019 11:30:20 -0400 Subject: [PATCH 6/9] :lipstick: lint --- dash/testing/dash_page.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dash/testing/dash_page.py b/dash/testing/dash_page.py index c63aea8e6d..176eea89e5 100644 --- a/dash/testing/dash_page.py +++ b/dash/testing/dash_page.py @@ -1,5 +1,4 @@ from bs4 import BeautifulSoup -import dash.testing.wait as wait class DashPageMixin(object): From 7031fcf14a20977b13b5666ec72e2d9cfc6a9aef Mon Sep 17 00:00:00 2001 From: byron Date: Fri, 25 Oct 2019 11:54:44 -0400 Subject: [PATCH 7/9] :see_no_evil: --- dash/testing/dash_page.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dash/testing/dash_page.py b/dash/testing/dash_page.py index 176eea89e5..17c80ddd9d 100644 --- a/dash/testing/dash_page.py +++ b/dash/testing/dash_page.py @@ -40,10 +40,10 @@ def _wait_for_callbacks(self): if self.driver.execute_script( "return window.store" ) and self.driver.execute_script( - "return window.store.getState().requestQueue" + "return window.store.getState().dependenciesRequest" ): return self.redux_state_rqs and all( - (_["responseTime"] for _ in self.redux_state_rqs) + (_.get("responseTime") for _ in self.redux_state_rqs) ) return True From 4fd17a99b8b778dc41ed9c2f6ecc3ee92e863230 Mon Sep 17 00:00:00 2001 From: byron Date: Fri, 25 Oct 2019 12:29:02 -0400 Subject: [PATCH 8/9] less strict --- dash/testing/dash_page.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dash/testing/dash_page.py b/dash/testing/dash_page.py index 17c80ddd9d..c7db11b69d 100644 --- a/dash/testing/dash_page.py +++ b/dash/testing/dash_page.py @@ -36,12 +36,12 @@ def redux_state_rqs(self): "return window.store.getState().requestQueue" ) + @property + def window_store(self): + return self.driver.execute_script("return window.store") + def _wait_for_callbacks(self): - if self.driver.execute_script( - "return window.store" - ) and self.driver.execute_script( - "return window.store.getState().dependenciesRequest" - ): + if self.window_store: return self.redux_state_rqs and all( (_.get("responseTime") for _ in self.redux_state_rqs) ) From 67442b53a2a50d02fdbce0aee5572659281b1c33 Mon Sep 17 00:00:00 2001 From: byron Date: Mon, 28 Oct 2019 15:07:39 -0400 Subject: [PATCH 9/9] :pencil2: add changelog --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f46a28667..b97cefe6b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,16 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## Unreleased ### Added - [#964](https://github.com/plotly/dash/pull/964) Adds support for preventing -updates in clientside functions. +updates in clientside functions. - Reject all updates with `throw window.dash_clientside.PreventUpdate;` - Reject a single output by returning `window.dash_clientside.no_update` - [#899](https://github.com/plotly/dash/pull/899) Add support for async dependencies and components + +### Fixed +- [#974](https://github.com/plotly/dash/pull/974) Fix and improve a percy snapshot behavior issue we found in dash-docs testing. It adds a flag `wait_for_callbacks` +to ensure that, in the context of a dash app testing, the percy snapshot action will happen only after all callbacks get fired. + ## [1.4.1] - 2019-10-17 ### Fixed - [#969](https://github.com/plotly/dash/pull/969) Fix warnings emitted by react devtools coming from our own devtools components.