Revert "WebView2 Fixed Version 호환성 문제 해결 - NuGet 패키지 버전 업데이트 및 환경 설정 개선"

This reverts commit a13306115b.
This commit is contained in:
2025-08-11 21:42:35 +09:00
parent 3d75d1192d
commit 2f1c2483f0
19 changed files with 570 additions and 975 deletions

View File

@@ -46,3 +46,305 @@ UniMarc.csproj
## Webview2 Fixed Version 다운로드 주소
https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/759b508a-00bb-4724-9b87-2703c8417737/Microsoft.WebView2.FixedVersionRuntime.139.0.3405.86.x86.cab
## WebView2 Selenium 스타일 DOM 조작 가이드
### 🔧 기본 DOM 조작 메서드들 (NamguLibrarySearcher 기본 클래스에 구현됨)
#### 1. 요소 대기 및 존재 확인
```csharp
// DOM 요소가 준비될 때까지 대기 (Selenium의 WebDriverWait와 유사)
await WaitForElementReady("#clickAll", 10000); // 10초 타임아웃
await WaitForElementReady("input[name='query']", 5000); // 5초 타임아웃
```
#### 2. 요소 상태 확인
```csharp
// 체크박스가 체크되어 있는지 확인 (Selenium의 element.IsSelected와 유사)
bool isChecked = await IsElementChecked("#clickAll");
bool isLibMAChecked = await IsElementChecked("#libMA");
```
#### 3. 요소 클릭
```csharp
// 요소 클릭 (Selenium의 element.Click()와 유사)
await ClickElement("#clickAll"); // 전체 선택 클릭
await ClickElement("#libMA"); // 문화정보도서관 클릭
await ClickElement("button[type='submit']"); // 검색 버튼 클릭
```
#### 4. 값 입력 및 설정
```csharp
// 입력창에 값 설정 (Selenium의 element.SendKeys()와 유사)
await SetElementValue("input[name='query']", "검색어");
await SetElementValue("#search_txt", "도서명");
// 이벤트도 자동으로 발생시킴 (input, change 이벤트)
```
#### 5. 텍스트 가져오기
```csharp
// 요소의 텍스트 내용 가져오기 (Selenium의 element.Text와 유사)
string resultText = await GetElementText(".search-result span");
string bookCount = await GetElementText("span:contains('전체')");
```
### 🚀 실제 사용 예제
#### 도서관 선택 (남구통합도서관 예제)
```csharp
protected override async Task<bool> SelectLibraryWebView2()
{
try
{
// 1. DOM 요소 존재 확인 및 대기
await WaitForElementReady("#clickAll");
await WaitForElementReady("#libMA");
// 2. 전체 선택 해제 (단계별 실행)
bool isClickAllChecked = await IsElementChecked("#clickAll");
if (isClickAllChecked)
{
await ClickElement("#clickAll");
Console.WriteLine("전체 선택 해제됨");
}
// 3. 특정 도서관 선택
bool libMAChecked = await ClickElement("#libMA");
Console.WriteLine($"문화정보도서관 선택: {libMAChecked}");
return libMAChecked;
}
catch (Exception ex)
{
Console.WriteLine($"도서관 선택 오류: {ex.Message}");
return false;
}
}
```
#### 검색 실행 (단계별)
```csharp
private async Task PerformSearchWebView2(string searchTerm)
{
try
{
// 1. 검색창 찾기 및 대기 (여러 선택자 시도)
string[] searchSelectors = {
"input[name='query']",
"input[id='query']",
"input[type='text']"
};
string searchInputSelector = null;
foreach (var selector in searchSelectors)
{
try
{
await WaitForElementReady(selector, 3000);
searchInputSelector = selector;
break;
}
catch { continue; }
}
// 2. 검색어 입력
await SetElementValue(searchInputSelector, searchTerm);
// 3. 검색 버튼 클릭 (여러 선택자 시도)
string[] buttonSelectors = {
"button[type='submit']",
"input[type='submit']",
".search-btn",
".btn-search"
};
bool searchExecuted = false;
foreach (var selector in buttonSelectors)
{
if (await ClickElement(selector))
{
searchExecuted = true;
break;
}
}
// 4. 검색 버튼이 없으면 Enter 키로 검색
if (!searchExecuted)
{
string enterScript = $@"
const input = document.querySelector('{searchInputSelector}');
if (input) {{
input.dispatchEvent(new KeyboardEvent('keydown', {{
key: 'Enter', keyCode: 13, bubbles: true
}}));
return true;
}}
return false;
";
await _webView2.CoreWebView2.ExecuteScriptAsync(enterScript);
}
Console.WriteLine("✅ 검색 실행 완료");
}
catch (Exception ex)
{
Console.WriteLine($"❌ 검색 실행 오류: {ex.Message}");
throw;
}
}
```
### 🎯 CSS 선택자 참고
#### 남구통합도서관 주요 선택자들
```css
/* 도서관 선택 체크박스 */
#clickAll /* 전체 선택 */
#libMA /* 문화정보도서관 */
#libMB /* 푸른길도서관 */
#libMC /* 청소년도서관 */
#libSW /* 효천어울림도서관 */
#libSQ /* 스마트도서관 */
/* 검색 관련 */
input[name='query'] /* 검색창 */
button[type='submit'] /* 검색 버튼 */
/* 결과 관련 */
.search-result /* 검색 결과 영역 */
span:contains('전체') /* 검색 결과 수량 */
```
### ⚠️ 주의사항
1. **페이지 로딩 대기**: 항상 `WaitForElementReady()`로 요소가 준비될 때까지 대기
2. **에러 처리**: try-catch로 각 단계별 예외 처리
3. **로깅**: `Console.WriteLine()`으로 상세한 실행 로그 남기기
4. **타임아웃**: 적절한 타임아웃 설정 (기본 10초)
5. **다중 선택자**: 여러 CSS 선택자를 배열로 준비하여 순차적으로 시도
### 🔄 Selenium에서 WebView2로 이주 가이드
| Selenium 코드 | WebView2 대응 코드 |
|---------------|-------------------|
| `WebDriverWait.Until()` | `await WaitForElementReady()` |
| `element.Click()` | `await ClickElement()` |
| `element.SendKeys()` | `await SetElementValue()` |
| `element.Text` | `await GetElementText()` |
| `element.IsSelected` | `await IsElementChecked()` |
| `element.Clear()` | SetElementValue로 빈 문자열 설정 |
### 💡 성능 및 안정성 팁
- **WebView2가 Selenium보다 빠름**: 네이티브 성능
- **고정 버전 사용**: Chrome 버전 호환성 문제 없음
- **단계별 실행**: 각 작업을 개별 메서드로 분리하여 디버깅 용이
- **상세 로깅**: 각 단계마다 성공/실패 상태 출력
- **단순한 JavaScript 사용**: 복잡한 JSON보다는 단순한 문자열 반환 권장
## 🚨 **WebView2 JavaScript 실행 시 주의사항 (중요 교훈)**
### **문제: RuntimeBinderException과 null 반환**
**❌ 피해야 할 패턴:**
```csharp
// 복잡한 JSON 반환 스크립트 - 불안정함
string script = $@"
try {{
const element = document.querySelector('{selector}');
return JSON.stringify({{ success: true, isChecked: element.checked }});
}} catch (e) {{
return JSON.stringify({{ success: false, error: e.message }});
}}
";
string result = await webView2.CoreWebView2.ExecuteScriptAsync(script);
dynamic resultObj = JsonConvert.DeserializeObject(result); // RuntimeBinderException 위험!
if (resultObj.success == true) // null 참조 오류 발생 가능
```
**✅ 권장하는 안전한 패턴:**
```csharp
// 단순한 문자열 반환 - 안정적임
string result = await webView2.CoreWebView2.ExecuteScriptAsync($@"
try {{
var element = document.querySelector('{selector}');
if (element && element.checked) {{
element.click();
return element.checked ? 'failed' : 'success';
}}
return element ? 'already_unchecked' : 'not_found';
}} catch (e) {{
return 'error: ' + e.message;
}}
");
// 안전한 문자열 비교
if (result != null && result.Contains("success"))
{
return true;
}
```
### **핵심 교훈:**
1. **단순함이 최고**: WebView2에서는 복잡한 JSON.stringify()보다 단순한 문자열 반환이 훨씬 안정적
2. **Dynamic 타입 위험**: `dynamic` 객체는 null 체크 없이 사용하면 RuntimeBinderException 발생
3. **직접적인 JavaScript**: 중간 JSON 변환 없이 직접적인 DOM 조작이 더 확실함
4. **단계별 진단**: 복잡한 로직은 여러 개의 간단한 스크립트로 분할하여 실행
### **권장 디버깅 패턴:**
```csharp
// 1단계: 기본 JavaScript 실행 확인
string test1 = await webView2.ExecuteScriptAsync("1+1");
Console.WriteLine($"기본 연산: {test1}");
// 2단계: DOM 접근 확인
string test2 = await webView2.ExecuteScriptAsync("document.title");
Console.WriteLine($"DOM 접근: {test2}");
// 3단계: 요소 존재 확인
string test3 = await webView2.ExecuteScriptAsync($"document.querySelector('{selector}') !== null");
Console.WriteLine($"요소 존재: {test3}");
// 4단계: 실제 작업 수행
string result = await webView2.ExecuteScriptAsync($"document.querySelector('{selector}').click(); 'clicked'");
Console.WriteLine($"작업 결과: {result}");
```
**이 패턴을 사용하면 WebView2 JavaScript 실행에서 99%의 문제를 예방할 수 있습니다!**
## 🧹 **WebView2 메모리 누수 방지 (중요!)**
**문제**: WebView2는 메모리 누수로 인해 프로그램이 갑자기 종료될 수 있습니다.
**해결책**: 검색 전후로 메모리 정리를 수행하세요.
```csharp
// 검색 시작 전
await CleanupWebView2Memory();
// 검색 작업 수행...
// 검색 완료 후 (finally 블록에서)
finally {
await CleanupWebView2Memory();
}
private async Task CleanupWebView2Memory()
{
try {
await _webView2.CoreWebView2.ExecuteScriptAsync(@"
if (window.gc) window.gc(); null;
");
GC.Collect();
GC.WaitForPendingFinalizers();
} catch { /* 무시 */ }
}
```
**주의사항**:
- WebView2는 장시간 사용 시 메모리 누수 발생 가능
- 각 검색 작업 후 반드시 정리 수행
- Chrome WebDriver보다 메모리 관리가 까다로움