네이버 에디터 자동화 버그 — 성공 로그를 찍고도 발행본엔 한 번도 안 들어간 원인

·

네이버 블로그에 글을 자동으로 채워 넣는 크롬 확장을 만들어 쓰고 있다. 리스트가 가운데 정렬로 나오는 버그를 이미 한 번 고쳤다고 생각했는데, 실제로는 그 수정이 발행본에 단 한 번도 반영된 적이 없었다. 성공 로그를 믿고 넘어갔다가 라이브 HTML을 직접 확인해보고서야 알아챘고, 그다음 시도한 수정도 또 다른 이유로 실패했다.

1차 수정 — DOM classList를 직접 치환

이전에 겪은 문제는 인라인 스타일도, 표준 편집 명령(justifyLeft)도 정렬에 반영되지 않는다는 것이었다. DOM을 직접 떠보니 정렬은 se-text-paragraph-align-* 클래스 하나로만 결정된다는 게 드러났고, 그래서 붙여넣기 성공 검증 직후 이 클래스를 스크립트로 직접 치환하는 방식을 썼다. 실행할 때마다 “정렬 보정 39개” 같은 성공 로그가 찍혔다.

function forceLeftAlign() {
  let n = 0;
  for (const { doc } of allDocs().docs) {
    for (const wrap of deepQueryAll(doc, ".se-components-wrap")) {
      for (const p of wrap.querySelectorAll(
        ".se-component:not(.se-documentTitle) .se-text-paragraph.se-text-paragraph-align-center",
      )) {
        p.classList.remove("se-text-paragraph-align-center");
        p.classList.add("se-text-paragraph-align-left");
        n++;
      }
    }
  }
  return n; // 여기서 나온 숫자가 매번 "성공"으로 찍혔다
}

이 로그가 계속 성공을 가리켰기 때문에, 문제는 해결된 것으로 여기고 다음 글을 계속 발행했다.

라이브 HTML을 직접 확인해보고 알아챈 진짜 상태

몇 편을 더 발행한 뒤, 발행된 글을 실제로 열어 확인해보니 여전히 가운데 정렬이었다. 처음엔 우연이라고 생각했는데, 네이버 블로그 공개 API로 발행된 HTML을 직접 받아 문단 클래스를 세어보니 결과가 명확했다.

$ curl -s "https://blog.naver.com/PostView.naver?blogId=...&logNo=..." \
  | grep -o 'se-text-paragraph-align-[a-z]*' | sort | uniq -c

     47 se-text-paragraph-align-center

“정렬 보정 39개” 로그가 찍혔던 글인데도, 라이브 HTML에는 -align-left단 하나도 없었다. 리스트·소제목 할 것 없이 전부 -center였다. 원인은 뷰와 저장 모델의 분리였다 — 네이버 스마트에디터는 화면에 렌더링되는 DOM과 실제로 저장되는 문서 모델이 별개다. classList.remove/add는 그 순간 브라우저 탭에 보이는 DOM만 바꿀 뿐, 저장 시점에 참조되는 내부 모델에는 반영되지 않았다. 확장이 찍은 “성공” 로그는 거짓말을 한 게 아니라, 자신이 확인할 수 있는 것(그 순간의 DOM)만 정직하게 보고했을 뿐이었다.

2차 수정 — 그럼 진짜 버튼을 누르자

DOM을 흉내 내는 방식이 근본적으로 안 통한다는 게 확인됐으니, 스마트에디터 자신의 정렬 툴바 버튼을 실제로 클릭하는 방식으로 바꿨다. 수동으로 편집 화면에서 리스트를 선택하고 “왼쪽 정렬” 버튼을 직접 눌러보니 발행 후에도 정렬이 유지되는 걸 확인했고, 개발자도구로 그 버튼을 찾아 선택자를 확보했다.

function findAlignLeftButton() {
  for (const { doc } of allDocs().docs) {
    const btn = deepQueryAll(doc, 'button[data-name="align"][data-value="left"]')[0];
    if (btn) return btn;
  }
  return null;
}

코드를 반영하고 자동화를 다시 돌렸는데, 로그에 “본문 문단을 못 찾음”이 찍히며 정렬 시도 자체가 실행되지 않았다. 원인은 별개였다 — 이건 다른 함수(firstBodyParagraph)가 querySelector로 첫 매치 하나만 보다가 빈 문단에 걸린 문제였고, 그건 어렵지 않게 고쳤다. 하지만 그 함수를 고치고 다시 돌려도 여전히 정렬은 걸리지 않았다.

진짜 원인 — 버튼은 닫힌 드롭다운 안에 없다

findAlignLeftButton()이 매번 null을 반환하고 있었다. 확장에 있던 “DOM 전체를 파일로 저장” 기능으로 닫힌 상태의 에디터 DOM을 떠서 직접 검색해봤다.

