mirror of
https://github.com/apache/superset.git
synced 2026-05-12 19:35:17 +00:00
fix: Flakiness around scrolling during taking tiled screenshots with Playwright (#36051)
(cherry picked from commit 63dfd95aa2)
This commit is contained in:
committed by
Joe Li
parent
70117eb55f
commit
933ec0a918
@@ -25,6 +25,9 @@ from PIL import Image
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Time to wait after scrolling for content to settle and load (in milliseconds)
|
||||
SCROLL_SETTLE_TIMEOUT_MS = 1000
|
||||
|
||||
if TYPE_CHECKING:
|
||||
try:
|
||||
from playwright.sync_api import Page
|
||||
@@ -77,7 +80,7 @@ def combine_screenshot_tiles(screenshot_tiles: list[bytes]) -> bytes:
|
||||
|
||||
|
||||
def take_tiled_screenshot(
|
||||
page: "Page", element_name: str, viewport_height: int = 2000
|
||||
page: "Page", element_name: str, tile_height: int
|
||||
) -> bytes | None:
|
||||
"""
|
||||
Take a tiled screenshot of a large dashboard by scrolling and capturing sections.
|
||||
@@ -85,7 +88,7 @@ def take_tiled_screenshot(
|
||||
Args:
|
||||
page: Playwright page object
|
||||
element_name: CSS class name of the element to screenshot
|
||||
viewport_height: Height of each tile in pixels
|
||||
tile_height: Height of each tile in pixels
|
||||
|
||||
Returns:
|
||||
Combined screenshot bytes or None if failed
|
||||
@@ -100,17 +103,17 @@ def take_tiled_screenshot(
|
||||
const el = document.querySelector(".{element_name}");
|
||||
const rect = el.getBoundingClientRect();
|
||||
return {{
|
||||
width: el.scrollWidth,
|
||||
height: el.scrollHeight,
|
||||
top: rect.top + window.scrollY,
|
||||
left: rect.left + window.scrollX,
|
||||
width: el.scrollWidth
|
||||
top: rect.top + window.scrollY,
|
||||
}};
|
||||
}}""")
|
||||
|
||||
dashboard_height = element_info["height"]
|
||||
dashboard_top = element_info["top"]
|
||||
dashboard_left = element_info["left"]
|
||||
dashboard_width = element_info["width"]
|
||||
dashboard_height = element_info["height"]
|
||||
dashboard_left = element_info["left"]
|
||||
dashboard_top = element_info["top"]
|
||||
|
||||
logger.info(
|
||||
f"Dashboard: {dashboard_width}x{dashboard_height}px at "
|
||||
@@ -118,60 +121,54 @@ def take_tiled_screenshot(
|
||||
)
|
||||
|
||||
# Calculate number of tiles needed
|
||||
num_tiles = max(1, (dashboard_height + viewport_height - 1) // viewport_height)
|
||||
logger.info(f"Taking {num_tiles} screenshot tiles")
|
||||
num_tiles = max(1, (dashboard_height + tile_height - 1) // tile_height)
|
||||
logger.info("Taking %s screenshot tiles", num_tiles)
|
||||
|
||||
screenshot_tiles = []
|
||||
|
||||
for i in range(num_tiles):
|
||||
# Calculate scroll position to show this tile's content
|
||||
scroll_y = dashboard_top + (i * viewport_height)
|
||||
scroll_y = dashboard_top + (i * tile_height)
|
||||
|
||||
# Scroll the window to the desired position
|
||||
page.evaluate(f"window.scrollTo(0, {scroll_y})")
|
||||
logger.debug(f"Scrolled window to {scroll_y} for tile {i + 1}/{num_tiles}")
|
||||
|
||||
logger.debug(
|
||||
"Scrolled window to %s for tile %s/%s", scroll_y, i + 1, num_tiles
|
||||
)
|
||||
# Wait for scroll to settle and content to load
|
||||
page.wait_for_timeout(2000) # 2 second wait per tile
|
||||
|
||||
# Get the current element position after scroll
|
||||
current_element_box = page.evaluate(f"""() => {{
|
||||
const el = document.querySelector(".{element_name}");
|
||||
const rect = el.getBoundingClientRect();
|
||||
return {{
|
||||
x: rect.left,
|
||||
y: rect.top,
|
||||
width: rect.width,
|
||||
height: rect.height
|
||||
}};
|
||||
}}""")
|
||||
page.wait_for_timeout(SCROLL_SETTLE_TIMEOUT_MS)
|
||||
|
||||
# Calculate what portion of the element we want to capture for this tile
|
||||
tile_start_in_element = i * viewport_height
|
||||
tile_start_in_element = i * tile_height
|
||||
remaining_content = dashboard_height - tile_start_in_element
|
||||
tile_content_height = min(viewport_height, remaining_content)
|
||||
|
||||
# Determine clip height (use visible element height in viewport)
|
||||
clip_height = min(tile_content_height, current_element_box["height"])
|
||||
clip_height = min(tile_height, remaining_content)
|
||||
clip_y = (
|
||||
0
|
||||
if tile_height < remaining_content
|
||||
else tile_height - remaining_content
|
||||
)
|
||||
clip_x = dashboard_left
|
||||
|
||||
# Skip tile if dimensions are invalid (width or height <= 0)
|
||||
# This can happen if element is completely scrolled out of viewport
|
||||
if clip_height <= 0:
|
||||
if clip_height <= 0 or clip_y < 0:
|
||||
logger.warning(
|
||||
"Skipping tile %s/%s due to invalid clip dimensions: "
|
||||
"width=%s, height=%s (element may be scrolled out of viewport)",
|
||||
"x=%s, y=%s, width=%s, height=%s "
|
||||
"(element may be scrolled out of viewport)",
|
||||
i + 1,
|
||||
num_tiles,
|
||||
current_element_box["width"],
|
||||
clip_x,
|
||||
clip_y,
|
||||
dashboard_width,
|
||||
clip_height,
|
||||
)
|
||||
continue
|
||||
|
||||
# Clip to capture only the current tile portion of the element
|
||||
clip = {
|
||||
"x": current_element_box["x"],
|
||||
"y": current_element_box["y"],
|
||||
"width": current_element_box["width"],
|
||||
"x": clip_x,
|
||||
"y": clip_y,
|
||||
"width": dashboard_width,
|
||||
"height": clip_height,
|
||||
}
|
||||
|
||||
@@ -182,12 +179,9 @@ def take_tiled_screenshot(
|
||||
logger.debug(f"Captured tile {i + 1}/{num_tiles} with clip {clip}")
|
||||
|
||||
# Combine all tiles
|
||||
logger.info("Combining screenshot tiles...")
|
||||
logger.debug("Captured tile %s/%s with clip %s", i + 1, num_tiles, clip)
|
||||
combined_screenshot = combine_screenshot_tiles(screenshot_tiles)
|
||||
|
||||
# Reset window scroll position
|
||||
page.evaluate("window.scrollTo(0, 0)")
|
||||
|
||||
return combined_screenshot
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@@ -162,11 +162,13 @@ class WebDriverPlaywright(WebDriverProxy):
|
||||
browser_args = app.config["WEBDRIVER_OPTION_ARGS"]
|
||||
browser = playwright.chromium.launch(args=browser_args)
|
||||
pixel_density = app.config["WEBDRIVER_WINDOW"].get("pixel_density", 1)
|
||||
viewport_height = self._window[1]
|
||||
viewport_width = self._window[0]
|
||||
context = browser.new_context(
|
||||
bypass_csp=True,
|
||||
viewport={
|
||||
"height": self._window[1],
|
||||
"width": self._window[0],
|
||||
"height": viewport_height,
|
||||
"width": viewport_width,
|
||||
},
|
||||
device_scale_factor=pixel_density,
|
||||
)
|
||||
@@ -266,15 +268,15 @@ class WebDriverPlaywright(WebDriverProxy):
|
||||
height_threshold = app.config.get(
|
||||
"SCREENSHOT_TILED_HEIGHT_THRESHOLD", 5000
|
||||
)
|
||||
viewport_height = app.config.get(
|
||||
"SCREENSHOT_TILED_VIEWPORT_HEIGHT", self._window[1]
|
||||
tile_height = app.config.get(
|
||||
"SCREENSHOT_TILED_VIEWPORT_HEIGHT", viewport_height
|
||||
)
|
||||
|
||||
# Use tiled screenshots for large dashboards
|
||||
use_tiled = (
|
||||
chart_count >= chart_threshold
|
||||
or dashboard_height > height_threshold
|
||||
)
|
||||
) and dashboard_height > tile_height
|
||||
|
||||
if use_tiled:
|
||||
logger.info(
|
||||
@@ -283,9 +285,11 @@ class WebDriverPlaywright(WebDriverProxy):
|
||||
f"{dashboard_height}px height. Using tiled screenshots."
|
||||
)
|
||||
)
|
||||
img = take_tiled_screenshot(
|
||||
page, element_name, viewport_height=viewport_height
|
||||
# set viewport height to tile height for easier calculations
|
||||
page.set_viewport_size(
|
||||
{"height": tile_height, "width": viewport_width}
|
||||
)
|
||||
img = take_tiled_screenshot(page, element_name, tile_height)
|
||||
if img is None:
|
||||
logger.warning(
|
||||
(
|
||||
|
||||
Reference in New Issue
Block a user