File: NewWithAgentInitTests.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 Hex1b.Automation;
using Aspire.TestUtilities;
using Xunit;
 
namespace Aspire.Cli.EndToEnd.Tests;
 
/// <summary>
/// End-to-end test verifying that the <c>aspire new</c> flow with AI agent initialization
/// completes without provenance verification errors when installing <c>@playwright/cli</c>.
/// </summary>
[OuterloopTest("Requires npm and network access to install @playwright/cli from the npm registry")]
public sealed class NewWithAgentInitTests(ITestOutputHelper output)
{
    /// <summary>
    /// Exercises the full <c>aspire new</c> → agent init → Playwright CLI install flow end-to-end.
    /// This is the primary regression test for provenance verification failures (e.g., tag format changes
    /// in upstream <c>@playwright/cli</c> releases).
    ///
    /// The test:
    /// 1. Runs <c>aspire new</c> to create a Starter project
    /// 2. Accepts the agent init prompt (instead of declining)
    /// 3. Selects Playwright CLI during skill selection
    /// 4. Verifies no errors appear (especially no "Provenance verification failed")
    /// 5. Verifies <c>playwright-cli</c> is installed and skill files are generated
    /// </summary>
    [Fact]
    [CaptureWorkspaceOnFailure]
    public async Task AspireNew_WithAgentInit_InstallsPlaywrightWithoutErrors()
    {
        var repoRoot = CliE2ETestHelpers.GetRepoRoot();
        var strategy = CliInstallStrategy.Detect(output.WriteLine);
        var workspace = TemporaryWorkspace.Create(output);
 
        using var terminal = CliE2ETestHelpers.CreateDockerTestTerminal(repoRoot, strategy, output, workspace: workspace);
        var counter = new SequenceCounter();
        var auto = new Hex1bTerminalAutomator(terminal, defaultTimeout: TimeSpan.FromSeconds(500));
        await using var terminalRun = CliE2ETestHelpers.StartRun(terminal, workspace, auto, counter, output, TestContext.Current.CancellationToken);
 
        await auto.PrepareDockerEnvironmentAsync(counter, workspace);
        await auto.InstallAspireCliAsync(strategy, counter);
 
        // Create .claude folder so agent init detects a Claude Code environment.
        // This needs to exist in the workspace root before aspire new creates the project
        // because agent init chains after project creation and looks for environment markers.
        await auto.TypeAsync("mkdir -p .claude");
        await auto.EnterAsync();
        await auto.WaitForSuccessPromptAsync(counter);
 
        // Run aspire new with the Starter template, going through all prompts manually
        // so we can ACCEPT the agent init prompt instead of declining it.
        // Pass --skill-locations and --skills as CLI flags so the test does not depend on
        // the position or count of entries in the interactive skill-selection menus, which
        // change whenever the bundle ships a new skill.
        await auto.TypeAsync("aspire new --skill-locations claudecode --skills playwright-cli");
        await auto.EnterAsync();
 
        // Template selection: accept default Starter App
        await auto.WaitUntilAsync(
            s => new CellPatternSearcher().Find("> Starter App").Search(s).Count > 0,
            timeout: TimeSpan.FromSeconds(60),
            description: "template selection list (> Starter App)");
        await auto.EnterAsync();
 
        // Project name
        await auto.WaitUntilAsync(
            s => new CellPatternSearcher().Find("Enter the project name").Search(s).Count > 0,
            timeout: TimeSpan.FromSeconds(10),
            description: "project name prompt");
        await auto.TypeAsync("StarterApp");
        await auto.EnterAsync();
 
        // Output path: accept default
        await auto.WaitUntilAsync(
            s => new CellPatternSearcher().Find("Enter the output path").Search(s).Count > 0,
            timeout: TimeSpan.FromSeconds(10),
            description: "output path prompt");
        await auto.EnterAsync();
 
        // URLs prompt: accept default No
        await auto.WaitUntilAsync(
            s => new CellPatternSearcher().Find("Use *.dev.localhost URLs").Search(s).Count > 0,
            timeout: TimeSpan.FromSeconds(10),
            description: "URLs prompt");
        await auto.EnterAsync();
 
        // Redis cache: accept default Yes
        await auto.WaitUntilAsync(
            s => new CellPatternSearcher().Find("Use Redis Cache").Search(s).Count > 0,
            timeout: TimeSpan.FromSeconds(10),
            description: "Redis cache prompt");
        await auto.EnterAsync();
 
        // Test project: accept default No
        await auto.WaitUntilAsync(
            s => new CellPatternSearcher().Find("Do you want to create a test project?").Search(s).Count > 0,
            timeout: TimeSpan.FromSeconds(10),
            description: "test project prompt");
        await auto.EnterAsync();
 
        // Agent init prompt: ACCEPT it (type 'y')
        await auto.WaitUntilAsync(
            s => s.ContainsText("configure AI agent environments"),
            timeout: TimeSpan.FromSeconds(120),
            description: "agent init prompt after aspire new");
        await auto.WaitAsync(500);
        await auto.TypeAsync("y");
 
        // Wait for agent init to complete (downloads @playwright/cli from npm).
        // Skill location and skill selection are provided via --skill-locations/--skills flags
        // on the aspire new invocation above, so no interactive navigation is needed here.
        // Fail the test immediately if a provenance verification error appears.
        await auto.WaitUntilAsync(s =>
        {
            if (s.ContainsText("Provenance verification failed"))
            {
                throw new InvalidOperationException(
                    "Provenance verification failed for @playwright/cli! " +
                    "This likely means the upstream package changed its tag format.");
            }
            return s.ContainsText("configuration complete");
        }, timeout: TimeSpan.FromMinutes(5), description: "agent init configuration complete (no provenance errors)");
        await auto.WaitForSuccessPromptAsync(counter);
 
        // Verify playwright-cli is installed and functional.
        await auto.TypeAsync("playwright-cli --version");
        await auto.EnterAsync();
        await auto.WaitForSuccessPromptAsync(counter);
 
        // Verify skill file was generated in the Claude Code location.
        await auto.TypeAsync("ls StarterApp/.claude/skills/playwright-cli/SKILL.md");
        await auto.EnterAsync();
        await auto.WaitUntilTextAsync("SKILL.md", timeout: TimeSpan.FromSeconds(10));
        await auto.WaitForSuccessPromptAsync(counter);
    }
}