Skip to content

네이버 카페 DevTools와 Playwright 업로드 실습

오늘은 Chrome DevTools에서 네이버 카페 글쓰기 화면의 요소를 직접 확인하고, 그 선택자를 Playwright 자동화 코드로 연결하는 실습을 했다.

처음 목표는 단순했다. 네이버 카페 글쓰기 툴바에 있는 사진 버튼을 코드로 제어할 수 있는지 확인하는 것이었다. 결과적으로 사진 버튼 선택자와 실제 파일 업로드 input을 찾아냈고, Playwright로 이미지 파일을 글쓰기 화면에 넣는 것까지 테스트했다.

오늘의 핵심 문장: 선택자는 자동화에서 화면 기능을 붙잡는 손잡이다.


1. 왜 DevTools를 봤나

네이버 카페 글쓰기 화면에는 사진, MYBOX, 동영상, 스티커, 인용구, 구분선, 파일, 링크 같은 버튼이 있다. 사람이 직접 누르면 간단하지만, 자동화하려면 먼저 코드가 어떤 요소를 눌러야 하는지 알아야 한다.

그래서 Chrome DevTools의 ElementsConsole을 사용했다.

  • Elements: 실제 HTML 구조 확인
  • Console: 선택자가 맞는지 바로 테스트
  • Styles/Computed: 요소에 적용된 CSS 확인
  • Network: 나중에 저장/업로드 요청을 분석할 때 사용

오늘은 그중에서도 Elements와 Console을 중심으로 실습했다.

2. 네이버 카페 사진 버튼 HTML 확인

먼저 DevTools에서 사진 버튼을 선택하고 Copy outerHTML로 버튼 HTML을 복사했다.

Code snippet: 사진 버튼 HTML
<button
  type="button"
  class="se-image-toolbar-button se-document-toolbar-basic-button se-text-icon-toolbar-button __se-sentry"
  data-group="documentToolbar"
  data-type="basic"
  data-name="image"
  data-log="dot.img"
>
  <span class="se-toolbar-icon"></span>
  <span class="se-toolbar-label" aria-hidden="true">사진</span>
  <span class="se-toolbar-tooltip">사진 추가</span>
</button>

여기서 가장 중요하게 본 것은 data-name="image"였다. 버튼의 화면 텍스트인 “사진”보다, 기능을 나타내는 속성인 data-name이 자동화 선택자로 더 적합하다고 판단했다.

그래서 사진 버튼 선택자는 아래처럼 잡았다.

Code snippet: 사진 버튼 선택자
document.querySelector('button[data-name="image"]')

Console에서 실행했을 때 실제 버튼 요소가 반환되었기 때문에, 이 선택자는 유효하다고 볼 수 있었다.

3. Console에서 사진 버튼 클릭 테스트

선택자가 맞는 것을 확인한 뒤, 바로 클릭 테스트를 했다.

Code snippet: Console에서 사진 버튼 클릭
document.querySelector('button[data-name="image"]').click()

이 코드를 실행하자 Windows 파일 선택창이 열렸다. 이 결과는 중요했다. 왜냐하면 사진 버튼이 단순히 화면 장식이 아니라, 실제 파일 업로드 동작까지 연결되어 있다는 뜻이기 때문이다.

이 시점에서 확인한 것은 다음과 같다.

  • 사진 버튼 선택자는 맞다.
  • 코드로 클릭할 수 있다.
  • 클릭하면 파일 선택창이 열린다.
  • 따라서 자동 업로드 가능성이 높다.

4. 실제 파일 input 찾기

브라우저에서 파일 업로드는 보통 <input type="file"> 요소가 담당한다. 그래서 Console에서 파일 input 개수를 확인했다.

Code snippet: 파일 input 개수 확인
document.querySelectorAll('input[type="file"]').length

결과는 3이었다. 네이버 에디터 안에 파일 업로드용 input이 3개 있다는 뜻이다.

다음으로 각 input의 상세 정보를 확인했다.

Code snippet: 파일 input 상세 정보 확인
[...document.querySelectorAll('input[type="file"]')].map((input, index) => ({
  index,
  accept: input.accept,
  multiple: input.multiple,
  name: input.name,
  className: input.className,
  style: input.getAttribute("style")
}))

확인 결과, input들은 이미지 확장자를 허용하고 있었고 multiple: true였다.

Result: input 정보 요약
accept: ".jpg,.jpeg,.gif,.png,.bmp,.heic,.heif,.webp"
multiple: true

그리고 첫 번째 input을 확인했을 때, 아래처럼 숨겨진 파일 input이었다.

Code snippet: 숨겨진 파일 input 확인
document.querySelector('#hidden-file')
Result: 실제 업로드 input
<input
  type="file"
  multiple
  accept=".jpg,.jpeg,.gif,.png,.bmp,.heic,.heif,.webp"
  id="hidden-file"
  style="display: none;"
>

여기서 중요한 사실을 알게 됐다.

사람은 사진 버튼을 누르지만, 실제 파일 업로드는 숨겨진 #hidden-file input이 담당한다.

