diff --git a/dash/CHANGELOG.md b/dash/CHANGELOG.md index d9fea4be7e..401c34cfde 100644 --- a/dash/CHANGELOG.md +++ b/dash/CHANGELOG.md @@ -1,5 +1,10 @@ ## Unreleased +### Added + +- [#918](https://github.com/plotly/dash/pull/918) Adds `wait_for_element_by_id` and `visit_and_snapshot` APIs in browser, adds `raw_command` option (it also has higher priority than +the default waitress one) and optional `start_timeout` argument to handle large application within process runner + ### Fixed - [#915](https://github.com/plotly/dash/issues/915) Fixes `dash-generate-components` on Windows diff --git a/dash/testing/application_runners.py b/dash/testing/application_runners.py index a0a248fdc7..c9575d2e3c 100644 --- a/dash/testing/application_runners.py +++ b/dash/testing/application_runners.py @@ -179,15 +179,31 @@ def __init__(self, keep_open=False, stop_timeout=3): self.proc = None # pylint: disable=arguments-differ - def start(self, app_module, application_name="app", port=8050): + def start( + self, + app_module=None, + application_name="app", + raw_command=None, + port=8050, + start_timeout=3, + ): """Start the server with waitress-serve in process flavor """ - entrypoint = "{}:{}.server".format(app_module, application_name) + if not (app_module or raw_command): # need to set a least one + logging.error( + "the process runner needs to start with" + " at least one valid command" + ) + return self.port = port - args = shlex.split( - "waitress-serve --listen=0.0.0.0:{} {}".format(port, entrypoint), + raw_command + if raw_command + else "waitress-serve --listen=0.0.0.0:{} {}:{}.server".format( + port, app_module, application_name + ), posix=not self.is_windows, ) + logger.debug("start dash process with %s", args) try: @@ -195,7 +211,7 @@ def start(self, app_module, application_name="app", port=8050): args, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) # wait until server is able to answer http request - wait.until(lambda: self.accessible(self.url), timeout=3) + wait.until(lambda: self.accessible(self.url), timeout=start_timeout) except (OSError, ValueError): logger.exception("process server has encountered an error") @@ -233,7 +249,7 @@ def __init__(self, keep_open=False, stop_timeout=3): self.proc = None # pylint: disable=arguments-differ - def start(self, app): + def start(self, app, start_timeout=2): """Start the server with waitress-serve in process flavor """ # app is a R string chunk @@ -267,7 +283,7 @@ def start(self, app): args, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) # wait until server is able to answer http request - wait.until(lambda: self.accessible(self.url), timeout=2) + wait.until(lambda: self.accessible(self.url), timeout=start_timeout) except (OSError, ValueError): logger.exception("process server has encountered an error") diff --git a/dash/testing/browser.py b/dash/testing/browser.py index 0283845202..51b7557903 100644 --- a/dash/testing/browser.py +++ b/dash/testing/browser.py @@ -173,6 +173,20 @@ def wait_for_element_by_css_selector(self, selector, timeout=None): ), ) + def wait_for_element_by_id(self, element_id, timeout=None): + """explicit wait until the element is present, + timeout if not set, equals to the fixture's `wait_timeout` + shortcut to `WebDriverWait` with `EC.presence_of_element_located` + """ + return self._wait_for( + EC.presence_of_element_located, + ((By.ID, element_id),), + timeout, + "timeout {}s => waiting for element id {}".format( + timeout if timeout else self._wait_timeout, element_id + ), + ) + def wait_for_style_to_equal(self, selector, style, val, timeout=None): """explicit wait until the element's style has expected `value` timeout if not set, equals to the fixture's `wait_timeout` @@ -433,6 +447,16 @@ def reset_log_timestamp(self): if entries: self._last_ts = entries[-1]["timestamp"] + def visit_and_snapshot(self, resource_path, hook_id): + try: + self.driver.get(self.server_url + resource_path) + self.wait_for_element_by_id(hook_id) + self.percy_snapshot(resource_path) + self.driver.back() + except WebDriverException as e: + logger.exception("snapshot at resource %s error", resource_path) + raise e + @property def driver(self): """expose the selenium webdriver as fixture property""" diff --git a/tests/unit/test_app_runners.py b/tests/unit/test_app_runners.py index 22bca7407a..784a8ff358 100644 --- a/tests/unit/test_app_runners.py +++ b/tests/unit/test_app_runners.py @@ -25,7 +25,7 @@ def test_threaded_server_smoke(dash_thread_server): sys.version_info < (3,), reason="requires python3 for process testing" ) def test_process_server_smoke(dash_process_server): - dash_process_server("simple_app") + dash_process_server('simple_app') r = requests.get(dash_process_server.url) assert r.status_code == 200, "the server is reachable" assert 'id="react-entry-point"' in r.text, "the entrypoint is present"