Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions eng/testing/scenarios/BuildWasmAppsJobsList.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
52 changes: 17 additions & 35 deletions src/mono/wasm/Wasm.Build.Tests/Blazor/AssetCachingTests.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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(
Expand All @@ -36,45 +32,51 @@ 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"))
counterLoaded.SetResult();
},
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');");
Expand All @@ -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://[^/]+/(?<name>[^\s]+\.wasm)\s+-\s+(?<code>\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));
}
}
}
44 changes: 44 additions & 0 deletions src/mono/wasm/Wasm.Build.Tests/Blazor/BlazorWebWasmLogClient.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Client for interacting with the request log API exposed by the BlazorWebWasm test server app.
/// </summary>
internal class BlazorWebWasmLogClient : IDisposable
{
private readonly HttpClient _httpClient;

public BlazorWebWasmLogClient(string baseUrl)
{
_httpClient = new HttpClient { BaseAddress = new Uri(baseUrl) };
}

/// <summary>
/// Retrieves logs for requests processed by the server since the last call to ClearRequestLogsAsync.
/// </summary>
public async Task<BlazorWebWasmRequestLog[]> GetRequestLogsAsync()
{
var response = await _httpClient.GetAsync("request-logs");
response.EnsureSuccessStatusCode();
var logs = await response.Content.ReadFromJsonAsync<BlazorWebWasmRequestLog[]>() ?? [];
return logs;
}

public async Task ClearRequestLogsAsync()
{
var response = await _httpClient.DeleteAsync("request-logs");
response.EnsureSuccessStatusCode();
}

public void Dispose()
{
_httpClient?.Dispose();
}
}
Original file line number Diff line number Diff line change
@@ -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
);
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<TargetFramework>net11.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoDefaultLaunchSettingsFile>true</NoDefaultLaunchSettingsFile>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@

protected override void OnAfterRender(bool firstRender)
{
Console.WriteLine("Counter.OnAfterRender");
if (firstRender)
{
Console.WriteLine("Counter.OnAfterRender");
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<TargetFramework>net11.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
public record BlazorWebWasmRequestLog(
DateTime Timestamp,
string Method,
string Path,
int StatusCode
);
33 changes: 33 additions & 0 deletions src/mono/wasm/testassets/BlazorWebWasm/BlazorWebWasm/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,24 @@

var app = builder.Build();

var requestLogs = new List<BlazorWebWasmRequestLog>();
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())
{
Expand All @@ -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();