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."""
