Skip to content

[cli] Add E2E sample upgrade tests for aspire-samples#15421

Draft
mitchdenny wants to merge 14 commits intomainfrom
feature/sample-upgrade-e2e-tests
Draft

[cli] Add E2E sample upgrade tests for aspire-samples#15421
mitchdenny wants to merge 14 commits intomainfrom
feature/sample-upgrade-e2e-tests

Conversation

@mitchdenny
Copy link
Member

@mitchdenny mitchdenny commented Mar 20, 2026

Description

This PR introduces E2E sample upgrade tests that validate aspire update can upgrade external repos to PR/CI builds of Aspire, and fixes two bugs in aspire update --channel that were blocking this scenario.

Bug Fixes in aspire update --channel

  1. NuGet config directory not passed to dotnet package add: When using an explicit channel (e.g., --channel pr-15421), the NuGet config with the channel's hive package source was created in a specific directory, but dotnet package add was invoked from the project directory where it couldn't discover that config. Fixed by passing the NuGet config directory as the working directory for dotnet package add.

  2. Package source mapping causes restore failures during individual package adds: The NuGet config's <packageSourceMapping> routes Aspire* packages exclusively to the hive source. During dotnet package add's implicit restore, OTHER Aspire packages still at their old versions can't be found in the hive (which only has the new channel versions). Fixed by using --no-restore on each dotnet package add when using an explicit channel, then running a single dotnet restore after all packages are updated.

Sample Upgrade E2E Test Infrastructure

  • SampleUpgradeHelpers.cs: Shared helpers for cloning repos, running aspire update with interactive prompt handling, running aspire run with dashboard URL capture, and stopping the apphost.
  • DashboardVerificationHelpers.cs: Playwright-based dashboard verification — navigates to the dashboard, verifies expected resources are present, takes a screenshot for CI artifacts. Also provides PollEndpointAsync for host-side HTTP endpoint polling with retry logic.
  • Host networking: Uses Docker's --network host mode so services started by aspire run inside the container are directly accessible from the test process for Playwright and HttpClient verification.
  • SampleUpgradeAspireWithNodeTests.cs: First sample test — clones dotnet/aspire-samples, upgrades aspire-with-node to the PR build, verifies the dashboard shows cache, weatherapi, and frontend resources, and takes a dashboard screenshot.

Validation

  • All 1672 CLI unit tests pass locally
  • E2E test verified via CI: SDK + all PackageReferences upgraded to PR version, aspire run starts successfully
  • Dashboard screenshot saved as CI artifact

Fixes # (issue)

Checklist

  • Is this a feature or a bug fix? Both - bug fix + new test infrastructure
  • Are there any breaking changes? No
  • Have you written unit tests? N/A - fixes are in E2E flow, validated by E2E test

Add infrastructure for testing that PR/CI builds of Aspire can upgrade
and run external repos from dotnet/aspire-samples. Includes:

- SampleUpgradeHelpers.cs: Shared helpers for clone, update, run, and
  verify workflows (CloneSampleRepoAsync, AspireUpdateInSampleAsync,
  AspireRunSampleAsync, StopAspireRunAsync, VerifyHttpEndpointAsync)
- SampleUpgradeAspireWithNodeTests.cs: First sample test targeting the
  aspire-with-node sample (Node.js + .NET + Redis)

Each test class becomes its own CI job via SplitTestsOnCI. The flow is:
install CLI from PR → git clone aspire-samples → aspire update → aspire
run → verify → Ctrl+C stop.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 20, 2026 01:25
@github-actions
Copy link
Contributor

github-actions bot commented Mar 20, 2026

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 15421

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 15421"

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds end-to-end test infrastructure to validate that Aspire CLI PR/CI builds can upgrade and run external samples from dotnet/aspire-samples, establishing a reusable pattern starting with the aspire-with-node sample.

Changes:

  • Added reusable Hex1bTerminalAutomator extension helpers for cloning sample repos, running aspire update, running/stopping aspire run, and basic HTTP verification.
  • Added the first sample upgrade E2E test covering aspire-with-node upgrade + run flow inside the existing Docker-based E2E harness.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
tests/Aspire.Cli.EndToEnd.Tests/SampleUpgradeAspireWithNodeTests.cs New E2E test that installs the CLI, clones aspire-samples, upgrades aspire-with-node, runs it, and stops it.
tests/Aspire.Cli.EndToEnd.Tests/Helpers/SampleUpgradeHelpers.cs New shared helpers to support sample-upgrade E2E scenarios (clone/update/run/stop/endpoint checks).

