eschutho opened a new pull request, #42253:
URL: https://github.com/apache/superset/pull/42253

   ### Decisions made that were not in the instructions
   
   - **Viewport scoping (investigated, not assumed):** the new non-tiled 
readiness check is scoped to viewport-intersecting chart holders only, same as 
the tiled path. Reasoning: `WebDriverPlaywright.get_screenshot()`'s non-tiled 
branches never call `page.set_viewport_size()` before capturing (that call only 
exists in the tiled branch, to resize to `tile_height`) — the browser viewport 
stays at the configured window size (e.g. `DEFAULT_DASHBOARD_WINDOW_SIZE = 
1600, 1200`) for the whole capture. `element.screenshot()` / 
`page.screenshot(full_page=True)` can capture below-the-fold content without 
ever scrolling the page or resizing the viewport, so 
`DashboardVirtualization`'s `IntersectionObserver`-based placeholders below the 
fold never mount anything real before the screenshot is taken. Requiring *all* 
chart holders (not just viewport-visible ones) to reach a terminal state would 
therefore deadlock on those by-design placeholders. This mirrors the tiled 
path's exact reasoning.
   - **Shared JS predicate, not reimplemented:** `screenshot_utils.py`'s 
`_TILE_READY_CHECK_JS` / `_FIND_UNREADY_CHART_HOLDERS_JS` / 
`_UNREADY_CHART_HOLDERS_JS_BODY` (added in #42119) already implement exactly 
the terminal-state check this fix needs. They're renamed to drop the leading 
underscore (`CHART_HOLDERS_READY_JS`, `FIND_UNREADY_CHART_HOLDERS_JS`, 
`UNREADY_CHART_HOLDERS_JS_BODY`) and imported into `webdriver.py` rather than 
reimplemented, so the two capture paths can't drift apart.
   - **Deduplicated the two copies of the wait:** `get_screenshot()` had two 
near-identical copies of the old spinner wait (one for `tiled_enabled=True` + 
small dashboard, one for `tiled_enabled=False`). Both are replaced by a single 
new `WebDriverPlaywright._wait_for_charts_ready()` static method to avoid 
maintaining the fix twice.
   
   ### SUMMARY
   
   #42119 fixed a "vacuous pass" bug in the *tiled* screenshot path: the 
per-tile spinner check only looked for the absence of currently-mounted 
`.loading` elements, so a chart holder that hadn't started rendering yet 
(nothing mounted, no spinner) passed the check immediately and got captured 
blank.
   
   The identical defect was still present, unfixed, in the *non-tiled/standard* 
capture path. `WebDriverPlaywright.get_screenshot()` had its own global spinner 
wait using the same old predicate:
   
   ```python
   page.wait_for_function(
       "() => document.querySelectorAll('.loading').length === 0",
       timeout=self._screenshot_load_wait * 1000,
   )
   ```
   
   If no chart has mounted a `.loading` element yet at the exact moment this 
check first runs (a timing gap between page-navigation-complete and React/query 
bootstrap), the predicate is satisfied immediately and silently — no timeout, 
no warning, no error. The result is a structurally valid PNG that's just 
visually blank.
   
   This PR replaces that predicate with the same positive terminal-state check 
#42119 introduced for the tiled path: every viewport-intersecting 
`[data-test="dashboard-component-chart-holder"]` must show either a rendered 
chart (`[data-test="slice-container"]`, no nested `.loading`) or an error/empty 
state (`[role="alert"]`, `.ant-empty`, `.missing-chart-container`). On timeout, 
it now logs a WARNING (not ERROR — a customer chart-loading issue, not a system 
fault, matching #42119's convention) with per-chart diagnostics (chart id + why 
it's unready: `nothing_mounted` / `waiting_on_database` / `spinner_mounted`) 
and re-raises, instead of the previous generic "timed out waiting for charts to 
load" message with no diagnostics.
   
   `log_context` (already threaded through `get_screenshot` for the tiled call 
site) is threaded into the new non-tiled wait too, for log correlation.
   
   This is the suspected root cause of a customer-reported blank-screenshot 
issue on dashboard PDF downloads: a structurally valid but visually blank 
capture, with no corresponding error anywhere, consistent with a silent 
capture-time vacuous pass rather than a loud failure.
   
   ### NOT changed
   
   - The tiled path (`take_tiled_screenshot` in 
`superset/utils/screenshot_utils.py`) — already fixed by #42119, untouched here 
except for the constant rename (no behavior change).
   - The tiling-routing decision (chart-count / height-threshold logic that 
decides tiled vs. non-tiled) — that's #42153's territory; not touched.
   
   ### TESTING INSTRUCTIONS
   
   - `tests/unit_tests/utils/webdriver_test.py`:
     - `test_uses_wait_for_function_to_detect_spinners` / 
`test_spinner_timeout_logs_warning_and_raises` updated to assert the new 
predicate and diagnostics instead of the old absence-of-`.loading` string.
     - New `TestWebDriverPlaywrightChartReadiness` class:
       - `test_chart_holder_with_nothing_mounted_does_not_satisfy_wait` — 
regression test for the vacuous-pass fix; asserts no screenshot is captured.
       - `test_all_chart_holders_ready_passes` — happy path, screenshot is 
returned.
       - `test_readiness_check_scoped_to_viewport_visible_holders` — asserts 
the JS predicate is viewport-scoped 
(`getBoundingClientRect`/`window.innerHeight`) and that `set_viewport_size` is 
never called on this path, proving the viewport-scoping finding above.
       - `test_log_context_threaded_into_readiness_wait` — asserts 
`log_context` appears in the timeout warning.
     - `test_spinner_timeout_logs_warning_and_raises` (updated) — asserts 
WARNING (not ERROR) level with per-chart diagnostics on timeout.
   - `tests/unit_tests/utils/test_screenshot_utils.py` updated for the constant 
rename only (no behavior change to the tiled path).
   - Ran `ruff check` / `ruff format` and `mypy` on both modified source files; 
no new mypy errors versus master (confirmed by diffing mypy output before/after 
the change — same 3 pre-existing errors, only line numbers shifted).
   - `pytest tests/unit_tests/utils/webdriver_test.py 
tests/unit_tests/utils/test_screenshot_utils.py` — 64 passed.
   
   ### ADDITIONAL INFORMATION
   - [ ] Has associated issue:
   - [ ] Required feature flags:
   - [ ] Changes UI
   - [ ] Includes DB Migration (follow approval process in 
[SIP-59](https://github.com/apache/superset/issues/13351))
     - [ ] Migration is atomic, supports rollback & is backwards-compatible
     - [ ] Confirm DB migration upgrade and downgrade tested
     - [ ] Runtime estimates and downtime expectations provided
   - [ ] Introduces new feature or API
   - [ ] Removes existing feature or API


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to