| File: BrowserLogsRunningSessionTests.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 #pragma warning disable ASPIREBROWSERLOGS001 // Type is for evaluation purposes only using Aspire.Hosting.Tests.Utils; using Microsoft.AspNetCore.InternalTesting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; namespace Aspire.Hosting.Browsers.Tests; [Trait("Partition", "2")] public class BrowserLogsRunningSessionTests { [Fact] public async Task RunningSessionRoutesPageEventsToResourceLogsAndReleasesHostOnCompletion() { var userDataDirectory = Directory.CreateTempSubdirectory(); try { var browserExecutable = Path.Combine(userDataDirectory.FullName, "browser"); File.WriteAllText(browserExecutable, string.Empty); TestBrowserHost? host = null; await using var registry = new BrowserHostRegistry( NullLogger<BrowserLogsSessionManager>.Instance, TimeProvider.System, createUserDataDirectory: (configuration, _) => BrowserLogsUserDataDirectory.CreatePersistent(userDataDirectory.FullName, configuration.Profile), createHostAsync: (configuration, identity, _, _) => { host = new TestBrowserHost(identity); return Task.FromResult<IBrowserHost>(host); }); var resourceLoggerService = ConsoleLoggingTestHelpers.GetResourceLoggerService(); var resourceName = "web-browser-logs"; BrowserLogsRunningSession? session = null; var logs = await ConsoleLoggingTestHelpers.CaptureLogsAsync(resourceLoggerService, resourceName, targetLogCount: 5, () => { session = BrowserLogsRunningSession.StartAsync( new BrowserConfiguration(browserExecutable, Profile: null, BrowserUserDataMode.Shared, AppHostKey: null), resourceName, "session-0001", new Uri("https://localhost:5001/"), registry, resourceLoggerService.GetLogger(resourceName), NullLogger<BrowserLogsSessionManager>.Instance, TimeProvider.System, CancellationToken.None).GetAwaiter().GetResult(); host!.PageSession!.RaiseEventAsync(new BrowserLogsConsoleApiCalledEvent( SessionId: "target-session-1", new BrowserLogsRuntimeConsoleApiCalledParameters { Type = "log", Args = [ new BrowserLogsCdpProtocolRemoteObject { Value = new BrowserLogsCdpProtocolStringValue("hello from tracked browser") } ] })).AsTask().GetAwaiter().GetResult(); }); Assert.NotNull(session); Assert.NotNull(host); Assert.Equal(browserExecutable, session.BrowserExecutable); Assert.Equal(host.DebugEndpoint, session.BrowserDebugEndpoint); Assert.Equal(BrowserHostOwnership.Owned, session.BrowserHostOwnership); Assert.Equal(42, session.ProcessId); Assert.Equal("target-1", session.TargetId); Assert.Equal("session-0001", host.PageSession?.SessionId); Assert.Equal(new Uri("https://localhost:5001/"), host.PageSession?.Url); Assert.Contains(logs, log => log.Content.EndsWith("[session-0001] [console.log] hello from tracked browser", StringComparison.Ordinal)); var completed = new TaskCompletionSource<(int? ExitCode, Exception? Error)>(TaskCreationOptions.RunContinuationsAsynchronously); var observerTask = session.StartCompletionObserver((exitCode, error) => { completed.TrySetResult((exitCode, error)); return Task.CompletedTask; }); host.PageSession!.Complete(new BrowserPageSessionResult(BrowserPageSessionCompletionKind.PageClosed, Error: null)); var result = await completed.Task.DefaultTimeout(); await observerTask.DefaultTimeout(); Assert.Null(result.ExitCode); Assert.Null(result.Error); Assert.True(host.Disposed); Assert.Equal(1, host.PageSession.DisposeCount); } finally { userDataDirectory.Delete(recursive: true); } } private sealed class TestBrowserHost(BrowserHostIdentity identity) : IBrowserHost { private readonly TaskCompletionSource _terminationSource = new(TaskCreationOptions.RunContinuationsAsynchronously); public BrowserHostIdentity Identity { get; } = identity; public BrowserHostOwnership Ownership => BrowserHostOwnership.Owned; public Uri DebugEndpoint { get; } = new("ws://127.0.0.1/devtools/browser/test"); public int? ProcessId => 42; public string BrowserDisplayName => "Test Browser"; public Task Termination => _terminationSource.Task; public bool Disposed { get; private set; } public TestBrowserPageSession? PageSession { get; private set; } public Task<IBrowserLogsCdpConnection> CreateCdpConnectionAsync( Func<BrowserLogsCdpProtocolEvent, ValueTask> eventHandler, ILogger<BrowserLogsSessionManager> logger, CancellationToken cancellationToken) => throw new NotSupportedException(); public Task<IBrowserPageSession> CreatePageSessionAsync( string sessionId, Uri url, BrowserConnectionDiagnosticsLogger connectionDiagnostics, Func<BrowserLogsCdpProtocolEvent, ValueTask> eventHandler, CancellationToken cancellationToken) { PageSession = new TestBrowserPageSession(sessionId, url, eventHandler); return Task.FromResult<IBrowserPageSession>(PageSession); } public ValueTask DisposeAsync() { Disposed = true; _terminationSource.TrySetResult(); return ValueTask.CompletedTask; } } private sealed class TestBrowserPageSession( string sessionId, Uri url, Func<BrowserLogsCdpProtocolEvent, ValueTask> eventHandler) : IBrowserPageSession { private readonly TaskCompletionSource<BrowserPageSessionResult> _completionSource = new(TaskCreationOptions.RunContinuationsAsynchronously); public string SessionId { get; } = sessionId; public Uri Url { get; } = url; public string TargetId => "target-1"; public string TargetSessionId => "target-session-1"; public Task<BrowserPageSessionResult> Completion => _completionSource.Task; public int DisposeCount { get; private set; } public Task<BrowserLogsCaptureScreenshotResult> CaptureScreenshotAsync(CancellationToken cancellationToken) { return Task.FromResult(new BrowserLogsCaptureScreenshotResult { Data = Convert.ToBase64String([0x89, 0x50, 0x4e, 0x47]) }); } public ValueTask RaiseEventAsync(BrowserLogsCdpProtocolEvent protocolEvent) { return eventHandler(protocolEvent); } public void Complete(BrowserPageSessionResult result) { _completionSource.TrySetResult(result); } public ValueTask DisposeAsync() { DisposeCount++; return ValueTask.CompletedTask; } } }