diff --git a/eng/testing/scenarios/BuildWasmAppsJobsList.txt b/eng/testing/scenarios/BuildWasmAppsJobsList.txt index 312c79d785d3b2..7347239029feff 100644 --- a/eng/testing/scenarios/BuildWasmAppsJobsList.txt +++ b/eng/testing/scenarios/BuildWasmAppsJobsList.txt @@ -14,6 +14,7 @@ Wasm.Build.Tests.Blazor.NativeTests Wasm.Build.Tests.Blazor.NoopNativeRebuildTest Wasm.Build.Tests.Blazor.WorkloadRequiredTests Wasm.Build.Tests.Blazor.EventPipeDiagnosticsTests +Wasm.Build.Tests.Blazor.AssetCachingTests Wasm.Build.Tests.BuildPublishTests Wasm.Build.Tests.ConfigSrcTests Wasm.Build.Tests.DllImportTests diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/AssetCachingTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/AssetCachingTests.cs index c6c4988dca8768..d0ad41c00462a1 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/AssetCachingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/AssetCachingTests.cs @@ -1,12 +1,9 @@ // 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.Generic; using System.IO; -using System.Text.RegularExpressions; +using System.Linq; using System.Threading.Tasks; -using Microsoft.Playwright; using Xunit; using Xunit.Abstractions; @@ -22,7 +19,6 @@ public AssetCachingTests(ITestOutputHelper output, SharedBuildPerTestClassFixtur } [Fact, TestCategory("no-fingerprinting")] - [ActiveIssue("https://github.com/dotnet/runtime/issues/122338")] // add it back to eng\testing\scenarios\BuildWasmAppsJobsList.txt public async Task BlazorApp_BasedOnFingerprinting_LoadsWasmAssetsFromCache() { var project = CopyTestAsset( @@ -36,12 +32,9 @@ public async Task BlazorApp_BasedOnFingerprinting_LoadsWasmAssetsFromCache() (string projectDir, string output) = BlazorPublish(project, Configuration.Release, new PublishOptions(AssertAppBundle: false)); var counterLoaded = new TaskCompletionSource(); - var wasmRequestRecorder = new WasmRequestRecorder(); var runOptions = new BlazorRunOptions(Configuration.Release) { - BrowserPath = "/counter", - OnServerMessage = wasmRequestRecorder.RecordIfWasmRequestFinished, OnConsoleMessage = (type, msg) => { if (msg.Contains("Counter.OnAfterRender")) @@ -49,32 +42,41 @@ public async Task BlazorApp_BasedOnFingerprinting_LoadsWasmAssetsFromCache() }, Test = async (page) => { + var baseUrl = page.Url; + + await page.GotoAsync($"{baseUrl}counter"); await counterLoaded.Task; + using var requestLogClient = new BlazorWebWasmLogClient(baseUrl); + var firstLoadRequestLogs = await requestLogClient.GetRequestLogsAsync(); + var firstLoadWasmRequests = firstLoadRequestLogs.Where(log => log.Path.EndsWith(".wasm")); + // Check server request logs after the first load. - Assert.NotEmpty(wasmRequestRecorder.ResponseCodes); - Assert.All(wasmRequestRecorder.ResponseCodes, r => Assert.Equal(200, r.ResponseCode)); + Assert.NotEmpty(firstLoadWasmRequests); + Assert.All(firstLoadWasmRequests, log => Assert.Equal(200, log.StatusCode)); - wasmRequestRecorder.ResponseCodes.Clear(); + await requestLogClient.ClearRequestLogsAsync(); counterLoaded = new(); // Perform browser navigation to cause resource reload. - // We use the initial base URL because the test server is not configured for SPA routing. await page.ReloadAsync(); await counterLoaded.Task; + var secondLoadRequestLogs = await requestLogClient.GetRequestLogsAsync(); + var secondLoadWasmRequests = secondLoadRequestLogs.Where(log => log.Path.EndsWith(".wasm")); + // Check server logs after the second load. if (EnvironmentVariables.UseFingerprinting) { // With fingerprinting we should not see any requests for Wasm assets during the second load. - Assert.Empty(wasmRequestRecorder.ResponseCodes); + Assert.Empty(secondLoadWasmRequests); } else { // Without fingerprinting we should see validation requests for Wasm assets // during the second load with response status 304. - Assert.NotEmpty(wasmRequestRecorder.ResponseCodes); - Assert.All(wasmRequestRecorder.ResponseCodes, r => Assert.Equal(304, r.ResponseCode)); + Assert.NotEmpty(secondLoadWasmRequests); + Assert.All(secondLoadWasmRequests, log => Assert.Equal(304, log.StatusCode)); } await page.EvaluateAsync("console.log('WASM EXIT 0');"); @@ -88,23 +90,3 @@ public async Task BlazorApp_BasedOnFingerprinting_LoadsWasmAssetsFromCache() var result = await BrowserRun(cmd, $"exec {publishedAppDllFileName}", runOptions); } } - -partial class WasmRequestRecorder -{ - public List<(string Name, int ResponseCode)> ResponseCodes { get; } = new(); - - [GeneratedRegex(@"Request finished HTTP/\d\.\d GET http://[^/]+/(?[^\s]+\.wasm)\s+-\s+(?\d+)")] - private static partial Regex LogRegex(); - - public void RecordIfWasmRequestFinished(string message) - { - var match = LogRegex().Match(message); - - if (match.Success) - { - var name = match.Groups["name"].Value; - var code = int.Parse(match.Groups["code"].Value); - ResponseCodes.Add((name, code)); - } - } -} diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorWebWasmLogClient.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorWebWasmLogClient.cs new file mode 100644 index 00000000000000..d2eb240f694423 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorWebWasmLogClient.cs @@ -0,0 +1,44 @@ +// 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.Net.Http; +using System.Net.Http.Json; +using System.Threading.Tasks; + +namespace Wasm.Build.Tests.Blazor; + +/// +/// Client for interacting with the request log API exposed by the BlazorWebWasm test server app. +/// +internal class BlazorWebWasmLogClient : IDisposable +{ + private readonly HttpClient _httpClient; + + public BlazorWebWasmLogClient(string baseUrl) + { + _httpClient = new HttpClient { BaseAddress = new Uri(baseUrl) }; + } + + /// + /// Retrieves logs for requests processed by the server since the last call to ClearRequestLogsAsync. + /// + public async Task GetRequestLogsAsync() + { + var response = await _httpClient.GetAsync("request-logs"); + response.EnsureSuccessStatusCode(); + var logs = await response.Content.ReadFromJsonAsync() ?? []; + return logs; + } + + public async Task ClearRequestLogsAsync() + { + var response = await _httpClient.DeleteAsync("request-logs"); + response.EnsureSuccessStatusCode(); + } + + public void Dispose() + { + _httpClient?.Dispose(); + } +} diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorWebWasmRequestLog.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorWebWasmRequestLog.cs new file mode 100644 index 00000000000000..963c8b4b227189 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorWebWasmRequestLog.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +#nullable enable + +namespace Wasm.Build.Tests.Blazor; + +public record BlazorWebWasmRequestLog( + DateTime Timestamp, + string Method, + string Path, + int StatusCode +); diff --git a/src/mono/wasm/testassets/BlazorWebWasm/BlazorWebWasm.Client/BlazorWebWasm.Client.csproj b/src/mono/wasm/testassets/BlazorWebWasm/BlazorWebWasm.Client/BlazorWebWasm.Client.csproj index bbdc1a530017ee..4a5b2d13c981c4 100644 --- a/src/mono/wasm/testassets/BlazorWebWasm/BlazorWebWasm.Client/BlazorWebWasm.Client.csproj +++ b/src/mono/wasm/testassets/BlazorWebWasm/BlazorWebWasm.Client/BlazorWebWasm.Client.csproj @@ -1,7 +1,7 @@ - net10.0 + net11.0 enable enable true diff --git a/src/mono/wasm/testassets/BlazorWebWasm/BlazorWebWasm.Client/Pages/Counter.razor b/src/mono/wasm/testassets/BlazorWebWasm/BlazorWebWasm.Client/Pages/Counter.razor index af02a34b595901..9e19f228cb0b9e 100644 --- a/src/mono/wasm/testassets/BlazorWebWasm/BlazorWebWasm.Client/Pages/Counter.razor +++ b/src/mono/wasm/testassets/BlazorWebWasm/BlazorWebWasm.Client/Pages/Counter.razor @@ -19,6 +19,9 @@ protected override void OnAfterRender(bool firstRender) { - Console.WriteLine("Counter.OnAfterRender"); + if (firstRender) + { + Console.WriteLine("Counter.OnAfterRender"); + } } } diff --git a/src/mono/wasm/testassets/BlazorWebWasm/BlazorWebWasm/BlazorWebWasm.csproj b/src/mono/wasm/testassets/BlazorWebWasm/BlazorWebWasm/BlazorWebWasm.csproj index c0dd1ef2e27b9a..f38134d4f56edc 100644 --- a/src/mono/wasm/testassets/BlazorWebWasm/BlazorWebWasm/BlazorWebWasm.csproj +++ b/src/mono/wasm/testassets/BlazorWebWasm/BlazorWebWasm/BlazorWebWasm.csproj @@ -1,7 +1,7 @@ - net10.0 + net11.0 enable enable true diff --git a/src/mono/wasm/testassets/BlazorWebWasm/BlazorWebWasm/BlazorWebWasmRequestLog.cs b/src/mono/wasm/testassets/BlazorWebWasm/BlazorWebWasm/BlazorWebWasmRequestLog.cs new file mode 100644 index 00000000000000..e6514af2d0b792 --- /dev/null +++ b/src/mono/wasm/testassets/BlazorWebWasm/BlazorWebWasm/BlazorWebWasmRequestLog.cs @@ -0,0 +1,6 @@ +public record BlazorWebWasmRequestLog( + DateTime Timestamp, + string Method, + string Path, + int StatusCode +); diff --git a/src/mono/wasm/testassets/BlazorWebWasm/BlazorWebWasm/Program.cs b/src/mono/wasm/testassets/BlazorWebWasm/BlazorWebWasm/Program.cs index ee077bf00fc887..76cbecfb24918f 100644 --- a/src/mono/wasm/testassets/BlazorWebWasm/BlazorWebWasm/Program.cs +++ b/src/mono/wasm/testassets/BlazorWebWasm/BlazorWebWasm/Program.cs @@ -10,6 +10,24 @@ var app = builder.Build(); +var requestLogs = new List(); +var requestLogsLock = new Lock(); + +app.Use(async (context, next) => +{ + await next.Invoke(); + var logEntry = new BlazorWebWasmRequestLog( + DateTime.UtcNow, + context.Request.Method, + context.Request.Path, + context.Response.StatusCode + ); + lock (requestLogsLock) + { + requestLogs.Add(logEntry); + } +}); + // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { @@ -31,4 +49,19 @@ .AddInteractiveWebAssemblyRenderMode() .AddAdditionalAssemblies(typeof(BlazorWebWasm.Client._Imports).Assembly); +app.MapGet("/request-logs", () => +{ + lock (requestLogsLock) + { + return requestLogs.ToList(); + } +}); +app.MapDelete("/request-logs", () => +{ + lock (requestLogsLock) + { + requestLogs.Clear(); + } +}); + app.Run();