This is an automated email from the ASF dual-hosted git repository.
eschutho pushed a commit to branch poc/per-chart-dashboard-reports
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to
refs/heads/poc/per-chart-dashboard-reports by this push:
new a46dcf79ee8 fix(reports): wait for chart holders to attach before
per-chart capture
a46dcf79ee8 is described below
commit a46dcf79ee89b18a0a0c64649f726dfbef1bf31d
Author: Elizabeth Thompson <[email protected]>
AuthorDate: Fri Jul 24 16:21:33 2026 +0000
fix(reports): wait for chart holders to attach before per-chart capture
Locator.count() reads the DOM at call time without waiting, and the
dashboard's standalone container attaches before React renders any chart
holders. In live testing this raced: some runs captured all charts, others
found 0 and fell back to the full-dashboard screenshot.
Wait for the first chart holder to attach (bounded by load_wait) before
counting; return an empty list if none appear so the caller's fallback
still engages for genuinely empty dashboards.
Co-Authored-By: Claude Fable 5 <[email protected]>
---
superset/utils/screenshot_utils.py | 12 ++++++++++++
tests/unit_tests/utils/test_screenshot_utils.py | 21 +++++++++++++++++++++
2 files changed, 33 insertions(+)
diff --git a/superset/utils/screenshot_utils.py
b/superset/utils/screenshot_utils.py
index 97bf282beb2..bc8c6c896ee 100644
--- a/superset/utils/screenshot_utils.py
+++ b/superset/utils/screenshot_utils.py
@@ -263,6 +263,18 @@ def take_per_chart_screenshots(
in DOM (layout) order. Empty list if no charts could be captured.
"""
chart_holders = page.locator(CHART_HOLDER_SELECTOR)
+ # count() reads the DOM at call time without waiting; the dashboard
+ # container can be attached before React has rendered any chart holders.
+ # Wait for the first holder so the count reflects the real layout.
+ try:
+ chart_holders.first.wait_for(state="attached", timeout=load_wait *
1000)
+ except PlaywrightTimeout:
+ logger.warning(
+ "No chart holders appeared within %ss; dashboard may be empty "
+ "or failed to render",
+ load_wait,
+ )
+ return []
chart_count = chart_holders.count()
logger.info("Capturing %s charts individually", chart_count)
diff --git a/tests/unit_tests/utils/test_screenshot_utils.py
b/tests/unit_tests/utils/test_screenshot_utils.py
index 8af63a14277..a7de93e3b4a 100644
--- a/tests/unit_tests/utils/test_screenshot_utils.py
+++ b/tests/unit_tests/utils/test_screenshot_utils.py
@@ -486,3 +486,24 @@ class TestTakePerChartScreenshots:
# Scroll settle wait plus animation wait
page.wait_for_timeout.assert_any_call(SCROLL_SETTLE_TIMEOUT_MS)
page.wait_for_timeout.assert_any_call(3000)
+
+ def test_returns_empty_when_no_holders_appear(self):
+ """If no chart holder ever attaches, return [] instead of hanging."""
+ page, _ = self._make_page_with_charts([])
+ page.locator.return_value.first.wait_for.side_effect =
PlaywrightTimeout(
+ "no holders"
+ )
+
+ result = take_per_chart_screenshots(page, load_wait=10)
+
+ assert result == []
+
+ def test_waits_for_first_holder_before_counting(self):
+ """The holder count must be taken after the first holder attaches."""
+ page, _ = self._make_page_with_charts([b"chart1"])
+
+ take_per_chart_screenshots(page, load_wait=42)
+
+ page.locator.return_value.first.wait_for.assert_called_once_with(
+ state="attached", timeout=42000
+ )