GitHub user 1sanqian added a comment to 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?
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");
}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?
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]