using System.Windows.Threading; namespace Paca.Tests.Infrastructure; /// /// WPF/WebView2 E2E 테스트용 STA 디스패처. 전용 STA 스레드에서 Dispatcher.Run() 으로 /// 메시지 펌프를 돌리며, 테스트 코드는 InvokeAsync 로 UI 스레드에 작업을 올린다. /// WebView2 이벤트(비동기)는 UI 스레드 디스패처에서 발생하므로 async 흐름도 이 펌프 위에서 완결된다. /// public sealed class StaDispatcher : IDisposable { private readonly Thread _thread; private readonly TaskCompletionSource _ready = new(TaskCreationOptions.RunContinuationsAsynchronously); private Dispatcher? _dispatcher; private StaDispatcher(Thread thread) { _thread = thread; } public Dispatcher Dispatcher => _dispatcher ?? throw new InvalidOperationException("STA dispatcher not started"); public static StaDispatcher Start() { var sta = default(StaDispatcher)!; var thread = new Thread(() => { sta._dispatcher = Dispatcher.CurrentDispatcher; WpfTestApp.Ensure(sta._dispatcher); sta._ready.TrySetResult(); Dispatcher.Run(); }); thread.SetApartmentState(ApartmentState.STA); thread.IsBackground = true; sta = new StaDispatcher(thread); thread.Start(); sta._ready.Task.Wait(5000); return sta; } public Task InvokeAsync(Func> func) => Dispatcher.InvokeAsync(func).Task.Unwrap(); public Task InvokeAsync(Func func) => Dispatcher.InvokeAsync(func).Task.Unwrap(); public T Invoke(Func func) => Dispatcher.Invoke(func); public void Invoke(Action action) => Dispatcher.Invoke(action); public void Dispose() { try { Dispatcher.InvokeShutdown(); } catch { } try { _thread.Join(5000); } catch { } } } /// WebView2 비동기 초기화/이벤트 대기 공통 유틸. public static class Wait { public static async Task Until(Func> poll, Func done, int timeoutMs = 15000, int intervalMs = 100) { var sw = System.Diagnostics.Stopwatch.StartNew(); while (sw.ElapsedMilliseconds < timeoutMs) { var v = await poll(); if (done(v)) return v; await Task.Delay(intervalMs); } throw new TimeoutException($"조건 미충족 ({timeoutMs}ms 초과)"); } }