백준허브 확장프로그램 구름 플랫폼 업로드 문제 해결하가
현재 노트: KR-010.00 f 백준허브 확장프로그램 구름 플랫폼 업로드 문제 해결하가
상위 분류: KR-010.00
관련 이슈 정리
https://github.com/BaekjoonHub/BaekjoonHub/issues/295
#디버깅 #Debugging
백준허브 구름LEVEL 자동 업로드 오류 해결기: DOM 구조 변화로 인한 웹 확장 프로그램 트러블슈팅
코딩 테스트 플랫폼에서 문제 풀이 후 GitHub에 자동으로 코드를 저장해주는 크롬 확장 프로그램 '백준허브(BaekjoonHub)'는 개발자들에게 매우 유용한 도구입니다. 하지만 최근 구름LEVEL 문제를 풀고 제출하는 과정에서 이 편리한 자동 업로드 기능이 작동하지 않는 문제를 겪었습니다.
이 글은 백준허브 플러그인이 구름IDE (정확히는 구름LEVEL 서비스)에서 동작하지 않는 문제를 해결하기 위한 트러블슈팅 과정을 상세히 기록한 글입니다. 다른 사람이 만든 웹 확장 프로그램에서 생긴 문제를 진단 및 분석하는 값진 결과를 얻었습니다.
1. 문제 상황: 구름LEVEL 제출 후 백준허브 자동 업로드 미작동
백준허브 확장 프로그램 버전 1.2.5 환경에서, 구름LEVEL(GoormLEVEL) 문제 제출 시 자동 업로드 기능이 작동하지 않는 현상이 발생했습니다. 문제 제출 후 GitHub 업로드를 알리는 스피너(로딩 인디케이터)가 표시되지 않았고, 브라우저 개발자 도구(Console)에도 백준허브 관련 명확한 오류 메시지가 나타나지 않아 초기 원인 파악에 어려움이 있었습니다.
2. 해결 시도: 웹 확장 프로그램 분석을 통한 문제 디버깅 과정
문제가 발생했을 때, 우선적으로 백준허브가 정상적으로 작동할 때의 '정답지'를 상상하며 역추적했습니다. 보통 제출 후 성공 메시지와 함께 GitHub 업로드 스피너가 나타나고, 최종적으로 코드가 GitHub 저장소에 푸시되어야 합니다. 이러한 정상 동작이 보이지 않았으므로, 하나씩 문제의 원인을 찾아나가기 시작했습니다.
단계 1: 개발자 도구(Console, Network)를 통한 초기 점검
-
정답지 확인 - 동작, 버전 설정 등
:
백준허브가 올바르게 동작한다면 스피너가 표시되고, GitHub에 코드가 업로드되어야 합니다. 브라우저 버전이나 확장 프로그램 설정에 문제가 없는지 기본적인 사항들을 먼저 확인했지만, 특이점은 없었습니다. -
개발자도구 네트워크 먼저 확인 - 콘솔 쪽
:
브라우저 개발자 도구(F12)를 열어 Console 탭과 Network 탭을 확인했습니다.- Console: 백준허브와 관련된 빨간색 오류 메시지는 보이지 않았습니다. 이는 확장 프로그램 자체의 로드 실패나 치명적인 문법 오류는 아님을 시사했습니다.
- Network: GitHub 관련 API 호출 기록이 전혀 없었습니다. 이는 업로드 로직 자체가 아예 시작되지 않았다는 강력한 증거가 되었습니다. 명확한 오류가 보이지 않자, 확장 프로그램의 내부 로직을 직접 들여다봐야겠다는 판단을 내렸습니다.
단계 2: 확장 프로그램 소스 코드 확인 및 추론 ("감"에 의존)
백준허브 확장 프로그램은 크롬 스토어에서 다운로드받을 수 있지만, 실제 파일들은 로컬에 저장되어 있습니다. (일반적인 경로: C:\Users\USERNAME\AppData\Local\Google\Chrome\User Data\Default\Extensions\ccammcjdkpgjmcpijpahlehmapgmphmk\1.2.5_0
). manifest.json
파일을 통해 확장 프로그램의 구조를 대략 파악한 뒤, '제출 후 자동 업로드' 기능에 문제가 있으니, 제출 결과 확인 및 데이터 파싱을 담당하는 부분부터 직관적으로 확인했습니다. 특히 구름LEVEL 관련 로직은 scripts/goormlevel
폴더에 있을 것이라고 추론했습니다.
단계 3: 주요 함수 및 변수 식별, 로깅을 통한 심층 디버깅
-
구조 파일 확인 - 디버깅 - 중요한 함수 식별 동작여부 확인 - 중요한 변수 , 객체 확인
:
scripts/goormlevel/goormlevel.js
파일에서startLoader
함수를 발견했습니다. 이 함수는 정답이 나왔는지 주기적으로 확인하고, 정답이 확인되면parseData()
를 호출하여 데이터를 파싱한 후beginUpload()
를 통해 업로드를 시작하는 핵심 로직을 담고 있었습니다.문제가
beginUpload
가 실행되지 않는 것이므로,parseData()
가 어떤 결과를 반환하는지 확인하는 것이 중요하다고 판단했습니다. 이에 따라 로컬 환경에서 해당 파일에console.log
를 추가하여 디버깅을 시작했습니다.[로컬 디버깅을 위한
scripts/goormlevel/goormlevel.js
코드 수정 예시]// scripts/goormlevel/goormlevel.js의 startLoader 함수 수정 function startLoader() { console.log('BaekjoonHub: startLoader called. Current Path:', window.location.pathname); loader = setInterval(async () => { console.log('BaekjoonHub: setInterval tick'); const enable = await checkEnable(); // checkEnable 함수는 정상 호출 확인 console.log('BaekjoonHub: enable status:', enable); if (!enable) { console.log('BaekjoonHub: Extension disabled, stopping loader.'); stopLoader(); } else { const solved = getSolvedResult(); // 제출 결과 (정답 여부) 확인 console.log('BaekjoonHub: getSolvedResult() in interval:', solved); if (solved) { log('정답이 나왔습니다. 업로드를 시작합니다.'); // 이 로그는 찍혔으나 업로드가 안됨 stopLoader(); try { const parsedData = await parseData(); log('BaekjoonHub: parsedData:', parsedData); // <--- 결정적 단서를 찾기 위한 로그 추가! if (parsedData && Object.keys(parsedData).length > 0 && parsedData.code) { // isNotEmpty(parsedData)와 유사한 조건 await beginUpload(parsedData); } else { log('BaekjoonHub LOCAL LOAD: parsedData is empty or missing code. Upload skipped.'); // 이 메시지가 콘솔에 보임 } } catch (error) { log('BaekjoonHub: Error during parseData or beginUpload:', error); log(error); } } } }, 2000); }
이 로그들을 통해
parseData
함수가 호출되는 것은 확인했지만, 곧바로 콘솔에BaekjoonHub LOCAL LOAD: parsedData is empty. Upload skipped.
라는 메시지가 나타나는 것을 발견했습니다. 이는parsedData
객체가 비어있거나, 업로드에 필요한 핵심 정보가 누락되었음을 의미했습니다.더 상세한 원인을 파악하기 위해
parsedData
객체 자체를 로깅해보니 다음과 같은 결정적인 결과가 나왔습니다.[콘솔에 찍힌
parsedData
객체 로그]{ "examSequence": 174704, "quizNumber": 1, "directory": "goormlevel/174704/1. A + B (2)", "message": "[난이도 1] Title: A + B (2), Time: 0.03 ms, Memory: 31.24 MB -BaekjoonHub", "fileName": "A + B (2).js", "readme": "# A + B (2) - 174704/1 \n\n[문제 링크](https://level.goorm.io/exam/174704/a-b-2/quiz/1) \n\n### 성능 요약\n\n메모리: 31.24 MB, 시간: 0.03 ms\n\n### 제출 일자\n\n2025년 05월 28일 16:32:25\n\n", "code": "" // <-- 문제의 핵심! 사용자가 제출한 소스 코드가 비어 있었습니다. }
parsedData.code
가 빈 문자열인 것이 문제의 핵심이었습니다. 즉, 제출한 소스 코드를 제대로 추출해오지 못하고 있다는 뜻이었습니다.
단계 4: DOM 구조 분석 및 원인 파악
parsedData.code
가 왜 비어있는지 알아내기 위해, 실제로 코드를 파싱하는 로직이 담긴 scripts/goormlevel/parsing.js
파일을 집중적으로 분석했습니다. parseData()
함수 내 코드 추출 로직은 다음과 같았습니다.
// scripts/goormlevel/parsing.js 파일 내 parseData() 함수 일부
// const code = [...document.querySelectorAll('#fileEditor textarea[readonly]')].map(($element) => $element.value)[currentLanguageIndex] || '';
이 코드는 #fileEditor
내부의 textarea[readonly]
요소를 찾아 그 value
속성에서 코드를 가져오려고 합니다. 그러나 구름LEVEL 사이트의 코드 에디터 부분을 개발자 도구로 확인해본 결과, 코드 에디터가 더 이상 textarea
태그를 사용하지 않고 div
기반(CodeMirror와 유사한) 구조로 변경되었음을 알 수 있었습니다.
[구름LEVEL 코드 에디터의 DOM 구조 스크린샷 (예시)]
이러한 DOM 구조 변화가 기존 백준허브의 코드 추출 로직과 맞지 않아 parsedData.code
가 빈 문자열로 반환되었던 것입니다. 초기 백준허브 개발 당시에는 textarea
기반 에디터였을 것이고, 이후 구름LEVEL 사이트 업데이트로 에디터 구조가 변경되었을 가능성이 높습니다.
3. 해결 방법: div
기반 에디터에 맞춰 코드 추출 로직 수정
원인이 명확해졌으므로, 로컬 환경에서 scripts/goormlevel/parsing.js
파일의 parseData()
함수 내 소스 코드 추출 로직을 구름LEVEL의 새로운 div
기반 에디터 구조에 맞춰 수정했습니다.
[수정된 scripts/goormlevel/parsing.js
파일의 parseData()
함수 핵심 코드]
// scripts/goormlevel/parsing.js 파일 내 parseData() 함수 수정
function parseData() {
// ... (생략된 기존 코드)
// 기존 코드 추출 로직 (주석 처리 또는 제거)
// const code_old = [...document.querySelectorAll('#fileEditor textarea[readonly]')].map(($element) => $element.value)[currentLanguageIndex] || '';
/* 새로운 코드 추출 로직 - div 기반 에디터 대응 */
let extractedCode = "";
// 코드 에디터의 주요 컨텐츠 영역을 나타내는 div 요소 선택
const allCodeEditorContentDivsInFileEditor = document.querySelectorAll("#fileEditor div.cm-content.cm-lineWrapping");
if (currentLanguageIndex >= 0 && allCodeEditorContentDivsInFileEditor.length > currentLanguageIndex) {
// 현재 선택된 언어 인덱스에 해당하는 에디터가 존재할 경우
const targetEditorDiv = allCodeEditorContentDivsInFileEditor[currentLanguageIndex];
if (targetEditorDiv) {
const lines = [];
const lineDivs = targetEditorDiv.querySelectorAll("div.cm-line"); // 각 줄을 나타내는 div 요소 선택
lineDivs.forEach((lineDiv) => {
lines.push(lineDiv.textContent); // 각 줄의 텍스트 콘텐츠 추출
});
extractedCode = lines.join("\n"); // 추출된 줄들을 줄바꿈 문자로 합치기
}
} else if (allCodeEditorContentDivsInFileEditor.length === 1 && currentLanguageIndex < 0) {
// 단일 에디터이거나 언어 인덱스가 명확하지 않을 경우 (예: 하나의 파일만 있을 때) 첫 번째 에디터 사용
const targetEditorDiv = allCodeEditorContentDivsInFileEditor[0];
const lines = [];
const lineDivs = targetEditorDiv.querySelectorAll("div.cm-line");
lineDivs.forEach((lineDiv) => {
lines.push(lineDiv.textContent);
});
extractedCode = lines.join("\n");
}
const code = extractedCode; // 최종적으로 이 변수에 추출된 코드가 담깁니다.
// ... (생략된 기존 코드)
// 모든 파싱된 데이터를 포함하는 객체 반환
const parsedData = {
// ... 기존 parsedData 객체 구성 로직
code: code // 추출된 코드를 parsedData.code에 할당
};
return parsedData;
}
이 수정된 확장 프로그램을 로컬에서 테스트하기 위해, 크롬 브라우저에서 chrome://extensions/
로 이동하여 "개발자 모드"를 활성화한 후 "압축 해제된 확장 프로그램을 로드합니다" 버튼을 통해 수정된 백준허브 확장 프로그램 폴더를 선택하여 로드했습니다.
4. 해결 후 정리: 자동 업로드 기능 정상 작동 확인
위와 같이 로컬에서 코드 추출 로직을 수정한 후 다시 구름LEVEL에서 문제를 제출해본 결과, parsedData.code
에 현재 선택된 언어의 실제 소스 코드가 정상적으로 채워지는 것을 확인했습니다.
그 결과 isNotEmpty(parsedData)
(또는 !isEmpty(parsedData)
) 조건이 true
를 반환하여 업로드 로직이 실행되었고, 백준허브 스피너가 정상적으로 나타난 후 GitHub에 코드가 성공적으로 업로드되는 것을 확인했습니다.
5. 추가 참고 및 회고: 웹 확장 프로그램과 웹 서비스의 변화
이번 디버깅을 통해 웹사이트의 UI/UX 개선이나 내부 라이브러리 변경이 크롬 확장 프로그램과 같은 외부 도구에 얼마나 큰 영향을 미칠 수 있는지 다시 한번 체감했습니다. 구름LEVEL이 더 나은 에디터 경험을 제공하기 위해 내부 DOM 구조를 textarea
에서 div
기반으로 변경했고, 이로 인해 기존 백준허브의 textarea
기반 추출 로직이 무력화된 것이 핵심 원인이었습니다.
이러한 문제는 웹 확장 프로그램 개발 시 늘 고려해야 할 부분이며, 웹 서비스 제공자가 DOM 구조를 변경하면 확장 프로그램 또한 업데이트가 필요할 수 있음을 상기시켜주었습니다. 백준허브처럼 유용한 오픈소스 프로젝트에 기여할 기회가 된다면, 이 수정 사항을 Pull Request로 제안하여 다른 사용자들도 동일한 문제를 겪지 않도록 돕고 싶습니다.
문제 상황 파악부터 개발자 도구 활용, 소스 코드 분석, DOM 구조 이해, 그리고 직접적인 코드 수정까지의 과정을 경험하며, 웹 확장 프로그램의 동작 원리와 웹 트러블슈팅 능력에 대한 이해를 한층 더 높일 수 있었던 값진 경험이었습니다.