Skip to main content

cli-e2e-testing

Use when creating, modifying, debugging, or reviewing Aspire CLI end-to-end tests that use Hex1b terminal automation under tests/Aspire.Cli.EndToEnd.Tests/.

Datos de origen

Repositorio
sebastienros/aspire
Última actividad en el origen
2 de junio de 2026 a las 22:01
Idioma detectado de SKILL.md
inglés
Estrellas
0
Forks
0

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Explorador de archivos
2 archivos

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
name
cli-e2e-testing
description
Use when creating, modifying, debugging, or reviewing Aspire CLI end-to-end tests that use Hex1b terminal automation under tests/Aspire.Cli.EndToEnd.Tests/.
# Aspire CLI End-to-End Testing with Hex1b This skill provides patterns and practices for writing end-to-end tests for the Aspire CLI using the Hex1b terminal automation library. ## Overview CLI E2E tests use the Hex1b library to automate terminal sessions, simulating real user interactions with the Aspire CLI. Tests run in CI with asciinema recordings for debugging. **Location**: `tests/Aspire.Cli.EndToEnd.Tests/` **Supported Platforms**: Linux only. Hex1b requires a Linux terminal environment. Tests are configured to skip on Windows and macOS in CI. ## Key Components ### Core Classes - **`Hex1bTerminal`**: The main terminal class from the Hex1b library for terminal automation - **`Hex1bTerminalAutomator`**: Async/await API for driving a `Hex1bTerminal` — the preferred approach for new tests - **`Hex1bAutomatorTestHelpers`** (shared helpers): Async extension methods on `Hex1bTerminalAutomator` (`WaitForSuccessPromptAsync`, `AspireNewAsync`, etc.) - **`CliE2EAutomatorHelpers`** (`Helpers/CliE2EAutomatorHelpers.cs`): CLI-specific async extension methods on `Hex1bTerminalAutomator` (`PrepareDockerEnvironmentAsync`, `InstallAspireCliAsync`, etc.) - **`CellPatternSearcher`**: Pattern matching for terminal cell content - **`SequenceCounter`** (`Helpers/SequenceCounter.cs`): Tracks command execution count for deterministic prompt detection - **`CliE2ETestHelpers`** (`Helpers/CliE2ETestHelpers.cs`): Environment variable helpers and terminal factory methods - **`TemporaryWorkspace`**: Creates isolated temporary directories for test execution - **`Hex1bTerminalInputSequenceBuilder`** *(legacy)*: Fluent builder API for building sequences of terminal input/output operations. Prefer `Hex1bTerminalAutomator` for new tests. ### Test Architecture Each test: 1. Creates a `TemporaryWorkspace` for isolation 2. Builds a `Hex1bTerminal` with headless mode and asciinema recording 3. Creates a `Hex1bTerminalAutomator` wrapping the terminal 4. Drives the terminal with async/await calls and awaits completion ## Test Structure ```csharp public sealed class SmokeTests(ITestOutputHelper output) { [Fact] public async Task MyCliTest() { 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); await auto.TypeAsync("aspire --version"); await auto.EnterAsync(); await auto.WaitForSuccessPromptAsync(counter); } } ``` ### TerminalRun Pattern **Always use `CliE2ETestHelpers.StartRun`** to wrap the terminal run. This returns a `TerminalRun` (implements `IAsyncDisposable`) that automatically: 1. Captures Aspire diagnostics via `CaptureAspireDiagnosticsAsync` (best effort) 2. Types `exit` and presses Enter to close the terminal 3. Awaits the pending run task This eliminates the need for manual `exit`/`await pendingRun` at the end of every test and ensures diagnostics are always captured, even when tests fail. ```csharp // DO: Use StartRun for consistent diagnostics capture and cleanup 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); // ... test body — no exit/pendingRun needed at the end // DON'T: Manually handle exit and pendingRun var pendingRun = terminal.RunAsync(TestContext.Current.CancellationToken); // ... test body ... await auto.TypeAsync("exit"); await auto.EnterAsync(); await pendingRun; ``` ## Running Tests Locally CLI E2E tests run inside Docker containers on Linux. The workflow is: build a portable archive with `localhive`, then point the tests at it. This is the primary way to iterate on E2E tests during development. ### Prerequisites - Docker Desktop running (with Linux containers) - .NET 10 SDK (installed via `./restore.sh` or `.\restore.cmd`) ### Quick Start (macOS / Linux) ```bash # 1. Build a portable archive with CLI + packages + bundle # Use linux-arm64 on Apple Silicon, linux-x64 on Intel/Linux ./localhive.sh -o /tmp/aspire-e2e -r linux-arm64 --archive # 2. Run a specific test ASPIRE_E2E_ARCHIVE=/tmp/aspire-e2e.tar.gz \ dotnet test tests/Aspire.Cli.EndToEnd.Tests/Aspire.Cli.EndToEnd.Tests.csproj \ -- --filter-method "*.CreateAndRunAspireStarterProject" # 3. Run all tests in a class ASPIRE_E2E_ARCHIVE=/tmp/aspire-e2e.tar.gz \ dotnet test tests/Aspire.Cli.EndToEnd.Tests/Aspire.Cli.EndToEnd.Tests.csproj \ -- --filter-class "*.SmokeTests" ``` ### Quick Start (Windows / PowerShell) ```powershell # 1. Build a portable archive (Docker Desktop uses linux-x64 via WSL2) .\localhive.ps1 -o C:\tmp\aspire-e2e -r linux-x64 -Archive # 2. Run a specific test $env:ASPIRE_E2E_ARCHIVE = "C:\tmp\aspire-e2e.tar.gz" dotnet test tests\Aspire.Cli.EndToEnd.Tests\Aspire.Cli.EndToEnd.Tests.csproj ` -- --filter-method "*.CreateAndRunAspireStarterProject" # 3. Run all tests in a class dotnet test tests\Aspire.Cli.EndToEnd.Tests\Aspire.Cli.EndToEnd.Tests.csproj ` -- --filter-class "*.SmokeTests" ``` ### Choosing the Right RID The archive must match the Docker container's architecture: | Host | Docker Desktop | RID | |------|---------------|-----| | Apple Silicon Mac | Linux arm64 containers | `linux-arm64` | | Intel Mac | Linux x64 containers | `linux-x64` | | Windows (any) | WSL2 Linux x64 | `linux-x64` | | Linux x64 | Native | `linux-x64` | | Linux arm64 | Native | `linux-arm64` | ### Development Workflow The typical loop when writing or debugging E2E tests: ```bash # 1. Make your code changes (CLI, hosting, templates, etc.) # 2. Rebuild the archive (picks up all changes — ~3 min) ./localhive.sh -o /tmp/aspire-e2e -r linux-arm64 --archive # 3. Run the specific test you're working on ASPIRE_E2E_ARCHIVE=/tmp/aspire-e2e.tar.gz \ dotnet test tests/Aspire.Cli.EndToEnd.Tests/Aspire.Cli.EndToEnd.Tests.csproj \ -- --filter-method "*.YourTestName" # 4. If it fails, check the asciinema recording # Recordings are saved under the test output TestResults/recordings/ directory # Play with: asciinema play /path/to/YourTestName.cast # 5. Fix and repeat from step 1 or 2 ``` ### Install Modes The `CliInstallStrategy` class auto-detects how to install the CLI in the test container. You can override via environment variables: | Env Var | Mode | Example | |---------|------|---------| | `ASPIRE_E2E_ARCHIVE` | LocalHive — extract archive into container | `/tmp/aspire-e2e.tar.gz` | | `ASPIRE_E2E_QUALITY` | Install script with quality | `dev`, `staging`, `release` | | `ASPIRE_E2E_VERSION` | Install script with version | `13.2.1` | | *(none, in CI)* | PullRequest — install from PR artifacts | Auto-detected | | *(none, locally)* | InstallScript (latest GA) | Auto-detected | **LocalHive** (via `ASPIRE_E2E_ARCHIVE`) is the recommended mode for local development — it uses your locally-built CLI, packages, and bundle so you test exactly what you've changed. ### Testing Against Released Versions Useful for verifying tests pass against shipped versions or catching regressions: ```bash # Test against latest GA release ASPIRE_E2E_QUALITY=release dotnet test tests/Aspire.Cli.EndToEnd.Tests/Aspire.Cli.EndToEnd.Tests.csproj \ -- --filter-method "*.CreateAndRunAspireStarterProject" # Test against daily builds ASPIRE_E2E_QUALITY=dev dotnet test ... # Test against a specific version ASPIRE_E2E_VERSION=13.2.1 dotnet test ... ``` ## SequenceCounter and Prompt Detection The `SequenceCounter` class tracks the number of shell commands executed. This enables deterministic waiting for command completion via a custom shell prompt. ### How It Works 1. `PrepareDockerEnvironmentAsync()` configures the shell with a custom prompt: `[N OK] $ ` or `[N ERR:code] $ ` 2. Each command increments the counter 3. `WaitForSuccessPromptAsync(counter)` waits for a prompt showing the current count with `OK` ```csharp var counter = new SequenceCounter(); var auto = new Hex1bTerminalAutomator(terminal, defaultTimeout: TimeSpan.FromSeconds(500)); await auto.PrepareDockerEnvironmentAsync(counter, workspace); // Sets up prompt, counter starts at 1 await auto.TypeAsync("echo hello"); await auto.EnterAsync(); await auto.WaitForSuccessPromptAsync(counter); // Waits for "[1 OK] $ ", then increments to 2 await auto.TypeAsync("ls -la"); await auto.EnterAsync(); await auto.WaitForSuccessPromptAsync(counter); // Waits for "[2 OK] $ ", then increments to 3 await auto.TypeAsync("exit"); await auto.EnterAsync(); ``` This approach is more reliable than arbitrary timeouts because it deterministically waits for each command to complete. ## Pattern Searching with CellPatternSearcher Use `CellPatternSearcher` to find text patterns in terminal output: ```csharp // Simple text search (literal string matching - PREFERRED) var waitingForPrompt = new CellPatternSearcher() .Find("Enter the project name"); // Literal string with special characters (use Find, not FindPattern!) var waitingForTemplate = new CellPatternSearcher() .Find("> Starter App (FastAPI/React)"); // Parentheses and slashes are literal // Regex pattern (only when you need wildcards/regex features) var waitingForAnyStarter = new CellPatternSearcher() .FindPattern("> Starter App.*"); // .* matches anything // Chained patterns (find "b", then scan right until "$", then right of " ") var waitingForShell = new CellPatternSearcher() .Find("b").RightUntil("$").Right(' ').Right(' '); // Use in WaitUntilAsync await auto.WaitUntilAsync( snapshot => waitingForPrompt.Search(snapshot).Count > 0, TimeSpan.FromSeconds(30), description: "waiting for prompt"); ``` ### Find vs FindPattern - **`Find(string)`**: Literal string matching. Use this for most cases. - **`FindPattern(string)`**: Regex pattern matching. Use only when you need regex features like wildcards. **Important**: If your search string contains regex special characters like `(`, `)`, `/`, `.`, `*`, `+`, `?`, `[`, `]`, `{`, `}`, `^`, `$`, `|`, or `\`, use `Find()` instead of `FindPattern()` to avoid regex interpretation. ## Extension Methods ### Hex1bAutomatorTestHelpers Extensions (Shared — Automator API) | Method | Description | |--------|-------------| | `WaitForSuccessPromptAsync(counter, timeout?)` | Waits for `[N OK] $ ` prompt, fails immediately if error prompt appears, and increments counter | | `WaitForAnyPromptAsync(counter, timeout?)` | Waits for any prompt (`OK` or `ERR`) and increments counter | | `WaitForErrorPromptAsync(counter, timeout?)` | Waits for `[N ERR:code] $ ` prompt and increments counter | | `RunCommandAsync(command, counter, timeout?)` | Types a command, presses Enter, and waits for success prompt (fails fast on error) | | `DeclineAgentInitPromptAsync()` | Declines the `aspire agent init` prompt if it appears | | `AspireNewAsync(projectName, counter, template?, useRedisCache?)` | Runs `aspire new` interactively, handling template selection, project name, output path, URLs, Redis, and test project prompts | See [AspireNew Helper](#aspirenew-helper) below for detailed usage. ### CliE2EAutomatorHelpers Extensions on Hex1bTerminalAutomator | Method | Description | |--------|-------------| | `PrepareDockerEnvironmentAsync(counter, workspace)` | Sets up Docker container environment with custom prompt and command tracking | | `InstallAspireCliAsync(installMode, counter)` | Installs the Aspire CLI inside the Docker container | | `ClearScreenAsync(counter)` | Clears the terminal screen and waits for prompt | ### SequenceCounterExtensions | Method | Description | |--------|-------------| | `IncrementSequence(counter)` | Manually increments the counter | ### Legacy Builder Extensions The following extensions on `Hex1bTerminalInputSequenceBuilder` are still available but should not be used in new tests: | Method | Description | |--------|-------------| | `WaitForSuccessPrompt(counter, timeout?)` | *(legacy)* Waits for `[N OK] $ ` prompt and increments counter | | `PrepareEnvironment(workspace, counter)` | *(legacy)* Sets up custom prompt with command tracking | | `SourceAspireBundleEnvironment(counter)` | *(legacy)* Sources bundle PATH environment variables | ## DO: Use CellPatternSearcher for Output Detection Wait for specific output patterns rather than arbitrary delays: ```csharp var waitingForMessage = new CellPatternSearcher() .Find("Project created successfully."); await auto.TypeAsync("aspire new"); await auto.EnterAsync(); await auto.WaitUntilAsync( s => waitingForMessage.Search(s).Count > 0, TimeSpan.FromMinutes(2), description: "waiting for project created message"); ``` ## DO: Use WaitForSuccessPromptAsync After Commands After running shell commands, use `WaitForSuccessPromptAsync()` to wait for the command to complete: ```csharp await auto.TypeAsync("dotnet build"); await auto.EnterAsync(); await auto.WaitForSuccessPromptAsync(counter); // Waits for prompt, verifies success await auto.TypeAsync("dotnet run"); await auto.EnterAsync(); await auto.WaitForSuccessPromptAsync(counter); ``` ## AspireNew Helper The `AspireNew` extension method centralizes the multi-step `aspire new` interactive flow. Use it instead of manually building the prompt sequence. ### AspireTemplate Enum | Value | Template | Arrow Keys |
Ver en GitHub
Este SKILL.md es muy grande, por eso SkillsMP muestra aqui solo la primera seccion. Ver en GitHub