| File: BrowserHostTests.cs | Web Access |
| Project: src\tests\Aspire.Hosting.Browsers.Tests\Aspire.Hosting.Browsers.Tests.csproj (Aspire.Hosting.Browsers.Tests) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. #pragma warning disable ASPIREFILESYSTEM001 // Type is for evaluation purposes only using System.IO.Pipelines; using System.Text; using System.Text.Json; using Microsoft.AspNetCore.InternalTesting; using Microsoft.Extensions.Logging.Abstractions; namespace Aspire.Hosting.Browsers.Tests; [Trait("Partition", "2")] public class BrowserHostTests { [Fact] public async Task OwnedBrowserHost_StartAsync_UsesPipeTransportAndDeletesEndpointMetadata() { var userDataDirectory = Directory.CreateTempSubdirectory(); try { var browserExecutable = Path.Combine(userDataDirectory.FullName, "browser"); await File.WriteAllTextAsync(browserExecutable, string.Empty); var devToolsActivePortPath = Path.Combine(userDataDirectory.FullName, "DevToolsActivePort"); await File.WriteAllTextAsync(devToolsActivePortPath, "12345\n/devtools/browser/stale"); var identity = new BrowserHostIdentity(browserExecutable, userDataDirectory.FullName); await BrowserEndpointDiscovery.WriteAsync( identity, profileDirectoryName: "Profile 1", new Uri("ws://127.0.0.1:9/devtools/browser/stale"), Environment.ProcessId, CancellationToken.None); FakePipeBrowserProcess? fakeProcess = null; IReadOnlyList<string>? capturedArguments = null; var host = await OwnedBrowserHost.StartAsync( identity, browserDisplayName: "Test Browser", BrowserLogsUserDataDirectory.CreatePersistent(userDataDirectory.FullName, profileDirectoryName: "Profile 1"), NullLogger<BrowserLogsSessionManager>.Instance, TimeProvider.System, CancellationToken.None, startPipeBrowserProcess: (executablePath, arguments) => { Assert.Equal(browserExecutable, executablePath); capturedArguments = [.. arguments]; fakeProcess = new FakePipeBrowserProcess(); return fakeProcess; }); try { Assert.Null(host.DebugEndpoint); Assert.Equal(4242, host.ProcessId); Assert.False(File.Exists(devToolsActivePortPath)); Assert.False(File.Exists(BrowserEndpointDiscovery.GetEndpointMetadataFilePath(userDataDirectory.FullName))); Assert.NotNull(capturedArguments); Assert.Contains($"--user-data-dir={userDataDirectory.FullName}", capturedArguments); Assert.Contains("--profile-directory=Profile 1", capturedArguments); Assert.Contains("about:blank", capturedArguments); Assert.DoesNotContain(capturedArguments, static argument => argument.StartsWith("--remote-debugging-address=", StringComparison.Ordinal)); Assert.DoesNotContain(capturedArguments, static argument => argument.StartsWith("--remote-debugging-port=", StringComparison.Ordinal)); await using var connection = await host.CreateCdpConnectionAsync( static _ => ValueTask.CompletedTask, NullLogger<BrowserLogsSessionManager>.Instance, CancellationToken.None); var enableDiscoveryTask = connection.EnableTargetDiscoveryAsync(CancellationToken.None); using var command = JsonDocument.Parse(await fakeProcess!.ReadFrameAsync().DefaultTimeout()); Assert.Equal(BrowserLogsCdpProtocol.TargetSetDiscoverTargetsMethod, command.RootElement.GetProperty("method").GetString()); var responseFrame = "{\"id\":" + command.RootElement.GetProperty("id").GetInt64() + ",\"result\":{}}"; await fakeProcess.SendFrameAsync(responseFrame).DefaultTimeout(); await enableDiscoveryTask.DefaultTimeout(); } finally { await host.DisposeAsync(); } Assert.True(fakeProcess?.Disposed is true); } finally { userDataDirectory.Delete(recursive: true); } } private sealed class FakePipeBrowserProcess : IBrowserLogsPipeBrowserProcess { private readonly Pipe _appToBrowser = new(); private readonly Stream _browserInput; private readonly Stream _browserOutput; private readonly Stream _browserRead; private readonly Stream _browserWrite; private readonly Pipe _browserToApp = new(); private readonly TaskCompletionSource<BrowserLogsProcessResult> _processCompletion = new(TaskCreationOptions.RunContinuationsAsynchronously); public FakePipeBrowserProcess() { _browserInput = _appToBrowser.Writer.AsStream(); _browserRead = _appToBrowser.Reader.AsStream(); _browserOutput = _browserToApp.Reader.AsStream(); _browserWrite = _browserToApp.Writer.AsStream(); } public int ProcessId => 4242; public Stream BrowserOutput => _browserOutput; public Stream BrowserInput => _browserInput; public Task<BrowserLogsProcessResult> ProcessTask => _processCompletion.Task; public bool Disposed { get; private set; } public async Task<byte[]> ReadFrameAsync() { using var frame = new MemoryStream(); var oneByte = new byte[1]; while (true) { var read = await _browserRead.ReadAsync(oneByte); if (read == 0) { throw new EndOfStreamException("The host pipe closed before a CDP frame was written."); } if (oneByte[0] == 0) { return frame.ToArray(); } frame.WriteByte(oneByte[0]); } } public async Task SendFrameAsync(string frame) { await _browserWrite.WriteAsync(Encoding.UTF8.GetBytes(frame)); await _browserWrite.WriteAsync(new byte[] { 0 }); await _browserWrite.FlushAsync(); } public async ValueTask DisposeAsync() { if (Disposed) { return; } Disposed = true; _processCompletion.TrySetResult(new BrowserLogsProcessResult(0)); await _browserInput.DisposeAsync(); await _browserOutput.DisposeAsync(); await _browserRead.DisposeAsync(); await _browserWrite.DisposeAsync(); } } }