브라우저 자동화 대기: 90초 타임아웃이 나도 화면부터 확인하는 이유

브라우저 자동화는 몇 초가 지났는지가 아니라 필요한 상태가 생겼는지를 기다려야 한다. 감시를 행동보다 먼저 등록하고, 완료 조건을 만족하면 진행하되 제한 시간을 넘으면 현재 화면을 확인한다. 이 방식은 글 작성·예약 발행을 자동화하는 개발자가 입력 반영과 저장 결과를 구분하는 데 쓸 수 있다.

2026년 10월 13일 실행 로그에는 NavigationReadinessTimeoutError가 30초 대기에서, 이어 90초로 늘린 대기에서도 남아 있다. 그 뒤 현재 화면 스냅숏을 읽고 진행해 8편을 예약했고, 각 글을 다시 열어 확인했다. 같은 실행 안에서도 30초 준비 대기 타임아웃은 여덟 번 더 났지만, 매번 화면을 읽고 다음 단계로 갔다.

30초를 90초로 바꾸면 해결될까?

준비 완료를 기다리다가 타임아웃이 나면 제한 시간부터 늘리고 싶어진다. 그런데 대기 시간이 부족한지, 기다리는 신호가 작업의 완료 조건과 다른지 먼저 나눠야 한다. 같은 종류의 대기가 90초에서도 끝나지 않았다는 보고만으로 브라우저 내부 원인을 단정할 수는 없다.

오류 이름이 말해 주는 것은 준비 상태 대기가 제한 시간 안에 끝나지 않았다는 사실이다. 입력이 실패했다거나 저장이 취소됐다는 뜻까지 담고 있지는 않다. 타임아웃 뒤 화면을 읽지 않고 클릭을 반복하면 현재 상태와 다른 행동을 할 수 있다.

붙여넣기에서도 비슷한 구분이 필요했다. 9월 27일 SmartEditor 조사에서는 합성 paste 이벤트가 처리됐지만 직후 본문은 한 문단으로 보였다. 나중에는 시험용 두 문단이 들어왔다.

이벤트 반환과 본문 반영을 같은 순간으로 취급한 검사가 잘못된 실패 판정을 만든 것이다.

최소 재현은 단순하다. 시험용 편집기에서 붙여넣기 직후 본문을 한 번 읽고, 변경 감시가 확인한 본문과 비교한다.

앞의 읽기만으로 실패를 판정하는 순서와, 감시를 먼저 붙이는 순서를 대조하면 된다. 실제 글을 발행할 필요는 없다.

먼저 구독하고 나서 행동한다

완료 조건은 행동 전에 정한다. 붙여넣기라면 의도한 전체 본문과 편집기 본문이 같은지다. 제목 입력, 이미지 준비, 예약 저장은 각각 다른 조건으로 나눈다.

본문 일치 하나로 저장까지 성공했다고 판단하지 않는다.

순서는 감시 등록, 행동 실행, 조건 확인이다. 행동 뒤에 감시를 붙이면 그 사이에 끝난 변경을 놓칠 수 있다.

변경 이벤트가 왔다는 사실만으로도 부족하다. 이벤트는 본문을 다시 읽을 계기이고, 읽은 값이 목표와 같은지가 완료 판정이다.

아래는 일반 DOM에서 순서를 확인하는 예제다. 브라우저 개발자 도구에서 실행할 수 있다. 실제 편집기의 붙여넣기를 흉내 낸 코드는 아니며, 비동기 변경과 감시 순서만 분리했다.

const editor = document.createElement("div");
document.body.append(editor);
const expected = "반영할 본문";
const timeoutMs = 5000; // 예제의 실패 상한

const done = new Promise((resolve, reject) => {
  const observer = new MutationObserver(check);
  const timer = setTimeout(() => {
    observer.disconnect();
    reject(new Error("본문 일치 대기 시간 초과"));
  }, timeoutMs);
  function check() {
    if (editor.textContent !== expected) return;
    clearTimeout(timer);
    observer.disconnect();
    resolve();
  }
  observer.observe(editor, {
    childList: true, characterData: true, subtree: true
  });
  check();
});

// 감시가 등록된 다음 비동기 변경을 일으킨다.
queueMicrotask(() => { editor.textContent = expected; });
try {
  await done;
} finally {
  editor.remove();
}

