Skip to content

Commit f1c8109

Browse files
committed
Stabilize browser suite route and timing flows
1 parent 6ec9d87 commit f1c8109

10 files changed

Lines changed: 77 additions & 47 deletions

File tree

full-suite-stabilization.plan.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,19 @@ Tracked failing tests from the current baseline:
238238
- switch `StandaloneAppFixture.NewPageAsync()` to isolated-by-default
239239
- add explicit `NewSharedPageAsync(...)` / explicit-key `NewSharedPagesAsync(...)` APIs for the few real shared-tab scenarios
240240
- evict and dispose shared contexts on any blank-page bootstrap failure before retrying
241+
- [x] `Release Pipeline` run `24233338921` fails remotely in `Reader`, `Editor`, and `Studio` after commit `6ec9d87`
242+
Symptom:
243+
- `Studio` route-heavy flows regress at `go-live-back` and related return paths even though first-route boot is already green
244+
- `Reader` still flakes in timing-sensitive playback assertions and the reverse previous-block transition despite the broad route bootstrap fixes
245+
- `Editor` still has route-changing action flows that pass locally in focused runs but fail under the full remote/browser load when they rely on raw navigation opens or brittle blur clicks
246+
Root cause:
247+
- some SPA route-changing controls still used raw Playwright clicks that waited for scheduled navigation instead of following the shared no-wait interaction contract
248+
- the remaining reader timing assertion budget did not account for the sample-poll granularity, and one reverse-transition test still mixed a poisoned pre-click baseline into its motion proof
249+
- a few editor scenarios still bypassed the shared route drivers or depended on a raw click outside the metadata rail to commit field changes
250+
Fix path:
251+
- move the remaining SPA route-changing interactions onto the shared `ClickAndContinueAsync(..., noWaitAfter: true)` path
252+
- harden reader timing and reverse-transition assertions against poll jitter while keeping the user-visible behavior contract intact
253+
- route editor open/theme/split/title flows through the shared route helpers and interaction driver so local and CI follow the same readiness path
241254

242255
## Ordered Plan
243256

@@ -306,3 +319,11 @@ Tracked failing tests from the current baseline:
306319
- `dotnet format ./PrompterOne.slnx` passed
307320
- post-format `dotnet build ./PrompterOne.slnx -warnaserror` passed
308321
- post-format `dotnet test @./tests/dotnet-test-progress.rsp --solution ./PrompterOne.slnx --max-parallel-test-modules 1` passed with `1162/1162` green in `7m 38.648s`
322+
- [x] Follow-up remediation for remote run `24233338921`
323+
Result:
324+
- `dotnet format ./PrompterOne.slnx` passed
325+
- `dotnet build ./PrompterOne.slnx -warnaserror` passed
326+
- `dotnet test @./tests/dotnet-test-progress.rsp --project ./tests/PrompterOne.Web.UITests.Editor/PrompterOne.Web.UITests.Editor.csproj` passed with `284/284`
327+
- `dotnet test @./tests/dotnet-test-progress.rsp --project ./tests/PrompterOne.Web.UITests.Reader/PrompterOne.Web.UITests.Reader.csproj` passed with `168/168`
328+
- `dotnet test @./tests/dotnet-test-progress.rsp --project ./tests/PrompterOne.Web.UITests.Studio/PrompterOne.Web.UITests.Studio.csproj` passed with `38/38`
329+
- `dotnet test @./tests/dotnet-test-progress.rsp --solution ./PrompterOne.slnx --max-parallel-test-modules 1` passed with `1162/1162` green in `7m 44.199s`

