Smoke 1회차 후속 — player full-path resolver + recorder type-step target + 부수 gap #12

Closed
opened 2026-04-07 20:18:44 +09:00 by kimminsung · 1 comment
Owner

배경

2026-04-07 smoke test 1회차 (docs/history/2026-04-07_smoke-1회차-결과.md) 결과, recorder까지는 정상 동작하나 player 재생 시 Box 명령이 재현되지 않음. 4개의 구조적 gap 발견.

해결할 Gap

Gap A (치명) — player UiaPlayerHost.ResolveElement AutomationId-only 매칭

현재 코드 (src/Recordingtest.Player/UiaPlayerHost.cs:135):

private static string ExtractAutomationId(string uiaPath)
{
    // Look for [@AutomationId='...'] in the LAST segment.
    var marker = "@AutomationId='";
    var idx = uiaPath.LastIndexOf(marker, StringComparison.Ordinal);
    ...
}

MetroWindow[@AutomationId='root']/ItemsControl 같이 마지막 segment에 AutomationId가 없는 path는 빈 문자열로 FindFirstDescendant를 호출 → 첫 descendant 아무거나 잡음 → 엉뚱한 element bounds로 offset 적용 → 잘못된 좌표 클릭.

수정 요구:

  • uia_path/로 분리해 ancestor chain 전체를 따라 descend
  • 각 segment에서 ClassName, AutomationId, Name 모두 활용해 매칭
  • 최종 element를 못 찾으면 명확히 throw (현재 false positive)

Gap B (치명) — recorder type 스텝 target 미설정

DragCollapser.FlushType()이 typeRes가 null이면 target을 빈 채로 emit. typeRes는 key event resolver skip 정책 때문에 항상 null.

수정 요구:

  • 가장 최근 focus_change RawEvent의 FocusedElementPath를 type 스텝의 target에 자동 상속
  • Or: 가장 최근 mouse_down 이벤트의 resolved element 재사용
  • 또는 두 전략 fallback: focus_change 우선 → mouse_down → null

이렇게 하면 player가 type 스텝을 skip하지 않고 정확한 element에 키 입력 보낼 수 있음.

Gap C (부수) — recorder가 SUT 외 창 이벤트 캡처

Win32 hook은 시스템 전역. 사용자 alt+tab 시 다른 창 path 섞임. 옵션:

  • --filter-window-handle <hwnd> 인자로 attach된 SUT의 hwnd를 받아 hook proc에서 WindowFromPoint 검사
  • Or: collapser가 SUT 메인 윈도우 path가 아닌 step을 drop

Gap D (부수) — yaml 한글 PowerShell 출력 시 인코딩 깨짐

Get-Content로 볼 때 한글이 cp949로 보임. 파일 자체는 UTF-8일 가능성. 검증 필요. ScenarioWriter가 BOM을 쓰는지 확인.

사이클

  1. Generator (4개 gap 동시 작업, 단위 테스트 + integration regression test 포함)
  2. Evaluator
  3. Smoke 2회차

비용 추정

200300k tokens

Related: #2, #6, #7, #11

