Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
507 changes: 0 additions & 507 deletions .circleci/config.yml

This file was deleted.

770 changes: 659 additions & 111 deletions .github/workflows/testing.yml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
lts/iron
24
333 changes: 333 additions & 0 deletions .test_durations

Large diffs are not rendered by default.

428 changes: 428 additions & 0 deletions components/dash-core-components/.test_durations

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,35 @@
import flaky

from dash import Dash, Input, Output, State, dcc, html
import plotly.graph_objects as go

from dash.exceptions import PreventUpdate
from dash.testing import wait


@pytest.mark.parametrize("responsive", [True, False, None])
@pytest.mark.parametrize("autosize", [True, False, None])
@pytest.mark.parametrize("height", [600, None])
@pytest.mark.parametrize("width", [600, None])
@pytest.mark.parametrize("is_responsive", [True, False, "auto"])
def test_grrs001_graph(dash_dcc, responsive, autosize, height, width, is_responsive):
@pytest.mark.parametrize(
"is_responsive,responsive,autosize,height,width",
[
# is_responsive=True: always responsive regardless of other params
(True, None, None, None, None),
(True, False, False, 600, 600), # still responsive even with fixed dims
# is_responsive=False: never responsive regardless of other params
(False, True, True, None, None), # not responsive even with autosize
(False, None, None, 600, 600),
# is_responsive="auto": behavior depends on other params
("auto", True, True, None, None), # responsive: all conditions met
("auto", True, True, 600, None), # responsive: one dim fixed is ok
("auto", True, False, None, None), # NOT responsive: autosize=False
(
"auto",
False,
True,
None,
None,
), # NOT responsive on resize: config.responsive=False
("auto", None, None, 600, 600), # NOT responsive: both dims fixed
],
)
def test_grrs001_graph(dash_dcc, is_responsive, responsive, autosize, height, width):
app = Dash(__name__, eager_loading=True)

