From 98d801442bbd0f069eac1652f2d46edd99684477 Mon Sep 17 00:00:00 2001 From: minsung Date: Wed, 8 Apr 2026 19:39:05 +0900 Subject: [PATCH] player: active foreground wait replaces fixed 600ms sleep (#14) BringSutToForeground() now polls GetForegroundWindow() == SUT hwnd at 25ms intervals up to 2s, followed by a 100ms tail settle, instead of the brittle fixed 600ms sleep. First-attempt replay of box-v6.yaml is now reliable (previously dropped the opening "BOX" keystrokes on a cold start). Co-Authored-By: Claude Opus 4.6 (1M context) --- PLAN.md | 5 ++--- PROGRESS.md | 2 +- src/Recordingtest.Player/UiaPlayerHost.cs | 27 +++++++++++++++++------ 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/PLAN.md b/PLAN.md index d41ec1a..4c40d5e 100644 --- a/PLAN.md +++ b/PLAN.md @@ -10,9 +10,8 @@ ## P1 — 라이브 검증 (사용자 환경 필요) -4. **이슈 #14 재생 안정화** — 첫 시도 BOX 타이핑 누락 문제. foreground settle 600ms→능동 대기 전환 또는 첫 type 이전 `Keyboard.Type` warm-up. `/contract player-foreground-stabilize`. -5. **recorder Gap I-1** — type 스텝 target을 `Automation.FocusedElement`로 직접 채워 null_target_steps=0 달성. `/contract recorder-focused-element-target`. -6. **engine-bridge v3** — ReflectionEngineStateProvider 실매핑 (smoke test 이후) +4. **recorder Gap I-1** — type 스텝 target을 `Automation.FocusedElement`로 직접 채워 null_target_steps=0 달성. Player fallback 의존도 줄이기. +5. **engine-bridge v3** — ReflectionEngineStateProvider 실매핑 (smoke test 이후) ## Follow-ups (non-blocking) diff --git a/PROGRESS.md b/PROGRESS.md index bf52c59..6146b4a 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -59,7 +59,7 @@ _(없음)_ - [ ] player: `wait_for` UIA 이벤트 매핑 강화 (현재 host passthrough). - [ ] player: `UiaPlayerHost` uia_path resolver가 마지막 `@AutomationId`만 사용 — 전체 ancestor chain 지원 필요. - [ ] recorder: IME 조합 키 처리 (contract risks). -- [ ] player: foreground settle이 경계선(600ms) — 1차 재생이 가끔 BOX 타이핑 누락. 능동 대기(focus-ready polling)로 전환 고려. (issue #14 후속) +- [x] ~~player: foreground settle 안정화~~ — 능동 대기(`GetForegroundWindow` polling 2s + 100ms settle)로 전환, 1차 재생 성공 확인 - [ ] recorder: null_target 이벤트 자체를 줄이기 — `Automation.FocusedElement` 직접 조회해 type 스텝 target 채우기 (issue #14 Gap I-1). 현재는 player fallback으로 우회. ## Blocked diff --git a/src/Recordingtest.Player/UiaPlayerHost.cs b/src/Recordingtest.Player/UiaPlayerHost.cs index 2a4c2a8..ff1c3ac 100644 --- a/src/Recordingtest.Player/UiaPlayerHost.cs +++ b/src/Recordingtest.Player/UiaPlayerHost.cs @@ -212,21 +212,34 @@ public sealed class UiaPlayerHost : IPlayerHost, IDisposable /// was launched from a shell that still has focus when the first Type/Hotkey /// step fires. /// + [System.Runtime.InteropServices.DllImport("user32.dll")] + private static extern IntPtr GetForegroundWindow(); + public void BringSutToForeground() { try { var w = _app?.GetMainWindow(_automation, TimeSpan.FromSeconds(5)); if (w is null) return; + var targetHwnd = w.Properties.NativeWindowHandle.ValueOrDefault; try { w.SetForeground(); } catch { /* best-effort */ } try { w.Focus(); } catch { /* best-effort */ } - // Small settle so the OS-level focus switch takes effect before - // the first SendInput. 150ms is enough in practice on Win10. - // Increased from 150→600ms because FlaUI Keyboard.Type drops the - // first couple of characters if SendInput fires before the OS - // finishes the focus transition (observed: "BOX" lost on first - // step, "10" succeeded later once the app had settled). - System.Threading.Thread.Sleep(600); + + // Issue #14 follow-up: active wait instead of fixed 600ms sleep. + // Poll until the OS reports the SUT as the foreground window, up + // to 2s. Previously a 600ms fixed sleep was threshold-sensitive + // and caused the first "BOX" keystroke to get dropped on a cold + // first run. + var deadline = DateTime.UtcNow.AddSeconds(2); + while (DateTime.UtcNow < deadline) + { + if (targetHwnd != IntPtr.Zero && GetForegroundWindow() == targetHwnd) + break; + System.Threading.Thread.Sleep(25); + } + // Tiny additional settle for the OS keyboard-focus IPC to finish + // after the foreground transition is observed. + System.Threading.Thread.Sleep(100); } catch {