Skip to content

Commit 64184e2

Browse files
committed
Stabilize learn startup readiness assertion
1 parent 96733b4 commit 64184e2

File tree

2 files changed

+37
-15
lines changed

2 files changed

+37
-15
lines changed

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ Local `AGENTS.md` files may tighten these values, but they must not loosen them
223223
- The task is complete only when every planned checklist item is done and all relevant tests are green.
224224
- Summarize the change, risks, and verification before marking the task complete.
225225
- When the user asks to fix GitHub Actions or CI, do not stop at locally green commands: after pushing, watch the relevant GitHub Actions run and continue iterating until the replacement run is green or an explicit external blocker is documented.
226+
- When the browser suite has already flaked across repeated CI runs, do not keep cycling one-off threshold bumps; prove stability with repeated local browser-suite runs and remove or harden brittle root-cause assertions before calling the fix durable.
226227

227228
### Documentation
228229

tests/PrompterOne.App.UITests/Learn/LearnStartupAlignmentTests.cs

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
using System.Globalization;
12
using PrompterOne.Shared.Contracts;
3+
using Xunit.Sdk;
24
using static Microsoft.Playwright.Assertions;
35

46
namespace PrompterOne.App.UITests;
@@ -11,6 +13,7 @@ public sealed class LearnStartupAlignmentTests(StandaloneAppFixture fixture) : I
1113
private const string LayoutReadyFalseValue = "false";
1214
private const string LayoutReadyTrueValue = "true";
1315
private const double MaxReadyOrpDeltaPx = 6;
16+
private const int RequiredStableReadySamples = 2;
1417
private const string StartupWord = "Good";
1518
private readonly StandaloneAppFixture _fixture = fixture;
1619

@@ -51,8 +54,7 @@ await page.WaitForFunctionAsync(
5154
string.Equals(sample.RowOpacity, HiddenOpacity, StringComparison.Ordinal) &&
5255
string.Equals(sample.RowVisibility, HiddenVisibility, StringComparison.Ordinal));
5356

54-
await WaitForLearnLayoutReadyAsync(page);
55-
var readyStartupWordSample = await ReadCurrentLearnLayoutAsync(page);
57+
var readyStartupWordSample = await WaitForStableLearnLayoutAsync(page);
5658
Assert.Equal(LayoutReadyTrueValue, readyStartupWordSample.LayoutReady);
5759
Assert.InRange(readyStartupWordSample.OrpDeltaPx, 0, MaxReadyOrpDeltaPx);
5860
}
@@ -147,20 +149,39 @@ private static string BuildStartupTraceScript()
147149

148150
private static string ToJsString(string value) => $"'{value.Replace("\\", "\\\\").Replace("'", "\\'")}'";
149151

150-
private static Task WaitForLearnLayoutReadyAsync(Microsoft.Playwright.IPage page) =>
151-
page.WaitForFunctionAsync(
152-
"""
153-
args => {
154-
const display = document.querySelector(`[data-testid="${args.displayTestId}"]`);
155-
return display?.getAttribute(args.layoutReadyAttributeName) === args.layoutReadyValue;
156-
}
157-
""",
158-
new
152+
private static async Task<LearnStartupTraceSample> WaitForStableLearnLayoutAsync(Microsoft.Playwright.IPage page)
153+
{
154+
LearnStartupTraceSample? lastSample = null;
155+
var stableReadySamples = 0;
156+
var deadline = DateTimeOffset.UtcNow.AddMilliseconds(BrowserTestConstants.Timing.ExtendedVisibleTimeoutMs);
157+
158+
while (DateTimeOffset.UtcNow < deadline)
159+
{
160+
var currentSample = await ReadCurrentLearnLayoutAsync(page);
161+
lastSample = currentSample;
162+
163+
var isStableReady =
164+
string.Equals(currentSample.LayoutReady, LayoutReadyTrueValue, StringComparison.Ordinal) &&
165+
!string.Equals(currentSample.RowOpacity, HiddenOpacity, StringComparison.Ordinal) &&
166+
!string.Equals(currentSample.RowVisibility, HiddenVisibility, StringComparison.Ordinal) &&
167+
currentSample.OrpDeltaPx >= 0 &&
168+
currentSample.OrpDeltaPx <= MaxReadyOrpDeltaPx;
169+
170+
stableReadySamples = isStableReady
171+
? stableReadySamples + 1
172+
: 0;
173+
174+
if (stableReadySamples >= RequiredStableReadySamples)
159175
{
160-
displayTestId = UiTestIds.Learn.Display,
161-
layoutReadyAttributeName = LayoutReadyAttributeName,
162-
layoutReadyValue = LayoutReadyTrueValue
163-
});
176+
return currentSample;
177+
}
178+
179+
await page.WaitForTimeoutAsync(BrowserTestConstants.Timing.DiagnosticPollDelayMs);
180+
}
181+
182+
throw new XunitException(
183+
$"Learn layout did not stabilize. LayoutReady: {lastSample?.LayoutReady ?? "<null>"}; RowOpacity: {lastSample?.RowOpacity ?? "<null>"}; RowVisibility: {lastSample?.RowVisibility ?? "<null>"}; OrpDeltaPx: {(lastSample is null ? "<null>" : lastSample.OrpDeltaPx.ToString(CultureInfo.InvariantCulture))}; Text: {lastSample?.Text ?? "<null>"}.");
184+
}
164185

165186
private static Task<LearnStartupTraceSample> ReadCurrentLearnLayoutAsync(Microsoft.Playwright.IPage page) =>
166187
page.EvaluateAsync<LearnStartupTraceSample>(

0 commit comments

Comments
 (0)