Skip to content

Complete managed Blazor WebAssembly debugging in VS Code - #20001

Open
Adam Ratzman (adamint) wants to merge 19 commits into
microsoft:mainfrom
adamint:adamint-complete-blazor-wasm-debugging
Open

Complete managed Blazor WebAssembly debugging in VS Code#20001
Adam Ratzman (adamint) wants to merge 19 commits into
microsoft:mainfrom
adamint:adamint-complete-blazor-wasm-debugging

Conversation

@adamint

@adamint Adam Ratzman (adamint) commented Sep 9, 2026

Copy link
Copy Markdown
Member

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 blazorwasm debugger 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.csharp 2.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-debugger E2E 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: actual monovsdbg_wasm breakpoint stops, matching Counter.razor stack 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

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No
  • Did you add public API?
    • Yes
      • If yes, did you have an API Review for it?
        • Yes
        • No
      • Did you add <remarks /> and <code /> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
      • If yes, have you done a threat model and had a security review?
        • Yes
        • No
    • No

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>
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 20001

Or

  • Run remotely in PowerShell:
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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 Medium severity

New issues introduced by this change (4)
Severity Finding
Medium severity extension/​src/​debugger/​AspireDebugSession.ts — A browser session can start after disposeCore() has already iterated _disposables (the new…
Medium severity extension/​src/​test-e2e/​browserDebugger.e2e.test.ts — This Mocha timeout expires at the same 300-second deadline as the inner proof, while…
Medium severity extension/​src/​testing/​e2eStateFileBridge.ts — The pinned C# 2.148.23 debugger does not leave an active blazorwasm session to discover here. Its…
Medium severity 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 registerDisposable in AspireDebugSession.ts.
  • Moderate (1 vote): Extend the browser E2E Mocha timeout to cover proof and cleanup budgets.
  • Moderate (1 vote): Accept zero-based breakpoint line 0 in e2eStateFileBridge.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.

Comment thread extension/src/debugger/AspireDebugSession.ts
Comment thread extension/src/test-e2e/browserDebugger.e2e.test.ts Outdated
Comment thread extension/src/testing/e2eStateFileBridge.ts Outdated
Comment thread extension/src/testing/e2eStateFileBridge.ts Outdated
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 9c129138-d412-4e1f-849a-62271950ddce
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

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
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Medium severity extension/​src/​testing/​e2eStateFileBridge.ts — This rejects line 0, even though breakpointLine is zero-based and findBreakpointLine()View resolved comment
Medium severity 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
Medium severity 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
Medium severity 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 ignores window.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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 the debug-in-browser command times out while VS Code is still starting the session, finally snapshots 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 from stopDebugging before onDidTerminateDebugSession fires, this finally immediately 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 left activeSessionIds after issuing the stop requests.

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Debugger lifecycle changes and pending live managed-breakpoint validation require human review.

Review tier: Balanced
Findings: None

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 High severity

New issues introduced by this change (1)
Severity Finding
High severity 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.browser workspace settings are merged after the initial noDebug value is derived, and this managed branch never restores it. A workspace setting of noDebug: true therefore survives into the blazorwasm attach configuration, so Debug in Browser launches without managed debugging or working C# breakpoints. Reset noDebug from launchOptions.debug here after the merge, as the Go, Java, and MAUI callbacks do.
    extension/src/testing/e2eStateFileBridge.ts:1393
  • Failed attach responses from the actual blazorwasm root 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. Include blazorwasm alongside the browser aliases and monovsdbg_wasm.

Comment thread extension/src/debugger/languages/browser.ts
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
High severity extension/​src/​debugger/​languages/​browser.ts — The inherited debuggers.browser object can still contain C#'s nested… View resolved comment

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 attach response from a root session whose type remains blazorwasm is ignored here, even though that type is explicitly accepted as a root below. On supported C# versions that do not rewrite the root to chrome/msedge, launch failure therefore polls until the full proof timeout instead of surfacing the adapter error immediately. Include blazorwasm in this failure predicate.

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Aspire Extension: Browser and WASM debug session support Blazor WebAssembly Debugging Support in Aspire

2 participants