header_style = dict(padding="10px", backgroundColor="yellow", flex="0 0 100px")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,7 @@ if (!listPath) {
const list = fs
.readFileSync(listPath, 'utf8')
.split('\n')
.map(item => item.trim())
.filter(item => Boolean(item));

// Get the mapping of attributes to elements
Expand Down
848 changes: 459 additions & 389 deletions components/dash-table/.test_durations

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions components/dash-table/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions components/dash-table/tests/selenium/conftest.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import platform
import time
import pytest
from functools import wraps
import inspect
Expand Down Expand Up @@ -617,6 +618,9 @@ def copy(self):
with self.hold(CMD):
self.send_keys("c")

# Small wait to let Chrome stabilize focus after clipboard operation
time.sleep(0.1)

def paste(self):
with self.hold(CMD):
self.send_keys("v")
Expand Down
4 changes: 4 additions & 0 deletions components/dash-table/tests/selenium/test_derived_props.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ def test_tdrp001_select_rows(test):
target.row(0).select()
target.row(1).select()

time.sleep(1)

assert test.find_element("#active_cell").get_attribute("innerHTML") in [
"None",
json.dumps([]),
Expand Down Expand Up @@ -413,6 +415,8 @@ def test_tdrp005_filtered_and_sorted_row_select(test):
target.row(1).select()
target.row(2).select()

time.sleep(1)

assert test.find_element("#active_cell").get_attribute("innerHTML") in [
"None",
json.dumps([]),
Expand Down
2 changes: 2 additions & 0 deletions components/dash-table/tests/selenium/test_sizing.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import pytest
from selenium.common.exceptions import StaleElementReferenceException
from selenium.webdriver.common.by import By

Expand Down Expand Up @@ -146,6 +147,7 @@ def callback(n_clicks):
assert test.get_log_errors() == []


@pytest.mark.skip(reason="Slow and unreliable sizing test - dash-table deprecated")
def test_szng001_widths_on_style_change(test):
base_props = dict(
data=[
Expand Down
1 change: 1 addition & 0 deletions components/dash-table/tests/selenium/test_sizing_x.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
)


@pytest.mark.skip(reason="Slow and unreliable sizing tests")
@pytest.mark.parametrize("props", basic_modes)
def test_szng004_on_focus(test, props):
on_focus(test, props, generate_mock_data)
1 change: 1 addition & 0 deletions components/dash-table/tests/selenium/test_sizing_y.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
)


@pytest.mark.skip(reason="Slow and unreliable sizing tests")
@pytest.mark.parametrize("props", basic_modes)
def test_szng005_on_focus(test, props):
on_focus(test, props, generate_markdown_mock_data)
1 change: 1 addition & 0 deletions components/dash-table/tests/selenium/test_sizing_z.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
)


@pytest.mark.skip(reason="Slow and unreliable sizing tests")
@pytest.mark.parametrize("props", basic_modes)
def test_szng006_on_focus(test, props):
on_focus(test, props, generate_mixed_markdown_data)
12 changes: 6 additions & 6 deletions dash/dash-renderer/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

69 changes: 37 additions & 32 deletions dash/testing/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
import logging
from typing import Union, Optional
import warnings
import percy
import requests
from percy import percy_snapshot as _percy_snapshot

from selenium import webdriver
from selenium.webdriver.support import expected_conditions as EC
Expand Down Expand Up @@ -68,7 +67,7 @@ def __init__(
self._percy_run = percy_run
self._pause = pause

self._driver = until(self.get_webdriver, timeout=1)
self._driver = until(self._try_get_webdriver, timeout=30)
self._driver.implicitly_wait(2)

self._wd_wait = WebDriverWait(self.driver, wait_timeout)
Expand All @@ -78,14 +77,9 @@ def __init__(
self._window_idx = 0 # switch browser tabs

if self._percy_run:
self.percy_runner = percy.Runner(
loader=percy.ResourceLoader(
webdriver=self.driver,
base_url="/assets",
root_dir=percy_assets_root,
)
)
self.percy_runner.initialize_build()
# Percy CLI handles build initialization via percy exec wrapper
self._percy_assets_root = percy_assets_root
# No explicit initialization needed

logger.debug("initialize browser with arguments")
logger.debug(" headless => %s", self._headless)
Expand All @@ -99,14 +93,12 @@ def __exit__(self, exc_type, exc_val, traceback):
try:
self.driver.quit()
if self._percy_run and self._percy_finalize:
logger.info("percy runner finalize build now")
self.percy_runner.finalize_build()
logger.info("percy finalize will be handled by percy build:finalize")
# With percy CLI, finalization handled by separate command
else:
logger.info("percy finalize relies on CI job")
except WebDriverException:
logger.exception("webdriver quit was not successful")
except percy.errors.Error: # type: ignore[reportAttributeAccessIssue]
logger.exception("percy runner failed to finalize properly")

def visit_and_snapshot(
self,
Expand Down Expand Up @@ -205,12 +197,15 @@ def percy_snapshot(
"""
)

# NEW: Use percy-python-selenium SDK
try:
self.percy_runner.snapshot(name=name, widths=widths)
except requests.HTTPError as err:
# Ignore retries.
if err.request.status_code != 400: # type: ignore[reportAttributeAccessIssue]
raise err
if os.getenv("PERCY_TOKEN"):
percy_options = {"widths": widths, "min_height": 1024}
_percy_snapshot(self.driver, name, **percy_options)
else:
logger.debug("Percy snapshots disabled - PERCY_TOKEN not set")
except Exception as err: # pylint: disable=broad-exception-caught
logger.warning("Percy snapshot failed: %s", err)

if convert_canvases:
self.driver.execute_script(
Expand Down Expand Up @@ -485,6 +480,14 @@ def _get_wd_options(self):

return options

def _try_get_webdriver(self):
"""Wrapper that catches exceptions so until() can retry on transient failures."""
try:
return self.get_webdriver()
except WebDriverException:
logger.exception("webdriver initialization failed, will retry")
return None

def _get_chrome(self):
options = self._get_wd_options()

Expand All @@ -506,6 +509,8 @@ def _get_chrome(self):
options.add_argument("--no-sandbox")
options.add_argument("--disable-gpu")
options.add_argument("--remote-debugging-port=0")
options.add_argument("--disable-notifications")
options.add_argument("--disable-popup-blocking")

options.set_capability("goog:loggingPrefs", {"browser": "SEVERE"})

Expand All @@ -515,19 +520,19 @@ def _get_chrome(self):
else webdriver.Chrome(options=options)
)

# Enable downloads in headless mode
# https://bugs.chromium.org/p/chromium/issues/detail?id=696481
if self._headless:
# pylint: disable=protected-access
chrome.command_executor._commands["send_command"] = ( # type: ignore[reportArgumentType]
"POST",
"/session/$sessionId/chromium/send_command",
)
params = {
"cmd": "Page.setDownloadBehavior",
"params": {"behavior": "allow", "downloadPath": self.download_path},
}
res = chrome.execute("send_command", params)
logger.debug("enabled headless download returns %s", res)
if self._headless and self.download_path and hasattr(chrome, "execute_cdp_cmd"):
try:
# Modern approach using CDP command (Chrome only)
# pylint: disable=no-member
chrome.execute_cdp_cmd( # type: ignore[union-attr]
"Page.setDownloadBehavior",
{"behavior": "allow", "downloadPath": self.download_path},
)
logger.debug("enabled headless download via CDP")
except Exception as e: # pylint: disable=broad-exception-caught
logger.warning("failed to set headless download behavior: %s", e)

chrome.set_window_position(0, 0)
return chrome
Expand Down
Loading
Loading