웹 관리자 페이지 전체 스크린샷 자동화 — Playwright로 170장을 8분 만에
방화벽이나 라우터처럼 웹 GUI 가 곧 설정 파일인 장비는 “지금 상태를 통째로 찍어두고 싶다”는 욕구가 생깁니다. 설정 XML 을 내보내는 것도 방법이지만, 다음 날 어떤 화면이 어떻게 바뀌었는지 눈으로 비교하려면 역시 스크린샷이 편합니다. 문제는 관리자 UI 가 보통 100 페이지 넘는다는 거죠. 그래서 Playwright 독립 스크립트로 사이드바 전체를 순회하면서 fullPage PNG 를 찍는 방식을 만들어서 쓰고 있습니다. 이 글은 “이런 방법이 있으니 가져다 쓰세요” 공유입니다.
170+
한 번에 찍는 페이지 수
~16MB
PNG 총 용량
8~10분
1회 실행 시간
0원
도구 비용 (OSS)
왜 필요했나
저희 환경에서는 방화벽 관리자 페이지를 건드리는 일이 자주 있습니다. 규칙 추가, DHCP 예약, VPN 설정, NAT 포워딩… 보통 변경 후 “이게 정말 내가 의도한 그 화면 맞나?” 다시 돌아가 확인할 일이 생깁니다. 그런데 되돌릴 화면이 없으면 불안합니다.
설정 XML 백업은 물론 돌려놓지만, “어느 날짜의 GUI 가 어떻게 보였는지” 는 XML 만으론 복원이 안 됩니다. 드롭다운 선택지, 현재 세션 수, 인터페이스별 RX/TX 카운터 — 이런 건 페이지를 열어야 보이죠. 그래서 페이지 렌더 결과 자체를 YYMMDD 폴더에 매일 떨어뜨려 두는 방식이 제일 잘 맞았습니다.
💡 이 방식이 유용한 경우
- 방화벽·라우터·NAS 처럼 GUI 가 설정의 주요 인터페이스인 장비
- 변경 추적을 하고 싶지만 감사 로그가 빈약한 제품
- AI 에게 “이 페이지 좀 봐 달라” 할 때 스크린샷을 바로 집어서 주고 싶은 경우
MCP 대신 독립 스크립트를 선택한 이유
처음엔 Playwright MCP (@playwright/mcp) 로 AI 에게 직접 찍게 해 볼까 했습니다. 그런데 관리자 UI 는 대부분 자체 서명 인증서 (self-signed cert) 를 씁니다. Playwright MCP 에서 이걸 통과시키려면 등록 단계에서 --ignore-https-errors 플래그를 붙여야 하고, 그럼 Claude Code 재시작이 필요합니다. 마찰이 있습니다.
그래서 독립 Node.js 스크립트로 가는 쪽을 택했습니다.ignoreHTTPSErrors: true 한 줄로 인증서 문제가 끝나고, AI 세션과 무관하게 백그라운드에서 돌릴 수 있습니다.
| 방식 | 장점 | 단점 |
|---|---|---|
| Playwright MCP | AI 가 직접 페이지를 열어보며 추론 가능 | 인증서 플래그 · 재시작 필요, 대량 루프 비효율 |
| 독립 Node 스크립트 | 한 번에 170+ 페이지, 백그라운드 실행, cron 친화적 | AI 와 상호작용 없음 (사후 이미지 분석만) |
1단계 — 사이드바 메뉴 전체 수집
스크립트를 두 개로 나눴습니다. 먼저 enumerate.js 로 관리자 UI 에 로그인한 다음 사이드바의 모든 하위 메뉴를 강제로 펼쳐서 <a> 전체를 수집합니다. 수집한 URL 목록은 pages.json 으로 떨어뜨립니다.
핵심 트릭은 document.querySelectorAll('#mainmenu li') 에 classList.add('opened') 를 걸고 sub.style.display = 'block' 으로 감춰진 서브 UL 을 강제로 렌더링하는 겁니다. 실제 클릭 이벤트를 돌리지 않아도 DOM 에는 전부 붙어 있으니map() 한 번이면 전체 메뉴 트리가 평면 리스트로 나옵니다.
const menu = await page.evaluate(() => {
// 서브메뉴 강제 오픈
document.querySelectorAll('#mainmenu li').forEach(li => {
li.classList.add('opened');
const sub = li.querySelector(':scope > ul');
if (sub) sub.style.display = 'block';
});
return [...document.querySelectorAll('#mainmenu a')].map(a => ({
text: (a.innerText || '').trim().replace(/\s+/g, ' '),
href: a.getAttribute('href') || '',
}));
});
// dedup + 외부·위험 링크 제거
const seen = new Set();
const DANGER = ['logout', 'reboot', 'halt', 'power'];
const pages = menu.filter(x => {
const h = (x.href || '').split('#')[0];
if (!h || seen.has(h)) return false;
if (/^#/.test(x.href)) return false;
if (/^https?:/i.test(x.href)) return false;
const low = (x.href + ' ' + x.text).toLowerCase();
if (DANGER.some(d => low.includes(d))) return false;
seen.add(h);
return true;
});이 필터링 한 번으로 보통 200+ 후보가 170 장 정도로 정리됩니다. 외부 도움말 링크 · 로그아웃 · 재부팅 같은 “절대 클릭하면 안 되는 링크”를 한 번에 거릅니다. 자세한 필터는 아래 안전 섹션에서 다시 정리합니다.
2단계 — 페이지 순회하며 스크린샷
두 번째 스크립트 shoot.js 는 pages.json 을 읽어 한 페이지씩 방문하면서 fullPage: true 옵션으로 PNG 를 저장합니다. 저장 경로는 날짜 폴더(YYMMDD)에 NNN_<url경로>__<메뉴이름>.png 형식입니다.
domcontentloaded 가 떠도 차트 · 그래프 위젯이 뒤늦게 그려지는 경우가 있어 page.waitForTimeout(1200) 을 한 번 넣어줍니다. 단순 1.2초 대기지만, 이거 없으면 Dashboard 같은 페이지가 “로딩 스피너만 나온 상태”로 찍힙니다.
for (let i = 0; i < pages.length; i++) {
const { href, text } = pages[i];
const num = String(i + 1).padStart(3, '0');
const fname = `${num}_${sanitize(href, text)}.png`;
const full = path.join(OUT, fname);
try {
await page.goto(BASE + href, { waitUntil: 'domcontentloaded', timeout: 20000 });
await page.waitForTimeout(1200); // 위젯 안정화
await page.screenshot({ path: full, fullPage: true });
index.push({ num, text, href, file: fname, status: 'ok' });
} catch (e) {
index.push({ num, text, href, file: fname, status: 'fail', error: e.message.split('\n')[0] });
}
}
// 마지막에 인덱스 파일 두 개 생성
fs.writeFileSync(path.join(OUT, '_index.json'), JSON.stringify(index, null, 2));
fs.writeFileSync(path.join(OUT, '_index.md'), renderMarkdownTable(index));완료되면 같은 폴더에 _index.md 가 생겨서 “몇 번이 어떤 메뉴인지” 를 표로 볼 수 있습니다. 나중에 특정 페이지만 찾아보기에 좋습니다.
위험 링크는 반드시 걸러낸다
관리자 UI 사이드바에는 클릭하면 돌이킬 수 없는 링크가 숨어 있습니다. 첫 실행 때 이걸 놓치면 순회 중에 방화벽이 재부팅되거나 세션이 끊겨서 스크립트가 로그인 화면만 반복해서 찍습니다. 제가 실제로 한 번 겪었습니다.
⚠️ 필수 제외 패턴
logout— 세션 끊어짐, 이후 페이지 전부 실패reboot— 장비 재부팅, 네트워크 전체 마비 가능halt/power— 장비 정지https?://시작 링크 — 외부 문서 (찍을 필요 없음)#로 시작 — 앵커 (같은 페이지 재촬영)
필터는 href + text 를 합쳐서 lowercase 로 매칭합니다. 언어 설정이 한글인 관리자 UI 에서 “재부팅” 같은 한국어 메뉴가 있을 수 있으니, URL 레벨에서 영문 키워드를 기준으로 거르는 게 더 안전했습니다.
저는 이렇게 씁니다
실제 제 사용 패턴을 공유하면 이렇습니다.
- 큰 변경 직전 스냅샷.
방화벽 규칙을 대량으로 손대기 전에
DATE=YYMMDD node shoot.js를 돌려 “되돌림 참고점” 을 남깁니다. - 주 1회 정기 실행.
월요일 오전에 찍어두면 한 주 동안 누가 뭘 바꿨는지 되짚을 때 편합니다. (cron 으로 자동화도 가능, 아래 마무리에서 언급)
- AI 에게 물어볼 때 근거로 첨부.
Claude Code 같은 CLI 에서 “이 NAT 규칙 이상한데 봐줘” 할 때 해당 스크린샷 파일 경로를 그대로 붙여 넣습니다. 이미지가 있으면 AI 판단 정확도가 훨씬 올라갑니다.
- 날짜 폴더 두 개를 나란히 놓고 눈으로 diff.
“어제 폴더” 와 “오늘 폴더” 를 같은 파일명으로 정렬해 놓으면 미리보기에서 스와이프만 해도 변경 감지가 됩니다. 픽셀 diff 까지는 안 가도 됩니다.
ℹ️ 자격 증명 관리 팁
비밀번호는 스크립트에 하드코딩하지 말고 홈 디렉터리 아래 권한 600 파일 (예: ~/.admin_creds) 에 USER=... / PASS=... 두 줄로 저장해 스크립트가 읽게 합니다. 커밋 사고를 막는 가장 간단한 장치입니다.
주의사항
- ✓폼 selector 는 바뀐다. 관리자 UI 버전업 이후
input[name="usernamefld"]같은 이름이 달라지면 로그인이 실패합니다. 로그인 페이지 HTML 소스에서 필드명 재확인. - ✓로그·그래프 페이지는 느리다. 1.2초 대기로 부족하면 해당 패턴(
/diag_logs/등)만 따로 예외 처리해 대기 시간을 늘리세요. - ✓자체 서명 인증서.
ignoreHTTPSErrors: true를 절대 빼지 마세요. 빼는 순간 모든 요청이 실패합니다. - ✓Claude Code 에서 돌릴 땐 백그라운드. Bash 기본 타임아웃이 2분이라 8~10분 걸리는 스크립트는
node shoot.js > shoot.log 2>&1 &로 돌리고 로그를 tail 하세요. - ✓이미지에 민감 정보가 찍힌다. VPN 공유키, 사용자 목록, 인증서 키 등이 화면에 표시되는 페이지는 결과 폴더를 절대 공개 리포지토리에 커밋하지 마세요.
.gitignore에 날짜 폴더 패턴을 등록해 두는 게 안전합니다.
마무리
스크립트 2개, 코드 100줄 남짓으로 “관리자 UI 의 오늘 상태 통째 백업” 이 가능해집니다. 핵심은 사이드바 강제 오픈 + fullPage PNG + 위험 링크 필터링 세 가지뿐이라, 방화벽뿐 아니라 NAS · 공유기 · 온프레미스 모니터링 대시보드 어디에도 같은 패턴이 그대로 먹힙니다.
다음에 시도해 볼 만한 개선은 (1) cron 으로 매주 자동 실행, (2) 전날 폴더와 이미지 픽셀 diff 로 변경된 페이지만 리포트, (3) 비밀번호를 파일이 아닌 1Password CLI · pass 같은 비밀번호 매니저에서 당겨오기 정도입니다. 저도 아직 (1) 까지만 했고 (2) 는 숙제로 남겨뒀습니다.
요약하면 한 문장입니다. 웹 UI 만 있는 장비라면 Playwright 로 눈에 보이는 모든 걸 날짜별로 쌓아 두세요.설정 복원이든, AI 에게 질문이든, 팀원과 공유든 이후 모든 작업이 쉬워집니다.
댓글
(3)로그인 하면 댓글을 작성할 수 있습니다.
Playwright MCP 로 뚫어보려다 자체서명 인증서 문제로 포기했던 경험이 있어서 더 공감됩니다. 독립 노드 스크립트가 훨씬 덜 귀찮네요.
Logout · Reboot · Halt 를 DANGER 로 거르는 부분이 진짜 중요합니다. 이거 안 하면 첫 실행에서 방화벽 재부팅되는 참사 봅니다.
방화벽 설정 변경 전후 스냅샷으로 쓰기 딱입니다. 팀원한테 설명할 때 '어제 폴더'와 '오늘 폴더' 이미지 비교해서 보여주면 끝.
관련 글
© 2026 TreeRU. All rights reserved.
본 콘텐츠의 저작권은 TreeRU에 있으며, 출처를 밝히지 않은 무단 전재 및 재배포를 금합니다. 인용 시 출처(treeru.com)를 반드시 명시해 주세요.