## 배경 2026-04-07 smoke test 1회차 (`docs/history/2026-04-07_smoke-1회차-결과.md`) 결과, recorder까지는 정상 동작하나 player 재생 시 Box 명령이 재현되지 않음. 4개의 구조적 gap 발견. ## 해결할 Gap ### Gap A (치명) — player `UiaPlayerHost.ResolveElement` AutomationId-only 매칭 현재 코드 (`src/Recordingtest.Player/UiaPlayerHost.cs:135`): ```csharp private static string ExtractAutomationId(string uiaPath) { // Look for [@AutomationId='...'] in the LAST segment. var marker = "@AutomationId='"; var idx = uiaPath.LastIndexOf(marker, StringComparison.Ordinal); ... } ``` → `MetroWindow[@AutomationId='root']/ItemsControl` 같이 마지막 segment에 AutomationId가 없는 path는 빈 문자열로 `FindFirstDescendant`를 호출 → 첫 descendant 아무거나 잡음 → 엉뚱한 element bounds로 offset 적용 → 잘못된 좌표 클릭. **수정 요구**: - `uia_path`를 `/`로 분리해 ancestor chain 전체를 따라 descend - 각 segment에서 `ClassName`, `AutomationId`, `Name` 모두 활용해 매칭 - 최종 element를 못 찾으면 명확히 throw (현재 false positive) ### Gap B (치명) — recorder type 스텝 target 미설정 `DragCollapser.FlushType()`이 typeRes가 null이면 target을 빈 채로 emit. typeRes는 key event resolver skip 정책 때문에 항상 null. **수정 요구**: - 가장 최근 `focus_change` RawEvent의 `FocusedElementPath`를 type 스텝의 target에 자동 상속 - Or: 가장 최근 mouse_down 이벤트의 resolved element 재사용 - 또는 두 전략 fallback: focus_change 우선 → mouse_down → null 이렇게 하면 player가 type 스텝을 skip하지 않고 정확한 element에 키 입력 보낼 수 있음. ### Gap C (부수) — recorder가 SUT 외 창 이벤트 캡처 Win32 hook은 시스템 전역. 사용자 alt+tab 시 다른 창 path 섞임. 옵션: - `--filter-window-handle <hwnd>` 인자로 attach된 SUT의 hwnd를 받아 hook proc에서 `WindowFromPoint` 검사 - Or: collapser가 SUT 메인 윈도우 path가 아닌 step을 drop ### Gap D (부수) — yaml 한글 PowerShell 출력 시 인코딩 깨짐 `Get-Content`로 볼 때 한글이 cp949로 보임. 파일 자체는 UTF-8일 가능성. 검증 필요. ScenarioWriter가 BOM을 쓰는지 확인. ## 사이클 1. Generator (4개 gap 동시 작업, 단위 테스트 + integration regression test 포함) 2. Evaluator 3. Smoke 2회차 ## 비용 추정 ~200~300k tokens Related: #2, #6, #7, #11
Author
Owner

Smoke 2차 gap fix — Verdict: pass

Generator (8784fec)

4개 Gap 모두 구조적 fix:

Gap 구현
A — player full-path resolver UiaPathParser.cs + IUiaPathResolver.cs + UiaPathResolver 신규. ancestor chain 따라 descend, id→name→class 우선순위, 누락 중간 wrapper 대비 bounded descendant fallback (depth 4). UiaPlayerHost.ResolveElement rewire, miss 시 null 반환
B — recorder type target inheritance DragCollapserlastFocusPath/lastMousePath 상태 추가. FlushType fallback chain: typeRes ?? focus ?? mouse
C — window filter SutProcessWindowFilter + WindowFilter.cs. 마우스 WindowFromPoint, 키는 GetForegroundWindow. Program.csapp.ProcessId로 wire
D — UTF-8 BOM-less ScenarioWriternew UTF8Encoding(false) 명시. round-trip 테스트가 BOM 부재 + 한글 보존 확인

Evaluator DoD

항목 결과
Build pass (0 warn / 0 err)
Tests pass 71/71 (60 → 71)
Gap A 실구현 pass (신규 3 파일 + rewire)
Gap B 실구현 pass (상태 + fallback chain)
Gap C 실구현 pass (WindowFromPoint + GetForegroundWindow)
Gap D 실구현 pass (BOM 부재 assertion)
Regression trap pass (새 타입은 compile trap, 나머지는 behavioral assertion)
PlayerEngine Thread.Sleep 0
SUT 폴더 쓰기 없음