setTimeout은 반영을 기다린 뒤 성공시키는 sleep이 아니다. 목표 상태가 오지 않을 때 대기를 실패로 끝내는 상한이다. 변경이 빨리 끝나면 타이머가 끝날 때까지 기다리지 않는다.

5초는 예제 설정일 뿐 운영 권장값이나 측정 결과가 아니다.

실제 SmartEditor 기록은 공백과 제로폭 문자를 정규화한 누적 본문을 비교했다. 비교 규칙도 완료 조건의 일부다. 이 예제처럼 정확한 문자열 일치가 필요한 입력에 공백 제거를 무조건 넣으면 의미 있는 차이까지 지울 수 있다.

신호가 안 오면 스냅숏으로 분기한다

이벤트 구독도 모든 대기를 해결하지는 않는다. 10월 13일 기록의 전환점은 더 오래 기다리는 대신 현재 스냅숏을 읽고 진행한 것이다. 나는 이를 준비 신호와 작업 가능 상태를 분리한 대응으로 본다.

8편 예약은 그 실행의 결과이지, 타임아웃을 무시해도 된다는 근거가 아니다.

  1. 대기가 끝나거나 실패하면 현재 화면과 URL을 읽는다.
  2. 필요한 입력란, 버튼, 입력값이 실제로 있는지 확인한다.
  3. 다음 행동의 전제 조건이 맞을 때만 진행한다.
  4. 조건이 없거나 화면이 다르면 마지막 확인 상태와 오류를 남기고 멈춘다.

스냅숏은 오래된 화면을 계속 믿기 위한 자료가 아니다. 단계가 바뀌면 새 상태를 읽어야 한다. 편집기 본문이 맞아도 이미지가 준비됐는지, 예약 상태가 저장됐는지는 별도로 확인한다.

저장이 애매하면 재시도보다 저장된 항목을 다시 여는 검증이 먼저다.

기다리는 기준확인하는 사실다음 행동
고정 sleep 종료정한 시간이 지났다완료로 판정할 근거가 부족하다
본문 일치 이벤트 대기목표 텍스트가 DOM에 반영됐다다음 입력 검증으로 간다
준비 대기 타임아웃 뒤 스냅숏현재 화면에 필요한 상태가 있다전제 조건에 맞는 단계만 진행한다
저장 후 재열기저장된 결과가 유지된다최종 결과와 대조한다

어디까지 적용할 수 있나?

MutationObserver는 관찰한 DOM 변경을 다룬다. 예제처럼 같은 문서 안의 요소를 읽을 수 있어야 한다. 페이지 이동으로 문서가 바뀌면 새 문서에 감시를 등록해야 하고, 서버 저장 여부는 DOM 본문 일치만으로 증명할 수 없다.

준비 신호 실패의 내부 원인, 이 방식의 반복 성공률, 다른 브라우저에서의 대기 시간은 이번 근거로 확인하지 못했다. 따라서 30초나 90초를 새 상수로 외우기보다 각 단계에서 무엇이 완료인지 정하는 편이 낫다.

결국 기준은 기다린 시간보다 확인한 상태다. 지금 자동화 코드의 sleep 한 곳을 골라, 그 뒤 행동에 꼭 필요한 상태를 적고 행동 전에 감시를 등록하자. 타임아웃 분기에는 무조건 재클릭하는 코드 대신 현재 화면을 읽고 멈출 조건을 넣으면 된다.

다음 글

본문 반영 조건을 구체화하려면 SmartEditor 붙여넣기와 본문 일치 검증으로 이어 가면 된다. 그 글은 편집기 입력을 다루고, 여기서는 준비 신호가 실패한 뒤의 진행·중단 기준을 확장했다.

재시도 전에 같은 작업이 이미 처리됐는지 확인하려면 텔레그램 블로그 작업의 중복 실행 방지를 보면 된다. 목록을 읽었다는 것과 전체 목록을 읽었다는 것도 다르다. 최종 대조용 목록 수집은 Blogger 피드 페이지네이션에서 실제 반환 수와 고유 ID를 확인하는 순서로 이어진다.

---

댓글

이 블로그의 인기 게시물

AI 코딩 스킬, 많을수록 좋을까 — 자산이 부채로 바뀌는 손익분기

M4 Max 128GB 로컬 LLM 4종 속도 비교 — 실측 기록과 한계

Opus 5는 더 잘 쓰고, 더 AI답게 쓴다 — 세 모델 첫날 실측