.RightText(" ERR:");
if (errorSearcher.Search(snapshot).Count > 0)
{
return true;
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

In AspireUpdateInSampleAsync, an error prompt ([N ERR:...]) causes the wait loop to return, but the method then increments the counter and continues as if the update succeeded. This can let failing aspire update runs pass silently and lead to confusing downstream failures. Consider tracking whether an ERR prompt was observed (similar to WaitForSuccessPromptFailFastAsync) and throwing an exception when it occurs (ideally including the sequence number / guidance to check the terminal recording).

Suggested change
return true;
throw new InvalidOperationException(
$"aspire update reported an error at sequence [{expectedCounter}]. " +
"Check the terminal recording for details.");

Copilot uses AI. Check for mistakes.
Comment on lines +24 to +29
var workspace = TemporaryWorkspace.Create(output);

using var terminal = CliE2ETestHelpers.CreateDockerTestTerminal(
repoRoot, installMode, output,
mountDockerSocket: true,
workspace: workspace);
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

TemporaryWorkspace implements IDisposable and deletes its temp directory on disposal. This test creates a workspace but never disposes it, which can leak temp directories over repeated runs (local dev and CI). Consider changing this to using var workspace = TemporaryWorkspace.Create(output); so cleanup happens after the Docker terminal is disposed.

Copilot uses AI. Check for mistakes.
Mitch Denny and others added 5 commits March 20, 2026 12:41
- Handle "Perform updates? [y/n]" confirmation prompt (was causing timeout)
- Add VerifySampleWasUpgradedAsync helper that checks the csproj no longer
  contains the original version and dumps the updated csproj to the recording
- Call verification step in the aspire-with-node test

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Pass --channel pr-{N} when in PullRequest install mode so aspire update
  resolves packages from the PR hive instead of stable nuget.org
- Add handlers for NuGet.config prompts that appear with explicit channels:
  "Which directory for NuGet.config file?" and "Apply these changes to NuGet.config?"
- These prompts only appear when using explicit (non-implicit) channels

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…hase

The aspire update --channel command uses a temp NuGet config for the search
phase but the apply phase (dotnet add package) needs the PR hive source in
the project NuGet config. Pre-creating NuGet.config ensures packages can be
resolved during apply.

Also navigate to sample directory before update and increase update timeout.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
aspire update --channel has issues with dotnet package add failing to resolve
PR hive packages during the apply phase. Instead:
1. Run aspire update (implicit channel) for structural migration
2. In PR mode, detect the PR version from the hive and use sed to replace
   all Aspire version strings in the csproj
3. Set up NuGet.config with the PR hive source for restore/build
4. Verify dotnet restore succeeds before running

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The nupkg filename pattern was case-sensitive and failed to match.
Use grep -oE with a version regex pattern instead of sed on a
specific package name. Also list hive packages for diagnostics
and add a guard that fails fast if version detection returns empty.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mitchdenny mitchdenny marked this pull request as draft March 20, 2026 03:55
Mitch Denny and others added 8 commits March 20, 2026 14:57
aspire run already restores as part of building. The explicit restore
was a debugging checkpoint that is no longer needed and wastes ~30s.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Instead of parsing terminal output with grep to verify upgrades,
mount a host temp directory into the Docker container. This lets
the test read the csproj via normal file I/O and use Assert.DoesNotContain /
Assert.Contains for cleaner, more reliable verification.

Removes the VerifySampleWasUpgradedAsync terminal helper in favor
of direct File.ReadAllTextAsync on the mounted path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Instead of upgrading to stable then using sed to replace versions,
pass --channel directly to aspire update so it resolves packages
from the PR hive. This tests the actual intended upgrade path.

Removes SetupPrHiveNuGetConfigAsync and UpgradeToPrVersionAsync
helpers since aspire update handles NuGet.config creation itself.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
aspire update --channel pr-{N} correctly updates the SDK version
and creates the NuGet.config, but the apply phase (dotnet package add)
fails for individual PackageReference entries due to a known issue
where --configfile is not passed to the underlying NuGet restore.

After aspire update, read the csproj from the mounted volume,
extract the PR version from the SDK attribute (which was set correctly),
and use sed to fix any remaining package references still on old versions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When using an explicit channel (e.g., a PR hive), aspire update creates a
NuGet.config with the channel's package source for the search phase, but
the apply phase calls 'dotnet package add' without making that config
discoverable. Since 'dotnet package add' doesn't support --configfile,
use the NuGet config directory as the working directory (same pattern as
InstallTemplateAsync).

Changes:
- DotNetCliRunner: Add AddPackageAsync overload with nugetConfigDirectory
  parameter; use it as working directory when provided
- ProjectUpdater: Store _nugetConfigDirectory field during explicit channel
  config creation; pass it through to UpdatePackageReferenceInProject
- Remove sed workaround from E2E test now that the fix is in place
- Update test mocks to implement new interface method

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When using an explicit channel (e.g., a PR hive), the NuGet.config's
package source mapping routes Aspire* packages exclusively to the hive
source. During 'dotnet package add', NuGet does an implicit restore that
fails because other Aspire packages still at old versions don't exist
in the hive source. Fix by:

1. Setting --no-restore on each 'dotnet package add' when using an
   explicit channel to avoid restore failures from partial upgrades
2. Running a single 'dotnet restore' after all packages are updated,
   when all versions are consistent

Also default TestDotNetCliRunner.RestoreAsync to succeed when no
callback is set, matching the pattern used by GetNuGetConfigPathsAsync.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…pgrade tests

- Add useHostNetwork parameter to CreateDockerTestTerminal (sets c.Network = "host")
- Modify AspireRunSampleAsync to capture and return dashboard URL from terminal output
- Add DashboardVerificationHelpers with Playwright-based dashboard verification
- Add PollEndpointAsync for host-side HTTP endpoint polling with retry
- Add Microsoft.Playwright package reference for browser-based dashboard testing
- Update SampleUpgradeAspireWithNodeTests to verify dashboard shows expected resources
- Dashboard screenshots saved to testresults/screenshots/ for CI artifact upload
- Update CLI E2E testing skill with sample upgrade test documentation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…re only

The NuGetConfigMerger already creates the NuGet.config in a discoverable
ancestor directory, so the working directory change on AddPackageAsync was
unnecessary. The only real bug was the implicit restore conflict from
package source mappings during dotnet package add for explicit channels.

Simplified to:
- _isExplicitChannel boolean flag (no DirectoryInfo field)
- Pass noRestore: true for explicit channels
- Final dotnet restore after all packages updated
- No IDotNetCliRunner interface changes

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Contributor

🎬 CLI E2E Test Recordings — 53 recordings uploaded (commit dc2a7b6)

View recordings
Test Recording
AddPackageInteractiveWhileAppHostRunningDetached ▶️ View Recording
AddPackageWhileAppHostRunningDetached ▶️ View Recording
AgentCommands_AllHelpOutputs_AreCorrect ▶️ View Recording
AgentInitCommand_DefaultSelection_InstallsSkillOnly ▶️ View Recording
AgentInitCommand_MigratesDeprecatedConfig ▶️ View Recording
AspireAddPackageVersionToDirectoryPackagesProps ▶️ View Recording
AspireUpdateRemovesAppHostPackageVersionFromDirectoryPackagesProps ▶️ View Recording
Banner_DisplayedOnFirstRun ▶️ View Recording
Banner_DisplayedWithExplicitFlag ▶️ View Recording
CertificatesClean_RemovesCertificates ▶️ View Recording
CertificatesTrust_WithNoCert_CreatesAndTrustsCertificate ▶️ View Recording
CertificatesTrust_WithUntrustedCert_TrustsCertificate ▶️ View Recording
ConfigSetGet_CreatesNestedJsonFormat ▶️ View Recording
CreateAndDeployToDockerCompose ▶️ View Recording
CreateAndDeployToDockerComposeInteractive ▶️ View Recording
CreateAndPublishToKubernetes ▶️ View Recording
CreateAndRunAspireStarterProject ▶️ View Recording
CreateAndRunAspireStarterProjectWithBundle ▶️ View Recording
CreateAndRunEmptyAppHostProject ▶️ View Recording
CreateAndRunJsReactProject ▶️ View Recording
CreateAndRunPythonReactProject ▶️ View Recording
CreateAndRunTypeScriptEmptyAppHostProject ▶️ View Recording
CreateAndRunTypeScriptStarterProject ▶️ View Recording
CreateStartAndStopAspireProject ▶️ View Recording
CreateTypeScriptAppHostWithViteApp ▶️ View Recording
DescribeCommandResolvesReplicaNames ▶️ View Recording
DescribeCommandShowsRunningResources ▶️ View Recording
DetachFormatJsonProducesValidJson ▶️ View Recording
DoctorCommand_DetectsDeprecatedAgentConfig ▶️ View Recording
DoctorCommand_WithSslCertDir_ShowsTrusted ▶️ View Recording
DoctorCommand_WithoutSslCertDir_ShowsPartiallyTrusted ▶️ View Recording
GlobalMigration_HandlesCommentsAndTrailingCommas ▶️ View Recording
GlobalMigration_HandlesMalformedLegacyJson ▶️ View Recording
GlobalMigration_PreservesAllValueTypes ▶️ View Recording
GlobalMigration_SkipsWhenNewConfigExists ▶️ View Recording
GlobalSettings_MigratedFromLegacyFormat ▶️ View Recording
InvalidAppHostPathWithComments_IsHealedOnRun ▶️ View Recording
LogsCommandShowsResourceLogs ▶️ View Recording
PsCommandListsRunningAppHost ▶️ View Recording
PsFormatJsonOutputsOnlyJsonToStdout ▶️ View Recording
PublishWithDockerComposeServiceCallbackSucceeds ▶️ View Recording
RestoreGeneratesSdkFiles ▶️ View Recording
RunWithMissingAwaitShowsHelpfulError ▶️ View Recording
SecretCrudOnDotNetAppHost ▶️ View Recording
SecretCrudOnTypeScriptAppHost ▶️ View Recording
StagingChannel_ConfigureAndVerifySettings_ThenSwitchChannels ▶️ View Recording
StopAllAppHostsFromAppHostDirectory ▶️ View Recording
StopAllAppHostsFromUnrelatedDirectory ▶️ View Recording
StopNonInteractiveMultipleAppHostsShowsError ▶️ View Recording
StopNonInteractiveSingleAppHost ▶️ View Recording
StopWithNoRunningAppHostExitsSuccessfully ▶️ View Recording
TypeScriptAppHostWithProjectReferenceIntegration ▶️ View Recording
UpgradeAndRunAspireWithNodeSample ▶️ View Recording

📹 Recordings uploaded automatically from CI run #23334857945

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants