Add BrowserUserDataMode to separate user data dir from profile#16457
Add BrowserUserDataMode to separate user data dir from profile#16457
Conversation
Introduce a BrowserUserDataMode enum (Shared/Isolated) on the tracked browser launch path so callers can choose between launching against the browser's real user data directory (like clicking the browser icon) or a temporary, isolated user data directory. - WithBrowserLogs(...) gains a userDataMode parameter - Configuration key: Aspire:Hosting:BrowserLogs[:Resource]:UserDataMode - Default is Isolated to preserve current behavior - Setting Profile while UserDataMode is Isolated is rejected - Shared + omitted profile lets Chromium pick its default profile - Shared + profile resolves the profile inside the real user data dir - Surface effective user data mode as a resource property Co-authored-by: Copilot <[email protected]>
- Flip BrowserUserDataMode default from Isolated to Shared so the tracked browser behaves like a normal browser launch by default. - Add a remarks section on BrowserUserDataMode.Shared explaining the Chromium singleton/SingletonLock + DevToolsActivePort interaction so callers understand why a second launch against an already-running real browser cannot establish a CDP endpoint. - Update the default-mode test accordingly. Co-authored-by: Copilot <[email protected]>
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 16457Or
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 16457" |
There was a problem hiding this comment.
Pull request overview
Adds a new user-data directory selection mode for tracked browser log sessions, separating “which Chromium user data dir to use” from “which profile inside that dir to use” to support either reusing real browser state or running from a clean temporary state.
Changes:
- Introduces
BrowserUserDataMode(Shareddefault /Isolated) and threads it throughWithBrowserLogs(...), configuration resolution, and resource properties. - Updates browser launch behavior to use the real user data directory in
Sharedmode, including profile directory resolution via directory entries and Chromium “Local State” metadata. - Expands unit tests to cover user data mode defaults/overrides and profile directory resolution behaviors.
Show a summary per file
| File | Description |
|---|---|
| tests/Aspire.Hosting.Tests/BrowserLogsSessionManagerTests.cs | Adds coverage for profile directory resolution (case-insensitive + Local State display-name mapping + ambiguity). |
| tests/Aspire.Hosting.Tests/BrowserLogsBuilderExtensionsTests.cs | Adds coverage for default Shared mode, config parsing, explicit override precedence, and Isolated + profile rejection. |
| src/Aspire.Hosting/BrowserLogs/BrowserLogsRunningSession.cs | Implements user-data-mode-aware user data dir selection and profile directory resolution; passes resolved directory name to --profile-directory. |
| src/Aspire.Hosting/BrowserLogs/BrowserLogsResource.cs | Adds the public BrowserUserDataMode enum and stores resolved/override mode on the resource. |
| src/Aspire.Hosting/BrowserLogs/BrowserLogsBuilderExtensions.cs | Extends public API + configuration parsing/validation and surfaces “User data mode” as a resource property. |
Copilot's findings
- Files reviewed: 5/5 changed files
- Comments generated: 1
Adds parsing for CDP events used to detect when a tracked target ends: - Target.targetDestroyed - Target.targetCrashed - Target.detachedFromTarget - Inspector.detached Also adds command-name constants (Target.closeTarget, Target.setDiscoverTargets) that the upcoming host abstraction will use to manage tracked targets without calling Browser.close. This is purely additive; nothing wires these events up yet. Co-authored-by: Copilot <[email protected]>
Defines the IBrowserHost interface plus BrowserHostIdentity and the BrowserHostOwnership enum. The interface separates 'a browser instance to attach CDP to' from 'a tracked log session' so the same host can back many sessions and so disposal can do the right thing for adopted browsers. Key invariants documented in the file: - Adopted hosts must never close the user's browser - Profile directory participates in identity (different profiles = different process) - Acquire/ReleaseAsync refcount, last release disposes - Completion task surfaces process exit / CDP socket close so sessions can fail fast No callers yet; this commit only adds the contract. Co-authored-by: Copilot <[email protected]>
…t routing Code-review feedback from gpt-5.4, gpt-5.3-codex, and claude-opus-4.7 on the Phase 2 foundation commits, plus a mechanical pass applying the same lens James used on the original feature PR (#16310). BrowserHostIdentity changes: - Removed ProfileDirectory from the identity. Chromium's singleton is keyed by user-data-dir, not by profile, so different profiles under the same user data root share a browser process. Profile selection is per-target, not per-host. The previous doc comment claiming otherwise was wrong. - Replaced record-struct synthesized equality (which is ordinal case-sensitive on strings) with explicit Equals/GetHashCode using StringComparer.OrdinalIgnoreCase on Windows / Ordinal elsewhere. Without this, paths that differ only in casing would create duplicate hosts on Windows. - Constructor now normalizes paths via Path.GetFullPath + TrimEndingDirectorySeparator so 'C:/foo' and 'C:\foo\' collapse to the same identity. - GetHashCode is null-safe against default(BrowserHostIdentity) since StringComparer.GetHashCode(null) throws. Protocol changes: - Documented above the new Target.* event records that the SUBJECT of these events lives in params (targetId / params.sessionId), not the envelope sessionId. The existing dispatch in BrowserLogsRunningSession filters on envelope sessionId only, so any future fanout layer that naively reuses that filter would silently drop these events. The comment makes the routing requirement unmissable. Co-authored-by: Copilot <[email protected]>
Apply the reliability fixes identified from the architecture review before continuing the larger adoption refactor: - Bound ChromeDevToolsConnection.DisposeAsync websocket close with a 3 second timeout so shutdown cannot hang indefinitely on a wedged browser. - Add Target.closeTarget and Target.setDiscoverTargets helpers and enable target discovery after each browser-CDP connection, including reconnects. - Add explicit Target lifecycle correlation helpers so future fanout routes targetDestroyed/targetCrashed by targetId and detachedFromTarget by params.sessionId instead of the usually-null envelope sessionId. - Make shared-mode default browser selection prefer Edge. Isolated mode keeps Chrome-first behavior. This avoids Chrome's default-profile remote-debugging guardrail for the default Shared experience. - Fail fast when Shared mode explicitly targets Google Chrome's default user data directory, with guidance to use Edge or Isolated mode. - Reshape the browser host contract around leases and target-session creation rather than public Acquire/Release refcount methods. This keeps exactly-once release and target ownership in the abstraction that will support adopted browsers. Adds targeted tests for the mode-aware default browser choice, browser host identity normalization/default safety, lease idempotency, and the Chrome shared-profile guard helper. Co-authored-by: Copilot <[email protected]>
Refactor tracked browser logs around shared browser hosts and per-target sessions so shared user-data mode can reuse an existing debug-enabled Chromium instance without ever closing the user's browser. Add endpoint metadata discovery, owned/adopted host ownership, target lifecycle monitoring, and stale metadata handling. Co-authored-by: Copilot <[email protected]>
Add architecture comments for the shared/adopted browser pieces and target discovery. Surface browser host ownership and last-error diagnostics in resource state, preserve failed-session diagnostics, and log host/target/reconnect failures through resource logs. Harden endpoint metadata validation for malformed adoption hints. Co-authored-by: Copilot <[email protected]>
Refresh ATS code-generation snapshots after adding BrowserUserDataMode to WithBrowserLogs. Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
|
Re-running the failed jobs in the CI workflow for this pull request because 1 job was identified as retry-safe transient failures in the CI run attempt.
|
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Unit tests are not going to determine if this feature works (though I'll add any low hanging fruit). Still need to figure out how to automate end to end testing this one. We're going to need to actually invest in playwright tests (I know how much you love those). |
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Consolidated BrowserLogs smoke-test/updateConsolidating the previous Windows smoke-test notes and the follow-up usability thread into one current status update. What changed after the previous runThe earlier comments were based on the intermediate design where The PR has since been updated so both modes use persistent Aspire-managed browser data hives instead:
That means the default The latest review feedback was also addressed:
Latest validation
|
Both BrowserUserDataMode.Shared and Isolated now use Aspire-managed persistent user data directories under %LocalAppData%\Aspire\BrowserData (Windows) or platform equivalents. Shared is machine-wide (per-browser); Isolated is keyed on AppHost:PathSha256. - Shared no longer points at the user's real Chromium profile; it uses ...\BrowserData\shared\<browser>. - Isolated uses ...\BrowserData\isolated\<appHostShaPrefix>\<browser>; replaces the previous temp-dir-per-session model. - Cross-process CDP adoption now runs for both modes via the existing aspire-debug-endpoint.json sidecar. - The browser process is no longer killed when the AppHost shuts down; it lives until the user closes it. - BrowserConfiguration carries the AppHost path SHA, captured once at Resolve time. - Removed dead helpers: IsNonDebuggableBrowserRunning, IsGoogleChromeDefaultUserDataDirectory, TryResolveUserDataDirectory and their resx strings/tests. Co-authored-by: Copilot <[email protected]>
JamesNK
left a comment
There was a problem hiding this comment.
3 issues found (all in the dispose/cleanup path): 2 resource leaks from unhandled exceptions in dispose sequences, 1 timeout mismatch between lease release and lock hold time. All are edge-case correctness issues in the new browser host lifecycle management.
- Wrap CleanupAsync lease release + _stopCts.Dispose in try/finally so the CTS is always disposed even when BrowserHostLease times out (JamesNK) - Wrap BrowserLogsSessionManager.DisposeAsync StopAsync loop in try/catch and move lock+factory disposal into finally so one failing session does not strand others (JamesNK) - Increase BrowserHostLease release timeout from 5s to 60s to exceed worst-case browser startup (30s) held under the registry lock, and swallow OperationCanceledException at the lease boundary so disposal never throws (JamesNK) - Comment timeout/poll values in BrowserHost.WaitForBrowserEndpointAsync and add an entry trace log for cold-start visibility (JamesNK) - Comment Local State magic string and case-insensitive profile match in BrowserHostRegistry (JamesNK) Co-authored-by: Copilot <[email protected]>
|
🎬 CLI E2E Test Recordings — 76 recordings uploaded (commit View all recordings
📹 Recordings uploaded automatically from CI run #24976401472 |
…ataMode Documents the browser logs AppHost feature introduced in microsoft/aspire#16310 and enhanced with BrowserUserDataMode in microsoft/aspire#16457. - New page: app-host/browser-logs.mdx - Explains WithBrowserLogs and the child browser logs resource - Documents Shared vs Isolated BrowserUserDataMode - Covers browser selection (msedge, chrome, explicit path) - Shows configuration via Aspire:Hosting:BrowserLogs settings - Includes both C# and TypeScript AppHost code examples - Adds sidebar entry under AppHost > Resource types Co-authored-by: Copilot <[email protected]>
|
Pull request created: #753
|
|
A draft documentation PR has been opened on microsoft/aspire.dev to cover the Target branch: What was documented (
Note 🔒 Integrity filter blocked 4 itemsThe following items were blocked because they don't meet the GitHub integrity level.
To allow these resources, lower tools:
github:
min-integrity: approved # merged | approved | unapproved | none
|
…ataMode Documents the browser logs AppHost feature introduced in microsoft/aspire#16310 and enhanced with BrowserUserDataMode in microsoft/aspire#16457. - New page: app-host/browser-logs.mdx - Explains WithBrowserLogs and the child browser logs resource - Documents Shared vs Isolated BrowserUserDataMode - Covers browser selection (msedge, chrome, explicit path) - Shows configuration via Aspire:Hosting:BrowserLogs settings - Includes both C# and TypeScript AppHost code examples - Adds sidebar entry under AppHost > Resource types Co-authored-by: Copilot <[email protected]>
Description
Follow-up to #16310 (tracked browser logs). This PR separates "which Chromium user data directory to use" from "which profile inside that directory to use", then finishes the shared/adopted browser-host path so browser logs can keep state across AppHost runs and stream browser logs back to the dashboard.
The latest design uses persistent Aspire-managed browser data hives for both modes. Aspire no longer points at the user's real Edge/Chrome profile, and
Isolatedis no longer temp-per-session.What's new
BrowserUserDataMode:Shared(default): use a persistent Aspire-managed user data directory shared across all AppHosts on the machine for the selected browser, for example%LocalAppData%\Aspire\BrowserData\shared\<browser>on Windows.Isolated: use a persistent Aspire-managed user data directory scoped to the current AppHost project, for example%LocalAppData%\Aspire\BrowserData\isolated\<AppHost:PathSha256>\<browser>on Windows.WithBrowserLogs(..., userDataMode: ...)and configuration viaAspire:Hosting:BrowserLogs[:{ResourceName}]:UserDataMode.BrowserConfiguration, with explicit builder overrides grouped inBrowserConfigurationOverrides.BrowserUserDataPathResolverfor the Aspire-managed hive layout andChromiumBrowserResolverfor OS/browser executable and profile directory resolution.profileas a profile directory selection inside the shared browser hive;profilewithIsolatedis rejected.BrowserUserDataModeand the newwithBrowserLogsargument.Shared/adopted browser architecture
BrowserHostRegistry, keyed by normalized(browser executable, user data root), with exactly-onceBrowserHostLeaserelease.--remote-debugging-address=127.0.0.1and--remote-debugging-port=0, write endpoint metadata, and leave the browser process running when the AppHost exits.aspire-debug-endpoint.jsondiscovery/validation. Metadata is treated as a hint, not truth: schema, executable path, user data root, PID liveness, profile compatibility, and/json/versionare validated before adoption; stale metadata is deleted.Target.closeTargetonly.Browser.closepath so shared/adopted browser sessions cannot accidentally close the browser.Target.targetDestroyed,Target.targetCrashed,Target.detachedFromTarget, andInspector.detached, using the event payload (params.targetId/params.sessionId) for correlation.Last errorclearing, endpoint metadata serialization, and adopted-browser resource diagnostics.Behavior
UserDataModeprofileSharedShared--profile-directoryIsolatedIsolatedReliability notes
DevToolsActivePortand logs trace diagnostics while waiting for a fresh endpoint file.DevToolsActivePortand Aspire endpoint metadata are not reused blindly, and endpoint metadata preserves non-ASCII paths/profile names.paramsare ignored consistently.Last errorproperty and unhealthy health report so the dashboard keeps the diagnostic visible even when another tracked browser session is still running._stopCtsis always disposed.Validation
./restore.shdotnet test tests/Aspire.Hosting.Tests/Aspire.Hosting.Tests.csproj -- --filter-class "*.BrowserLogsCdpProtocolTests" --filter-class "*.BrowserLogsCdpConnectionTests" --filter-class "*.BrowserConnectionDiagnosticsLoggerTests" --filter-class "*.BrowserPageSessionTests" --filter-class "*.BrowserLogsRunningSessionTests" --filter-class "*.ChromiumDevToolsActivePortParserTests" --filter-class "*.BrowserLogsSessionManagerTests" --filter-class "*.BrowserLogsBuilderExtensionsTests" --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true"— 69 passed after adding baseline page-session, deterministic reconnect, running-session glue, adoption, and in-memory CDP connection coverage.BrowserLogsCdpConnectiontest 20 times.BrowserLogsRunningSessionglue test 20 times.dotnet build src/Aspire.Hosting/Aspire.Hosting.csproj /p:TreatWarningsAsErrors=falsedotnet testforTwoPassScanning_GeneratesWithEnvironmentOnTestRedisBuilderin TypeScript, Go, Python, Java, and Rust code-generation test projects — 5 passed.git diff --checkfb3fd5c0b:playground/BrowserTelemetrywithaspire start --isolated.open-tracked-browsercreated anOwnedEdge session and dashboard console logs showed browser console + network entries with markerbrowserlogs-e2e-1777265425.open-tracked-browserusedBrowser host ownership: Adopted, reused the same browser CDP endpoint, and dashboard console logs showed adopted-session console + network entries with markerbrowserlogs-adopt-e2e-1777265512.Fixes # (issue)
Checklist
<remarks />and<code />elements on your triple slash comments?aspire.devissue: