diff --git a/superset/utils/screenshot_utils.py b/superset/utils/screenshot_utils.py index 3cc33fb10fe..ccd2c80aa37 100644 --- a/superset/utils/screenshot_utils.py +++ b/superset/utils/screenshot_utils.py @@ -89,6 +89,7 @@ def take_tiled_screenshot( element_name: str, tile_height: int, load_wait: int = 60, + animation_wait: int = 0, ) -> bytes | None: """ Take a tiled screenshot of a large dashboard by scrolling and capturing sections. @@ -98,6 +99,7 @@ def take_tiled_screenshot( element_name: CSS class name of the element to screenshot tile_height: Height of each tile in pixels load_wait: Seconds to wait for charts to load per tile (default 60) + animation_wait: Seconds to wait for chart animations per tile (default 0) Returns: Combined screenshot bytes or None if failed @@ -174,6 +176,12 @@ def take_tiled_screenshot( load_wait, ) + # Wait for chart animations (e.g. ECharts) to finish after spinner clears. + # The global animation wait before tiling only covers the first tile; + # subsequent tiles need their own wait after data loads. + if animation_wait > 0: + page.wait_for_timeout(animation_wait * 1000) + # Calculate what portion of the element we want to capture for this tile tile_start_in_element = i * tile_height remaining_content = dashboard_height - tile_start_in_element diff --git a/superset/utils/webdriver.py b/superset/utils/webdriver.py index 1ace36e349b..2a48a90b2da 100644 --- a/superset/utils/webdriver.py +++ b/superset/utils/webdriver.py @@ -369,6 +369,7 @@ class WebDriverPlaywright(WebDriverProxy): element_name, tile_height, load_wait=self._screenshot_load_wait, + animation_wait=selenium_animation_wait, ) if img is None: logger.warning( diff --git a/tests/unit_tests/utils/test_screenshot_utils.py b/tests/unit_tests/utils/test_screenshot_utils.py index c6127374f04..bf264a54b8d 100644 --- a/tests/unit_tests/utils/test_screenshot_utils.py +++ b/tests/unit_tests/utils/test_screenshot_utils.py @@ -371,3 +371,29 @@ class TestTakeTiledScreenshot: sig = inspect.signature(take_tiled_screenshot) assert sig.parameters["load_wait"].default == 60 + + def test_per_tile_animation_wait_called_per_tile(self, mock_page): + """animation_wait adds an extra wait per tile after the spinner check.""" + with patch("superset.utils.screenshot_utils.combine_screenshot_tiles"): + take_tiled_screenshot( + mock_page, "dashboard", tile_height=2000, animation_wait=5 + ) + + # 3 tiles × (1 scroll settle + 1 animation wait) = 6 total calls + assert mock_page.wait_for_timeout.call_count == 6 + + animation_calls = [ + call + for call in mock_page.wait_for_timeout.call_args_list + if call[0][0] == 5 * 1000 + ] + assert len(animation_calls) == 3 + + def test_animation_wait_default_is_zero(self): + """animation_wait defaults to 0 so no extra per-tile wait by default.""" + import inspect + + from superset.utils.screenshot_utils import take_tiled_screenshot + + sig = inspect.signature(take_tiled_screenshot) + assert sig.parameters["animation_wait"].default == 0