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]

Reply via email to