File: BannerTests.cs
Web Access
Project: src\tests\Aspire.Cli.EndToEnd.Tests\Aspire.Cli.EndToEnd.Tests.csproj (Aspire.Cli.EndToEnd.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Aspire.Cli.EndToEnd.Tests.Helpers;
using Aspire.Cli.Tests.Utils;
using Hex1b;
using Hex1b.Automation;
using Xunit;
 
namespace Aspire.Cli.EndToEnd.Tests;
 
/// <summary>
/// End-to-end tests for Aspire CLI banner display functionality.
/// These tests verify that the banner appears on first run and when explicitly requested.
/// </summary>
public sealed class BannerTests(ITestOutputHelper output)
{
    [Fact]
    public async Task Banner_DisplayedOnFirstRun()
    {
        var workspace = TemporaryWorkspace.Create(output);
 
        var prNumber = CliE2ETestHelpers.GetRequiredPrNumber();
        var commitSha = CliE2ETestHelpers.GetRequiredCommitSha();
        var isCI = CliE2ETestHelpers.IsRunningInCI;
        var recordingPath = CliE2ETestHelpers.GetTestResultsRecordingPath(nameof(Banner_DisplayedOnFirstRun));
 
        var builder = Hex1bTerminal.CreateBuilder()
            .WithHeadless()
            .WithDimensions(160, 48)
            .WithAsciinemaRecording(recordingPath)
            .WithPtyProcess("/bin/bash", ["--norc"]);
 
        using var terminal = builder.Build();
 
        var pendingRun = terminal.RunAsync(TestContext.Current.CancellationToken);
 
        // Pattern to detect the ASPIRE banner text (the welcome message)
        // The banner displays "Welcome to the" followed by ASCII art "ASPIRE"
        var bannerPattern = new CellPatternSearcher()
            .Find("Welcome to the");
 
        // Pattern to detect the telemetry notice (shown on first run)
        var telemetryNoticePattern = new CellPatternSearcher()
            .Find("Telemetry");
 
        var counter = new SequenceCounter();
        var sequenceBuilder = new Hex1bTerminalInputSequenceBuilder();
 
        sequenceBuilder.PrepareEnvironment(workspace, counter);
 
        if (isCI)
        {
            sequenceBuilder.InstallAspireCliFromPullRequest(prNumber, counter);
            sequenceBuilder.SourceAspireCliEnvironment(counter);
            sequenceBuilder.VerifyAspireCliVersion(commitSha, counter);
        }
 
        // Delete the first-time use sentinel file to simulate first run
        // The sentinel is stored at ~/.aspire/cli/cli.firstUseSentinel
        // Using 'aspire cache clear' because it's not an informational
        // command and so will show the banner.
        sequenceBuilder
            .ClearFirstRunSentinel(counter)
            .VerifySentinelDeleted(counter)
            .ClearScreen(counter)
            .Type("aspire cache clear")
            .Enter()
            .WaitUntil(s =>
            {
                // Verify the banner appears
                var hasBanner = bannerPattern.Search(s).Count > 0;
                var hasTelemetryNotice = telemetryNoticePattern.Search(s).Count > 0;
 
                // Both should appear on first run
                return hasBanner && hasTelemetryNotice;
            }, TimeSpan.FromSeconds(30))
            .WaitForSuccessPrompt(counter)
            .Type("exit")
            .Enter();
 
        var sequence = sequenceBuilder.Build();
 
        await sequence.ApplyAsync(terminal, TestContext.Current.CancellationToken);
 
        await pendingRun;
    }
 
    [Fact]
    public async Task Banner_DisplayedWithExplicitFlag()
    {
        var workspace = TemporaryWorkspace.Create(output);
 
        var prNumber = CliE2ETestHelpers.GetRequiredPrNumber();
        var commitSha = CliE2ETestHelpers.GetRequiredCommitSha();
        var isCI = CliE2ETestHelpers.IsRunningInCI;
        var recordingPath = CliE2ETestHelpers.GetTestResultsRecordingPath(nameof(Banner_DisplayedWithExplicitFlag));
 
        var builder = Hex1bTerminal.CreateBuilder()
            .WithHeadless()
            .WithAsciinemaRecording(recordingPath)
            .WithPtyProcess("/bin/bash", ["--norc"]);
 
        using var terminal = builder.Build();
 
        var pendingRun = terminal.RunAsync(TestContext.Current.CancellationToken);
 
        // Pattern to detect the ASPIRE banner welcome text
        // The banner displays "Welcome to the" followed by ASCII art "ASPIRE"
        var bannerPattern = new CellPatternSearcher()
            .Find("Welcome to the");
 
        // Pattern to detect version info in the banner
        // The format is "CLI — version X.Y.Z"
        var versionPattern = new CellPatternSearcher()
            .Find("CLI");
 
        var counter = new SequenceCounter();
        var sequenceBuilder = new Hex1bTerminalInputSequenceBuilder();
 
        sequenceBuilder.PrepareEnvironment(workspace, counter);
 
        if (isCI)
        {
            sequenceBuilder.InstallAspireCliFromPullRequest(prNumber, counter);
            sequenceBuilder.SourceAspireCliEnvironment(counter);
            sequenceBuilder.VerifyAspireCliVersion(commitSha, counter);
        }
 
        // Clear screen to have a clean slate for pattern matching
        sequenceBuilder
            .ClearScreen(counter)
            .Type("aspire --banner")
            .Enter()
            .WaitUntil(s =>
            {
                // Verify the banner appears with version info
                var hasBanner = bannerPattern.Search(s).Count > 0;
                var hasVersion = versionPattern.Search(s).Count > 0;
 
                return hasBanner && hasVersion;
            }, TimeSpan.FromSeconds(30))
            .WaitForSuccessPrompt(counter)
            .Type("exit")
            .Enter();
 
        var sequence = sequenceBuilder.Build();
 
        await sequence.ApplyAsync(terminal, TestContext.Current.CancellationToken);
 
        await pendingRun;
    }
 
    [Fact]
    [ActiveIssue("https://github.com/dotnet/aspire/issues/14307")]
    public async Task Banner_NotDisplayedWithNoLogoFlag()
    {
        var workspace = TemporaryWorkspace.Create(output);
 
        var prNumber = CliE2ETestHelpers.GetRequiredPrNumber();
        var commitSha = CliE2ETestHelpers.GetRequiredCommitSha();
        var isCI = CliE2ETestHelpers.IsRunningInCI;
        var recordingPath = CliE2ETestHelpers.GetTestResultsRecordingPath(nameof(Banner_NotDisplayedWithNoLogoFlag));
 
        var builder = Hex1bTerminal.CreateBuilder()
            .WithHeadless()
            .WithAsciinemaRecording(recordingPath)
            .WithPtyProcess("/bin/bash", ["--norc"]);
 
        using var terminal = builder.Build();
 
        var pendingRun = terminal.RunAsync(TestContext.Current.CancellationToken);
 
        // Pattern to detect the ASPIRE banner - should NOT appear
        // The banner displays "Welcome to the" followed by ASCII art "ASPIRE"
        var bannerPattern = new CellPatternSearcher()
            .Find("Welcome to the");
 
        // Pattern to detect the help text (confirms command completed)
        var helpPattern = new CellPatternSearcher()
            .Find("Commands:");
 
        var counter = new SequenceCounter();
        var sequenceBuilder = new Hex1bTerminalInputSequenceBuilder();
 
        sequenceBuilder.PrepareEnvironment(workspace, counter);
 
        if (isCI)
        {
            sequenceBuilder.InstallAspireCliFromPullRequest(prNumber, counter);
            sequenceBuilder.SourceAspireCliEnvironment(counter);
            sequenceBuilder.VerifyAspireCliVersion(commitSha, counter);
        }
 
        // Delete the first-time use sentinel file to simulate first run,
        // but use --nologo to suppress the banner
        sequenceBuilder
            .ClearFirstRunSentinel(counter)
            .ClearScreen(counter)
            .Type("aspire --nologo --help")
            .Enter()
            .WaitUntil(s =>
            {
                // Wait for help output to confirm command completed
                var hasHelp = helpPattern.Search(s).Count > 0;
                if (!hasHelp)
                {
                    return false;
                }
 
                // Verify the banner does NOT appear
                var hasBanner = bannerPattern.Search(s).Count > 0;
                if (hasBanner)
                {
                    throw new InvalidOperationException(
                        "Unexpected banner displayed when --nologo flag was used!");
                }
 
                return true;
            }, TimeSpan.FromSeconds(30))
            .WaitForSuccessPrompt(counter)
            .Type("exit")
            .Enter();
 
        var sequence = sequenceBuilder.Build();
 
        await sequence.ApplyAsync(terminal, TestContext.Current.CancellationToken);
 
        await pendingRun;
    }
}