Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,22 @@ private async Task DeployStarterWithManagedRedisToAzureContainerAppsCore(Cancell
.WaitUntil(s => waitingForUrlsPrompt.Search(s).Count > 0, TimeSpan.FromSeconds(10))
.Enter() // Select "No" for localhost URLs (default)
.WaitUntil(s => waitingForRedisPrompt.Search(s).Count > 0, TimeSpan.FromSeconds(10))
.Enter() // Select "Yes" for Redis Cache (first/default option)
.WaitForSuccessPrompt(counter, TimeSpan.FromMinutes(5));
.Enter(); // Select "Yes" for Redis Cache (first/default option)

// Handle the agent init prompt that appears after aspire new completes.
// The CLI may show "Would you like to configure AI agent environments?" — decline it.
var waitingForAgentInitPrompt = new CellPatternSearcher()
.Find("configure AI agent environments");
var waitingForNewSuccessPrompt = new CellPatternSearcher()
.FindPattern(counter.Value.ToString())
.RightText(" OK] $ ");

sequenceBuilder
.WaitUntil(s => waitingForAgentInitPrompt.Search(s).Count > 0
|| waitingForNewSuccessPrompt.Search(s).Count > 0, TimeSpan.FromMinutes(5))
.Wait(500)
.Type("n").Enter()
.WaitForSuccessPrompt(counter, TimeSpan.FromMinutes(1));

// Step 4: Navigate to project directory
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The WaitUntil matches on either the agent init prompt or the success prompt. But .Wait(500).Type("n").Enter() runs unconditionally. If aspire new completes without showing the agent init prompt (success prompt matches instead), typing "n" + Enter at the shell runs n as a command (exit code 127 / ERR), causing WaitForSuccessPrompt to time out.

This needs to check which condition was actually matched and only type "n" when the agent init prompt was seen.

output.WriteLine("Step 4: Navigating to project directory...");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ private async Task DeployPythonStarterWithPurgeTaskCore(CancellationToken cancel
await auto.PrepareEnvironmentAsync(workspace, counter);

// Step 2: Set up CLI environment (in CI)
// Python apphosts need the full bundle because
// the prebuilt AppHost server is required for aspire new with Python templates.
// Python templates need the full bundle (not just the CLI binary) because
// the prebuilt AppHost server is required for aspire new with non-C# templates.
if (DeploymentE2ETestHelpers.IsRunningInCI)
{
var prNumber = DeploymentE2ETestHelpers.GetPrNumber();
Expand All @@ -102,7 +102,7 @@ private async Task DeployPythonStarterWithPurgeTaskCore(CancellationToken cancel
await auto.TypeAsync("aspire add Aspire.Hosting.Azure.AppContainers");
await auto.EnterAsync();

if (DeploymentE2ETestHelpers.IsRunningInCI && DeploymentE2ETestHelpers.GetPrNumber() <= 0)
if (DeploymentE2ETestHelpers.IsRunningInCI)
{
await auto.WaitUntilTextAsync("(based on NuGet.config)", timeout: TimeSpan.FromSeconds(60));
await auto.EnterAsync();
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ private async Task DeployPythonFastApiTemplateToAzureAppServiceCore(Cancellation
await auto.PrepareEnvironmentAsync(workspace, counter);

// Step 2: Set up CLI environment (in CI)
// Python apphosts need the full bundle because
// the prebuilt AppHost server is required for aspire new with Python templates.
// Python templates need the full bundle (not just the CLI binary) because
// the prebuilt AppHost server is required for aspire new with non-C# templates.
if (DeploymentE2ETestHelpers.IsRunningInCI)
{
var prNumber = DeploymentE2ETestHelpers.GetPrNumber();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,43 @@ internal static async Task SourceAspireBundleEnvironmentAsync(
await auto.EnterAsync();
await auto.WaitForSuccessPromptAsync(counter);
}

/// <summary>
/// Runs <c>aspire deploy</c> interactively, answering parameter prompts via terminal automation.
/// The deploy pipeline handles image building, pushing, Helm chart generation, and deployment.
/// </summary>
/// <param name="auto">The terminal automator.</param>
/// <param name="counter">Sequence counter for prompt tracking.</param>
/// <param name="parameterResponses">
/// Ordered list of (promptSubstring, valueToType) tuples.
/// Each entry matches by the parameter name appearing in the prompt text.
/// Entries are consumed in order — first match wins.
/// </param>
/// <param name="pipelineTimeout">Timeout for the entire deploy pipeline. Defaults to 15 minutes.</param>
internal static async Task AspireDeployInteractiveAsync(
this Hex1bTerminalAutomator auto,
SequenceCounter counter,
IReadOnlyList<(string PromptText, string Value)> parameterResponses,
TimeSpan? pipelineTimeout = null)
{
Comment on lines +115 to +132
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method largely duplicates AspireDeployInteractiveAsync already present in tests/Aspire.Cli.EndToEnd.Tests/Helpers/KubernetesDeployTestHelpers.cs. If both projects need the same behavior, consider moving the shared implementation to tests/Shared (or another common helper) to avoid future drift (e.g., prompt handling, failure detection, timeouts).

Copilot uses AI. Check for mistakes.
var timeout = pipelineTimeout ?? TimeSpan.FromMinutes(15);

await auto.TypeAsync("aspire deploy");
await auto.EnterAsync();

// Answer each parameter prompt in order.
// The CLI shows parameter prompts via Spectre.Console TextPrompt with the parameter name as the label.
for (var i = 0; i < parameterResponses.Count; i++)
{
var (promptText, value) = parameterResponses[i];

await auto.WaitUntilTextAsync(promptText, timeout: TimeSpan.FromMinutes(5));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5 mins??

await auto.TypeAsync(value);
await auto.EnterAsync();
}

// Wait for pipeline completion
await auto.WaitUntilTextAsync("PIPELINE SUCCEEDED", timeout: timeout);
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this reusable helper, waiting only for "PIPELINE SUCCEEDED" means a failed deploy will typically burn the full timeout before surfacing, making failures slower and harder to diagnose. Consider waiting for either "PIPELINE SUCCEEDED" or "PIPELINE FAILED" and throwing immediately when the failed marker is observed (similar to other deployment E2E tests).

Suggested change
await auto.WaitUntilTextAsync("PIPELINE SUCCEEDED", timeout: timeout);
var pipelineSucceededPattern = new CellPatternSearcher().Find("PIPELINE SUCCEEDED");
var pipelineFailedPattern = new CellPatternSearcher().Find("PIPELINE FAILED");
var pipelineFailed = false;
await auto.WaitUntilAsync(
s =>
{
if (pipelineFailedPattern.Search(s).Count > 0)
{
pipelineFailed = true;
return true;
}
return pipelineSucceededPattern.Search(s).Count > 0;
},
timeout: timeout,
description: "pipeline completion");
if (pipelineFailed)
{
throw new InvalidOperationException("Deployment pipeline failed.");
}

Copilot uses AI. Check for mistakes.
await auto.WaitForSuccessPromptAsync(counter, TimeSpan.FromSeconds(30));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ private async Task DeployPythonFastApiTemplateToAzureContainerAppsCore(Cancellat
await auto.PrepareEnvironmentAsync(workspace, counter);

// Step 2: Set up CLI environment (in CI)
// Python apphosts need the full bundle because
// the prebuilt AppHost server is required for aspire new with Python templates.
// Python templates need the full bundle (not just the CLI binary) because
// the prebuilt AppHost server is required for aspire new with non-C# templates.
if (DeploymentE2ETestHelpers.IsRunningInCI)
{
var prNumber = DeploymentE2ETestHelpers.GetPrNumber();
Expand All @@ -88,8 +88,6 @@ private async Task DeployPythonFastApiTemplateToAzureContainerAppsCore(Cancellat
}
await auto.SourceAspireBundleEnvironmentAsync(counter);
}

// Step 3: Create Python FastAPI project using aspire new with interactive prompts
output.WriteLine("Step 3: Creating Python FastAPI project...");
await auto.AspireNewAsync(projectName, counter, template: AspireTemplate.PythonReact, useRedisCache: false);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,8 @@ private async Task DeployTypeScriptExpressTemplateToAzureContainerAppsCore(Cance
await auto.TypeAsync("aspire add Aspire.Hosting.Azure.AppContainers");
await auto.EnterAsync();

// In CI, aspire add shows a version selection prompt
if (DeploymentE2ETestHelpers.IsRunningInCI)
{
await auto.WaitUntilTextAsync("(based on NuGet.config)", timeout: TimeSpan.FromSeconds(60));
await auto.EnterAsync(); // select first version (PR build)
}

// TypeScript AppHost with bundle install: aspire add completes without a NuGet
// version selection prompt — the bundle provides the correct package versions directly.
await auto.WaitForSuccessPromptAsync(counter, TimeSpan.FromSeconds(180));

// Step 6: Modify apphost.ts to add Azure Container App Environment for deployment
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ private async Task DeployTypeScriptVnetSqlServerInfrastructureCore(CancellationT
await auto.TypeAsync("aspire init --language typescript");
await auto.EnterAsync();

// The init flow may show different prompts depending on environment:
// - Template version prompt (NuGet.config) — dismiss if shown
// - Agent init prompt — decline
// - Success prompt — init completed silently
var sawTemplateVersionPrompt = false;
var sawAgentInitPrompt = false;
await auto.WaitUntilAsync(s =>
Expand Down Expand Up @@ -145,39 +149,18 @@ await auto.WaitUntilAsync(s =>
output.WriteLine("Step 4a: Adding Azure Container Apps hosting package...");
await auto.TypeAsync("aspire add Aspire.Hosting.Azure.AppContainers");
await auto.EnterAsync();

if (DeploymentE2ETestHelpers.IsRunningInCI)
{
await auto.WaitUntilTextAsync("(based on NuGet.config)", timeout: TimeSpan.FromSeconds(60));
await auto.EnterAsync();
}

await auto.WaitForSuccessPromptAsync(counter, TimeSpan.FromSeconds(180));

// Step 4b: Add Aspire.Hosting.Azure.Network
output.WriteLine("Step 4b: Adding Azure Network hosting package...");
await auto.TypeAsync("aspire add Aspire.Hosting.Azure.Network");
await auto.EnterAsync();

if (DeploymentE2ETestHelpers.IsRunningInCI)
{
await auto.WaitUntilTextAsync("(based on NuGet.config)", timeout: TimeSpan.FromSeconds(60));
await auto.EnterAsync();
}

await auto.WaitForSuccessPromptAsync(counter, TimeSpan.FromSeconds(180));

// Step 4c: Add Aspire.Hosting.Azure.Sql
output.WriteLine("Step 4c: Adding Azure SQL hosting package...");
await auto.TypeAsync("aspire add Aspire.Hosting.Azure.Sql");
await auto.EnterAsync();

if (DeploymentE2ETestHelpers.IsRunningInCI)
{
await auto.WaitUntilTextAsync("(based on NuGet.config)", timeout: TimeSpan.FromSeconds(60));
await auto.EnterAsync();
}

await auto.WaitForSuccessPromptAsync(counter, TimeSpan.FromSeconds(180));

// Step 5: Modify apphost.ts to add VNet + PE + SQL infrastructure
Expand Down
Loading