GitHub user 1sanqian edited a comment on the discussion: How can I integrate Superset charts with PlayWright to ensure that all charts and data on the page are fully rendered and there are no unexpected errors before taking a screenshot?
Could you please take a look at this code and see if it can effectively solve the problems of server load and complete screenshots/charts?[@dosu](https://go.dosu.dev/dosubot). private async Task<(bool isValid, string reason)> PerformFinalChartValidationAsync(IPage page, string originalTemplateUrl) { var validationStartTime = DateTime.UtcNow; var maxValidationTime = TimeSpan.FromMinutes(5); var validationAttempt = 0; Log.Information("Starting 5-minute final chart validation cycle..."); while (DateTime.UtcNow - validationStartTime < maxValidationTime) { validationAttempt++; var elapsed = DateTime.UtcNow - validationStartTime; Log.Information("Final validation attempt {Attempt} at {Elapsed:F1} minutes", validationAttempt, elapsed.TotalMinutes); try { Log.Information("Step 1: Waiting for main dashboard container..."); await page.WaitForSelectorAsync(".dashboard, [class*='dashboard'], .main-content, [class*='main']", new PageWaitForSelectorOptions { Timeout = 10000 }).ConfigureAwait(false); Log.Information("Dashboard container found"); Log.Information("Step 2: Waiting for all chart containers..."); var chartContainers = await page.QuerySelectorAllAsync(".chart-container, [class*='chart-container'], [data-test*='chart'], .viz-chart"); Log.Information("Found {Count} chart containers", chartContainers.Count); var visibleContainers = 0; var failedContainers = new List<string>(); foreach (var container in chartContainers) { try { await container.WaitForElementStateAsync(ElementState.Visible, new ElementHandleWaitForElementStateOptions { Timeout = 30000 }); visibleContainers++; } catch (Exception ex) { var containerInfo = await container.EvaluateAsync<string>("element => element.outerHTML.substring(0, 200)").ConfigureAwait(false); failedContainers.Add($"Container failed to become visible: {containerInfo}"); Log.Information(ex, "Chart container failed to become visible within timeout"); } } Log.Information("Chart containers visibility check: {Visible}/{Total} visible", visibleContainers, chartContainers.Count); if (failedContainers.Count > 0) { Log.Warning("Some chart containers failed to become visible: {Failed}/{Total}", failedContainers.Count, chartContainers.Count); if (failedContainers.Count > chartContainers.Count * 0.5) { var pageDiagnostics = await GatherPageDiagnosticsAsync(page); throw new Exception($"过多图表容器不可见 ({failedContainers.Count}/{chartContainers.Count}): {string.Join("; ", failedContainers)}. 页面诊断: {pageDiagnostics}"); } } Log.Information("All chart containers are visible"); Log.Information("Step 3: Waiting for all loading elements to disappear (SCREENSHOT_LOAD_WAIT style)..."); var loadingSelectors = new[] { ".loading", ".spinner", ".loader", "[class*='loading']", "[class*='is-loading']", "[class*='css-19o2u55']", "[class*='spinner']", "[class*='ant-spin']", ".anticon-loading" }; var startTime = DateTime.UtcNow; var maxWaitTime = TimeSpan.FromSeconds(60); while (DateTime.UtcNow - startTime < maxWaitTime) { var hasLoadingElements = false; foreach (var selector in loadingSelectors) { try { var loadingElements = await page.QuerySelectorAllAsync(selector); foreach (var loadingElement in loadingElements) { var isAttached = await loadingElement.EvaluateAsync<bool>("element => document.contains(element)"); if (isAttached) { var isVisible = await loadingElement.IsVisibleAsync(); if (isVisible) { hasLoadingElements = true; break; } } } if (hasLoadingElements) break; } catch { } } if (!hasLoadingElements) { Log.Information("All loading elements have disappeared (detached)"); break; } await page.WaitForTimeoutAsync(500); } Log.Information("Step 4: Waiting for each chart's content to be visible..."); var chartContentValidationErrors = new List<string>(); foreach (var chart in chartContainers) { try { var chartId = await chart.GetAttributeAsync("data-test-chart-id") ?? await chart.GetAttributeAsync("data-chart-id") ?? await chart.GetAttributeAsync("id"); if (!string.IsNullOrEmpty(chartId)) { await page.WaitForSelectorAsync($"#chart-id-{chartId}", new PageWaitForSelectorOptions { State = WaitForSelectorState.Visible, Timeout = 10000 }); Log.Information("Chart content visible for chart ID: {ChartId}", chartId); } else { await chart.WaitForSelectorAsync("svg, canvas, .chart-content, [class*='chart']", new ElementHandleWaitForSelectorOptions { State = WaitForSelectorState.Visible, Timeout = 10000 }); Log.Information("Chart content visible for container (fallback method)"); } } catch (Exception ex) { var containerInfo = await chart.EvaluateAsync<string>("element => element.outerHTML.substring(0, 200)").ConfigureAwait(false); chartContentValidationErrors.Add($"Chart content validation failed: {containerInfo}"); Log.Warning(ex, "Failed to validate chart content visibility for container"); } } if (chartContentValidationErrors.Count > 0) { Log.Warning("Some charts failed content validation: {Count}/{Total}", chartContentValidationErrors.Count, chartContainers.Count); if (chartContentValidationErrors.Count > chartContainers.Count * 0.3) // 30% threshold { throw new Exception($"过多图表内容未正确渲染 ({chartContentValidationErrors.Count}/{chartContainers.Count}): {string.Join("; ", chartContentValidationErrors)}"); } } Log.Information("Step 5: Waiting for animation delay and headstart (Superset SCREENSHOT_SELENIUM_ANIMATION_WAIT)..."); var animationWaitMs = 2000; var headstartMs = 500; var totalWaitMs = animationWaitMs + headstartMs; await page.WaitForTimeoutAsync(totalWaitMs); Log.Information("Animation and headstart delay completed ({AnimationMs}ms + {HeadstartMs}ms = {TotalMs}ms) - Superset timing parameters", animationWaitMs, headstartMs, totalWaitMs); var remainingLoadingElements = 0; foreach (var selector in loadingSelectors) { try { var count = await page.Locator(selector).CountAsync(); remainingLoadingElements += count; } catch { } } if (remainingLoadingElements > 0) { Log.Information("Warning: {Count} loading elements still present after validation", remainingLoadingElements); if (remainingLoadingElements <= 3) { Log.Information("Accepting validation with {Count} remaining loading elements (likely minor UI elements)", remainingLoadingElements); } else { Log.Information("Accepting validation with {Count} remaining loading elements (likely minor UI elements)", remainingLoadingElements); return (false, $"TimeOut"); } } Log.Information("Step 6: Checking for error alerts and attempting recovery..."); var (errorMessages, recoveryAttempted) = await CheckAndHandleErrorAlertsWithRecoveryAsync(page, chartContainers); if (errorMessages.Count > 0) { Log.Warning("Found {Count} error alerts on page", errorMessages.Count); foreach (var errorMsg in errorMessages) { Log.Warning("Error alert: {Message}", errorMsg); } var criticalErrors = errorMessages.Where(msg => msg.Contains("502") || msg.Contains("500") || msg.Contains("Bad Gateway") || msg.Contains("Connection failed") || msg.Contains("Network error")).ToList(); if (criticalErrors.Count > 0) { var temporaryErrors = criticalErrors.Where(msg => msg.Contains("502") || msg.Contains("503") || msg.Contains("504") || msg.Contains("Bad Gateway") || msg.Contains("Service Unavailable") || msg.Contains("Gateway Timeout") || msg.Contains("Connection failed") || msg.Contains("Network error")).ToList(); var permanentErrors = criticalErrors.Except(temporaryErrors).ToList(); Log.Information("Detected temporary server errors, will refresh and retry..."); throw new Exception($"临时服务器错误: {string.Join("; ", temporaryErrors)}"); } else { if (recoveryAttempted) { Log.Information("Recovery attempted for non-critical errors, waiting for charts to reload..."); await page.WaitForTimeoutAsync(3000); var remainingErrors = await CheckForRemainingErrorsAsync(page); if (remainingErrors.Count > 0) { Log.Information("Some errors persist after recovery attempt @{error}", string.Join("; ", remainingErrors)); return (false, $"TimeOut"); } else { Log.Information("Chart recovery successful, proceeding with screenshot"); } } else { Log.Information("Non-critical errors found but no recovery attempted, proceeding with screenshot"); } } } var totalChartContainers = chartContainers.Count; Log.Information("Final validation: {TotalCharts} chart containers validated", totalChartContainers); if (totalChartContainers == 0) { throw new Exception("未找到任何图表容器"); } Log.Information("Step 7: Final stability check..."); var isStable = await page.EvaluateAsync<bool>(@" () => { // 检查页面是否还在滚动或有动画 return !window.scrolling && document.readyState === 'complete'; } ").ConfigureAwait(false); if (!isStable) { Log.Information("Page not fully stable, waiting additional time..."); await page.WaitForTimeoutAsync(1000); } Log.Information("Superset-style validation completed successfully"); Log.Information("Final validation passed, executing page preparation steps for screenshot..."); await ScrollToBottomWithPlaywrightAsync(page).ConfigureAwait(false); await WaitForPageStableWithPlaywrightAsync(page, maxWaitSeconds: 3).ConfigureAwait(false); try { var collapseButton = page.Locator("button[data-test='filter-bar__collapse-button']"); await collapseButton.WaitForAsync(new LocatorWaitForOptions { State = WaitForSelectorState.Visible, Timeout = 10000 }).ConfigureAwait(false); await collapseButton.WaitForAsync(new LocatorWaitForOptions { State = WaitForSelectorState.Attached, Timeout = 10000 }).ConfigureAwait(false); await collapseButton.ClickAsync(new LocatorClickOptions { Timeout = 10000 }).ConfigureAwait(false); await page.WaitForTimeoutAsync(500); Log.Information("Successfully clicked collapse button after final validation"); } catch (Exception ex) { Log.Information(ex, "Failed to click collapse button after final validation"); } await ClearHoverStateWithPlaywrightAsync(page).ConfigureAwait(false); Log.Information("Page preparation completed after final validation"); return (true, "Superset样式验证通过"); } catch (Exception ex) { Log.Information("Validation attempt {Attempt} failed: {Error}", validationAttempt, ex.Message); if (DateTime.UtcNow - validationStartTime < maxValidationTime) { Log.Information("Refreshing page and retrying validation (attempt {Attempt})...", validationAttempt + 1); try { await page.ReloadAsync(new PageReloadOptions { WaitUntil = WaitUntilState.DOMContentLoaded }); await page.WaitForTimeoutAsync(2000); continue; } catch (Exception reloadEx) { Log.Information(reloadEx, "Failed to reload page for retry"); continue; } } else { var totalElapsed = DateTime.UtcNow - validationStartTime; Log.Information("Final chart validation timeout after {Elapsed:F1} minutes and {Attempts} attempts", totalElapsed.TotalMinutes, validationAttempt); return (false, "TimeOut"); } } } var finalElapsed = DateTime.UtcNow - validationStartTime; Log.Error("Final chart validation timeout after {Elapsed:F1} minutes", finalElapsed.TotalMinutes); return (false, "TimeOut"); } GitHub link: https://github.com/apache/superset/discussions/36845#discussioncomment-15359900 ---- This is an automatically sent email for [email protected]. To unsubscribe, please send an email to: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
