diff --git a/tests/Aspire.Cli.EndToEnd.Tests/NewWithAgentInitTests.cs b/tests/Aspire.Cli.EndToEnd.Tests/NewWithAgentInitTests.cs index 5682523299a..f7c3857743b 100644 --- a/tests/Aspire.Cli.EndToEnd.Tests/NewWithAgentInitTests.cs +++ b/tests/Aspire.Cli.EndToEnd.Tests/NewWithAgentInitTests.cs @@ -53,7 +53,10 @@ public async Task AspireNew_WithAgentInit_InstallsPlaywrightWithoutErrors() // Run aspire new with the Starter template, going through all prompts manually // so we can ACCEPT the agent init prompt instead of declining it. - await auto.TypeAsync("aspire new"); + // Pass --skill-locations and --skills as CLI flags so the test does not depend on + // the position or count of entries in the interactive skill-selection menus, which + // change whenever the bundle ships a new skill. + await auto.TypeAsync("aspire new --skill-locations claudecode --skills playwright-cli"); await auto.EnterAsync(); // Template selection: accept default Starter App @@ -107,32 +110,9 @@ await auto.WaitUntilAsync( await auto.WaitAsync(500); await auto.TypeAsync("y"); - // Agent init: skill location - select Claude Code - await auto.WaitUntilAsync( - s => s.ContainsText("skill files be installed"), - timeout: TimeSpan.FromSeconds(60), - description: "skill location prompt"); - await auto.TypeAsync(" "); // Toggle off default Standard location - await auto.DownAsync(); - await auto.TypeAsync(" "); // Toggle on Claude Code location - await auto.EnterAsync(); - - // Agent init: skill selection - this test validates Playwright acquisition, - // so deselect the default Aspire bundle skills and select only Playwright CLI. - await auto.WaitUntilAsync( - s => s.ContainsText("skills should be installed"), - timeout: TimeSpan.FromSeconds(30), - description: "skill selection prompt"); - await auto.TypeAsync(" "); // Toggle off Aspire - await auto.DownAsync(); - await auto.TypeAsync(" "); // Toggle off aspireify - await auto.DownAsync(); - await auto.TypeAsync(" "); // Toggle off aspire-deployment - await auto.DownAsync(); - await auto.TypeAsync(" "); // Toggle on Playwright CLI - await auto.EnterAsync(); - // Wait for agent init to complete (downloads @playwright/cli from npm). + // Skill location and skill selection are provided via --skill-locations/--skills flags + // on the aspire new invocation above, so no interactive navigation is needed here. // Fail the test immediately if a provenance verification error appears. await auto.WaitUntilAsync(s => { diff --git a/tests/Aspire.Dashboard.Tests/Integration/Playwright/BrowserTokenAuthenticationTests.cs b/tests/Aspire.Dashboard.Tests/Integration/Playwright/BrowserTokenAuthenticationTests.cs index e5f3dac09a2..2b24ca26dfe 100644 --- a/tests/Aspire.Dashboard.Tests/Integration/Playwright/BrowserTokenAuthenticationTests.cs +++ b/tests/Aspire.Dashboard.Tests/Integration/Playwright/BrowserTokenAuthenticationTests.cs @@ -28,7 +28,11 @@ public sealed class BrowserTokenDashboardServerWithHttpAndHttpsFixture : Dashboa { public BrowserTokenDashboardServerWithHttpAndHttpsFixture() { - Configuration[DashboardConfigNames.DashboardFrontendUrlName.ConfigKey] = "https://localhost:0;http://localhost:0"; + // Bind to 127.0.0.1 rather than the "localhost" hostname: Kestrel rejects dynamic-port + // binding (":0") on "localhost" with "Dynamic port binding is not supported when binding + // to localhost. You must either bind to 127.0.0.1:0 or [::1]:0, or both." The WebKit test + // navigates to the resolved address with IgnoreHTTPSErrors, so the loopback IP is fine. + Configuration[DashboardConfigNames.DashboardFrontendUrlName.ConfigKey] = "https://127.0.0.1:0;http://127.0.0.1:0"; Configuration[DashboardConfigNames.DashboardFrontendAuthModeName.ConfigKey] = nameof(FrontendAuthMode.BrowserToken); Configuration[DashboardConfigNames.DashboardFrontendBrowserTokenName.ConfigKey] = "VALID_TOKEN"; } diff --git a/tests/Aspire.Hosting.Tests/DistributedApplicationTests.cs b/tests/Aspire.Hosting.Tests/DistributedApplicationTests.cs index 4cdf75dd632..579c97de408 100644 --- a/tests/Aspire.Hosting.Tests/DistributedApplicationTests.cs +++ b/tests/Aspire.Hosting.Tests/DistributedApplicationTests.cs @@ -1809,7 +1809,6 @@ public async Task ProxylessContainerCanBeReferenced() await clientA.GetStringAsync("/").DefaultTimeout(TestConstants.DefaultOrchestratorTestTimeout); var s = app.Services.GetRequiredService(); - var serviceList = await s.ListAsync().DefaultTimeout(); var exeList = await s.ListAsync().DefaultTimeout(); var service = Assert.Single(exeList, c => $"{testName}-servicea".Equals(c.AppModelResourceName)); @@ -1831,7 +1830,8 @@ public async Task ProxylessContainerCanBeReferenced() Assert.Equal(port, Assert.Single(redisContainer.Spec.Ports!).HostPort); } - var otherRedisService = GetEndpointService(serviceList, redisNoPort.Resource, redisNoPort.Resource.PrimaryEndpoint); + var otherRedisService = await WaitForAllocatedProxylessServiceAsync(s, redisNoPort.Resource, redisNoPort.Resource.PrimaryEndpoint) + .DefaultTimeout(TestConstants.DefaultOrchestratorTestLongTimeout); var otherRedisPort = AssertAllocatedProxylessPort(otherRedisService); var otherRedisEnv = Assert.Single(service.Spec.Env!, e => e.Name == $"ConnectionStrings__{testName}-redisNoPort"); sslVal = redisNoPort.Resource.TlsEnabled ? ",ssl=true" : string.Empty; @@ -1908,7 +1908,8 @@ public async Task WithEndpointProxySupportDisablesProxies() Assert.Equal(port, Assert.Single(redisContainer.Spec.Ports!).HostPort); } - var otherRedisService = GetEndpointService(serviceList, redisNoPort.Resource, redisNoPort.Resource.PrimaryEndpoint); + var otherRedisService = await WaitForAllocatedProxylessServiceAsync(s, redisNoPort.Resource, redisNoPort.Resource.PrimaryEndpoint) + .DefaultTimeout(TestConstants.DefaultOrchestratorTestLongTimeout); var otherRedisPort = AssertAllocatedProxylessPort(otherRedisService); var otherRedisEnv = Assert.Single(service.Spec.Env!, e => e.Name == $"ConnectionStrings__{testName}-redisNoPort"); sslVal = redisNoPort.Resource.TlsEnabled ? ",ssl=true" : string.Empty; @@ -2304,11 +2305,25 @@ private static IResourceBuilder AddRedisContainer(IDistribute .WithImageRegistry(AspireTestContainerRegistry); } - private static Service GetEndpointService(IEnumerable services, RedisResource redis, EndpointReference endpoint) + private static Task WaitForAllocatedProxylessServiceAsync(IKubernetesService kubernetesService, RedisResource redis, EndpointReference endpoint, CancellationToken cancellationToken = default) { var hasMultipleEndpoints = redis.Annotations.OfType().Count() > 1; var expectedServiceName = hasMultipleEndpoints ? $"{redis.Name}-{endpoint.EndpointName}" : redis.Name; - return Assert.Single(services, s => string.Equals(s.Metadata.Name, expectedServiceName, StringComparison.Ordinal)); + + // A proxyless endpoint's host port is assigned synchronously by Aspire (Service.Spec.Port), + // but DCP reports the actually-bound port back asynchronously via Service.Status.EffectivePort. + // Orchestrator startup intentionally does NOT wait for DCP to echo the effective address of + // proxyless services (they are excluded from the startup address-wait in DcpExecutor) because + // connection strings are built from the Aspire-assigned Spec.Port and are usable immediately. + // As a result, a fast agent can observe the Service before DCP has populated EffectivePort, so + // wait until it appears before asserting on it (otherwise the EffectivePort assertion in + // AssertAllocatedProxylessPort races and is null on fast Linux CI agents while passing on Windows). + return KubernetesHelper.GetResourceByNameAsync( + kubernetesService, + expectedServiceName, + string.Empty, + service => service.Status?.EffectivePort is not null, + cancellationToken); } private static int AssertAllocatedProxylessPort(Service service)