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>
Ships the E2E infrastructure filed against task #199 (UnsTab drag-drop Playwright
smoke). The Blazor Server interactive-render assertion through a test-owned pipeline
needs a dedicated diagnosis pass — filed as task #242 — but the Playwright harness
lands here so that follow-up starts from a known-good scaffolding rather than
setting up the project from scratch.
## New project tests/ZB.MOM.WW.OtOpcUa.Admin.E2ETests
- AdminWebAppFactory — boots the Admin pipeline with Kestrel on a free loopback port,
swaps the SQL DbContext for EF Core InMemory, replaces the LDAP cookie auth with
TestAuthHandler, mirrors the Razor-components/auth/antiforgery pipeline, and seeds
a cluster + draft generation with areas warsaw / berlin and a line-a1 in warsaw.
Not a WebApplicationFactory<Program> because WAF's TestServer transport doesn't
coexist cleanly with Kestrel-on-a-real-port, which Playwright needs.
- TestAuthHandler — stamps every request with a FleetAdmin claim so tests hit
authenticated routes without the LDAP bind.
- PlaywrightFixture — one Chromium launch shared across tests; throws
PlaywrightBrowserMissingException when the binary isn't installed so tests can
Assert.Skip rather than fail hard.
- UnsTabDragDropE2ETests.Admin_host_serves_HTTP_via_Playwright_scaffolding — proves
the full stack comes up: Kestrel bind, InMemory DbContext, test auth, Playwright
navigation, Razor route pipeline responds with HTML < 500. One passing test.
## Prerequisite
Chromium must be installed locally:
pwsh tests/ZB.MOM.WW.OtOpcUa.Admin.E2ETests/bin/Debug/net10.0/playwright.ps1 install chromium
Absent the browser, the suite Assert.Skip's cleanly — CI without the install step
still reports green. Once installed, `dotnet test` runs the scaffolding smoke in ~12s.
## Follow-up (task #242)
Diagnose why `/clusters/{id}/draft/{gen}` → UNS-tab click → drag-drop flow times out
under the test-owned Program.cs replica. Candidate causes: route-ordering difference,
missing SignalR hub mapping timing, JS interop asset differences, culture middleware.
Once the interactive circuit boots, add:
- happy-path drag-drop assertion (source row → target area → Confirm → assert re-parent)
- 409 conflict variant (preview → external DB mutation → Confirm → assert red-header modal)