5. 선택자 분석 결과

기능 선택자 역할
사진 버튼 button[data-name="image"] 사용자가 누르는 사진 추가 버튼
실제 파일 input #hidden-file 이미지 파일을 실제로 받는 숨겨진 input
이미지 삽입 확인 .se-component.se-image, .se-image img, img 업로드 후 이미지 요소가 생겼는지 확인

6. Playwright 코드로 옮기기

Console에서 확인한 선택자를 Playwright 코드로 옮겼다. 핵심 흐름은 다음과 같다.

  1. 네이버 카페 글쓰기 페이지로 이동한다.
  2. 사진 버튼을 찾는다.
  3. 사진 버튼이 보일 때까지 기다린다.
  4. 사진 버튼을 클릭한다.
  5. 파일 선택창에 이미지 파일을 넣는다.
  6. 결과 스크린샷을 저장한다.

6-1. 브라우저 열기

Code snippet: 브라우저 실행
const browser = await chromium.launch({
  headless: false,
  slowMo: 80,
  ...(CHROME_EXECUTABLE_PATH ? { executablePath: CHROME_EXECUTABLE_PATH } : {}),
});

headless: false는 브라우저를 눈에 보이게 실행한다는 뜻이다. 자동화 과정을 직접 확인해야 했기 때문에 화면이 보이는 방식으로 실행했다.

6-2. 글쓰기 페이지 열기

Code snippet: 글쓰기 페이지 이동
await page.goto(WRITE_URL, {
  waitUntil: "domcontentloaded",
});

이 코드는 네이버 카페 글쓰기 URL로 이동한다. 로그인 세션이 없으면 네이버 로그인 페이지로 이동하기 때문에, 처음에는 여기서 한 번 막혔다.

6-3. 사진 버튼 찾고 기다리기

Code snippet: 사진 버튼 locator
const photoButton = page.locator('button[data-name="image"]');
await photoButton.waitFor({
  state: "visible",
  timeout: 15000,
});

DevTools에서 찾은 button[data-name="image"] 선택자를 Playwright의 locator()에 그대로 사용했다.

여기서 waitFor()를 쓴 이유는 페이지 로딩이 끝나기 전에 클릭하면 실패할 수 있기 때문이다. 사진 버튼이 실제로 화면에 보일 때까지 기다린 뒤 다음 단계로 넘어간다.

6-4. 사진 버튼 클릭 후 파일 선택창 처리

Code snippet: 파일 선택 이벤트 기다리기
const fileChooserPromise = page.waitForEvent("filechooser", {
  timeout: 10000,
});

await photoButton.click();

const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(imagePath);

이 코드는 사람이 사진 버튼을 누르고 파일을 고르는 동작을 자동화한다.

  • waitForEvent("filechooser"): 파일 선택창이 열리기를 기다림
  • photoButton.click(): 사진 버튼 클릭
  • fileChooser.setFiles(imagePath): 업로드할 이미지 파일 지정

6-5. 파일 선택 이벤트가 안 잡힐 때 예비 경로

Code snippet: 숨겨진 input으로 직접 업로드
await page.locator("#hidden-file").setInputFiles(imagePath);

만약 파일 선택창 이벤트가 감지되지 않으면, DevTools에서 찾은 실제 input인 #hidden-file에 직접 파일을 넣는다.

이 부분이 가능한 이유는 앞에서 Console로 #hidden-file이 실제 파일 input이라는 것을 확인했기 때문이다.

6-6. 업로드 결과 확인

Code snippet: 이미지 요소 개수 확인
const uploadedImageCount = await page
  .locator(".se-component.se-image, .se-image img, img")
  .count();

업로드 후 페이지 안에 이미지 관련 요소가 몇 개 있는지 확인했다. 다만 이 선택자는 페이지 전체의 img까지 포함하기 때문에, 정확한 성공 판정용으로는 조금 더 개선할 필요가 있다.

더 나은 방향은 에디터 본문 영역 안에서만 이미지를 찾는 것이다.

Code snippet: 개선 방향 예시
const editor = page.locator(".se-main-container, .se-content");
const imageCount = await editor.locator(".se-component.se-image img").count();

7. 실습 중 만난 문제와 해결

문제 1. WSL Chromium 실행 실패

처음에는 WSL에서 Playwright Chromium을 실행하려고 했다. 하지만 아래 오류가 발생했다.

Error snippet: WSL Chromium 라이브러리 오류
error while loading shared libraries: libnspr4.so:
cannot open shared object file: No such file or directory

이 문제는 WSL 안에 Chromium 실행에 필요한 시스템 라이브러리가 없어서 생긴 문제였다. 해결 방법은 두 가지였다.

  • WSL에 Playwright 의존성 설치
  • Windows Chrome을 직접 지정해서 실행

이번 실습에서는 두 번째 방법을 사용했다.

Code snippet: Windows Chrome 지정
const browser = await chromium.launch({
  headless: false,
  slowMo: 80,
  ...(CHROME_EXECUTABLE_PATH ? { executablePath: CHROME_EXECUTABLE_PATH } : {}),
});

