def check_input_group_button_callbacks(runner): runner.find_element("#input-group-button").click() wait.until( lambda: runner.find_element("#input-group-button-input").get_attribute( "value") != "", timeout=4, )
def test_dltx001_download_text(dash_dcc): text = "Hello, world!" filename = "hello.txt" # Create app. app = dash.Dash(__name__, prevent_initial_callbacks=True) app.layout = html.Div( [html.Button("Click", id="btn"), dcc.Download(id="download")]) @app.callback(Output("download", "data"), Input("btn", "n_clicks")) def download(_): return dcc.send_string(text, filename) dash_dcc.start_server(app) # Check that there is nothing before clicking fp = os.path.join(dash_dcc.download_path, filename) assert not os.path.isfile(fp) dash_dcc.find_element("#btn").click() # Check that a file has been download, and that it's content matches the original text. until(lambda: os.path.exists(fp), 10) with open(fp, "r") as f: content = f.read() assert content == text
def start(self, app, **kwargs): """Start the app server in threading flavor.""" app.server.add_url_rule(self.stop_route, self.stop_route, self._stop_server) def _handle_error(): self._stop_server() app.server.errorhandler(500)(_handle_error) def run(): app.scripts.config.serve_locally = True app.css.config.serve_locally = True if "port" not in kwargs: kwargs["port"] = self.port else: self.port = kwargs["port"] app.run_server(threaded=True, **kwargs) self.thread = threading.Thread(target=run) self.thread.daemon = True try: self.thread.start() except RuntimeError: # multiple call on same thread logger.exception("threaded server failed to start") self.started = False self.started = self.thread.is_alive() # wait until server is able to answer http request wait.until(lambda: self.accessible(self.url), timeout=1)
def check_chapter(chapter): dash_duo.wait_for_element( '#{}-graph:not(.dash-graph--pending)'.format(chapter)) for key in dash_duo.redux_state_paths: assert dash_duo.find_elements( "#{}".format(key)), "each element should exist in the dom" value = (chapters[chapter][0]["{}-controls".format(chapter)].value if chapter == "chapter3" else chapters[chapter]["{}-controls".format(chapter)].value) # check the actual values dash_duo.wait_for_text_to_equal("#{}-label".format(chapter), value) wait.until( lambda: (dash_duo.driver.execute_script( "return document." 'querySelector("#{}-graph:not(.dash-graph--pending) .js-plotly-plot").' .format(chapter) + "layout.title.text") == value), TIMEOUT, ) rqs = dash_duo.redux_state_rqs assert rqs, "request queue is not empty" assert all((rq["status"] == 200 and not rq["rejected"] for rq in rqs))
def test_head001_renames_only_row(test, row): test.start_server(get_app()) target = test.table("table") title = [ target.column("rows").get_text(0), target.column("rows").get_text(1), target.column("rows").get_text(2), ] target.column("rows").edit(row) alert = test.driver.switch_to.alert alert.send_keys("modified") alert.accept() for i in range(3): wait.until( lambda: target.column("rows").get_text(i) == "modified" if row == i else title[i], 3, ) assert test.get_log_errors() == []
def percy_snapshot(self, name="", wait_for_callbacks=False, convert_canvases=False): """percy_snapshot - visual test api shortcut to `percy_runner.snapshot`. It also combines the snapshot `name` with the Python version. """ snapshot_name = "{} - py{}.{}".format( name, sys.version_info.major, sys.version_info.minor ) logger.info("taking snapshot name => %s", snapshot_name) try: if wait_for_callbacks: # the extra one second sleep adds safe margin in the context # of wait_for_callbacks time.sleep(1) until(self._wait_for_callbacks, timeout=40, poll=0.3) except TestingTimeoutError: # API will log the error but this TimeoutError should not block # the test execution to continue and it will still do a snapshot # as diff reference for the build run. logger.error( "wait_for_callbacks failed => status of invalid rqs %s", self.redux_state_rqs, ) if convert_canvases: self.driver.execute_script( """ const stash = window._canvasStash = []; Array.from(document.querySelectorAll('canvas')).forEach(c => { const i = document.createElement('img'); i.src = c.toDataURL(); i.width = c.width; i.height = c.height; i.setAttribute('style', c.getAttribute('style')); i.className = c.className; i.setAttribute('data-canvasnum', stash.length); stash.push(c); c.parentElement.insertBefore(i, c); c.parentElement.removeChild(c); }); """ ) self.percy_runner.snapshot(name=snapshot_name) self.driver.execute_script( """ const stash = window._canvasStash; Array.from( document.querySelectorAll('img[data-canvasnum]') ).forEach(i => { const c = stash[+i.getAttribute('data-canvasnum')]; i.parentElement.insertBefore(c, i); i.parentElement.removeChild(i); }); delete window._canvasStash; """ ) else: self.percy_runner.snapshot(name=snapshot_name)
def check_spinner_callbacks(runner): runner.find_element("#loading-button").click() wait.until( lambda: runner.find_element("#loading-output").text == "Output loaded 1 times", timeout=4, )
def test_snap001_index_page_links(dash_doc, index_pages): dash_doc.wait_for_element(".toc .toc--chapter-content") dash_doc.percy_snapshot("index - 1") bad_links = [] timeout_pages = [] good_links = ['/', '/search'] for resource in index_pages: if resource.startswith('/'): hook_id = "wait-for-page-{}".format(resource) res = resource.lstrip("/") try: dash_doc.driver.get("{}/{}".format( dash_doc.server_url.rstrip("/"), res)) dash_doc.wait_for_element_by_id(hook_id) if res in ['basic-callbacks', 'datatable/callbacks']: # these two pages have an intermittent problem with their # resource queues not clearing properly. While we sort this out, # just wait a reasonably long time on these pages. sleep(3) else: # everything else we can just wait for all callbacks to finish sleep(1) until(dash_doc._wait_for_callbacks, timeout=40, poll=0.3) # hide non-repeatable elements before the snapshot selectors_to_hide = ",".join([ "#my-dashbio-molecule2d", "#molecule2d-selectedatomids", "#molecule2d-modeldata", ".forna-container", "#first_output_3", "#second_output_3", "#third_output_3" ]) dash_doc.driver.execute_script( "document.querySelectorAll('" + selectors_to_hide + "').forEach(el=>el.style.visibility = 'hidden');") dash_doc.percy_snapshot(res, wait_for_callbacks=False) except Exception as e: timeout_pages.append('{} --- on page {}'.format( str(e), resource)) linked_paths = dash_doc.driver.execute_script( 'return Array.from(document.querySelectorAll(\'a[href^="/"]\'))' '.map(a=>a.attributes.href.value)') for link in linked_paths: if (link.rstrip('/') not in URL_TO_CONTENT_MAP and link not in good_links and '.mp4' not in link ): # we link to a video on the devtools page msg = '{} --- on page {}'.format(link, resource) logger.info(msg) bad_links.append(msg) try: dash_doc.driver.execute_script("window.history.go(-1)") except Exception as e: raise Exception([ Exception(['Error going back while on page ', resource]), e ]) assert (bad_links + timeout_pages) == []
def test_cbsc016_extra_components_callback(dash_duo): lock = Lock() app = Dash(__name__) app._extra_components.append(dcc.Store(id="extra-store", data=123)) app.layout = html.Div([ dcc.Input(id="input", value="initial value"), html.Div(html.Div([1.5, None, "string", html.Div(id="output-1")])), ]) store_data = Value("i", 0) @app.callback( Output("output-1", "children"), [Input("input", "value"), Input("extra-store", "data")], ) def update_output(value, data): with lock: store_data.value = data return value dash_duo.start_server(app) assert dash_duo.find_element("#output-1").text == "initial value" input_ = dash_duo.find_element("#input") dash_duo.clear_input(input_) input_.send_keys("A") wait.until(lambda: dash_duo.find_element("#output-1").text == "A", 2) assert store_data.value == 123 assert dash_duo.get_logs() == []
def check_chapter(chapter): dash_duo.wait_for_element("#{}-graph:not(.dash-graph--pending)".format(chapter)) for key in dash_duo.redux_state_paths["strs"]: assert dash_duo.find_elements( "#{}".format(key) ), "each element should exist in the dom" value = ( chapters[chapter][0]["{}-controls".format(chapter)].value if chapter == "chapter3" else chapters[chapter]["{}-controls".format(chapter)].value ) # check the actual values dash_duo.wait_for_text_to_equal("#{}-label".format(chapter), value) wait.until( lambda: ( dash_duo.driver.execute_script( 'return document.querySelector("' + "#{}-graph:not(.dash-graph--pending) .js-plotly-plot".format( chapter ) + '").layout.title.text' ) == value ), TIMEOUT, ) assert dash_duo.redux_state_rqs == [], "pendingCallbacks is empty"
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. """ snapshot_name = "{} - py{}.{}".format(name, sys.version_info.major, sys.version_info.minor) logger.info("taking snapshot name => %s", snapshot_name) try: if wait_for_callbacks: # the extra one second sleep adds safe margin in the context # of wait_for_callbacks time.sleep(1) until(self._wait_for_callbacks, timeout=40, poll=0.3) except TestingTimeoutError: # API will log the error but this TimeoutError should not block # the test execution to continue and it will still do a snapshot # as diff reference for the build run. logger.error( "wait_for_callbacks failed => status of invalid rqs %s", list(_ for _ in self.redux_state_rqs if not _.get("responseTime")), ) logger.debug("full content of the rqs => %s", self.redux_state_rqs) self.percy_runner.snapshot(name=snapshot_name)
def check_nav_callbacks(runner): runner.find_element("#button-link").click() wait.until( lambda: runner.find_element("#button-clicks").text == "Button clicked 1 times", timeout=4, )
def _click_radio(runner, radio_id, option): label_id = f"_dbcprivate_radioitems_{radio_id}_input_{option}" wait.until( lambda: len(runner.find_elements(f"#{label_id}")) != 0, timeout=8, ) runner.driver.find_element_by_id(label_id).click()
def check_button_callbacks(runner): runner.find_element("#example-button").click() wait.until( lambda: runner.find_element("#example-output").text == "Clicked 1 times.", timeout=4, )
def test_dlfi001_download_file(dash_dcc): filename = "chuck.jpg" asset_folder = os.path.join(os.path.dirname(__file__), "download-assets") # Create app. app = Dash(__name__, prevent_initial_callbacks=True) app.layout = html.Div( [html.Button("Click", id="btn"), dcc.Download(id="download")]) @app.callback(Output("download", "data"), Input("btn", "n_clicks")) def download(_): return dcc.send_file(os.path.join(asset_folder, filename)) dash_dcc.start_server(app) # Check that there is nothing before clicking fp = os.path.join(dash_dcc.download_path, filename) assert not os.path.isfile(fp) dash_dcc.find_element("#btn").click() # Check that a file has been download, and that it's content matches the original. until(lambda: os.path.exists(fp), 10) with open(fp, "rb") as f: content = f.read() with open(os.path.join(asset_folder, filename), "rb") as f: original = f.read() assert content == original assert dash_dcc.get_logs() == []
def check_list_group_callbacks(runner): runner.find_element("#button-item").click() wait.until( lambda: runner.find_element("#counter").text == "Button clicked 1 times", timeout=4, )
def test_head002_preserves_hidden_columns_on_rename(test): test.start_server(get_app(dict(merge_duplicate_headers=True))) target = test.table("table") wait.until(lambda: target.column(6).get().get_attribute("colspan") == "4", 3) assert target.column(6).get_text() == "" target.column(8).hide(2) target.column(6).hide(2) target.column(1).hide(2) target.column(5).edit() alert = test.driver.switch_to.alert alert.send_keys("Chill") alert.accept() target.toggle_columns().open() for el in target.toggle_columns().get_hidden(): el.click() target.toggle_columns().close() wait.until(lambda: target.column(6).get().get_attribute("colspan") == "4", 3) assert target.column(6).get_text() == "Chill" assert test.get_log_errors() == []
def check_tabs_active_tab_callbacks(runner): # Get julia to wait until it's loaded wait.until(lambda: len(runner.find_elements(".card")) > 0, timeout=4) # Default view on landing wait.until( lambda: runner.find_element("div.card-body > p.card-text").text == "This is tab 1!", timeout=4, ) wait.until( lambda: runner.find_element("div.card-body > button.btn-success").text == "Click here", timeout=4, ) # Activate the second tab runner.find_elements("a.nav-link")[1].click() wait.until( lambda: runner.find_element("div.card-body > p.card-text").text == "This is tab 2!", timeout=4, ) wait.until( lambda: runner.find_element("div.card-body > button.btn-danger").text == "Don't click here", timeout=4, )
def test_dbpo003_popover_legacy(dash_duo): app = Dash(external_stylesheets=[themes.BOOTSTRAP]) app.layout = html.Div( [ html.Div("No Target Here", id="not-a-target"), html.Hr(), Popover( [PopoverHeader("Test Header"), PopoverBody("Test content")], id="popover", target="popover-target", trigger="legacy", ), html.Div("Target", id="popover-target"), ], className="container p-5 w-50", ) dash_duo.start_server(app) dash_duo.wait_for_element_by_id("popover-target").click() dash_duo.wait_for_text_to_equal(".popover-body", "Test content", timeout=4) # Try clicking on the popover - shouldn't dismiss dash_duo.wait_for_element_by_id("popover").click() dash_duo.wait_for_text_to_equal(".popover-body", "Test content", timeout=4) # Try clicking outside the popover - should dismiss dash_duo.wait_for_element_by_id("not-a-target").click() wait.until( lambda: len(dash_duo.find_elements("#popover")) == 0, timeout=4, )
def test_scrol001_fixed_alignment(test, fixed_rows, fixed_columns, ops): props = {**base_props, **fixed_rows, **fixed_columns, **ops} app = dash.Dash(__name__) app.layout = DataTable(**props) test.start_server(app) target = test.table("table") assert target.is_ready() fixed_width = test.driver.execute_script( "return parseFloat(getComputedStyle(document.querySelector('#table .cell-0-0')).width) || 0;" ) assert -get_margin(test) == pytest.approx(fixed_width, abs=1) scroll_by(test, 200) wait.until( lambda: -get_margin(test) == pytest.approx(fixed_width + 200, abs=1), 3) scroll_by(test, -200) wait.until(lambda: -get_margin(test) == pytest.approx(fixed_width, abs=1), 3) assert test.get_log_errors() == []
def check_popover_callbacks(runner): assert len(runner.find_elements("#popover")) == 0 runner.find_element("#toggle").click() wait.until( lambda: len(runner.find_elements("#popover")) > 0, timeout=4, )
def test_cdpr002_updatemodes(dash_dcc): app = dash.Dash(__name__) app.layout = html.Div([ dcc.DatePickerRange( id="date-picker-range", start_date_id="startDate", end_date_id="endDate", start_date_placeholder_text="Select a start date!", end_date_placeholder_text="Select an end date!", updatemode="bothdates", ), html.Div(id="date-picker-range-output"), ]) @app.callback( Output("date-picker-range-output", "children"), [ Input("date-picker-range", "start_date"), Input("date-picker-range", "end_date"), ], ) def update_output(start_date, end_date): return "{} - {}".format(start_date, end_date) dash_dcc.start_server(app=app) start_date = dash_dcc.find_element("#startDate") start_date.click() end_date = dash_dcc.find_element("#endDate") end_date.click() assert ( dash_dcc.find_element( "#date-picker-range-output").text == "None - None" ), "the output should not update when both clicked but no selection happen" start_date.click() dash_dcc.find_elements(dash_dcc.date_picker_day_locator)[4].click() assert (dash_dcc.find_element( "#date-picker-range-output").text == "None - None" ), "the output should not update when only one is selected" eday = dash_dcc.find_elements(dash_dcc.date_picker_day_locator)[-4] wait.until(lambda: eday.is_displayed() and eday.is_enabled(), timeout=2) eday.click() date_tokens = set(start_date.get_attribute("value").split("/")) date_tokens.update(end_date.get_attribute("value").split("/")) assert (set( itertools.chain(*[ _.split("-") for _ in dash_dcc.find_element( "#date-picker-range-output").text.split(" - ") ])) == date_tokens), "date should match the callback output"
def test_grbs002_wrapped_graph_has_no_infinite_loop(dash_dcc, is_eager): df = pd.DataFrame(np.random.randn(50, 50)) figure = { "data": [ {"x": df.columns, "y": df.index, "z": df.values, "type": "heatmap"} ], "layout": {"xaxis": {"scaleanchor": "y"}}, } app = dash.Dash(__name__, eager_loading=is_eager) app.layout = html.Div( style={ "backgroundColor": "red", "height": "100vmin", "width": "100vmin", "overflow": "hidden", "position": "relative", }, children=[ dcc.Loading( children=[ dcc.Graph( id="graph", figure=figure, style={ "position": "absolute", "top": 0, "left": 0, "backgroundColor": "blue", "width": "100%", "height": "100%", "overflow": "hidden", }, ) ] ) ], ) call_count = Value("i", 0) @app.callback(Output("graph", "figure"), [Input("graph", "relayoutData")]) def selected_df_figure(selection): call_count.value += 1 figure["data"][0]["x"] = df.columns figure["data"][0]["y"] = df.index figure["data"][0]["z"] = df.values return figure dash_dcc.start_server(app) wait.until(lambda: dash_dcc.driver.title == "Dash", timeout=2) sleep(1) # TODO: not sure 2 calls actually makes sense here, shouldn't it be 1? # but that's what we had as of the 608 fix, PR 621, so let's lock that # in for now. assert call_count.value == 2
def test_inin003_aborted_callback(dash_duo): """Raising PreventUpdate OR returning no_update prevents update and triggering dependencies.""" initial_input = "initial input" initial_output = "initial output" app = Dash(__name__) app.layout = html.Div( [ dcc.Input(id="input", value=initial_input), html.Div(initial_output, id="output1"), html.Div(initial_output, id="output2"), ] ) callback1_count = Value("i", 0) callback2_count = Value("i", 0) @app.callback(Output("output1", "children"), [Input("input", "value")]) def callback1(value): callback1_count.value += 1 if callback1_count.value > 2: return no_update raise PreventUpdate("testing callback does not update") return value @app.callback( Output("output2", "children"), [Input("output1", "children")] ) def callback2(value): callback2_count.value += 1 return value dash_duo.start_server(app) input_ = dash_duo.find_element("#input") input_.send_keys("xyz") dash_duo.wait_for_text_to_equal("#input", "initial inputxyz") until( lambda: callback1_count.value == 4, timeout=3, msg="callback1 runs 4x (initial page load and 3x through send_keys)", ) assert ( callback2_count.value == 0 ), "callback2 is never triggered, even on initial load" # double check that output1 and output2 children were not updated assert dash_duo.find_element("#output1").text == initial_output assert dash_duo.find_element("#output2").text == initial_output assert not dash_duo.get_logs() dash_duo.percy_snapshot(name="aborted")
def test_scrol001_fixed_alignment(test, fixed_rows, fixed_columns, ops): base_props = dict( id="table", columns=[{ "name": i, "id": i } for i in df.columns], row_selectable="single", row_deletable=True, data=df.to_dict("records"), fixed_rows={ "headers": True, "data": 1 }, style_cell=dict(width=150), style_table=dict(width=500), ) props = {**base_props, **fixed_rows, **fixed_columns, **ops} app = dash.Dash(__name__) app.layout = DataTable(**props) test.start_server(app) target = test.table("table") assert target.is_ready() fixed_width = test.driver.execute_script( "return parseFloat(getComputedStyle(document.querySelector('#table .cell-0-0')).width) || 0;" ) margin_left = test.driver.execute_script( "return parseFloat(getComputedStyle(document.querySelector('#table .cell-0-1')).marginLeft);" ) assert -margin_left == fixed_width test.driver.execute_script( "document.querySelector('#table .row-1').scrollBy(200, 0);") wait.until( lambda: -test.driver.execute_script( "return parseFloat(getComputedStyle(document.querySelector('#table .cell-0-1')).marginLeft);" ) == fixed_width + 200, 3, ) test.driver.execute_script( "document.querySelector('#table .row-1').scrollBy(-200, 0);") wait.until( lambda: -test.driver.execute_script( "return parseFloat(getComputedStyle(document.querySelector('#table .cell-0-1')).marginLeft);" ) == fixed_width, 3, )
def wait_for_no_elements(self, selector, timeout=None): """Explicit wait until an element is NOT found. timeout defaults to the fixture's `wait_timeout`.""" until( # if we use get_elements it waits a long time to see if they appear # so this one calls out directly to execute_script lambda: self.driver.execute_script( f"return document.querySelectorAll('{selector}').length") == 0, timeout or self._wait_timeout, )
def check_dropdown_callbacks(runner): runner.find_element(".btn").click() runner.find_element("#dropdown-button").click() wait.until( lambda: runner.find_element("#item-clicks").text == "Button clicked 1 times.", timeout=4, )
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. """ snapshot_name = "{} - py{}.{}".format(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 check_toast_auto_callbacks(runner): wait.until( lambda: len(runner.find_elements("#auto-toast")) == 0, timeout=5, ) runner.find_element("#auto-toast-toggle").click() wait.until( lambda: len(runner.find_elements("#auto-toast")) > 0, timeout=4, )
def check_tabs_card_callbacks(runner): tab_links = runner.find_elements("#card-tabs > div.nav-item > a.nav-link") wait.until(lambda: tab_links[1].text == "Tab 2", timeout=4) tab_links[1].click() wait.until( lambda: runner.find_element("#card-content").text == "This is tab tab-2", timeout=4, )