diff --git a/.devcontainer/devcontainer-lock.json b/.devcontainer/devcontainer-lock.json index 4832aa467..6bb12f6d3 100644 --- a/.devcontainer/devcontainer-lock.json +++ b/.devcontainer/devcontainer-lock.json @@ -5,15 +5,15 @@ "resolved": "ghcr.io/azure/azure-dev/azd@sha256:01bbec064fcb34f7c8730b3e3c2cc210699e213419664524982268b2910c4f73", "integrity": "sha256:01bbec064fcb34f7c8730b3e3c2cc210699e213419664524982268b2910c4f73" }, - "ghcr.io/devcontainers/features/azure-cli:1": { - "version": "1.2.9", - "resolved": "ghcr.io/devcontainers/features/azure-cli@sha256:4549175fbfd3475d1d62e82f6e5425d03954a6ae06027b2515b0ba41a8206417", - "integrity": "sha256:4549175fbfd3475d1d62e82f6e5425d03954a6ae06027b2515b0ba41a8206417" + "ghcr.io/devcontainers/features/azure-cli:": { + "version": "1.3.0", + "resolved": "ghcr.io/devcontainers/features/azure-cli@sha256:d98f1066c077be0fa9d115b718f458bd803e415181b4a96f82a6f5d9f77241ac", + "integrity": "sha256:d98f1066c077be0fa9d115b718f458bd803e415181b4a96f82a6f5d9f77241ac" }, "ghcr.io/devcontainers/features/docker-in-docker:": { - "version": "2.17.0", - "resolved": "ghcr.io/devcontainers/features/docker-in-docker@sha256:25b9f05705ffba7dbe503230ac76081419306f8c8bc88e0ce78c4ecd99a0c78c", - "integrity": "sha256:25b9f05705ffba7dbe503230ac76081419306f8c8bc88e0ce78c4ecd99a0c78c" + "version": "3.0.1", + "resolved": "ghcr.io/devcontainers/features/docker-in-docker@sha256:ca2508495b01ba29eba93e8153772a2daa65eaa86471cc6863fe2a3d21933df9", + "integrity": "sha256:ca2508495b01ba29eba93e8153772a2daa65eaa86471cc6863fe2a3d21933df9" }, "ghcr.io/devcontainers/features/dotnet:2": { "version": "2.5.0", diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 218096142..eab883d53 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -7,7 +7,7 @@ "version": "10.0" }, "ghcr.io/devcontainers/features/powershell:2.0.2": {}, - "ghcr.io/devcontainers/features/azure-cli:1": { + "ghcr.io/devcontainers/features/azure-cli:": { "installBicep": true, "installPowerShell": true }, diff --git a/tests/TechHub.E2E.Tests/Helpers/BlazorHelpers.cs b/tests/TechHub.E2E.Tests/Helpers/BlazorHelpers.cs index 9e53f7b00..2300f2912 100644 --- a/tests/TechHub.E2E.Tests/Helpers/BlazorHelpers.cs +++ b/tests/TechHub.E2E.Tests/Helpers/BlazorHelpers.cs @@ -649,6 +649,11 @@ public static async Task FillBlazorInputAsync( var escapedValue = value.Replace("\\", "\\\\").Replace("'", "\\'"); var inputSelector = await locator.EvaluateAsync( "el => el.tagName.toLowerCase() + (el.type ? '[type=' + el.type + ']' : '')"); + var targetInputIndex = await locator.EvaluateAsync( + @"el => { + const selector = el.tagName.toLowerCase() + (el.type ? '[type=' + el.type + ']' : ''); + return Array.from(document.querySelectorAll(selector)).indexOf(el); + }"); await locator.Page.WaitForConditionAsync($@" () => {{ if (window.location.href.includes('{urlQueryParam}=')) return true; @@ -660,10 +665,17 @@ await locator.Page.WaitForConditionAsync($@" // returns the first in DOM order which may be the hidden mobile one. const inputs = document.querySelectorAll('{inputSelector}'); let input = null; - for (const inp of inputs) {{ - if (inp.offsetParent !== null || inp.getClientRects().length > 0) {{ - input = inp; - break; + // Prefer the originally targeted input index so retries keep firing + // on the same DOM slot (desktop vs mobile variants can coexist). + if ({targetInputIndex} >= 0 && {targetInputIndex} < inputs.length) {{ + input = inputs[{targetInputIndex}]; + }} + else {{ + for (const inp of inputs) {{ + if (inp.offsetParent !== null || inp.getClientRects().length > 0) {{ + input = inp; + break; + }} }} }} if (!input && inputs.length > 0) input = inputs[0]; diff --git a/tests/TechHub.E2E.Tests/Web/GitHubCopilotFeaturesTests.cs b/tests/TechHub.E2E.Tests/Web/GitHubCopilotFeaturesTests.cs index 3c5c24442..87221369c 100644 --- a/tests/TechHub.E2E.Tests/Web/GitHubCopilotFeaturesTests.cs +++ b/tests/TechHub.E2E.Tests/Web/GitHubCopilotFeaturesTests.cs @@ -424,15 +424,12 @@ public async Task GitHubCopilotFeatures_TimelineEntry_ExpandCollapse_ShouldWork( await Page.GotoRelativeAsync(PageUrl); await Page.WaitForBlazorReadyAsync(); - // The first entry auto-expands on page load; use the second entry to test the toggle. - var entries = Page.Locator(".features-timeline-entry"); - await Assertions.Expect(entries.First).ToBeVisibleAsync(); - - var testEntry = entries.Nth(1); + // Don't assume a fixed index is collapsed. Data can contain duplicate slugs, + // which may cause multiple entries to start expanded. + var testEntry = Page.Locator( + ".features-timeline-entry:has(.features-timeline-card[aria-expanded='false'])").First; var testCard = testEntry.Locator(".features-timeline-card"); await Assertions.Expect(testEntry).ToBeVisibleAsync(); - - // Second entry starts collapsed (only the first entry auto-expands) await Assertions.Expect(testCard).ToHaveAttributeAsync("aria-expanded", "false"); // Act - Click to expand