tests/PrompterOne.Web.UITests.Editor/Editor/EditorDatePickerThemeTests.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,7 @@ await UiScenarioArtifacts.CapturePageAsync(
5858

5959
private static async Task SwitchThemeAsync(IPage page, string theme)
6060
{
61-
await page.GotoAsync(
62-
BrowserTestConstants.Routes.Settings,
63-
new() { WaitUntil = WaitUntilState.DOMContentLoaded });
64-
await Expect(page.GetByTestId(UiTestIds.Settings.Page)).ToBeVisibleAsync(
65-
new() { Timeout = BrowserTestConstants.Timing.ExtendedVisibleTimeoutMs });
61+
await ShellRouteDriver.OpenSettingsAsync(page, "editor-date-picker-settings");
6662
await page.GetByTestId(UiTestIds.Settings.NavAppearance).ClickAsync();
6763
await Expect(page.GetByTestId(UiTestIds.Settings.AppearancePanel)).ToBeVisibleAsync();
6864
await page.GetByTestId(UiTestIds.Settings.ThemeOption(theme)).ClickAsync();

tests/PrompterOne.Web.UITests.Editor/Editor/EditorDocumentSplitFlowTests.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ await UiScenarioArtifacts.CapturePageAsync(
5353
await Expect(EditorMonacoDriver.SourceInput(page)).ToHaveValueAsync(EditorSplitFeedbackTestData.SplitSource);
5454
await Assert.That(new Uri(page.Url).AbsolutePath).IsEqualTo(AppRoutes.Editor);
5555

56-
await page.GetByTestId(UiTestIds.Editor.SplitResultOpenLibrary).ClickAsync();
56+
await UiInteractionDriver.ClickAndContinueAsync(
57+
page.GetByTestId(UiTestIds.Editor.SplitResultOpenLibrary),
58+
noWaitAfter: true);
5759
await ShellRouteDriver.WaitForLibraryReadyAsync(page);
5860
await page.GetByTestId(UiTestIds.Library.FolderAll).ClickAsync();
5961

@@ -92,7 +94,9 @@ await UiScenarioArtifacts.CapturePageAsync(
9294
await Expect(EditorMonacoDriver.SourceInput(page)).ToHaveValueAsync(EditorSplitFeedbackTestData.SplitBySpeakerSource);
9395
await Assert.That(new Uri(page.Url).AbsolutePath).IsEqualTo(AppRoutes.Editor);
9496

95-
await page.GetByTestId(UiTestIds.Editor.SplitResultOpenLibrary).ClickAsync();
97+
await UiInteractionDriver.ClickAndContinueAsync(
98+
page.GetByTestId(UiTestIds.Editor.SplitResultOpenLibrary),
99+
noWaitAfter: true);
96100
await ShellRouteDriver.WaitForLibraryReadyAsync(page);
97101
await page.GetByTestId(UiTestIds.Library.FolderAll).ClickAsync();
98102

tests/PrompterOne.Web.UITests.Editor/Editor/EditorLightThemeSurfaceTests.cs

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,10 @@ public Task EditorScreen_LightTheme_UsesReadableMainPanelsAndMonacoSurface() =>
2525
UiScenarioArtifacts.ResetScenario(ScenarioName);
2626

2727
await SwitchThemeAsync(page);
28-
await page.GotoAsync(
29-
BrowserTestConstants.Routes.EditorDemo,
30-
new() { WaitUntil = WaitUntilState.DOMContentLoaded });
31-
await Expect(page.GetByTestId(UiTestIds.Editor.Page)).ToBeVisibleAsync(
32-
new() { Timeout = BrowserTestConstants.Timing.ExtendedVisibleTimeoutMs });
28+
await EditorRouteDriver.OpenReadyAsync(page, BrowserTestConstants.Routes.EditorDemo, "editor-light-theme-editor");
3329
await Expect(page.Locator("html")).ToHaveAttributeAsync(
3430
BrowserTestConstants.SettingsFlow.HtmlThemeAttribute,
3531
BrowserTestConstants.SettingsFlow.LightTheme);
36-
await EditorMonacoDriver.WaitUntilReadyAsync(page);
3732

3833
var toolbar = page.GetByTestId(UiTestIds.Editor.Toolbar);
3934
var metadata = page.GetByTestId(UiTestIds.Editor.MetadataRail);
@@ -68,11 +63,7 @@ await Expect(page.Locator("html")).ToHaveAttributeAsync(
6863

6964
private static async Task SwitchThemeAsync(IPage page)
7065
{
71-
await page.GotoAsync(
72-
BrowserTestConstants.Routes.Settings,
73-
new() { WaitUntil = WaitUntilState.DOMContentLoaded });
74-
await Expect(page.GetByTestId(UiTestIds.Settings.Page)).ToBeVisibleAsync(
75-
new() { Timeout = BrowserTestConstants.Timing.ExtendedVisibleTimeoutMs });
66+
await ShellRouteDriver.OpenSettingsAsync(page, "editor-light-theme-settings");
7667
await page.GetByTestId(UiTestIds.Settings.NavAppearance).ClickAsync();
7768
await Expect(page.GetByTestId(UiTestIds.Settings.AppearancePanel)).ToBeVisibleAsync();
7869
await page.GetByTestId(UiTestIds.Settings.ThemeOption(BrowserTestConstants.SettingsFlow.LightTheme)).ClickAsync();

tests/PrompterOne.Web.UITests.Editor/Editor/EditorMetadataTitleFlowTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public Task EditorScreen_MetadataTitleEditUpdatesHeader_AndKeepsFrontMatterOutOf
2424

2525
await Expect(titleInput).ToHaveValueAsync(BrowserTestConstants.Scripts.ProductLaunchTitle);
2626
await titleInput.FillAsync(BrowserTestConstants.Editor.RetitledScript);
27-
await authorInput.ClickAsync();
27+
await UiInteractionDriver.ClickAndContinueAsync(authorInput);
2828

2929
await Expect(page.GetByTestId(UiTestIds.Header.Title))
3030
.ToHaveTextAsync(BrowserTestConstants.Editor.RetitledScript);

tests/PrompterOne.Web.UITests.Editor/Editor/EditorThemeFlowTests.cs

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,7 @@ public Task EditorScreen_LightTheme_EmotionMenu_UsesReadableDropdownAndCustomToo
2222

2323
try
2424
{
25-
await page.GotoAsync(
26-
BrowserTestConstants.Routes.Settings,
27-
new() { WaitUntil = WaitUntilState.DOMContentLoaded });
28-
await Expect(page.GetByTestId(UiTestIds.Settings.Page)).ToBeVisibleAsync(
29-
new() { Timeout = BrowserTestConstants.Timing.ExtendedVisibleTimeoutMs });
25+
await ShellRouteDriver.OpenSettingsAsync(page, "editor-theme-settings");
3026

3127
await page.GetByTestId(UiTestIds.Settings.NavAppearance).ClickAsync();
3228
await Expect(page.GetByTestId(UiTestIds.Settings.AppearancePanel)).ToBeVisibleAsync();
@@ -35,12 +31,7 @@ await Expect(page.Locator("html")).ToHaveAttributeAsync(
3531
BrowserTestConstants.SettingsFlow.HtmlThemeAttribute,
3632
BrowserTestConstants.SettingsFlow.LightTheme);
3733

38-
await page.GotoAsync(
39-
BrowserTestConstants.Routes.EditorDemo,
40-
new() { WaitUntil = WaitUntilState.DOMContentLoaded });
41-
await Expect(page.GetByTestId(UiTestIds.Editor.Page)).ToBeVisibleAsync(
42-
new() { Timeout = BrowserTestConstants.Timing.ExtendedVisibleTimeoutMs });
43-
await EditorMonacoDriver.WaitUntilReadyAsync(page);
34+
await EditorRouteDriver.OpenReadyAsync(page, BrowserTestConstants.Routes.EditorDemo, "editor-theme-editor");
4435
await Expect(page.Locator("html")).ToHaveAttributeAsync(
4536
BrowserTestConstants.SettingsFlow.HtmlThemeAttribute,
4637
BrowserTestConstants.SettingsFlow.LightTheme);

tests/PrompterOne.Web.UITests.Reader/Reader/ReaderPlaybackTimingTests.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,18 @@ await Expect(playToggle)
5151
await Assert.That(samples.Select(sample => sample.Word).ToArray()).IsEquivalentTo(BrowserTestConstants.ReaderTiming.ExpectedWords, CollectionOrdering.Matching);
5252
await Assert.That(samples.Select(sample => sample.EffectiveWpm).ToArray()).IsEquivalentTo(TeleprompterEffectiveWpmSequence, CollectionOrdering.Matching);
5353

54+
var timingTolerance = BrowserTestConstants.ReaderTiming.TeleprompterTimingToleranceMs
55+
+ BrowserTestConstants.ReaderTiming.CapturePollIntervalMs;
56+
5457
for (var sampleIndex = 1; sampleIndex < samples.Length; sampleIndex++)
5558
{
5659
var previousSample = samples[sampleIndex - 1];
5760
var currentSample = samples[sampleIndex];
5861
var observedDelay = currentSample.AtMs - previousSample.AtMs;
5962
var expectedDelay = previousSample.DurationMs + previousSample.PauseMs;
6063

61-
await Assert.That(observedDelay).IsBetween(expectedDelay - BrowserTestConstants.ReaderTiming.TeleprompterTimingToleranceMs, expectedDelay + BrowserTestConstants.ReaderTiming.TeleprompterTimingToleranceMs);
64+
await Assert.That(observedDelay)
65+
.IsBetween(expectedDelay - timingTolerance, expectedDelay + timingTolerance);
6266
}
6367
});
6468

tests/PrompterOne.Web.UITests.Reader/Teleprompter/TeleprompterPlaybackContinuityTests.cs

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -78,19 +78,19 @@ public Task Teleprompter_PreviousBlockTransition_ReversesAndBringsTheReturningBl
7878
await page.GetByTestId(UiTestIds.Teleprompter.NextBlock).ClickAsync();
7979
await Expect(page.GetByTestId(UiTestIds.Teleprompter.BlockIndicator))
8080
.ToHaveTextAsync(BrowserTestConstants.Regexes.ReaderSecondBlockIndicator);
81-
await page.WaitForTimeoutAsync(BrowserTestConstants.Timing.ReaderTransitionSettleDelayMs);
8281

8382
var outgoingCard = page.GetByTestId(UiTestIds.Teleprompter.Card(1));
8483
var returningCard = page.GetByTestId(UiTestIds.Teleprompter.Card(0));
8584
await Expect(outgoingCard).ToBeVisibleAsync();
86-
87-
var samples = new List<ReaderTransitionSample>
88-
{
89-
await CaptureReaderTransitionSampleAsync(outgoingCard, returningCard)
90-
};
85+
await Expect(outgoingCard)
86+
.ToHaveAttributeAsync(UiDataAttributes.Teleprompter.CardState, UiDataAttributes.Teleprompter.ActiveState);
87+
await Expect(returningCard)
88+
.ToHaveAttributeAsync(UiDataAttributes.Teleprompter.CardState, UiDataAttributes.Teleprompter.PreviousState);
89+
await WaitForStableCardPositionAsync(returningCard);
9190

9291
await page.GetByTestId(UiTestIds.Teleprompter.PreviousBlock).ClickAsync();
9392

93+
var samples = new List<ReaderTransitionSample>();
9494
for (var sampleIndex = 0; sampleIndex < BrowserTestConstants.Teleprompter.TransitionProbeSampleCount; sampleIndex++)
9595
{
9696
await page.WaitForTimeoutAsync(BrowserTestConstants.Teleprompter.TransitionProbeIntervalMs);
@@ -101,6 +101,8 @@ await CaptureReaderTransitionSampleAsync(outgoingCard, returningCard)
101101
// so its fixed-index DOM position can jump between layout states on slower CI runners.
102102
// The user-visible contract is that the previous block returns from above and becomes active again.
103103
await AssertMovesDownWithoutReversal(samples.Select(sample => sample.IncomingTop).ToArray(), "Returning previous block");
104+
await Expect(returningCard)
105+
.ToHaveAttributeAsync(UiDataAttributes.Teleprompter.CardState, UiDataAttributes.Teleprompter.ActiveState);
104106
await Expect(page.GetByTestId(UiTestIds.Teleprompter.BlockIndicator))
105107
.ToHaveTextAsync(BrowserTestConstants.Regexes.ReaderFirstBlockIndicator);
106108
});
@@ -151,6 +153,23 @@ private static async Task<ReaderTransitionSample> CaptureReaderTransitionSampleA
151153
private static Task<double> GetElementTopAsync(ILocator locator) =>
152154
locator.EvaluateAsync<double>("element => element.getBoundingClientRect().top");
153155

156+
private static async Task WaitForStableCardPositionAsync(ILocator locator)
157+
{
158+
var previousTop = await GetElementTopAsync(locator);
159+
160+
for (var attempt = 0; attempt < BrowserTestConstants.Teleprompter.TransitionProbeSampleCount; attempt++)
161+
{
162+
await Task.Delay(BrowserTestConstants.Teleprompter.TransitionProbeIntervalMs);
163+
var currentTop = await GetElementTopAsync(locator);
164+
if (Math.Abs(currentTop - previousTop) <= BrowserTestConstants.Teleprompter.TransitionReversalTolerancePx)
165+
{
166+
return;
167+
}
168+
169+
previousTop = currentTop;
170+
}
171+
}
172+
154173
private static async Task AssertMovesUpWithoutReversal(IReadOnlyList<double> positions, string label)
155174
{
156175
await Assert.That(positions).IsNotEmpty();
@@ -169,11 +188,20 @@ private static async Task AssertMovesDownWithoutReversal(IReadOnlyList<double> p
169188
{
170189
await Assert.That(positions).IsNotEmpty();
171190

172-
var maximumPosition = positions.Max();
173-
var totalTravel = maximumPosition - positions[0];
191+
var minimumIndex = 0;
192+
for (var index = 1; index < positions.Count; index++)
193+
{
194+
if (positions[index] < positions[minimumIndex])
195+
{
196+
minimumIndex = index;
197+
}
198+
}
199+
200+
var finalPosition = positions[^1];
201+
var totalTravel = finalPosition - positions[minimumIndex];
174202
await Assert.That(totalTravel >= BrowserTestConstants.Teleprompter.TransitionMinimumTravelPx).IsTrue().Because($"{label} did not travel downward enough. Samples: {FormatPositions(positions)}");
175203

176-
for (var index = 1; index < positions.Count; index++)
204+
for (var index = minimumIndex + 1; index < positions.Count; index++)
177205
{
178206
await Assert.That(positions[index] >= positions[index - 1] - BrowserTestConstants.Teleprompter.TransitionReversalTolerancePx).IsTrue().Because($"{label} moved back up. Samples: {FormatPositions(positions)}");
179207
}

tests/PrompterOne.Web.UITests.Studio/Infrastructure/StudioRouteDriver.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -140,12 +140,7 @@ private static async Task NavigateBackAsync(
140140

141141
await Expect(backControl).ToBeVisibleAsync();
142142
await Expect(backControl).ToHaveAttributeAsync("href", expectedBackRoute);
143-
await backControl.ScrollIntoViewIfNeededAsync();
144-
await backControl.ClickAsync(new()
145-
{
146-
Force = true,
147-
Timeout = BrowserTestConstants.Timing.ExtendedVisibleTimeoutMs
148-
});
143+
await UiInteractionDriver.ClickAndContinueAsync(backControl, noWaitAfter: true);
149144

150145
await WaitForGoLiveExitAsync(page);
151146
await waitForTargetAsync(page);

tests/PrompterOne.Web.UITests/Support/EditorIsolatedDraftDriver.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ private static async Task SetTitleAsync(IPage page, string title)
105105
}
106106

107107
await titleInput.FillAsync(title);
108-
await page.GetByTestId(UiTestIds.Editor.Author).ClickAsync();
108+
await UiInteractionDriver.ClickAndContinueAsync(page.GetByTestId(UiTestIds.Editor.Author));
109109
await Expect(titleInput).ToHaveValueAsync(title);
110110
await Expect(page.GetByTestId(UiTestIds.Header.Title)).ToHaveTextAsync(title);
111111
}

0 commit comments

Comments
 (0)