남은 honest gap (non-blocking)

  • UiaPathResolver bounded descendant fallback은 휴리스틱, smoke 2차에서 튜닝 필요
  • Win32 키 필터는 GetForegroundWindow only — 빠른 alt+tab 전환 시 race 가능성
  • focus_change 이벤트는 filter 우회 (UIA 콜백이 이미 SUT-scoped)
  • UiaPlayerHostGetMainWindow를 root로 사용 — 멀티윈도우 SUT 미지원
  • 라이브 SUT 미실행 (샌드박스 제약) — 실환경 검증은 smoke 2회차에서

비용

단계 Tokens
Generator ~92k
Evaluator ~58k
Orchestrator 분담 ~15k
합계 ~165k

다음 단계

Smoke 2회차 — 사용자 환경에서 recorder (PID attach) → 짧은 Box 시나리오 → player 재생. 이번엔 click이 정확한 element를 잡고, type 스텝이 focus_change 상속으로 target 채워지고, 외부 창 이벤트가 필터되어 깔끔한 yaml이 나와야 함.

closing.

## Smoke 2차 gap fix — Verdict: **pass** ✅ ### Generator (`8784fec`) 4개 Gap 모두 구조적 fix: | Gap | 구현 | |-----|------| | A — player full-path resolver | `UiaPathParser.cs` + `IUiaPathResolver.cs` + `UiaPathResolver` 신규. ancestor chain 따라 descend, id→name→class 우선순위, 누락 중간 wrapper 대비 bounded descendant fallback (depth 4). `UiaPlayerHost.ResolveElement` rewire, miss 시 null 반환 | | B — recorder type target inheritance | `DragCollapser`에 `lastFocusPath`/`lastMousePath` 상태 추가. `FlushType` fallback chain: `typeRes ?? focus ?? mouse` | | C — window filter | `SutProcessWindowFilter` + `WindowFilter.cs`. 마우스 `WindowFromPoint`, 키는 `GetForegroundWindow`. `Program.cs`가 `app.ProcessId`로 wire | | D — UTF-8 BOM-less | `ScenarioWriter`가 `new UTF8Encoding(false)` 명시. round-trip 테스트가 BOM 부재 + 한글 보존 확인 | ### Evaluator DoD | 항목 | 결과 | |------|------| | Build | pass (0 warn / 0 err) | | Tests | pass **71/71** (60 → 71) | | Gap A 실구현 | pass (신규 3 파일 + rewire) | | Gap B 실구현 | pass (상태 + fallback chain) | | Gap C 실구현 | pass (WindowFromPoint + GetForegroundWindow) | | Gap D 실구현 | pass (BOM 부재 assertion) | | Regression trap | pass (새 타입은 compile trap, 나머지는 behavioral assertion) | | PlayerEngine Thread.Sleep | 0 | | SUT 폴더 쓰기 | 없음 | ### 남은 honest gap (non-blocking) - `UiaPathResolver` bounded descendant fallback은 휴리스틱, smoke 2차에서 튜닝 필요 - Win32 키 필터는 `GetForegroundWindow` only — 빠른 alt+tab 전환 시 race 가능성 - `focus_change` 이벤트는 filter 우회 (UIA 콜백이 이미 SUT-scoped) - `UiaPlayerHost`는 `GetMainWindow`를 root로 사용 — 멀티윈도우 SUT 미지원 - 라이브 SUT 미실행 (샌드박스 제약) — 실환경 검증은 smoke 2회차에서 ### 비용 | 단계 | Tokens | |------|--------| | Generator | ~92k | | Evaluator | ~58k | | Orchestrator 분담 | ~15k | | **합계** | **~165k** | ### 다음 단계 **Smoke 2회차** — 사용자 환경에서 recorder (PID attach) → 짧은 Box 시나리오 → player 재생. 이번엔 click이 정확한 element를 잡고, type 스텝이 focus_change 상속으로 target 채워지고, 외부 창 이벤트가 필터되어 깔끔한 yaml이 나와야 함. closing.
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: kimminsung/recordingtest#12