Task #242 partial — UnsTab interactive E2E test bodies + harness upgrades (tests Skip-guarded pending blazor.web.js asset plumbing)
Carries the interactive drag-drop + 409 concurrent-edit test bodies (full Playwright
flows against the real @ondragstart/@ondragover/@ondrop handlers + modal + EF state
round-trip), plus several harness upgrades that push the in-process
WebApplication-based fixture closer to a working Blazor Server circuit. The
interactive tests are marked [Fact(Skip=...)] pending resolution of one remaining
blocker documented in the class docstring.
Harness upgrades (AdminWebAppFactory):
- Environment set to Development so 500s surface exception stacks (rather than
the generic error page) during future diagnosis.
- ContentRootPath pointed at the Admin assembly dir so wwwroot + manifest files
resolve.
- Wired SignalR hubs (/hubs/fleet, /hubs/alerts) so ClusterDetail's HubConnection
negotiation no longer 500s at first render.
- Services property exposed so tests can open scoped DI contexts against the
running host (scheduled peer-edit simulation, post-commit state assertion).
Remaining blocker (reason for Skip):
/_framework/blazor.web.js returns HTTP 200 with a zero-byte body. The asset's
route is declared in OtOpcUa.Admin.staticwebassets.endpoints.json, but the
underlying file is shipped by the framework NuGet package
(Microsoft.AspNetCore.App.Internal.Assets/_framework/blazor.web.js) rather than
copied into the Admin wwwroot. MapStaticAssets can't resolve it without wiring
a composite FileProvider or the WebRootPath machinery. Three viable next-session
approaches listed in the class docstring:
(a) Composite FileProvider mapping /_framework/* → NuGet cache.
(b) Subprocess harness spawning real dotnet run of Admin project with an
InMemory-DB override (closest to production composition).
(c) MSBuild ItemGroup in the test csproj that copies framework files into the
test output + ContentRoot=test assembly dir with UseStaticFiles.
Scaffolding smoke test (Admin_host_serves_HTTP_via_Playwright_scaffolding) stays
green unchanged.
Suite state: 1 passed, 2 skipped, 0 failed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ZB.MOM.WW.OtOpcUa.Admin.Hubs;
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration;
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
|
||||
@@ -32,13 +33,36 @@ public sealed class AdminWebAppFactory : IAsyncDisposable
|
||||
public long SeededGenerationId { get; private set; }
|
||||
public string SeededClusterId { get; } = "e2e-cluster";
|
||||
|
||||
/// <summary>
|
||||
/// Root service provider of the running host. Tests use this to create scopes that
|
||||
/// share the InMemory DB with the Blazor-rendered page — e.g. to assert post-commit
|
||||
/// state, or to simulate a concurrent peer edit that bumps the DraftRevisionToken
|
||||
/// between preview-open and Confirm-click.
|
||||
/// </summary>
|
||||
public IServiceProvider Services => _app?.Services
|
||||
?? throw new InvalidOperationException("AdminWebAppFactory: StartAsync has not been called");
|
||||
|
||||
public async Task StartAsync()
|
||||
{
|
||||
var port = GetFreeTcpPort();
|
||||
BaseUrl = $"http://127.0.0.1:{port}";
|
||||
|
||||
var builder = WebApplication.CreateBuilder(Array.Empty<string>());
|
||||
// Point the content root at the Admin project's build output so wwwroot/ (app.css,
|
||||
// site CSS, icons) + the Blazor framework assets served from the Admin assembly
|
||||
// resolve. Without this the default content root is the test project's bin dir and
|
||||
// blazor.web.js + app.css return 404, which keeps the interactive circuit from
|
||||
// booting at all.
|
||||
var adminAssemblyDir = System.IO.Path.GetDirectoryName(
|
||||
typeof(Admin.Components.App).Assembly.Location)!;
|
||||
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
|
||||
{
|
||||
ContentRootPath = adminAssemblyDir,
|
||||
});
|
||||
builder.WebHost.UseUrls(BaseUrl);
|
||||
// E2E host runs in Development so unhandled exceptions during Blazor render surface
|
||||
// as visible 500s with stacks the test can capture — prod-style generic errors make
|
||||
// diagnosis of circuit / DI misconfig effectively impossible.
|
||||
builder.Environment.EnvironmentName = Microsoft.Extensions.Hosting.Environments.Development;
|
||||
|
||||
// --- Mirror the Admin composition in Program.cs, but with the InMemory DB + test
|
||||
// auth swaps instead of SQL Server + LDAP cookie auth.
|
||||
@@ -72,6 +96,12 @@ public sealed class AdminWebAppFactory : IAsyncDisposable
|
||||
_app.UseAuthorization();
|
||||
_app.UseAntiforgery();
|
||||
_app.MapRazorComponents<Admin.Components.App>().AddInteractiveServerRenderMode();
|
||||
// The ClusterDetail + other pages connect SignalR hubs at render time — the
|
||||
// endpoints must exist or the Blazor circuit surfaces a 500 on first interactive
|
||||
// step. No background pollers (FleetStatusPoller etc.) are registered so the hubs
|
||||
// stay quiet until something pushes through IHubContext, which the E2E tests don't.
|
||||
_app.MapHub<FleetStatusHub>("/hubs/fleet");
|
||||
_app.MapHub<AlertHub>("/hubs/alerts");
|
||||
|
||||
// Seed the draft BEFORE starting the host so Playwright sees a ready page on first nav.
|
||||
using (var scope = _app.Services.CreateScope())
|
||||
|
||||
Reference in New Issue
Block a user