File: PlaywrightCliInstallTests.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 Aspire.TestUtilities;
using Xunit;
 
namespace Aspire.Cli.EndToEnd.Tests;
 
/// <summary>
/// End-to-end test verifying that the Playwright CLI installation flow works correctly
/// through <c>aspire agent init</c>, including npm provenance verification and skill file generation.
/// </summary>
[OuterloopTest("Requires npm and network access to install @playwright/cli from the npm registry")]
public sealed class PlaywrightCliInstallTests(ITestOutputHelper output)
{
    /// <summary>
    /// Verifies the full Playwright CLI installation lifecycle:
    /// 1. Playwright CLI is not initially installed
    /// 2. An Aspire project is created
    /// 3. <c>aspire agent init</c> is run with Claude Code environment selected
    /// 4. Playwright CLI is installed and available on PATH
    /// 5. The <c>.claude/skills/playwright-cli/SKILL.md</c> skill file is generated
    /// </summary>
    [Fact]
    public async Task AgentInit_InstallsPlaywrightCli_AndGeneratesSkillFiles()
    {
        var workspace = TemporaryWorkspace.Create(output);
 
        var prNumber = CliE2ETestHelpers.GetRequiredPrNumber();
        var commitSha = CliE2ETestHelpers.GetRequiredCommitSha();
        var isCI = CliE2ETestHelpers.IsRunningInCI;
        var recordingPath = CliE2ETestHelpers.GetTestResultsRecordingPath(
            nameof(AgentInit_InstallsPlaywrightCli_AndGeneratesSkillFiles));
 
        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);
 
        // Patterns for prompt detection
        var workspacePrompt = new CellPatternSearcher().Find("workspace:");
        var agentEnvPrompt = new CellPatternSearcher().Find("agent environments");
        var additionalOptionsPrompt = new CellPatternSearcher().Find("additional options");
        var playwrightOption = new CellPatternSearcher().Find("Install Playwright CLI");
        var configComplete = new CellPatternSearcher().Find("configuration complete");
        var skillFileExists = new CellPatternSearcher().Find("SKILL.md");
 
        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);
        }
 
        // Step 1: Verify playwright-cli is not installed.
        sequenceBuilder
            .Type("playwright-cli --version 2>&1 || true")
            .Enter()
            .WaitForSuccessPrompt(counter);
 
        // Step 2: Create an Aspire project (accept all defaults).
        var starterAppTemplate = new CellPatternSearcher().FindPattern("> Starter App");
        var projectNamePrompt = new CellPatternSearcher().Find("Enter the project name");
        var outputPathPrompt = new CellPatternSearcher().Find("Enter the output path");
        var urlsPrompt = new CellPatternSearcher().Find("*.dev.localhost URLs");
        var redisPrompt = new CellPatternSearcher().Find("Use Redis Cache");
        var testProjectPrompt = new CellPatternSearcher().Find("Do you want to create a test project?");
 
        sequenceBuilder
            .Type("aspire new")
            .Enter()
            .WaitUntil(s => starterAppTemplate.Search(s).Count > 0, TimeSpan.FromSeconds(60))
            .Enter() // Select Starter App template
            .WaitUntil(s => projectNamePrompt.Search(s).Count > 0, TimeSpan.FromSeconds(30))
            .Type("TestProject")
            .Enter()
            .WaitUntil(s => outputPathPrompt.Search(s).Count > 0, TimeSpan.FromSeconds(10))
            .Enter() // Accept default output path
            .WaitUntil(s => urlsPrompt.Search(s).Count > 0, TimeSpan.FromSeconds(10))
            .Enter() // Accept default URL setting
            .WaitUntil(s => redisPrompt.Search(s).Count > 0, TimeSpan.FromSeconds(10))
            .Enter() // Accept default Redis setting
            .WaitUntil(s => testProjectPrompt.Search(s).Count > 0, TimeSpan.FromSeconds(10))
            .Enter() // Accept default test project setting
            .WaitForSuccessPrompt(counter);
 
        // Step 3: Navigate into the project and create .claude folder to trigger Claude Code detection.
        sequenceBuilder
            .Type("cd TestProject && mkdir -p .claude")
            .Enter()
            .WaitForSuccessPrompt(counter);
 
        // Step 4: Run aspire agent init.
        // First prompt: workspace path
        sequenceBuilder
            .Type("aspire agent init")
            .Enter()
            .WaitUntil(s => workspacePrompt.Search(s).Count > 0, TimeSpan.FromSeconds(30))
            .Wait(500)
            .Enter(); // Accept default workspace path
 
        // Second prompt: agent environments (select Claude Code)
        sequenceBuilder
            .WaitUntil(s => agentEnvPrompt.Search(s).Count > 0, TimeSpan.FromSeconds(60))
            .Type(" ") // Toggle first option (Claude Code)
            .Enter();
 
        // Third prompt: additional options (select Playwright CLI installation)
        // Aspire skill file (priority 0) appears first, Playwright CLI (priority 1) second.
        sequenceBuilder
            .WaitUntil(s => additionalOptionsPrompt.Search(s).Count > 0, TimeSpan.FromSeconds(30))
            .WaitUntil(s => playwrightOption.Search(s).Count > 0, TimeSpan.FromSeconds(10))
            .Type(" ") // Toggle first option (Aspire skill file)
            .Key(Hex1b.Input.Hex1bKey.DownArrow) // Move to Playwright CLI option
            .Type(" ") // Toggle Playwright CLI option
            .Enter();
 
        // Wait for installation to complete (this downloads from npm, can take a while)
        sequenceBuilder
            .WaitUntil(s => configComplete.Search(s).Count > 0, TimeSpan.FromMinutes(3))
            .WaitForSuccessPrompt(counter);
 
        // Step 5: Verify playwright-cli is now installed.
        sequenceBuilder
            .Type("playwright-cli --version")
            .Enter()
            .WaitForSuccessPrompt(counter);
 
        // Step 6: Verify the skill file was generated.
        sequenceBuilder
            .Type("ls .claude/skills/playwright-cli/SKILL.md")
            .Enter()
            .WaitUntil(s => skillFileExists.Search(s).Count > 0, TimeSpan.FromSeconds(10))
            .WaitForSuccessPrompt(counter);
 
        sequenceBuilder
            .Type("exit")
            .Enter();
 
        var sequence = sequenceBuilder.Build();
 
        await sequence.ApplyAsync(terminal, TestContext.Current.CancellationToken);
 
        await pendingRun;
    }
}