/
githubmirror
/
aspnetcore
Обзор
Документация
Войти
/
githubmirror
/
aspnetcore
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Shared/E2ETesting/WaitAssert.cs
239 строк
9 KB
Javier Calvarro Nelson
[Blazor] Add Blazor WebAssembly Service Defaults template (#64807)
12 апр 2026, 11:03
Не верифицирован
12 апр 2026, 11:03
5b2e38c
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.ExceptionServices; using System.Text.Json; using System.Threading; using OpenQA.Selenium; using OpenQA.Selenium.DevTools; using OpenQA.Selenium.Interactions; using OpenQA.Selenium.Support.UI; using Xunit; namespace Microsoft.AspNetCore.E2ETesting; // XUnit assertions, but hooked into Selenium's polling mechanism public static class WaitAssert { private static int _failureCount; public static TimeSpan DefaultTimeout = TimeSpan.FromSeconds(E2ETestOptions.Instance.DefaultWaitTimeoutInSeconds); public static TimeSpan FailureTimeout = TimeSpan.FromSeconds(E2ETestOptions.Instance.DefaultAfterFailureWaitTimeoutInSeconds); public static void Equal<T>(this IWebDriver driver, T expected, Func<T> actual) => WaitAssertCore(driver, () => Assert.Equal(expected, actual())); public static void NotEqual<T>(this IWebDriver driver, T expected, Func<T> actual) => WaitAssertCore(driver, () => Assert.NotEqual(expected, actual())); public static void True(this IWebDriver driver, Func<bool> actual) => WaitAssertCore(driver, () => Assert.True(actual())); public static void True(this IWebDriver driver, Func<bool> actual, TimeSpan timeout) => WaitAssertCore(driver, () => Assert.True(actual()), timeout); public static void True(this IWebDriver driver, Func<bool> actual, TimeSpan timeout, string message) => WaitAssertCore(driver, () => Assert.True(actual(), message), timeout); public static void True(this IWebDriver driver, Func<bool> actual, string message) => WaitAssertCore(driver, () => Assert.True(actual(), message)); public static void False(this IWebDriver driver, Func<bool> actual, string message) => WaitAssertCore(driver, () => Assert.False(actual(), message)); public static void False(this IWebDriver driver, Func<bool> actual) => WaitAssertCore(driver, () => Assert.False(actual())); public static void Contains(this IWebDriver driver, string expectedSubstring, Func<string> actualString) => WaitAssertCore(driver, () => Assert.Contains(expectedSubstring, actualString())); public static void Collection<T>(this IWebDriver driver, Func<IEnumerable<T>> actualValues, params Action<T>[] elementInspectors) => WaitAssertCore(driver, () => Assert.Collection(actualValues(), elementInspectors)); public static void Empty(this IWebDriver driver, Func<IEnumerable> actualValues) => WaitAssertCore(driver, () => Assert.Empty(actualValues())); public static void Single(this IWebDriver driver, Func<IEnumerable> actualValues) => WaitAssertCore(driver, () => Assert.Single(actualValues())); public static IWebElement Exists(this IWebDriver driver, By finder) => Exists(driver, finder, default); public static TElement Exists<TElement>(this IWebDriver driver, Func<TElement> actual, TimeSpan timeout) => WaitAssertCore(driver, actual, timeout); public static void DoesNotExist(this IWebDriver driver, By finder, TimeSpan timeout = default) => WaitAssertCore(driver, () => { var elements = driver.FindElements(finder); Assert.Empty(elements); }, timeout); public static IWebElement Exists(this IWebDriver driver, By finder, TimeSpan timeout) => WaitAssertCore(driver, () => { var elements = driver.FindElements(finder); Assert.NotEmpty(elements); var result = elements[0]; return result; }, timeout); public static void Click(this IWebDriver driver, By selector) => WaitAssertCore(driver, () => { driver.FindElement(selector).Click(); }); private static void WaitAssertCore(IWebDriver driver, Action assertion, TimeSpan timeout = default) { WaitAssertCore<object>(driver, () => { assertion(); return null; }, timeout); } // Number of failures before the timeout is halved. private const int FailuresPerStep = 5; private static TimeSpan GetAdjustedTimeout() { var failures = Volatile.Read(ref _failureCount); if (failures <= 0) { return DefaultTimeout; } // Halve the timeout every FailuresPerStep failures: // 0-4 failures: 120s, 5-9: 60s, 10-14: 30s, 15-19: 15s, 20-24: 7.5s, 25+: floor var steps = failures / FailuresPerStep; var seconds = DefaultTimeout.TotalSeconds / Math.Pow(2, steps); return TimeSpan.FromSeconds(Math.Max(seconds, FailureTimeout.TotalSeconds)); } private static TResult WaitAssertCore<TResult>(IWebDriver driver, Func<TResult> assertion, TimeSpan timeout = default) { if (timeout == default) { timeout = GetAdjustedTimeout(); } Exception lastException = null; TResult result = default; try { new WebDriverWait(driver, timeout).Until(_ => { try { result = assertion(); return true; } catch (Exception e) { lastException = e; return false; } }); } catch (WebDriverTimeoutException) { // Increment the failure count so subsequent assertions use progressively shorter timeouts. // The timeout halves with each failure until it reaches the FailureTimeout floor. Interlocked.Increment(ref _failureCount); var currentUrl = string.Empty; try { currentUrl = driver.Url; } catch { /* Browser may be in a bad state */ } var innerHtml = driver.FindElement(By.CssSelector(":first-child"))?.GetDomProperty("innerHTML"); var fileId = $"{Guid.NewGuid():N}.png"; var screenShotPath = Path.Combine(Path.GetFullPath(E2ETestOptions.Instance.ScreenShotsPath), fileId); var errors = driver.GetBrowserLogs(LogLevel.All).Select(c => c.ToString()).ToList(); var networkDetails = GetNetworkResponseDetails(driver); TakeScreenShot(driver, screenShotPath); var exceptionInfo = lastException != null ? ExceptionDispatchInfo.Capture(lastException) : CaptureException(() => assertion()); throw new BrowserAssertFailedException(errors, exceptionInfo.SourceException, screenShotPath, innerHtml, currentUrl, networkDetails); } return result; } private static ExceptionDispatchInfo CaptureException(Action assertion) { try { assertion(); throw new InvalidOperationException("The assertion succeeded after the timeout."); } catch (Exception ex) { return ExceptionDispatchInfo.Capture(ex); } } private static void TakeScreenShot(IWebDriver driver, string screenShotPath) { if (driver is ITakesScreenshot takesScreenshot && E2ETestOptions.Instance.ScreenShotsPath != null) { try { Directory.CreateDirectory(E2ETestOptions.Instance.ScreenShotsPath); var screenShot = takesScreenshot.GetScreenshot(); screenShot.SaveAsFile(screenShotPath); } catch (Exception ex) { Console.WriteLine($"Failed to take a screenshot {ex.ToString()}"); } } } private static List<string> GetNetworkResponseDetails(IWebDriver driver) { var details = new List<string>(); try { var performanceLogs = driver.Manage().Logs.GetLog("performance"); foreach (var entry in performanceLogs) { if (!entry.Message.Contains("Network.responseReceived")) { continue; } try { using var doc = JsonDocument.Parse(entry.Message); var message = doc.RootElement.GetProperty("message"); var parameters = message.GetProperty("params"); var response = parameters.GetProperty("response"); var url = response.GetProperty("url").GetString(); var status = response.GetProperty("status").GetInt32(); var mimeType = response.TryGetProperty("mimeType", out var mt) ? mt.GetString() : "unknown"; // Only include framework-related URLs and error responses to keep output manageable if (url is not null && (url.Contains("_framework") || url.Contains("_blazor") || status >= 400)) { details.Add($" [{status}] {mimeType} - {url}"); } } catch { // Skip entries we can't parse } } } catch { // Performance logs might not be available } return details; } }