$ grep -c 'se-align-left-toolbar-button' editor-dump.html
0
$ grep -c 'data-value="left"' editor-dump.html
0
$ grep -c 'align-drop-down-with-justify' editor-dump.html
1

왼쪽 정렬 옵션 버튼은 덤프 안에 아예 존재하지 않았다. 대신 있던 건 이거였다.

<div data-type="drop-down" data-name="align">
  <button data-name="align-drop-down-with-justify" aria-expanded="false"
          class="se-align-center-toolbar-button">
    <!-- 툴팁: "정렬 열기" -->
  </button>
</div>

정렬은 버튼이 아니라 드롭다운이었다. 툴바에 항상 보이는 건 현재 정렬 상태를 표시하는 토글 하나뿐이고, 왼쪽/가운데/오른쪽 옵션 버튼은 이 드롭다운을 클릭해서 열어야만 DOM에 렌더링된다. 처음에 개발자도구로 버튼을 찾을 때는 실제로 정렬이 적용된 화면, 즉 드롭다운이 이미 열려 있던 상태를 검사한 것이었다 — 그 상태의 DOM을 “항상 있는 DOM”으로 착각한 게 두 번째 실패의 원인이었다.

function findAlignDropDownToggle() {
  for (const { doc } of allDocs().docs) {
    for (const box of deepQueryAll(doc, '[data-type="drop-down"][data-name="align"]')) {
      const btn = box.querySelector("button");
      if (btn?.getBoundingClientRect().width > 0) return btn;
    }
  }
  return null;
}

// 1) 토글을 클릭해 드롭다운을 연다 (이미 열려 있으면 건너뜀)
// 2) 옵션 버튼이 DOM에 나타날 때까지 폴링
// 3) 그제서야 왼쪽 정렬 버튼을 클릭

이번엔 실제 발행에서 정렬이 유지되는 걸 확인했다.

정리

같은 버그를 두 번 “고쳤다”고 생각했는데 둘 다 틀렸다. 첫 번째는 검증 방법 자체가 문제였다 — 로컬 DOM 상태만 확인하고 라이브 발행본은 확인하지 않아서, 반영이 안 되는데도 “성공”으로 착각한 채 여러 편을 발행했다. 두 번째는 관찰 시점의 문제였다 — 이미 열려 있는 UI 상태를 검사하고 그걸 항상 존재하는 상태로 일반화했다. 두 실패 모두 원인이 코드 밖에 있었다는 공통점이 있다. 리치 UI를 자동화로 조작할 때는 로그가 성공을 가리켜도 실제 결과물(이 경우 라이브 HTML)을 별도로 검증해야 하고, 화면 요소의 선택자는 스크린샷 한 장이 아니라 닫힌 상태와 열린 상태의 DOM을 각각 떠서 확정해야 한다.

자주 묻는 질문

로그가 성공이라고 찍혔는데 왜 실제로는 반영이 안 됐나요?

확장이 확인한 건 '지금 이 순간 브라우저 탭의 DOM에 클래스가 바뀌었는가'였습니다. 네이버 스마트에디터는 화면에 보이는 뷰와 실제로 저장되는 문서 모델이 분리된 구조라, DOM을 직접 조작해도 저장 시점에는 그 변경이 남지 않았습니다. 로그는 '그 순간의 DOM 상태'를 정직하게 보고했을 뿐, '저장될 상태'를 보장하지 않았던 것입니다.

그럼 어떻게 확인해야 하나요?

로컬 DOM이 아니라 발행된 라이브 URL의 실제 HTML을 떼어봐야 합니다. 네이버 블로그 공개 API로 발행된 글의 HTML을 직접 받아서 se-text-paragraph-align-* 클래스를 grep으로 세어보고 나서야, '정렬 보정 39개' 로그가 찍힌 글도 실제로는 전부 -align-center로 발행돼 있다는 걸 확인할 수 있었습니다.

왜 버튼 선택자가 처음부터 틀렸나요?

실제로 정렬이 적용된 화면(드롭다운이 열린 상태)에서 개발자도구로 버튼 요소를 확인했는데, 그 상태의 DOM을 '항상 존재하는 DOM'으로 착각했습니다. 닫힌 상태에서는 정렬 드롭다운 컨테이너 안에 현재 상태를 표시하는 토글 버튼 하나만 있고, 왼쪽/가운데/오른쪽 옵션 버튼은 드롭다운이 열려야 렌더링됩니다.

재발을 막으려면 뭘 바꿨나요?

화면에 보이는 요소의 선택자를 스크린샷 한 장으로 확정하지 않기로 했습니다. 확장에 이미 있던 'DOM 전체를 파일로 저장하는' 기능을 열린 상태와 닫힌 상태 양쪽에서 각각 떠서 grep으로 대조하는 걸 표준 절차로 삼았습니다.

#네이버블로그#크롬확장#DOM#자동화#버그