microsoft/aspire · error · InvalidOperationException

BrowserMessageStrings.BrowserLogsUnableToLocateBrowser

Error message

BrowserMessageStrings.BrowserLogsUnableToLocateBrowser

What it means

BrowserHostRegistry.AcquireAsync throws this before launching anything when ChromiumBrowserResolver.TryResolveExecutable cannot find a Chromium-family browser executable matching configuration.Browser. A tracked browser host needs a real browser binary; without one the lease cannot be created. It surfaces as an InvalidOperationException built from a localized resource string.

Solutions

  1. Install a supported Chromium-based browser (Chrome, Edge, or Chromium) on the machine.
  2. Verify the browser is discoverable: on PATH or in standard install locations for the OS.
  3. Request a Browser value that matches a browser actually installed (e.g. fall back from Chrome to Edge).
  4. Add browser installation steps to CI container images or setup scripts.

Example fix

// before
var config = new BrowserConfiguration { Browser = SupportedBrowser.Chrome }; // Chrome not installed
var lease = await registry.AcquireAsync(config, ct);

// after
var browser = ChromiumBrowserResolver.CanResolve(SupportedBrowser.Chrome)
    ? SupportedBrowser.Chrome
    : SupportedBrowser.Edge; // install or select an available browser
Defensive patterns

Strategy: validation

Validate before calling

// Verify a Chromium browser is available before acquiring
static bool HasChromiumBrowser() =>
    OperatingSystem.IsWindows() && (File.Exists(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Google/Chrome/Application/chrome.exe")) || File.Exists(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Microsoft/Edge/Application/msedge.exe")))
    || OperatingSystem.IsLinux() && (File.Exists("/usr/bin/google-chrome") || File.Exists("/usr/bin/chromium") || File.Exists("/usr/bin/microsoft-edge"))
    || OperatingSystem.IsMacOS() && Directory.Exists("/Applications/Google Chrome.app");

Try / catch

try { lease = await registry.AcquireAsync(config, ct); }
catch (InvalidOperationException ex) when (ex.Message.Contains("browser", StringComparison.OrdinalIgnoreCase)) { logger.LogWarning(ex, "No supported Chromium browser found; skipping browser logs."); }

Prevention

When it happens

Trigger: Calling AcquireAsync (directly or via lease acquisition) with a BrowserConfiguration whose Browser value resolves to no installed executable: browser not installed, installed at a non-standard path, or running in a container/CI image without any Chromium browser.

Common situations: Fresh dev machine with no Chrome/Edge; slim Linux containers or GitHub Actions runners lacking browsers; user selects a specific browser type that is not installed; custom browser install location not on PATH or standard registry locations.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16). Data as JSON: /api/errors/1a996225fc9d02b9. Report an issue: GitHub.

Appendix: source

Thrown at src/Aspire.Hosting.Browsers/BrowserHostRegistry.cs:57

        TimeProvider timeProvider,
        Func<BrowserConfiguration, string, BrowserLogsUserDataDirectory>? createUserDataDirectory,
        Func<BrowserConfiguration, BrowserHostIdentity, BrowserLogsUserDataDirectory, CancellationToken, Task<IBrowserHost>>? createHostAsync,
        bool enableEndpointMetadataAdoption = false)
    {
        _endpointDiscovery = new BrowserEndpointDiscovery(logger);
        _createUserDataDirectory = createUserDataDirectory ?? CreateUserDataDirectory;
        _createHostAsync = createHostAsync ?? CreateHostCoreAsync;
        _enableEndpointMetadataAdoption = enableEndpointMetadataAdoption;
        _logger = logger;
        _timeProvider = timeProvider;
    }

    public async Task<BrowserHostLease> AcquireAsync(BrowserConfiguration configuration, CancellationToken cancellationToken)
    {
        ObjectDisposedException.ThrowIf(Volatile.Read(ref _disposed) != 0, this);

        var browserExecutable = ChromiumBrowserResolver.TryResolveExecutable(configuration.Browser)
            ?? throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, BrowserMessageStrings.BrowserLogsUnableToLocateBrowser, configuration.Browser));
        var userDataDirectory = _createUserDataDirectory(configuration, browserExecutable);
        var identity = new BrowserHostIdentity(browserExecutable, userDataDirectory.Path);

        // The core AcquireAsync flow has to make one atomic decision per browser identity:
        //
        // 1. If the registry already has a host for this executable + user data root, reuse it and increment the lease
        //    count.
        // 2. Otherwise, create a host exactly once and publish it into the registry with the first lease.
        //
        // Keep the lock held across CreateHostCoreAsync. That method starts a new process by default, and can adopt a
        // WebSocket endpoint when an explicit attach mode enables endpoint metadata. If two callers ran that decision
        // concurrently they could both miss the dictionary entry and race to adopt/start a browser for the same profile.
        var lockAcquired = false;
        var hostPublished = false;
        try
        {
            lockAcquired = await TryWaitForLockAsync(cancellationToken).ConfigureAwait(false);
            ObjectDisposedException.ThrowIf(!lockAcquired, this);

View on GitHub (pinned to 25830f84bd)