실행할 때는 환경변수로 Chrome 경로를 넘겼다.

Command snippet: Windows Chrome으로 실행
$env:CHROME_EXECUTABLE_PATH = "C:\Program Files\Google\Chrome\Application\chrome.exe"

문제 2. 로그인 세션이 없어서 사진 버튼을 못 찾음

새 브라우저로 실행했을 때 처음에는 네이버 로그인 페이지로 이동했다. 그래서 사진 버튼 선택자인 button[data-name="image"]를 찾지 못했다.

Error snippet: 로그인 페이지로 이동
navigated to "https://nid.naver.com/nidlogin.login?url=..."
waiting for locator('button[data-name="image"]') to be visible
Timeout 15000ms exceeded

이를 해결하기 위해 로그인 상태를 저장하도록 했다.

Code snippet: 로그인 세션 저장
await context.storageState({
  path: STORAGE_STATE,
});

이후에는 .auth/naver-storage-state.json 파일을 이용해 로그인 상태를 다시 사용할 수 있게 했다.

문제 3. 자동 생성 테스트 PNG가 네이버에서 거부됨

처음에는 작은 테스트 PNG 파일을 자동 생성해서 업로드했다. 하지만 네이버에서 아래와 같은 오류가 나왔다.

Error snippet: 파일 전송 오류
파일 전송 오류
총 1개의 파일을 전송하지 못했습니다.
알 수 없는 파일

이 문제는 자동 생성한 테스트 파일이 너무 작거나 네이버 파일 검증에서 일반 이미지로 처리되지 않았을 가능성이 있었다. 그래서 실제 다운로드 폴더에 있던 이미지 파일을 지정해서 다시 테스트했다.

Command snippet: 실제 이미지 파일 지정
$env:IMAGE_PATH = "C:\Users\misoh\Downloads\CaptureX_2026-06-17_162107_ozcoding-publicdoc.notion.site.png"

문제 4. 자동화 브라우저와 내가 보고 있던 브라우저가 다름

중간에 “업로드가 됐다”고 판단했지만, 실제로 사용자가 보고 있던 기존 Chrome에는 이미지가 보이지 않았다.

이유는 간단했다. Playwright가 연 브라우저는 기존에 사용자가 보고 있던 Chrome이 아니라 자동화용으로 새로 열린 Chrome이었다. 즉, 자동화 브라우저 안에서는 이미지가 들어갔지만, 사용자가 보고 있던 기존 브라우저에는 반영되지 않았다.

그래서 결과 확인을 위해 브라우저가 바로 닫히지 않도록 KEEP_OPEN=1 옵션을 추가했다.

Code snippet: 브라우저 유지 옵션
if (KEEP_OPEN) {
  console.log("KEEP_OPEN=1 이므로 브라우저를 닫지 않습니다.");
  await new Promise(() => {});
}

8. 오늘 만든 실행 명령

지정한 CSS 학습 이미지 파일을 올리고 브라우저를 닫지 않는 실행 파일을 만들었다.

Command snippet: 실행 명령
& "\\wsl.localhost\Ubuntu-24.04\home\misoh\projects\Beginner-track\run-naver-upload-capturex-keep-open.cmd"

이 명령은 다음 설정을 포함한다.

  • Windows Chrome 사용
  • 지정한 CaptureX PNG 파일 사용
  • 자동 진행
  • 업로드 후 브라우저 닫지 않음
  • 발행 버튼은 누르지 않음

9. 오늘 실습의 결론

오늘 실습을 통해 확인한 것은 단순히 “사진이 올라간다”가 아니었다. 더 중요한 것은 자동화가 만들어지는 과정이었다.

  1. DevTools에서 HTML 구조를 본다.
  2. 자동화에 쓸 수 있는 안정적인 선택자를 고른다.
  3. Console에서 선택자가 맞는지 검증한다.
  4. Playwright 코드의 locator()로 옮긴다.
  5. 실패하면 오류 메시지를 기준으로 원인을 나눈다.
  6. 브라우저, 로그인 세션, 파일 형식 같은 현실적인 문제를 하나씩 해결한다.

이번 실습에서 가장 중요한 선택자는 두 개였다.

Code snippet: 오늘의 핵심 선택자
const photoButton = page.locator('button[data-name="image"]');

await page.locator("#hidden-file").setInputFiles(imagePath);

button[data-name="image"]는 사람이 누르는 사진 버튼이고, #hidden-file은 실제 파일을 받는 숨겨진 input이다. 이 둘을 연결하니 네이버 카페 사진 업로드 자동화의 첫 단계를 만들 수 있었다.

다음 실습

  • 제목 입력 영역 선택자 찾기
  • 본문 입력 영역 선택자 찾기
  • 인용구 버튼 선택자 찾기
  • 구분선 버튼 선택자 찾기
  • 네이버 카페용 안전 HTML 템플릿 붙여넣기
  • WordPress용 글 발행 자동화와 연결하기

다음부터는 사진 업로드처럼 기능을 하나씩 쪼개서, 선택자 분석표를 채워가는 방식으로 진행하면 된다.