| 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.Automation; using Hex1b.Input; 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 repoRoot = CliE2ETestHelpers.GetRepoRoot(); var installMode = CliE2ETestHelpers.DetectDockerInstallMode(repoRoot); var workspace = TemporaryWorkspace.Create(output); using var terminal = CliE2ETestHelpers.CreateDockerTestTerminal(repoRoot, installMode, output, workspace: workspace); var pendingRun = terminal.RunAsync(TestContext.Current.CancellationToken); var counter = new SequenceCounter(); var auto = new Hex1bTerminalAutomator(terminal, defaultTimeout: TimeSpan.FromSeconds(500)); await auto.PrepareDockerEnvironmentAsync(counter, workspace); await auto.InstallAspireCliInDockerAsync(installMode, counter); // Step 1: Verify playwright-cli is not installed. await auto.TypeAsync("playwright-cli --version 2>&1 || true"); await auto.EnterAsync(); await auto.WaitForSuccessPromptAsync(counter); // Step 2: Create an Aspire project (accept all defaults). await auto.AspireNewAsync("TestProject", counter); // Step 3: Navigate into the project and create .claude folder to trigger Claude Code detection. await auto.TypeAsync("cd TestProject && mkdir -p .claude"); await auto.EnterAsync(); await auto.WaitForSuccessPromptAsync(counter); // Step 4: Run aspire agent init. // First prompt: workspace path await auto.TypeAsync("aspire agent init"); await auto.EnterAsync(); await auto.WaitUntilTextAsync("workspace:", timeout: TimeSpan.FromSeconds(30)); await auto.WaitAsync(500); await auto.EnterAsync(); // Accept default workspace path // Second prompt: agent environments (select Claude Code) await auto.WaitUntilTextAsync("agent environments", timeout: TimeSpan.FromSeconds(60)); await auto.TypeAsync(" "); // Toggle first option (Claude Code) await auto.EnterAsync(); // Third prompt: additional options (select Playwright CLI installation) // Aspire skill file (priority 0) appears first, Playwright CLI (priority 1) second. await auto.WaitUntilTextAsync("additional options", timeout: TimeSpan.FromSeconds(30)); await auto.WaitUntilTextAsync("Install Playwright CLI", timeout: TimeSpan.FromSeconds(10)); await auto.TypeAsync(" "); // Toggle first option (Aspire skill file) await auto.DownAsync(); // Move to Playwright CLI option await auto.TypeAsync(" "); // Toggle Playwright CLI option await auto.EnterAsync(); // Wait for installation to complete (this downloads from npm, can take a while) await auto.WaitUntilTextAsync("configuration complete", timeout: TimeSpan.FromMinutes(3)); await auto.WaitForSuccessPromptAsync(counter); // Step 5: Verify playwright-cli is now installed. await auto.TypeAsync("playwright-cli --version"); await auto.EnterAsync(); await auto.WaitForSuccessPromptAsync(counter); // Step 6: Verify the skill file was generated. await auto.TypeAsync("ls .claude/skills/playwright-cli/SKILL.md"); await auto.EnterAsync(); await auto.WaitUntilTextAsync("SKILL.md", timeout: TimeSpan.FromSeconds(10)); await auto.WaitForSuccessPromptAsync(counter); await auto.TypeAsync("exit"); await auto.EnterAsync(); await pendingRun; } /// <summary> /// Verifies that when <c>aspire agent init</c> is run from a different directory than the /// workspace root, <c>playwright-cli install --skills</c> generates skill files in the /// workspace root, not the current working directory. /// /// This is a regression test for https://github.com/microsoft/aspire/issues/15140 where /// the missing <c>WorkingDirectory</c> on <c>ProcessStartInfo</c> caused skill files /// to be dropped in the CLI process's current working directory. /// </summary> [Fact] public async Task AgentInit_WhenCwdDiffersFromWorkspaceRoot_PlacesSkillFilesInWorkspaceRoot() { var repoRoot = CliE2ETestHelpers.GetRepoRoot(); var installMode = CliE2ETestHelpers.DetectDockerInstallMode(repoRoot); var workspace = TemporaryWorkspace.Create(output); using var terminal = CliE2ETestHelpers.CreateDockerTestTerminal(repoRoot, installMode, output, workspace: workspace); var pendingRun = terminal.RunAsync(TestContext.Current.CancellationToken); var counter = new SequenceCounter(); var auto = new Hex1bTerminalAutomator(terminal, defaultTimeout: TimeSpan.FromSeconds(500)); await auto.PrepareDockerEnvironmentAsync(counter, workspace); await auto.InstallAspireCliInDockerAsync(installMode, counter); // Step 1: Create an Aspire project. await auto.AspireNewAsync("TestProject", counter); // Step 2: Create .claude folder inside the project to trigger Claude Code detection. // Crucially, do NOT cd into the project — stay in the parent directory. await auto.TypeAsync("mkdir -p TestProject/.claude"); await auto.EnterAsync(); await auto.WaitForSuccessPromptFailFastAsync(counter); // Step 3: Run aspire agent init from the PARENT directory. // When prompted for workspace root, provide the project subdirectory. await auto.TypeAsync("aspire agent init"); await auto.EnterAsync(); await auto.WaitUntilTextAsync("workspace:", timeout: TimeSpan.FromSeconds(30)); await auto.WaitAsync(500); // Clear the default path and type the project subdirectory instead. // The default will be the git root or CWD — we need to override it. await auto.Ctrl().KeyAsync(Hex1bKey.U); // Clear the line await auto.TypeAsync("TestProject"); await auto.EnterAsync(); // Select Claude Code environment. await auto.WaitUntilTextAsync("agent environments", timeout: TimeSpan.FromSeconds(60)); await auto.TypeAsync(" "); await auto.EnterAsync(); // Select Playwright CLI installation. await auto.WaitUntilTextAsync("additional options", timeout: TimeSpan.FromSeconds(30)); await auto.WaitUntilTextAsync("Install Playwright CLI", timeout: TimeSpan.FromSeconds(10)); await auto.TypeAsync(" "); // Toggle first option (Aspire skill file) await auto.DownAsync(); await auto.TypeAsync(" "); // Toggle Playwright CLI await auto.EnterAsync(); await auto.WaitUntilTextAsync("configuration complete", timeout: TimeSpan.FromMinutes(3)); await auto.WaitForSuccessPromptFailFastAsync(counter); // Step 4: Verify skill file exists in the workspace root (project subdirectory). await auto.TypeAsync("ls TestProject/.claude/skills/playwright-cli/SKILL.md"); await auto.EnterAsync(); await auto.WaitUntilTextAsync("SKILL.md", timeout: TimeSpan.FromSeconds(10)); await auto.WaitForSuccessPromptFailFastAsync(counter); // Step 5: Verify no stray skill files were created in the CWD (parent directory). await auto.TypeAsync("test -d .claude/skills/playwright-cli && echo 'STRAY_FILES_FOUND' || echo 'NO_STRAY_FILES'"); await auto.EnterAsync(); await auto.WaitUntilTextAsync("NO_STRAY_FILES", timeout: TimeSpan.FromSeconds(10)); await auto.WaitForSuccessPromptFailFastAsync(counter); await auto.TypeAsync("exit"); await auto.EnterAsync(); await pendingRun; } }