diff --git a/CHANGELOG.md b/CHANGELOG.md index ebc971e73e..92abb8c228 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,11 @@ updates in clientside functions. - [#899](https://github.com/plotly/dash/pull/899) Add support for async dependencies and components - [#973](https://github.com/plotly/dash/pull/973) Adds support for resource caching and adds a fallback caching mechanism through etag + +### 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. diff --git a/dash/testing/browser.py b/dash/testing/browser.py index 61c76c222f..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): @@ -445,7 +447,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 @@ -498,14 +500,18 @@ 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: 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) - 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..c7db11b69d 100644 --- a/dash/testing/dash_page.py +++ b/dash/testing/dash_page.py @@ -36,6 +36,17 @@ 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.window_store: + return self.redux_state_rqs and all( + (_.get("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(