This is an automated email from the ASF dual-hosted git repository.

elizabeth pushed a commit to branch elizabeth/chart-timeout-logging
in repository https://gitbox.apache.org/repos/asf/superset.git

commit bfda8fa6a4dfd4805305b143c93d65a8f88c963a
Author: Elizabeth Thompson <[email protected]>
AuthorDate: Thu Oct 16 15:06:40 2025 -0700

    fix(reports): improve chart timeout telemetry
---
 superset/utils/webdriver.py              |  29 +++++++-
 tests/unit_tests/utils/webdriver_test.py | 110 +++++++++++++++++++++++++++++++
 2 files changed, 138 insertions(+), 1 deletion(-)

diff --git a/superset/utils/webdriver.py b/superset/utils/webdriver.py
index 457440856a..66062ee3af 100644
--- a/superset/utils/webdriver.py
+++ b/superset/utils/webdriver.py
@@ -603,7 +603,34 @@ class WebDriverSelenium(WebDriverProxy):
                     )
                 )
             except TimeoutException:
-                logger.info("Timeout Exception caught")
+                chart_elements = driver.find_elements(By.CLASS_NAME, 
"chart-container")
+                grid_elements = driver.find_elements(By.CLASS_NAME, 
"grid-container")
+                loading_elements = driver.find_elements(By.CLASS_NAME, 
"loading")
+                chart_identifiers = [
+                    (
+                        element.get_attribute("data-test-chart-id")
+                        or element.get_attribute("data-chart-id")
+                        or element.get_attribute("id")
+                    )
+                    for element in chart_elements[:5]
+                ]
+                logger.warning(
+                    (
+                        "Timeout waiting for chart containers at url %s; "
+                        "%i chart containers found, %i grid containers 
present, "
+                        "%i loading elements still visible; sample chart "
+                        "identifiers: %s"
+                    ),
+                    url,
+                    len(chart_elements),
+                    len(grid_elements),
+                    len(loading_elements),
+                    chart_identifiers,
+                )
+                logger.warning(
+                    "Dashboard DOM preview (first 2000 chars): %s",
+                    driver.page_source[:2000],
+                )
                 # Fallback to allow a screenshot of an empty dashboard
                 try:
                     WebDriverWait(driver, 0).until(
diff --git a/tests/unit_tests/utils/webdriver_test.py 
b/tests/unit_tests/utils/webdriver_test.py
index ab3490e095..3f318710d2 100644
--- a/tests/unit_tests/utils/webdriver_test.py
+++ b/tests/unit_tests/utils/webdriver_test.py
@@ -18,6 +18,7 @@
 from unittest.mock import MagicMock, patch, PropertyMock
 
 import pytest
+from selenium.common.exceptions import TimeoutException
 
 from superset.utils.webdriver import (
     check_playwright_availability,
@@ -273,6 +274,115 @@ class TestWebDriverSelenium:
         # Should create driver without errors
         mock_driver_class.assert_called_once()
 
+    def test_get_screenshot_logs_chart_timeout_details(self):
+        """Test Selenium logging when chart containers fail to render in 
time."""
+
+        def make_chart_element(identifier: str) -> MagicMock:
+            chart_element = MagicMock()
+
+            def get_attribute(name: str) -> str | None:
+                values = {
+                    "data-test-chart-id": f"chart-{identifier}",
+                    "data-chart-id": None,
+                    "id": f"chart-id-{identifier}",
+                }
+                return values.get(name)
+
+            chart_element.get_attribute.side_effect = get_attribute
+            return chart_element
+
+        with (
+            patch("superset.utils.webdriver.app") as mock_app,
+            patch("superset.utils.webdriver.logger") as mock_logger,
+            patch("superset.utils.webdriver.WebDriverWait") as mock_wait,
+            patch.object(WebDriverSelenium, "auth") as mock_auth,
+            patch.object(WebDriverSelenium, "destroy") as mock_destroy,
+            patch("superset.utils.webdriver.sleep") as mock_sleep,
+        ):
+            mock_app.config = {
+                "SCREENSHOT_SELENIUM_HEADSTART": 0,
+                "SCREENSHOT_LOCATE_WAIT": 5,
+                "SCREENSHOT_LOAD_WAIT": 5,
+                "SCREENSHOT_SELENIUM_ANIMATION_WAIT": 0,
+                "SCREENSHOT_REPLACE_UNEXPECTED_ERRORS": False,
+                "SCREENSHOT_SELENIUM_RETRIES": 1,
+            }
+
+            chart_elements = [make_chart_element("1"), make_chart_element("2")]
+            grid_elements = [MagicMock()]
+            loading_elements = [MagicMock(), MagicMock()]
+
+            mock_driver = MagicMock()
+            mock_driver.find_elements.side_effect = lambda by, value: {
+                "chart-container": chart_elements,
+                "grid-container": grid_elements,
+                "loading": loading_elements,
+            }.get(value, [])
+            mock_driver.page_source = "X" * 2100
+            mock_auth.return_value = mock_driver
+
+            element = MagicMock()
+            element.screenshot_as_png = b"fake-screenshot"
+
+            wait_presence = MagicMock()
+            wait_presence.until.return_value = element
+
+            wait_chart = MagicMock()
+            wait_chart.until.side_effect = TimeoutException()
+
+            wait_fallback = MagicMock()
+            wait_fallback.until.return_value = [MagicMock()]
+
+            wait_loading = MagicMock()
+            wait_loading.until_not.return_value = True
+
+            mock_wait.side_effect = [
+                wait_presence,
+                wait_chart,
+                wait_fallback,
+                wait_loading,
+            ]
+
+            driver = WebDriverSelenium("chrome")
+
+            mock_user = MagicMock()
+            mock_user.username = "test_user"
+
+            result = driver.get_screenshot(
+                "http://example.com";, "dashboard-component", mock_user
+            )
+
+            assert result == b"fake-screenshot"
+            mock_sleep.assert_any_call(0)
+            mock_destroy.assert_called_once_with(mock_driver, 1)
+
+            warning_calls = mock_logger.warning.call_args_list
+            assert len(warning_calls) >= 2
+
+            timeout_args = warning_calls[0].args
+            assert timeout_args[1] == "http://example.com";
+            assert timeout_args[2] == len(chart_elements)
+            assert timeout_args[3] == len(grid_elements)
+            assert timeout_args[4] == len(loading_elements)
+            assert timeout_args[5] == ["chart-1", "chart-2"]
+            actual_timeout_message = timeout_args[0] % timeout_args[1:]
+            expected_timeout_message = (
+                "Timeout waiting for chart containers at url 
http://example.com; "
+                "2 chart containers found, 1 grid containers present, "
+                "2 loading elements still visible; sample chart identifiers: "
+                "['chart-1', 'chart-2']"
+            )
+            assert actual_timeout_message == expected_timeout_message
+
+            dom_preview_args = warning_calls[1].args
+            assert dom_preview_args[1] == mock_driver.page_source[:2000]
+            actual_dom_preview_message = dom_preview_args[0] % 
dom_preview_args[1:]
+            expected_dom_preview_message = (
+                "Dashboard DOM preview (first 2000 chars): "
+                f"{mock_driver.page_source[:2000]}"
+            )
+            assert actual_dom_preview_message == expected_dom_preview_message
+
 
 class TestPlaywrightAvailabilityCheck:
     """Test comprehensive Playwright availability checking."""

Reply via email to