오늘 실습은 네이버 카페 글쓰기 화면에서 사진을 자동으로 올릴 수 있는지 확인하는 과정이었다.
처음부터 Playwright 코드를 작성한 것이 아니라, 먼저 Chrome DevTools에서 사진 버튼의 HTML 구조를 확인하고, Console에서 선택자를 하나씩 테스트했다. 그 다음 확인된 선택자를 Playwright 코드로 옮겨 실제 이미지 업로드까지 연결했다.
자동화는 코드를 먼저 짜는 것이 아니라, 화면에서 제어할 대상을 정확히 찾는 것에서 시작된다.
1. 오늘의 목표
오늘 목표는 네이버 카페 글쓰기 화면에서 아래 과정을 자동화할 수 있는지 확인하는 것이었다.
- 사진 버튼을 코드로 찾는다.
- 사진 버튼을 코드로 클릭한다.
- 파일 선택창이 열리는지 확인한다.
- 실제 업로드 input을 찾는다.
- Playwright로 이미지 파일을 넣는다.
- 네이버 카페 글쓰기 본문에 이미지가 들어가는지 확인한다.
이번 실습에서는 실제 게시글 등록 버튼은 누르지 않았다. 글쓰기 화면에 사진이 들어가는 것까지만 확인했다.
2. DevTools에서 사진 버튼 찾기
먼저 네이버 카페 글쓰기 화면에서 Chrome DevTools를 열었다.
- Chrome에서 글쓰기 화면 열기
F12또는 우클릭 후검사- 상단 탭에서
Elements선택 - 툴바의
사진버튼을 선택 - 해당 요소에서
Copy outerHTML
이렇게 해서 확인한 사진 버튼 HTML은 다음과 같았다.
Code snippet 1: 네이버 카페 사진 버튼 HTML
<button
type="button"
title=""
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>
이 코드의 역할
이 HTML은 네이버 카페 글쓰기 툴바의 사진 버튼 구조다. 여기서 자동화에 쓸 만한 단서는 data-name="image"였다.
화면에 보이는 글자인 사진으로 찾을 수도 있지만, 자동화에서는 기능을 나타내는 속성인 data-name="image"가 더 안정적이라고 판단했다.
3. Console에서 사진 버튼 선택자 테스트
Elements에서 HTML을 확인한 뒤, 바로 Console에서 선택자가 맞는지 테스트했다.
Code snippet 2: 사진 버튼 선택자 확인
document.querySelector('button[data-name="image"]')
이 코드의 역할
현재 페이지에서 button 태그 중 data-name 값이 image인 요소를 찾는다.
Console 결과로 실제 <button> 요소가 반환되었다. 이 말은 선택자가 맞다는 뜻이다.
Result: 선택자 테스트 결과
<button
type="button"
class="se-image-toolbar-button ..."
data-name="image"
>
...
</button>
이 단계에서 얻은 결론은 간단했다.
button[data-name="image"]는 네이버 카페 사진 버튼을 찾는 선택자로 사용할 수 있다.
4. Console에서 사진 버튼 클릭 테스트
선택자가 맞는 것을 확인했으니, 이번에는 실제로 클릭이 되는지 테스트했다.
Code snippet 3: 사진 버튼 클릭
document.querySelector('button[data-name="image"]').click()
이 코드의 역할
앞에서 찾은 사진 버튼을 JavaScript로 클릭한다. 사람이 마우스로 사진 버튼을 누르는 동작을 코드로 실행한 것이다.
이 코드를 실행하자 Windows 파일 선택창이 열렸다. 즉, 사진 버튼은 코드로 제어할 수 있었다.
여기서 확인한 것:
- 사진 버튼 선택자가 맞다.
- 코드로 클릭할 수 있다.
- 클릭하면 파일 선택창이 열린다.
- 따라서 Playwright 자동화로 연결할 수 있다.
5. 실제 파일 업로드 input 찾기
사진 버튼을 클릭하면 파일 선택창이 열린다. 그렇다면 페이지 어딘가에는 실제 파일을 받는 <input type="file"> 요소가 있어야 한다.
그래서 Console에서 파일 input 개수를 확인했다.
Code snippet 4: 파일 input 개수 확인
document.querySelectorAll('input[type="file"]').length
이 코드의 역할
현재 페이지에 있는 모든 파일 업로드 input을 찾고, 그 개수를 확인한다.
실행 결과는 3이었다. 네이버 에디터 안에 파일 업로드 input이 3개 존재한다는 뜻이다.
6. 파일 input 상세 정보 확인
input이 3개 있다는 것만으로는 어떤 것이 사진 업로드용인지 알 수 없다. 그래서 각 input의 정보를 자세히 확인했다.
Code snippet 5: 파일 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들을 배열로 바꾼 뒤, 각 input의 핵심 정보를 보기 좋게 정리한다.
accept: 허용하는 파일 확장자multiple: 여러 파일 업로드 가능 여부style: 화면에 보이는지 숨겨져 있는지
Result: 확인된 input 정보
accept: ".jpg,.jpeg,.gif,.png,.bmp,.heic,.heif,.webp"
multiple: true
확인 결과, 이미지 확장자를 허용하고 있었고 여러 파일 업로드도 가능했다.
7. 숨겨진 파일 input 확인
다음으로 첫 번째 파일 input을 직접 확인했다.
Code snippet 6: 첫 번째 파일 input 확인
document.querySelectorAll('input[type="file"]')[0]
이 코드의 역할
파일 input 중 첫 번째 요소를 직접 확인한다.
확인 결과는 아래와 같았다.
Result: 숨겨진 파일 input
<input
type="file"
multiple
accept=".jpg,.jpeg,.gif,.png,.bmp,.heic,.heif,.webp"
id="hidden-file"
style="display: none;"
>
화면에서는 보이지 않았지만, 실제 업로드를 담당하는 input이었다. id는 hidden-file이었다.
그래서 다시 id 선택자로 확인했다.
Code snippet 7: hidden-file 선택자 확인
document.querySelector('#hidden-file')
이 코드의 역할
id="hidden-file"인 요소를 찾는다. 결과로 파일 input이 반환되었기 때문에, 실제 업로드 input 선택자는 #hidden-file로 정리할 수 있었다.
8. DevTools 테스트 결과 정리
| 대상 | 선택자 | 확인 방법 | 역할 |
|---|---|---|---|
| 사진 버튼 | button[data-name="image"] |
Console에서 querySelector |
사람이 누르는 사진 추가 버튼 |
| 파일 input | #hidden-file |
Console에서 querySelector |
실제 이미지 파일을 받는 숨겨진 input |
이제 선택자 2개가 준비되었다. 다음 단계는 이 선택자를 Playwright 코드로 옮기는 것이다.
9. Playwright로 사진 버튼 제어하기
DevTools Console에서 검증한 선택자를 Playwright의 locator()로 옮겼다.
Code snippet 8: 사진 버튼 locator
const photoButton = page.locator('button[data-name="image"]');
await photoButton.waitFor({
state: "visible",
timeout: 15000,
});
이 코드의 역할
네이버 카페 글쓰기 화면에서 사진 버튼을 찾고, 화면에 보일 때까지 기다린다.
waitFor()를 쓰는 이유는 페이지가 아직 완전히 로딩되지 않았을 때 바로 클릭하면 실패할 수 있기 때문이다.
10. 사진 버튼 클릭 후 파일 선택창 기다리기
Playwright에서는 파일 선택창이 열리는 순간을 이벤트로 잡을 수 있다.
Code snippet 9: 파일 선택창 이벤트 처리
const fileChooserPromise = page.waitForEvent("filechooser", {
timeout: 10000,
});
await photoButton.click();
이 코드의 역할
먼저 파일 선택창 이벤트를 기다리도록 준비하고, 그 다음 사진 버튼을 클릭한다.
순서가 중요하다. 버튼을 먼저 누르고 나중에 이벤트를 기다리면, 파일 선택 이벤트를 놓칠 수 있다.
11. 이미지 파일 넣기
파일 선택창이 열리면, 사람이 파일을 고르는 대신 코드가 이미지 파일 경로를 넣는다.
Code snippet 10: 이미지 파일 업로드
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(imagePath);
이 코드의 역할
파일 선택창에 업로드할 이미지 파일을 지정한다. 이번 실습에서는 아래 파일을 사용했다.
업로드 테스트 파일
C:\Users\misoh\Downloads\CaptureX_2026-06-17_162107_ozcoding-publicdoc.notion.site.png
12. 파일 선택창 이벤트가 안 잡힐 때의 예비 방식
브라우저나 페이지 상태에 따라 filechooser 이벤트가 잡히지 않을 수도 있다. 그래서 DevTools에서 찾은 실제 input인 #hidden-file을 예비 경로로 사용했다.
Code snippet 11: 숨겨진 input에 직접 파일 넣기
await page.locator("#hidden-file").setInputFiles(imagePath);
이 코드의 역할
숨겨진 파일 input에 직접 이미지 파일을 넣는다. 이 방식은 DevTools에서 #hidden-file이 실제 업로드 input이라는 것을 확인했기 때문에 사용할 수 있었다.
13. 업로드 결과 확인하기
업로드 후에는 페이지에 이미지 관련 요소가 생겼는지 확인했다.
Code snippet 12: 업로드 결과 확인
const uploadedImageCount = await page
.locator(".se-component.se-image, .se-image img, img")
.count();
console.log(`현재 페이지에서 감지된 이미지 관련 요소 수: ${uploadedImageCount}`);
이 코드의 역할
페이지 안에서 이미지 관련 요소가 몇 개 있는지 센다. 업로드 후 이미지 요소가 늘어나면 삽입 여부를 어느 정도 확인할 수 있다.
다만 이 선택자는 페이지 전체의 img까지 포함하기 때문에 완벽한 성공 판정은 아니다. 나중에는 에디터 본문 영역 안에서만 이미지를 찾도록 개선해야 한다.
14. 결과 스크린샷 저장하기
자동화 결과는 눈으로도 확인해야 하므로 스크린샷을 저장했다.
Code snippet 13: 결과 스크린샷 저장
await page.screenshot({
path: RESULT_SCREENSHOT,
fullPage: true,
});
이 코드의 역할
현재 브라우저 화면 전체를 이미지로 저장한다. 업로드가 제대로 되었는지 나중에 확인할 수 있다.
15. 실습 중 발생한 문제
문제 1. 자동 생성 PNG는 네이버에서 거부됨
처음에는 아주 작은 테스트 PNG 파일을 자동 생성해서 업로드했다. 하지만 네이버에서 아래와 같은 오류가 발생했다.
Error snippet: 파일 전송 오류
파일 전송 오류
총 1개의 파일을 전송하지 못했습니다.
오류 파일은 문서에서 삭제됩니다.
알 수 없는 파일
그래서 실제 다운로드 폴더에 있던 이미지 파일을 지정해서 다시 테스트했다.
Code snippet 14: 실제 이미지 파일 지정
$env:IMAGE_PATH = "C:\Users\misoh\Downloads\CaptureX_2026-06-17_162107_ozcoding-publicdoc.notion.site.png"
이 코드의 역할
자동 생성 이미지 대신 실제 PNG 파일을 업로드 대상으로 지정한다.
문제 2. 자동화 브라우저와 기존 브라우저가 다름
Playwright는 자동화용 브라우저를 새로 연다. 그래서 자동화 브라우저 안에 사진이 들어가도, 내가 원래 보고 있던 Chrome 화면에는 보이지 않을 수 있다.
이 문제를 확인하기 위해 업로드 후 브라우저를 닫지 않는 옵션을 추가했다.
Code snippet 15: 브라우저 닫지 않기
if (KEEP_OPEN) {
console.log("KEEP_OPEN=1 이므로 브라우저를 닫지 않습니다.");
await new Promise(() => {});
}
이 코드의 역할
업로드 후 브라우저를 계속 열어둔다. 사용자가 직접 네이버 카페 글쓰기 화면에서 이미지가 들어갔는지 확인할 수 있게 하기 위한 코드다.
16. 최종 실행 명령
지정한 CaptureX 이미지를 사용하고, 업로드 후 브라우저를 닫지 않는 실행 파일을 만들었다.
Code snippet 16: 실행 명령
& "\\wsl.localhost\Ubuntu-24.04\home\misoh\projects\Beginner-track\run-naver-upload-capturex-keep-open.cmd"
이 코드의 역할
Windows Chrome을 열고, 네이버 카페 글쓰기 화면으로 이동한 뒤, 지정한 PNG 파일을 사진 버튼을 통해 업로드한다. 그리고 결과를 확인할 수 있도록 브라우저를 닫지 않는다.
17. 오늘의 핵심 코드만 모아보기
Code snippet 17: 네이버 카페 사진 업로드 핵심 흐름
await page.goto(WRITE_URL);
const photoButton = page.locator('button[data-name="image"]');
await photoButton.waitFor({ state: "visible" });
const fileChooserPromise = page.waitForEvent("filechooser");
await photoButton.click();
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(imagePath);
이 코드의 역할
네이버 카페 글쓰기 페이지로 이동하고, 사진 버튼을 찾고, 클릭하고, 파일 선택창에 이미지 파일을 넣는 핵심 흐름이다.
18. 오늘 배운 점
- DevTools의 Elements에서 자동화할 요소의 HTML 구조를 확인할 수 있다.
- Console에서
document.querySelector()로 선택자가 맞는지 바로 검증할 수 있다. - 네이버 에디터는 사람이 보는 버튼과 실제 업로드 input이 분리되어 있다.
- 사진 버튼은
button[data-name="image"]로 찾을 수 있었다. - 실제 파일 업로드 input은
#hidden-file이었다. - Playwright의
locator()와setFiles()를 사용하면 이미지 업로드를 자동화할 수 있다. - 자동화 브라우저와 내가 보고 있던 브라우저는 다를 수 있으므로, 결과 확인 방식을 따로 만들어야 한다.
이번 실습의 결론: 선택자를 정확히 찾으면 네이버 카페의 사진 업로드도 코드로 제어할 수 있다.
다음 실습 계획
- 제목 입력 영역 선택자 찾기
- 본문 입력 영역 선택자 찾기
- 인용구 버튼 선택자 찾기
- 구분선 버튼 선택자 찾기
- 네이버 카페용 HTML 템플릿 붙여넣기
- 사진 + 제목 + 본문을 한 번에 입력하는 자동화로 확장하기