Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
016dbfb
Add BrowserUserDataMode to separate user data dir from profile
davidfowl Apr 25, 2026
1cdce4e
Make Shared the default user data mode and document singleton caveat
davidfowl Apr 25, 2026
b00e33a
Add Target lifecycle and Inspector.detached protocol parsing
davidfowl Apr 25, 2026
1fb3f58
Add IBrowserHost abstraction with ownership distinction
davidfowl Apr 25, 2026
71f2f07
Apply review feedback to BrowserHostIdentity and document Target even…
davidfowl Apr 25, 2026
e5d4fc4
Harden browser logs foundation for shared browser adoption
davidfowl Apr 25, 2026
e6a1f56
Support shared browser host adoption for browser logs
davidfowl Apr 25, 2026
bbcaa81
Improve browser logs diagnostics
davidfowl Apr 25, 2026
11d5f9b
Update browser logs codegen snapshots
davidfowl Apr 25, 2026
64169bf
Add browser logs component tests
davidfowl Apr 25, 2026
ac96567
Reuse browser endpoint probe HttpClient
davidfowl Apr 25, 2026
ef381b0
Use typed browser endpoint probe response
davidfowl Apr 25, 2026
3f259b1
Remove redundant adopted browser disposal guard
davidfowl Apr 25, 2026
fba86ac
Document browser host registry decisions
davidfowl Apr 25, 2026
ba981ec
Add browser observation notes to registry comments
davidfowl Apr 25, 2026
46edb61
Document browser log runtime behavior
davidfowl Apr 25, 2026
07c9794
Name browser logs timeout values
davidfowl Apr 25, 2026
c812643
Rename browser logs CDP transport types
davidfowl Apr 25, 2026
adb4bbe
Document browser endpoint discovery flow
davidfowl Apr 25, 2026
8b664f4
Link Chromium singleton documentation
davidfowl Apr 25, 2026
57baf51
Clarify browser page session model
davidfowl Apr 25, 2026
ef4423d
Dispose browser host registry lock
davidfowl Apr 25, 2026
cb4bada
Move browser logs configuration resolution
davidfowl Apr 25, 2026
960ef62
Clean up browser configuration state
davidfowl Apr 25, 2026
d39fc3a
Move Chromium browser resolution helpers
davidfowl Apr 26, 2026
5a50201
Document browser logs JSON formats
davidfowl Apr 26, 2026
57cea6f
Localize browser logs failure messages
davidfowl Apr 26, 2026
ce82bc8
Move browser launch helpers out of running session
davidfowl Apr 26, 2026
37573bb
Clean up browser logs helper ownership
davidfowl Apr 26, 2026
7de3f07
Address browser logs review feedback
davidfowl Apr 26, 2026
16bf244
Add browser logs baseline coverage
davidfowl Apr 26, 2026
92ae583
Test browser logs CDP connection
davidfowl Apr 26, 2026
1e0574b
Test browser logs running session glue
davidfowl Apr 26, 2026
faa9cbb
Test browser page session reconnect
davidfowl Apr 26, 2026
2a0694b
Redefine Shared/Isolated as Aspire-managed persistent profiles
davidfowl Apr 27, 2026
fb3fd5c
Address PR review feedback
davidfowl Apr 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 69 additions & 12 deletions src/Aspire.Hosting/BrowserLogs/BrowserLogsBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ public static class BrowserLogsBuilderExtensions
internal const string BrowserExecutablePropertyName = "Browser executable";
internal const string ProfileConfigurationKey = "Profile";
internal const string ProfilePropertyName = "Profile";
internal const string UserDataModeConfigurationKey = "UserDataMode";
internal const string UserDataModePropertyName = "User data mode";
internal const BrowserUserDataMode DefaultUserDataMode = BrowserUserDataMode.Shared;
internal const string TargetUrlPropertyName = "Target URL";
internal const string ActiveSessionsPropertyName = "Active sessions";
internal const string BrowserSessionsPropertyName = "Browser sessions";
Expand All @@ -44,8 +47,16 @@ public static class BrowserLogsBuilderExtensions
/// browser names such as <c>"msedge"</c> and <c>"chrome"</c>, or an explicit browser executable path.
/// </param>
/// <param name="profile">
/// Optional Chromium profile directory name to use. When not specified, the tracked browser uses the configured
/// value from <c>Aspire:Hosting:BrowserLogs</c> if present.
/// Optional Chromium profile name or directory name to use. Only valid when the effective user data mode
/// is <see cref="BrowserUserDataMode.Shared"/>. When not specified, the tracked browser uses the
/// configured value from <c>Aspire:Hosting:BrowserLogs</c> if present.
/// </param>
/// <param name="userDataMode">
/// Optional <see cref="BrowserUserDataMode"/> that selects whether the tracked browser launches against
/// the browser's real user data directory (<see cref="BrowserUserDataMode.Shared"/>, the default) or a
/// temporary user data directory (<see cref="BrowserUserDataMode.Isolated"/>). When not specified, the
/// tracked browser uses the configured value from <c>Aspire:Hosting:BrowserLogs</c> and otherwise
/// defaults to <see cref="BrowserUserDataMode.Shared"/>.
/// </param>
/// <returns>A reference to the original <see cref="IResourceBuilder{T}"/> for further chaining.</returns>
/// <remarks>
Expand All @@ -64,10 +75,13 @@ public static class BrowserLogsBuilderExtensions
/// endpoints when selecting the browser target URL.
/// </para>
/// <para>
/// Browser and profile settings can also be supplied from configuration using
/// <c>Aspire:Hosting:BrowserLogs:Browser</c> and <c>Aspire:Hosting:BrowserLogs:Profile</c>, or scoped to a
/// specific resource with <c>Aspire:Hosting:BrowserLogs:{ResourceName}:Browser</c> and
/// <c>Aspire:Hosting:BrowserLogs:{ResourceName}:Profile</c>. Explicit method arguments override configuration.
/// Browser, profile, and user data mode settings can also be supplied from configuration using
/// <c>Aspire:Hosting:BrowserLogs:Browser</c>, <c>Aspire:Hosting:BrowserLogs:Profile</c>, and
/// <c>Aspire:Hosting:BrowserLogs:UserDataMode</c>, or scoped to a specific resource with
/// <c>Aspire:Hosting:BrowserLogs:{ResourceName}:Browser</c>,
/// <c>Aspire:Hosting:BrowserLogs:{ResourceName}:Profile</c>, and
/// <c>Aspire:Hosting:BrowserLogs:{ResourceName}:UserDataMode</c>. Explicit method arguments override
/// configuration.
/// </para>
/// </remarks>
/// <example>
Expand All @@ -82,7 +96,11 @@ public static class BrowserLogsBuilderExtensions
/// </example>
[Experimental("ASPIREBROWSERLOGS001", UrlFormat = "https://aka.ms/aspire/diagnostics/{0}")]
[AspireExport(Description = "Adds a child browser logs resource that opens tracked browser sessions and captures browser logs.")]
public static IResourceBuilder<T> WithBrowserLogs<T>(this IResourceBuilder<T> builder, string? browser = null, string? profile = null)
public static IResourceBuilder<T> WithBrowserLogs<T>(
this IResourceBuilder<T> builder,
string? browser = null,
string? profile = null,
BrowserUserDataMode? userDataMode = null)
Comment thread
davidfowl marked this conversation as resolved.
where T : IResourceWithEndpoints
{
ArgumentNullException.ThrowIfNull(builder);
Expand All @@ -92,8 +110,14 @@ public static IResourceBuilder<T> WithBrowserLogs<T>(this IResourceBuilder<T> bu
builder.ApplicationBuilder.Services.TryAddSingleton<IBrowserLogsSessionManager, BrowserLogsSessionManager>();

var parentResource = builder.Resource;
var settings = ResolveSettings(builder.ApplicationBuilder.Configuration, parentResource.Name, browser, profile);
var browserLogsResource = new BrowserLogsResource($"{parentResource.Name}-browser-logs", parentResource, settings, browser, profile);
var settings = ResolveSettings(builder.ApplicationBuilder.Configuration, parentResource.Name, browser, profile, userDataMode);
var browserLogsResource = new BrowserLogsResource(
$"{parentResource.Name}-browser-logs",
parentResource,
settings,
browser,
profile,
userDataMode);
browserLogsResource.Annotations.Add(NameValidationPolicyAnnotation.None);

builder.ApplicationBuilder.AddResource(browserLogsResource)
Expand Down Expand Up @@ -171,7 +195,8 @@ static ImmutableArray<ResourcePropertySnapshot> CreateInitialProperties(string r
List<ResourcePropertySnapshot> properties =
[
new(CustomResourceKnownProperties.Source, resourceName),
new(BrowserPropertyName, settings.Browser)
new(BrowserPropertyName, settings.Browser),
new(UserDataModePropertyName, settings.UserDataMode.ToString())
];

if (settings.Profile is { } profile)
Expand Down Expand Up @@ -222,7 +247,12 @@ static void ThrowIfBlankWhenSpecified(string? value, string paramName)
}
}

internal static BrowserLogsSettings ResolveSettings(IConfiguration configuration, string resourceName, string? browser, string? profile)
internal static BrowserLogsSettings ResolveSettings(
IConfiguration configuration,
string resourceName,
string? browser,
string? profile,
BrowserUserDataMode? userDataMode)
{
var browserLogsSection = configuration.GetSection(BrowserLogsConfigurationSectionName);
var resourceSection = browserLogsSection.GetSection(resourceName);
Expand All @@ -234,6 +264,10 @@ internal static BrowserLogsSettings ResolveSettings(IConfiguration configuration
var resolvedProfile = profile
?? resourceSection[ProfileConfigurationKey]
?? browserLogsSection[ProfileConfigurationKey];
var resolvedUserDataMode = userDataMode
?? ParseUserDataMode(resourceSection[UserDataModeConfigurationKey])
?? ParseUserDataMode(browserLogsSection[UserDataModeConfigurationKey])
?? DefaultUserDataMode;

if (string.IsNullOrWhiteSpace(resolvedBrowser))
{
Expand All @@ -245,7 +279,30 @@ internal static BrowserLogsSettings ResolveSettings(IConfiguration configuration
throw new InvalidOperationException("Tracked browser configuration resolved an empty profile value.");
}

return new BrowserLogsSettings(resolvedBrowser, resolvedProfile);
if (resolvedUserDataMode == BrowserUserDataMode.Isolated && resolvedProfile is not null)
{
throw new InvalidOperationException(
$"Tracked browser configuration set '{ProfileConfigurationKey}' to '{resolvedProfile}' while '{UserDataModeConfigurationKey}' is '{BrowserUserDataMode.Isolated}'. " +
$"Profiles can only be selected when '{UserDataModeConfigurationKey}' is '{BrowserUserDataMode.Shared}'.");
}

return new BrowserLogsSettings(resolvedBrowser, resolvedProfile, resolvedUserDataMode);
}

private static BrowserUserDataMode? ParseUserDataMode(string? value)
{
if (string.IsNullOrWhiteSpace(value))
{
return null;
}

if (Enum.TryParse<BrowserUserDataMode>(value, ignoreCase: true, out var parsed))
{
return parsed;
}

throw new InvalidOperationException(
$"Tracked browser configuration value '{value}' is not a valid '{UserDataModeConfigurationKey}'. Expected '{BrowserUserDataMode.Shared}' or '{BrowserUserDataMode.Isolated}'.");
}

internal static string GetDefaultBrowser(Func<string, string?> resolveBrowserExecutable)
Expand Down
37 changes: 34 additions & 3 deletions src/Aspire.Hosting/BrowserLogs/BrowserLogsResource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,41 @@

namespace Aspire.Hosting;

internal readonly record struct BrowserLogsSettings(string Browser, string? Profile);
/// <summary>
/// Selects the Chromium user data directory used by tracked browser sessions.
/// </summary>
public enum BrowserUserDataMode
{
/// <summary>
/// Use the browser's real user data directory so the tracked session reuses real cookies, sessions,
/// extensions, and profile selection. Behaves like clicking the browser icon.
/// </summary>
/// <remarks>
/// NOTE: When the target browser is already running with the same user data directory, Chromium will
/// typically forward the launch to the existing instance and exit the new process. The tracked session
/// relies on <c>--remote-debugging-port</c> and the <c>DevToolsActivePort</c> file written by the
/// launched process; if launching forwards to an existing browser, the DevTools endpoint may not be
/// discoverable and the session will fail to start. Users must close existing browser windows for the
/// selected user data directory before starting a tracked session in this mode.
/// </remarks>
Comment thread
davidfowl marked this conversation as resolved.
Outdated
Shared,

/// <summary>
/// Launch the tracked browser against a temporary user data directory so the session starts from clean
/// state and does not affect the user's normal browser profiles.
/// </summary>
Isolated,
}

internal readonly record struct BrowserLogsSettings(string Browser, string? Profile, BrowserUserDataMode UserDataMode);

internal sealed class BrowserLogsResource(
string name,
IResourceWithEndpoints parentResource,
BrowserLogsSettings initialSettings,
string? browserOverride,
string? profileOverride)
string? profileOverride,
BrowserUserDataMode? userDataModeOverride)
: Resource(name)
{
public IResourceWithEndpoints ParentResource { get; } = parentResource;
Expand All @@ -22,10 +49,14 @@ internal sealed class BrowserLogsResource(

public string? Profile { get; } = initialSettings.Profile;

public BrowserUserDataMode UserDataMode { get; } = initialSettings.UserDataMode;

public string? BrowserOverride { get; } = browserOverride;

public string? ProfileOverride { get; } = profileOverride;

public BrowserUserDataMode? UserDataModeOverride { get; } = userDataModeOverride;

public BrowserLogsSettings ResolveCurrentSettings(IConfiguration configuration) =>
BrowserLogsBuilderExtensions.ResolveSettings(configuration, ParentResource.Name, BrowserOverride, ProfileOverride);
BrowserLogsBuilderExtensions.ResolveSettings(configuration, ParentResource.Name, BrowserOverride, ProfileOverride, UserDataModeOverride);
}
Loading
Loading