|
| 1 | +using System.Globalization; |
1 | 2 | using PrompterOne.Shared.Contracts; |
2 | 3 | using static Microsoft.Playwright.Assertions; |
3 | 4 |
|
4 | 5 | namespace PrompterOne.App.UITests; |
5 | 6 |
|
6 | 7 | public sealed class LearnFidelityTests(StandaloneAppFixture fixture) : IClassFixture<StandaloneAppFixture> |
7 | 8 | { |
| 9 | + private const string LearnPlaybackProbeStartKey = "__prompterOneLearnFidelityPlaybackStartMs"; |
8 | 10 | private readonly record struct ContextGapMeasurement(double LeftGapPx, double RightGapPx); |
9 | 11 | private readonly record struct ContextRailClipMeasurement(double LeftClipPx, double RightClipPx); |
10 | 12 | private readonly record struct FocusWordSlackMeasurement(double SlackPx); |
@@ -370,34 +372,25 @@ await StepUntilWordAsync( |
370 | 372 | [Fact] |
371 | 373 | public async Task LearnScreen_WpmIncrease_AcceleratesPlaybackCadence() |
372 | 374 | { |
373 | | - var page = await fixture.NewPageAsync(); |
374 | | - |
375 | | - try |
376 | | - { |
377 | | - await page.GotoAsync(BrowserTestConstants.Routes.LearnDemo); |
378 | | - await Expect(page.GetByTestId(UiTestIds.Learn.Page)) |
379 | | - .ToBeVisibleAsync(new() { Timeout = BrowserTestConstants.Timing.ExtendedVisibleTimeoutMs }); |
380 | | - |
381 | | - var baselineAdvance = await MeasurePlaybackAdvanceAsync(page); |
382 | | - |
383 | | - await page.GotoAsync(BrowserTestConstants.Routes.LearnDemo); |
384 | | - await Expect(page.GetByTestId(UiTestIds.Learn.Page)) |
385 | | - .ToBeVisibleAsync(new() { Timeout = BrowserTestConstants.Timing.ExtendedVisibleTimeoutMs }); |
386 | | - |
387 | | - await IncreaseLearnSpeedAsync(page, BrowserTestConstants.Learn.PlaybackSpeedIncreaseClicks); |
388 | | - await Expect(page.Locator($"#{UiDomIds.Learn.Speed}")) |
389 | | - .ToHaveTextAsync(BrowserTestConstants.Learn.FasterPlaybackSpeedText); |
| 375 | + var slowPlaybackDuration = await MeasureLearnPlaybackDurationAsync( |
| 376 | + async page => |
| 377 | + { |
| 378 | + await DecreaseLearnSpeedAsync(page, BrowserTestConstants.ReaderTiming.LearnSlowWpmAdjustmentClicks); |
| 379 | + await Expect(page.Locator($"#{UiDomIds.Learn.Speed}")) |
| 380 | + .ToHaveTextAsync(BrowserTestConstants.ReaderTiming.LearnSlowWpm.ToString(CultureInfo.InvariantCulture)); |
| 381 | + }); |
390 | 382 |
|
391 | | - var fasterAdvance = await MeasurePlaybackAdvanceAsync(page); |
| 383 | + var fastPlaybackDuration = await MeasureLearnPlaybackDurationAsync( |
| 384 | + async page => |
| 385 | + { |
| 386 | + await IncreaseLearnSpeedAsync(page, BrowserTestConstants.ReaderTiming.LearnFastWpmAdjustmentClicks); |
| 387 | + await Expect(page.Locator($"#{UiDomIds.Learn.Speed}")) |
| 388 | + .ToHaveTextAsync(BrowserTestConstants.ReaderTiming.LearnFastWpm.ToString(CultureInfo.InvariantCulture)); |
| 389 | + }); |
392 | 390 |
|
393 | | - Assert.True( |
394 | | - fasterAdvance >= baselineAdvance + BrowserTestConstants.Learn.MinimumPlaybackAdvanceDeltaWords, |
395 | | - $"Expected higher WPM to advance more words. Baseline advanced {baselineAdvance}, faster mode advanced {fasterAdvance}."); |
396 | | - } |
397 | | - finally |
398 | | - { |
399 | | - await page.Context.CloseAsync(); |
400 | | - } |
| 391 | + Assert.True( |
| 392 | + fastPlaybackDuration <= slowPlaybackDuration - BrowserTestConstants.ReaderTiming.MinimumSpeedProbePlaybackDeltaMs, |
| 393 | + $"Expected higher WPM to finish faster. Slow mode took {slowPlaybackDuration} ms, faster mode took {fastPlaybackDuration} ms."); |
401 | 394 | } |
402 | 395 |
|
403 | 396 | private static Task<double> MeasureOrpDeltaAsync(Microsoft.Playwright.IPage page) => |
@@ -483,25 +476,76 @@ private static async Task IncreaseLearnSpeedAsync(Microsoft.Playwright.IPage pag |
483 | 476 | } |
484 | 477 | } |
485 | 478 |
|
486 | | - private static async Task<int> MeasurePlaybackAdvanceAsync(Microsoft.Playwright.IPage page) |
| 479 | + private static async Task DecreaseLearnSpeedAsync(Microsoft.Playwright.IPage page, int clickCount) |
487 | 480 | { |
488 | | - var startWordNumber = await ReadProgressWordNumberAsync(page); |
| 481 | + for (var clickIndex = 0; clickIndex < clickCount; clickIndex++) |
| 482 | + { |
| 483 | + await page.GetByTestId(UiTestIds.Learn.SpeedDown).ClickAsync(); |
| 484 | + } |
| 485 | + } |
489 | 486 |
|
490 | | - await page.GetByTestId(UiTestIds.Learn.PlayToggle).ClickAsync(); |
491 | | - await page.WaitForTimeoutAsync(BrowserTestConstants.Timing.LearnPlaybackProbeWindowMs); |
492 | | - await page.GetByTestId(UiTestIds.Learn.PlayToggle).ClickAsync(); |
| 487 | + private async Task<int> MeasureLearnPlaybackDurationAsync(Func<Microsoft.Playwright.IPage, Task> configurePageAsync) |
| 488 | + { |
| 489 | + var page = await fixture.NewPageAsync(); |
493 | 490 |
|
494 | | - var endWordNumber = await ReadProgressWordNumberAsync(page); |
495 | | - return endWordNumber - startWordNumber; |
| 491 | + try |
| 492 | + { |
| 493 | + await page.GotoAsync(BrowserTestConstants.Routes.LearnReaderTiming); |
| 494 | + await Expect(page.GetByTestId(UiTestIds.Learn.Page)) |
| 495 | + .ToBeVisibleAsync(new() { Timeout = BrowserTestConstants.Timing.ExtendedVisibleTimeoutMs }); |
| 496 | + |
| 497 | + await configurePageAsync(page); |
| 498 | + |
| 499 | + return await MeasurePlaybackDurationToWordAsync( |
| 500 | + page, |
| 501 | + BrowserTestConstants.ReaderTiming.WordCount); |
| 502 | + } |
| 503 | + finally |
| 504 | + { |
| 505 | + await page.Context.CloseAsync(); |
| 506 | + } |
496 | 507 | } |
497 | 508 |
|
498 | | - private static async Task<int> ReadProgressWordNumberAsync(Microsoft.Playwright.IPage page) |
| 509 | + private static async Task<int> MeasurePlaybackDurationToWordAsync(Microsoft.Playwright.IPage page, int targetWordNumber) |
499 | 510 | { |
500 | | - var progressLabel = await page.GetByTestId(UiTestIds.Learn.ProgressLabel).TextContentAsync() ?? string.Empty; |
501 | | - var parts = progressLabel.Split(' ', StringSplitOptions.RemoveEmptyEntries); |
502 | | - return parts.Length >= 2 && int.TryParse(parts[1], out var wordNumber) |
503 | | - ? wordNumber |
504 | | - : 0; |
| 511 | + await page.EvaluateAsync( |
| 512 | + """ |
| 513 | + probe => { |
| 514 | + window[probe.key] = performance.now(); |
| 515 | + } |
| 516 | + """, |
| 517 | + new |
| 518 | + { |
| 519 | + key = LearnPlaybackProbeStartKey |
| 520 | + }); |
| 521 | + await page.GetByTestId(UiTestIds.Learn.PlayToggle).ClickAsync(); |
| 522 | + await page.WaitForFunctionAsync( |
| 523 | + """ |
| 524 | + probe => { |
| 525 | + const element = document.querySelector(`[data-testid="${probe.progressLabelTestId}"]`); |
| 526 | + const text = element?.textContent ?? ''; |
| 527 | + const match = /Word\s+(\d+)/.exec(text); |
| 528 | + return match !== null && Number(match[1]) >= probe.targetWordNumber; |
| 529 | + } |
| 530 | + """, |
| 531 | + new |
| 532 | + { |
| 533 | + progressLabelTestId = UiTestIds.Learn.ProgressLabel, |
| 534 | + targetWordNumber |
| 535 | + }, |
| 536 | + new() |
| 537 | + { |
| 538 | + Timeout = BrowserTestConstants.ReaderTiming.SampleCaptureTimeoutMs |
| 539 | + }); |
| 540 | + |
| 541 | + return await page.EvaluateAsync<int>( |
| 542 | + """ |
| 543 | + probe => Math.round(performance.now() - (window[probe.key] ?? performance.now())) |
| 544 | + """, |
| 545 | + new |
| 546 | + { |
| 547 | + key = LearnPlaybackProbeStartKey |
| 548 | + }); |
505 | 549 | } |
506 | 550 |
|
507 | 551 | private static Task<ContextGapMeasurement> MeasureContextGapsAsync(Microsoft.Playwright.IPage page) => |
|
0 commit comments