Complete managed Blazor WebAssembly debugging in VS Code - #20001
Complete managed Blazor WebAssembly debugging in VS Code#20001Adam Ratzman (adamint) wants to merge 19 commits into
Conversation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 20001Or
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 20001" |
Preserve browser, WinUI, and Deno E2E fixtures and debugger prerequisites. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9c129138-d412-4e1f-849a-62271950ddce
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Four moderate lifecycle and E2E validation issues remain unresolved.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review tier: Balanced
Findings: 4
New issues introduced by this change (4)
| Severity | Finding |
|---|---|
extension/src/debugger/AspireDebugSession.ts — A browser session can start after disposeCore() has already iterated _disposables (the new… |
|
extension/src/test-e2e/browserDebugger.e2e.test.ts — This Mocha timeout expires at the same 300-second deadline as the inner proof, while… |
|
extension/src/testing/e2eStateFileBridge.ts — The pinned C# 2.148.23 debugger does not leave an active blazorwasm session to discover here. Its… |
|
extension/src/testing/e2eStateFileBridge.ts — This rejects line 0, even though breakpointLine is zero-based and findBreakpointLine()… |
What changed in this PR
Adds managed Blazor WebAssembly debugging through the C# extension while retaining js-debug for generic browser resources.
Changes:
- Adds managed launch configuration and C# extension version validation.
- Improves browser-session shutdown, ownership, and retry handling.
- Adds localized diagnostics, tests, E2E scenarios, and CI coverage.
Required changes:
- Moderate (1 vote): Register late browser cleanup through
registerDisposableinAspireDebugSession.ts. - Moderate (1 vote): Extend the browser E2E Mocha timeout to cover proof and cleanup budgets.
- Moderate (1 vote): Accept zero-based breakpoint line
0ine2eStateFileBridge.ts. - Moderate (1 vote): Discover the actual browser root and managed session topology produced by the pinned C# debugger.
| File | Description |
|---|---|
extension/src/types/extensionApi.ts |
Extends E2E command and session contracts. |
extension/src/testing/e2eStateFileBridge.ts |
Implements managed-breakpoint E2E proof collection. |
extension/src/test/strings.test.ts |
Verifies localized debugger messages. |
extension/src/test/runSessionRegistry.test.ts |
Tests retryable notification delivery. |
extension/src/test/e2eStateFileBridge.test.ts |
Tests E2E bridge behavior and cleanup. |
extension/src/test/e2eShardMatrix.test.ts |
Validates browser-debugger CI entries. |
extension/src/test/e2eLaunchProfile.test.ts |
Validates fixtures and debugger prerequisites. |
extension/src/test/browserDebugger.test.ts |
Covers browser configuration and lifecycle. |
extension/src/test/aspireDebugSession.test.ts |
Covers late starts and stop retries. |
extension/src/test/aspireDcpServer.test.ts |
Covers confirmed-stop DCP lifecycle. |
extension/src/test/adapterTracker.test.ts |
Verifies browser adapter termination behavior. |
extension/src/test-e2e/helpers.ts |
Adds reusable Blazor proof assertions. |
extension/src/test-e2e/browserDebugger.e2e.test.ts |
Adds managed-browser E2E scenarios. |
extension/src/loc/strings.ts |
Adds localized debugger errors. |
extension/src/debugger/languages/browser.ts |
Routes Blazor projects to managed debugging. |
extension/src/debugger/debuggerExtensions.ts |
Protects Aspire-owned debug metadata. |
extension/src/debugger/browserDebugSessionTermination.ts |
Implements confirmed browser termination. |
extension/src/debugger/AspireDebugSession.ts |
Integrates browser lifecycle and retry handling. |
extension/src/debugger/adapterTracker.ts |
Prevents duplicate browser termination reports. |
extension/src/dcp/types.ts |
Extends session lifecycle contracts. |
extension/src/dcp/RunSessionRegistry.ts |
Adds startup tracking and retryable delivery. |
extension/src/dcp/AspireDcpServer.ts |
Coordinates startup and confirmed browser stops. |
extension/src/capabilities.ts |
Validates C# extension versions. |
extension/scripts/run-e2e.js |
Generates managed Blazor E2E fixtures. |
extension/package.nls.json |
Registers localized messages. |
extension/loc/xlf/aspire-vscode.xlf |
Updates the localization catalog. |
extension/CONTRIBUTING.md |
Documents browser-debugger E2E execution. |
.github/workflows/extension-e2e-tests.yml |
Adds Linux/Chrome and Windows/Edge jobs. |
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9c129138-d412-4e1f-849a-62271950ddce
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
Register termination cleanup through the session lifetime helper so late browser starts cannot retain listeners after shutdown. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9c129138-d412-4e1f-849a-62271950ddce
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
Supply the .NET 11 evaluated-reference discovery contract for the generated .NET 10 fixture, matching the BlazorHosted sample. Start a debug AppHost, honor state-file argument redaction, and retain each managed proof independently. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9c129138-d412-4e1f-849a-62271950ddce
Recognize C# browser aliases and the unparented managed WASM adapter. Keep only one scenario server active during browser proof to reduce extension-host memory pressure. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9c129138-d412-4e1f-849a-62271950ddce
Share proof/control timeout budgets across the extension host and ExTester, give server transitions a single deadline, and leave margin for state delivery before Mocha times out. Validate zero-based breakpoint line zero and cover C# rewritten roots with sibling and detached WASM adapters. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9c129138-d412-4e1f-849a-62271950ddce
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
The hosted-global natural-closure proof may time out because window.close() is unreliable after navigation.
Review tier: Balanced
Findings: None
Issues resolved since last review (4)
| Severity | Finding |
|---|---|
extension/src/testing/e2eStateFileBridge.ts — This rejects line 0, even though breakpointLine is zero-based and findBreakpointLine()… View resolved comment |
|
extension/src/testing/e2eStateFileBridge.ts — The pinned C# 2.148.23 debugger does not leave an active blazorwasm session to discover here. Its… View resolved comment |
|
extension/src/test-e2e/browserDebugger.e2e.test.ts — This Mocha timeout expires at the same 300-second deadline as the inner proof, while… View resolved comment |
|
extension/src/debugger/AspireDebugSession.ts — A browser session can start after disposeCore() has already iterated _disposables (the new… View resolved comment |
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
extension/src/testing/e2eStateFileBridge.ts:1608
- The “natural” path cannot reliably close this browser. The proof navigates with
location.assign()first, which adds a history entry; Chromium ignoreswindow.close()for a launch-created tab once it is no longer script-closable. The hosted-global scenario will therefore wait for termination until it times out rather than exercising natural browser closure. Close the launched target through browser/DevTools automation (or another mechanism that actually closes the browser window) before waiting for the root-session termination.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9c129138-d412-4e1f-849a-62271950ddce
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
Two moderate E2E cleanup races can leave late or terminating debug sessions active across scenarios.
Review tier: Balanced
Findings: None
Suppressed comments (2)
Previously missed (2) — in code that hasn't changed since the last review.
extension/src/testing/e2eStateFileBridge.ts:1400
- The timeout race does not cancel or retain
operation(). If thedebug-in-browsercommand times out while VS Code is still starting the session,finallysnapshots only the sessions active at that instant and later disposes the start listener; a session that starts afterward is never stopped and can contaminate the remaining scenarios. Keep the timed-out startup observable through the cleanup allowance (or dynamically stop proof sessions that appear during cleanup) before disposing the listeners.
extension/src/testing/e2eStateFileBridge.ts:1666 - Cleanup only awaits the
stopDebugging()calls, but this PR's browser lifecycle correctly treats those promises as request acknowledgements rather than termination confirmation. If a failed proof returns fromstopDebuggingbeforeonDidTerminateDebugSessionfires, thisfinallyimmediately disposes the listener and returns while the root/children are still active, so the next scenario can inherit the previous browser/debug adapters. Keep the termination subscription alive and wait (within the cleanup budget) until the proof-owned IDs have leftactiveSessionIdsafter issuing the stop requests.
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>\nCopilot-Session: 9c129138-d412-4e1f-849a-62271950ddce
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
Set the standalone gateway base path, distinguish interactive Counter rendering from SSR, and retain launch errors when dialog cleanup fails. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9c129138-d412-4e1f-849a-62271950ddce
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
One critical and two moderate configuration and failure-reporting issues remain unresolved.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review tier: Balanced
Findings: 1
New issues introduced by this change (1)
| Severity | Finding |
|---|---|
extension/src/debugger/languages/browser.ts — The inherited debuggers.browser object can still contain C#'s nested… |
Suppressed comments (2)
Previously missed (2) — in code that hasn't changed since the last review.
extension/src/debugger/languages/browser.ts:93
debuggers.browserworkspace settings are merged after the initialnoDebugvalue is derived, and this managed branch never restores it. A workspace setting ofnoDebug: truetherefore survives into theblazorwasmattach configuration, so Debug in Browser launches without managed debugging or working C# breakpoints. ResetnoDebugfromlaunchOptions.debughere after the merge, as the Go, Java, and MAUI callbacks do.
extension/src/testing/e2eStateFileBridge.ts:1393- Failed attach responses from the actual
blazorwasmroot are excluded from this failure check. If that root rejects its attach request before a browser child starts, the response is recorded but every proof wait runs to the five-minute deadline and reports a timeout instead of the adapter error. Includeblazorwasmalongside the browser aliases andmonovsdbg_wasm.
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9c129138-d412-4e1f-849a-62271950ddce
Use js-debug's public CDP proxy for Page.close, preserve transport traces, and run isolated HTTP-only fixtures. Require root, page and managed termination before accepting natural closure. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9c129138-d412-4e1f-849a-62271950ddce
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
Managed-breakpoint E2E validation remains pending for this broad debugger and lifecycle change.
Review tier: Balanced
Findings: None
Issues resolved since last review (1)
| Severity | Finding |
|---|---|
extension/src/debugger/languages/browser.ts — The inherited debuggers.browser object can still contain C#'s nested… View resolved comment |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
Prebuild the browser fixture before C# project discovery, budget cold browser attachment within the existing proof deadline, and dismiss details-only launch error dialogs. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9c129138-d412-4e1f-849a-62271950ddce
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
Root blazorwasm attach failures are not surfaced immediately and instead wait for the full proof timeout.
Review tier: Balanced
Findings: None
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
extension/src/testing/e2eStateFileBridge.ts:1399
- A failed
attachresponse from a root session whose type remainsblazorwasmis ignored here, even though that type is explicitly accepted as a root below. On supported C# versions that do not rewrite the root tochrome/msedge, launch failure therefore polls until the full proof timeout instead of surfacing the adapter error immediately. Includeblazorwasmin this failure predicate.
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |


Description
Blazor browser debugging currently starts a JavaScript debugger even when Aspire supplies a WebAssembly client project. This hands those launches to the C# extension's
blazorwasmdebugger so managed C# breakpoints work. Generic browser resources stay on js-debug.The browser resource now follows the root VS Code session lifetime. Stop waits for termination, startup/stop overlap stays retryable, and child sessions cannot report the resource as terminated.
Supersedes the browser lifecycle work in #18626. Fixes #17797. Fixes #17795.
User-facing usage
Start the Aspire AppHost in VS Code and select Debug in Browser on the Blazor resource. Set a breakpoint in the client's Counter component, navigate to that page, and click the counter button. Edge and Chrome are supported. Managed debugging requires
ms-dotnettools.csharp2.145.15-prerelease or later; missing or older versions produce install/update guidance.Validation
At
9b0f7891cd1e2bb89e5cdbc926cca9aa17e2bbd7, all checks have completed without remaining failures. CI run 34314035384, attempt 2.The real
browser-debuggerE2E shard passes all eight cases on Linux/Chrome and Windows/Edge. Its three managed scenarios cover standalone gateway, hosted-global, and hosted-per-page WebAssembly. All six scenario proof artifacts were inspected: actualmonovsdbg_wasmbreakpoint stops, matchingCounter.razorstack frames, confirmed session termination, and an enabled Debug in Browser command afterward. Hosted-global closes the actual browser page through CDP; the other scenarios use explicit stop.The final startup changes also pass 157 focused tests, TypeScript compilation, lint, and E2E VSIX packaging. Fixtures are built before VS Code opens to avoid competing AssemblyInfo writes. Cold browser startup has a test-only 90-second allowance within the existing 300-second proof deadline; production defaults and breakpoint/teardown requirements are unchanged.
Remaining intermittency: the first attempt failed Windows standalone attachment before page discovery, and an unrelated Windows dynamic-debug teardown hit
EBUSY. Both passed the automatic rerun. The standalone failure's initial managed-debugger shutdown cause is not yet established; the passing rerun is not evidence that this flake is fixed.Security considerations
Generic browser launches retain an isolated js-debug-managed profile and reject workspace profile overrides. The C# extension owns the managed bridge and browser process for Blazor launches. CI downloads version-pinned debugger VSIX files and verifies their SHA-256 digests.
Checklist
<remarks />and<code />elements on your triple slash comments?