Skip to content

Commit ed734d2

Browse files
committed
Stabilize reader alignment assertions
1 parent ecb8e21 commit ed734d2

4 files changed

Lines changed: 93 additions & 69 deletions

File tree

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public sealed class TeleprompterAlignmentTooltipFlowTests(StandaloneAppFixture f
1212
private const string LeftRailTooltipStep = "01-left-rail-tooltip";
1313
private const string RightRailTooltipScenario = "teleprompter-alignment-tooltips-right";
1414
private const string RightRailTooltipStep = "02-right-rail-tooltip";
15-
private const int RevealProbeSchedulerSlackPolls = 4;
15+
private const int RevealProbeSchedulerSlackPolls = 8;
1616

1717
private readonly record struct ElementBounds(double Left, double Top, double Right, double Bottom);
1818

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

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -30,21 +30,24 @@ await Expect(page.GetByTestId(UiTestIds.Teleprompter.Page))
3030
.ToBeVisibleAsync(new() { Timeout = BrowserTestConstants.Timing.ExtendedVisibleTimeoutMs });
3131

3232
var focalGuide = page.GetByTestId(UiTestIds.Teleprompter.FocalGuide);
33-
var firstWord = page.GetByTestId(UiTestIds.Teleprompter.CardWord(0, 0, 0));
33+
var firstWordSelector = TeleprompterReaderAlignmentAssertions.BuildDataTestSelector(
34+
UiTestIds.Teleprompter.CardWord(0, 0, 0));
35+
var firstWord = page.Locator(firstWordSelector);
3436

3537
await Expect(firstWord).ToBeVisibleAsync();
36-
await AssertGuideAlignmentAsync(page, focalGuide, firstWord);
38+
await TeleprompterReaderAlignmentAssertions.AssertWordAlignedToGuideAsync(page, firstWordSelector);
3739

3840
await page.GetByTestId(UiTestIds.Teleprompter.FocalSlider).EvaluateAsync(
3941
$$"""
4042
element => {
4143
element.value = '{{BrowserTestConstants.Teleprompter.AdjustedFocalPointPercent}}';
4244
element.dispatchEvent(new Event('input', { bubbles: true }));
45+
element.dispatchEvent(new Event('change', { bubbles: true }));
4346
}
4447
""");
4548

4649
await Expect(focalGuide).ToHaveAttributeAsync("style", BrowserTestConstants.Teleprompter.AdjustedFocalGuideStyle);
47-
await AssertGuideAlignmentAsync(page, focalGuide, firstWord);
50+
await TeleprompterReaderAlignmentAssertions.AssertWordAlignedToGuideAsync(page, firstWordSelector);
4851
}
4952
finally
5053
{
@@ -219,29 +222,6 @@ await Expect(cameraLayer).ToHaveAttributeAsync(
219222
BrowserTestConstants.Teleprompter.ActiveStateValue);
220223
}
221224

222-
private static async Task AssertGuideAlignmentAsync(
223-
Microsoft.Playwright.IPage page,
224-
Microsoft.Playwright.ILocator focalGuide,
225-
Microsoft.Playwright.ILocator word)
226-
{
227-
var attemptCount = BrowserTestConstants.Teleprompter.AlignmentTimeoutMs /
228-
BrowserTestConstants.Teleprompter.AlignmentPollDelayMs;
229-
var lastDelta = double.MaxValue;
230-
231-
for (var attempt = 0; attempt < attemptCount; attempt++)
232-
{
233-
lastDelta = await MeasureVerticalCenterDeltaAsync(focalGuide, word);
234-
if (Math.Abs(lastDelta) <= BrowserTestConstants.Teleprompter.AlignmentTolerancePx)
235-
{
236-
return;
237-
}
238-
239-
await page.WaitForTimeoutAsync(BrowserTestConstants.Teleprompter.AlignmentPollDelayMs);
240-
}
241-
242-
await Assert.That(Math.Abs(lastDelta)).IsBetween(0, BrowserTestConstants.Teleprompter.AlignmentTolerancePx);
243-
}
244-
245225
private static async Task<double> MeasureVerticalCenterDeltaAsync(
246226
Microsoft.Playwright.ILocator focalGuide,
247227
Microsoft.Playwright.ILocator word)

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

