diff --git a/tests/ZB.MOM.WW.OtOpcUa.Admin.E2ETests/AdminWebAppFactory.cs b/tests/ZB.MOM.WW.OtOpcUa.Admin.E2ETests/AdminWebAppFactory.cs index 4c99048..6c11859 100644 --- a/tests/ZB.MOM.WW.OtOpcUa.Admin.E2ETests/AdminWebAppFactory.cs +++ b/tests/ZB.MOM.WW.OtOpcUa.Admin.E2ETests/AdminWebAppFactory.cs @@ -47,18 +47,25 @@ public sealed class AdminWebAppFactory : IAsyncDisposable var port = GetFreeTcpPort(); BaseUrl = $"http://127.0.0.1:{port}"; - // 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. + // Point the content root at the Admin project's build output so the Admin + // assembly + its sibling staticwebassets manifest are discoverable. The manifest + // maps /_framework/* to the framework NuGet cache + /app.css to the Admin source + // wwwroot; StaticWebAssetsLoader.UseStaticWebAssets reads it and wires a composite + // file provider automatically. var adminAssemblyDir = System.IO.Path.GetDirectoryName( typeof(Admin.Components.App).Assembly.Location)!; var builder = WebApplication.CreateBuilder(new WebApplicationOptions { ContentRootPath = adminAssemblyDir, + ApplicationName = typeof(Admin.Components.App).Assembly.GetName().Name, }); builder.WebHost.UseUrls(BaseUrl); + // UseStaticWebAssets reads {ApplicationName}.staticwebassets.runtime.json (or the + // development variant via the ASPNETCORE_HOSTINGSTARTUPASSEMBLIES convention) and + // composes a PhysicalFileProvider per declared ContentRoot. This is what + // `dotnet run` does automatically via the MSBuild targets — we replicate it + // explicitly for the test-owned pipeline. + builder.WebHost.UseStaticWebAssets(); // 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. @@ -78,8 +85,13 @@ public sealed class AdminWebAppFactory : IAsyncDisposable .AddPolicy("CanPublish", p => p.RequireRole(Admin.Services.AdminRoles.FleetAdmin)); builder.Services.AddCascadingAuthenticationState(); + // One InMemory database name per fixture — the lambda below runs on every DbContext + // construction, so capturing a stable string (not calling Guid.NewGuid() inline) is + // critical: every scope (seed, Blazor circuit, test assertions) must share the same + // backing store or rows written in one scope disappear in the next. + var dbName = $"e2e-{Guid.NewGuid():N}"; builder.Services.AddDbContext(opt => - opt.UseInMemoryDatabase($"e2e-{Guid.NewGuid():N}")); + opt.UseInMemoryDatabase(dbName)); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/tests/ZB.MOM.WW.OtOpcUa.Admin.E2ETests/UnsTabDragDropE2ETests.cs b/tests/ZB.MOM.WW.OtOpcUa.Admin.E2ETests/UnsTabDragDropE2ETests.cs index 6ad2c8a..40bc5a4 100644 --- a/tests/ZB.MOM.WW.OtOpcUa.Admin.E2ETests/UnsTabDragDropE2ETests.cs +++ b/tests/ZB.MOM.WW.OtOpcUa.Admin.E2ETests/UnsTabDragDropE2ETests.cs @@ -8,37 +8,27 @@ using ZB.MOM.WW.OtOpcUa.Configuration; namespace ZB.MOM.WW.OtOpcUa.Admin.E2ETests; /// -/// Phase 6.4 UnsTab drag-drop E2E. Task #199 landed the scaffolding; task #242 drives the -/// Blazor Server interactive circuit through a real drag-drop → confirm-modal → apply flow -/// and a 409 concurrent-edit flow. Both interactive tests are currently -/// -guarded — see below. +/// Phase 6.4 UnsTab drag-drop E2E. Task #199 landed the scaffolding; task #242 (this file) +/// drives the Blazor Server interactive circuit through a real drag-drop → confirm-modal +/// → apply flow and a 409 concurrent-edit flow, both via Chromium. /// /// /// /// Prerequisite. Chromium must be installed locally: /// pwsh tests/ZB.MOM.WW.OtOpcUa.Admin.E2ETests/bin/Debug/net10.0/playwright.ps1 install chromium. +/// When the binary is missing the tests rather than fail hard, +/// so CI pipelines that don't run the install step still report green. /// /// -/// Current blocker (both interactive tests skipped). The Blazor Server circuit -/// never boots in the test-owned pipeline because _framework/blazor.web.js -/// returns HTTP 200 with a zero-byte body. The asset's route is declared in the Admin -/// project's OtOpcUa.Admin.staticwebassets.endpoints.json manifest, but the -/// underlying file is shipped via the framework NuGet -/// (Microsoft.AspNetCore.App.Internal.Assets/_framework/blazor.web.js) rather -/// than the Admin's wwwroot. points the content -/// root at the Admin assembly directory + maps hubs + runs in Development, so routing -/// / auth / DbContext / hub negotiation all succeed — the only gap is wiring the -/// framework-asset file provider into MapStaticAssets or UseStaticFiles. -/// The drag-drop + 409 scenarios are fully written; un-skipping them is a matter of -/// plumbing, not rewriting the test logic. -/// -/// -/// Options for closing the gap. (a) Layer a composite file provider that maps -/// /_framework/* into the NuGet cache at test-init time. (b) Launch the real -/// dotnet run --project Admin process as a subprocess with an InMemory DB -/// override — closest to the production composition. (c) Copy the framework asset -/// files into the test project's output via MSBuild so UseStaticFiles with -/// ContentRootPath=Admin bin finds them. +/// Harness notes. points the content root at +/// the Admin assembly directory + sets ApplicationName + calls +/// UseStaticWebAssets so /_framework/blazor.web.js + /app.css +/// resolve from the Admin's staticwebassets.development.json manifest (which +/// stitches together Admin wwwroot + the framework NuGet cache). Hubs +/// /hubs/fleet + /hubs/alerts are mapped so ClusterDetail's +/// HubConnection negotiation doesn't 500 at first render. The InMemory +/// database name is captured as a stable string per fixture instance so the seed +/// scope + Blazor circuit scope + test-assertion scope all share one backing store. /// /// [Trait("Category", "E2E")] @@ -73,9 +63,7 @@ public sealed class UnsTabDragDropE2ETests } } - [Fact(Skip = "Task #242 blocked on blazor.web.js asset resolution — see class docstring. " + - "Test body is complete + validated against the scaffolding; un-skip once the framework " + - "file provider is wired into AdminWebAppFactory.")] + [Fact] public async Task Dragging_line_onto_new_area_shows_preview_modal_then_confirms_the_move() { await using var app = new AdminWebAppFactory(); @@ -126,7 +114,7 @@ public sealed class UnsTabDragDropE2ETests } } - [Fact(Skip = "Task #242 blocked on blazor.web.js asset resolution — see class docstring.")] + [Fact] public async Task Preview_shown_then_peer_edit_applied_surfaces_409_conflict_modal() { await using var app = new AdminWebAppFactory();