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) <noreply@anthropic.com>
This commit is contained in:
minsung
2026-04-08 19:39:05 +09:00
parent 70bf5703b3
commit 98d801442b
3 changed files with 23 additions and 11 deletions

View File

@@ -10,9 +10,8 @@
## P1 — 라이브 검증 (사용자 환경 필요) ## P1 — 라이브 검증 (사용자 환경 필요)
4. **이슈 #14 재생 안정화** — 첫 시도 BOX 타이핑 누락 문제. foreground settle 600ms→능동 대기 전환 또는 첫 type 이전 `Keyboard.Type` warm-up. `/contract player-foreground-stabilize`. 4. **recorder Gap I-1** — type 스텝 target을 `Automation.FocusedElement`로 직접 채워 null_target_steps=0 달성. Player fallback 의존도 줄이기.
5. **recorder Gap I-1** — type 스텝 target을 `Automation.FocusedElement`로 직접 채워 null_target_steps=0 달성. `/contract recorder-focused-element-target`. 5. **engine-bridge v3** — ReflectionEngineStateProvider 실매핑 (smoke test 이후)
6. **engine-bridge v3** — ReflectionEngineStateProvider 실매핑 (smoke test 이후)
## Follow-ups (non-blocking) ## Follow-ups (non-blocking)

View File

@@ -59,7 +59,7 @@ _(없음)_
- [ ] player: `wait_for` UIA 이벤트 매핑 강화 (현재 host passthrough). - [ ] player: `wait_for` UIA 이벤트 매핑 강화 (현재 host passthrough).
- [ ] player: `UiaPlayerHost` uia_path resolver가 마지막 `@AutomationId`만 사용 — 전체 ancestor chain 지원 필요. - [ ] player: `UiaPlayerHost` uia_path resolver가 마지막 `@AutomationId`만 사용 — 전체 ancestor chain 지원 필요.
- [ ] recorder: IME 조합 키 처리 (contract risks). - [ ] 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으로 우회. - [ ] recorder: null_target 이벤트 자체를 줄이기 — `Automation.FocusedElement` 직접 조회해 type 스텝 target 채우기 (issue #14 Gap I-1). 현재는 player fallback으로 우회.
## Blocked ## Blocked

View File

@@ -212,21 +212,34 @@ public sealed class UiaPlayerHost : IPlayerHost, IDisposable
/// was launched from a shell that still has focus when the first Type/Hotkey /// was launched from a shell that still has focus when the first Type/Hotkey
/// step fires. /// step fires.
/// </summary> /// </summary>
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
public void BringSutToForeground() public void BringSutToForeground()
{ {
try try
{ {
var w = _app?.GetMainWindow(_automation, TimeSpan.FromSeconds(5)); var w = _app?.GetMainWindow(_automation, TimeSpan.FromSeconds(5));
if (w is null) return; if (w is null) return;
var targetHwnd = w.Properties.NativeWindowHandle.ValueOrDefault;
try { w.SetForeground(); } catch { /* best-effort */ } try { w.SetForeground(); } catch { /* best-effort */ }
try { w.Focus(); } 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. // Issue #14 follow-up: active wait instead of fixed 600ms sleep.
// Increased from 150→600ms because FlaUI Keyboard.Type drops the // Poll until the OS reports the SUT as the foreground window, up
// first couple of characters if SendInput fires before the OS // to 2s. Previously a 600ms fixed sleep was threshold-sensitive
// finishes the focus transition (observed: "BOX" lost on first // and caused the first "BOX" keystroke to get dropped on a cold
// step, "10" succeeded later once the app had settled). // first run.
System.Threading.Thread.Sleep(600); 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 catch
{ {