Lines changed: 3 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -154,52 +154,13 @@ private static async Task AssertProductLaunchTpsRenderingAsync(Microsoft.Playwri
154154

155155
private static async Task AssertCurrentActiveWordAlignedAsync(Microsoft.Playwright.IPage page)
156156
{
157-
var activeWord = page.Locator(BrowserTestConstants.Teleprompter.ActiveWordSelector);
157+
var activeWordSelector = BrowserTestConstants.Teleprompter.ActiveWordSelector;
158+
var activeWord = page.Locator(activeWordSelector);
158159
await Expect(activeWord).ToBeVisibleAsync(new()
159160
{
160161
Timeout = BrowserTestConstants.Timing.ReaderPlaybackAdvanceTimeoutMs
161162
});
162-
await AssertGuideAlignmentAsync(
163-
page,
164-
page.GetByTestId(UiTestIds.Teleprompter.FocalGuide),
165-
activeWord);
166-
}
167-
168-
private static async Task AssertGuideAlignmentAsync(
169-
Microsoft.Playwright.IPage page,
170-
Microsoft.Playwright.ILocator focalGuide,
171-
Microsoft.Playwright.ILocator word)
172-
{
173-
var attemptCount = BrowserTestConstants.Teleprompter.AlignmentTimeoutMs /
174-
BrowserTestConstants.Teleprompter.AlignmentPollDelayMs;
175-
var lastDelta = double.MaxValue;
176-
177-
for (var attempt = 0; attempt < attemptCount; attempt++)
178-
{
179-
lastDelta = await MeasureVerticalCenterDeltaAsync(focalGuide, word);
180-
if (Math.Abs(lastDelta) <= BrowserTestConstants.Teleprompter.AlignmentTolerancePx)
181-
{
182-
return;
183-
}
184-
185-
await page.WaitForTimeoutAsync(BrowserTestConstants.Teleprompter.AlignmentPollDelayMs);
186-
}
187-
188-
await Assert.That(Math.Abs(lastDelta)).IsBetween(0, BrowserTestConstants.Teleprompter.AlignmentTolerancePx);
189-
}
190-
191-
private static async Task<double> MeasureVerticalCenterDeltaAsync(
192-
Microsoft.Playwright.ILocator focalGuide,
193-
Microsoft.Playwright.ILocator word)
194-
{
195-
var focalGuideBox = await focalGuide.BoundingBoxAsync();
196-
var wordBox = await word.BoundingBoxAsync();
197-
198-
await Assert.That(focalGuideBox).IsNotNull();
199-
await Assert.That(wordBox).IsNotNull();
200-
201-
return (focalGuideBox.Y + (focalGuideBox.Height / 2d)) -
202-
(wordBox.Y + (wordBox.Height / 2d));
163+
await TeleprompterReaderAlignmentAssertions.AssertWordAlignedToGuideAsync(page, activeWordSelector);
203164
}
204165

205166
private static async Task<ReaderWordProbe> GetWordProbeAsync(Microsoft.Playwright.IPage page, int cardIndex, string wordText)
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using Microsoft.Playwright;
2+
using PrompterOne.Shared.Contracts;
3+
using static Microsoft.Playwright.Assertions;
4+
5+
namespace PrompterOne.Web.UITests;
6+
7+
internal static class TeleprompterReaderAlignmentAssertions
8+
{
9+
internal static string BuildDataTestSelector(string testId) =>
10+
$"[{BrowserTestConstants.Html.DataTestAttribute}='{testId}']";
11+
12+
internal static async Task AssertWordAlignedToGuideAsync(IPage page, string wordSelector)
13+
{
14+
var word = page.Locator(wordSelector);
15+
await Expect(page.GetByTestId(UiTestIds.Teleprompter.FocalGuide)).ToBeVisibleAsync();
16+
await Expect(word).ToBeVisibleAsync(new()
17+
{
18+
Timeout = BrowserTestConstants.Timing.ReaderPlaybackAdvanceTimeoutMs
19+
});
20+
21+
var attemptCount = BrowserTestConstants.Timing.ReaderPlaybackAdvanceTimeoutMs /
22+
BrowserTestConstants.Teleprompter.AlignmentPollDelayMs;
23+
ReaderAlignmentProbe lastProbe = new();
24+
25+
for (var attempt = 0; attempt < attemptCount; attempt++)
26+
{
27+
lastProbe = await CaptureAlignmentProbeAsync(page, wordSelector);
28+
if (lastProbe.IsMeasurable &&
29+
lastProbe.IsActiveCard &&
30+
Math.Abs(lastProbe.CenterDelta) <= BrowserTestConstants.Teleprompter.AlignmentTolerancePx)
31+
{
32+
return;
33+
}
34+
35+
await page.WaitForTimeoutAsync(BrowserTestConstants.Teleprompter.AlignmentPollDelayMs);
36+
}
37+
38+
await Assert.That(lastProbe.IsMeasurable).IsTrue();
39+
await Assert.That(lastProbe.IsActiveCard).IsTrue();
40+
await Assert.That(Math.Abs(lastProbe.CenterDelta))
41+
.IsBetween(0d, BrowserTestConstants.Teleprompter.AlignmentTolerancePx);
42+
}
43+
44+
private static Task<ReaderAlignmentProbe> CaptureAlignmentProbeAsync(IPage page, string wordSelector) =>
45+
page.EvaluateAsync<ReaderAlignmentProbe>(
46+
"""
47+
args => {
48+
const focalGuide = document.querySelector(`[data-test="${args.focalGuideTestId}"]`);
49+
const word = document.querySelector(args.wordSelector);
50+
51+
if (!(focalGuide instanceof HTMLElement) || !(word instanceof HTMLElement)) {
52+
return { isMeasurable: false, isActiveCard: false, centerDelta: Number.MAX_VALUE };
53+
}
54+
55+
const card = word.closest(`[${args.cardStateAttributeName}]`);
56+
const focalGuideRect = focalGuide.getBoundingClientRect();
57+
const wordRect = word.getBoundingClientRect();
58+
59+
return {
60+
isMeasurable: true,
61+
isActiveCard: card instanceof HTMLElement &&
62+
card.getAttribute(args.cardStateAttributeName) === args.activeState,
63+
centerDelta:
64+
(focalGuideRect.top + focalGuideRect.height / 2) -
65+
(wordRect.top + wordRect.height / 2)
66+
};
67+
}
68+
""",
69+
new
70+
{
71+
activeState = UiDataAttributes.Teleprompter.ActiveState,
72+
cardStateAttributeName = UiDataAttributes.Teleprompter.CardState,
73+
focalGuideTestId = UiTestIds.Teleprompter.FocalGuide,
74+
wordSelector
75+
});
76+
77+
private sealed class ReaderAlignmentProbe
78+
{
79+
public bool IsMeasurable { get; set; }
80+
public bool IsActiveCard { get; set; }
81+
public double CenterDelta { get; set; } = double.MaxValue;
82+
}
83+
}

0 commit comments

Comments
 (0)