--- title: Chrome DevTools Protocol로 브라우저 제어하기 description: CDP를 이용해서 Chrome과 Edge 브라우저를 프로그래밍 방식으로 제어하는 방법 date: 2024-11-29 tags: [CDP, 브라우저 제어, Chrome, Edge, JavaScript, Node.js] ---
Chrome DevTools Protocol로 브라우저 제어하기
웹 브라우저를 프로그래밍 방식으로 완전히 조작할 수 있는 Chrome DevTools Protocol (CDP). 실제로 벽돌깨기 게임을 스크립트로 플레이하도록 만드는 과정까지 정리함.CDP란?
Chrome DevTools Protocol은 Chrome 브라우저의 디버깅 인터페이스로, WebSocket을 통해 브라우저와 직접 통신할 수 있는 API.가능한 작업들
- DOM 조작: 실시간으로 HTML 요소 수정
- JavaScript 실행: 페이지에서 코드 실행
- 이벤트 시뮬레이션: 키보드/마우스 입력 생성
- 네트워크 모니터링: HTTP 요청/응답 추적
- 성능 분석: 메모리, CPU 사용량 측정
- 스크린샷: 페이지 캡처
설정 방법
1단계: Chrome/Edge를 디버깅 모드로 실행
# Chrome
start chrome --remote-debugging-port=9222 --user-data-dir="c:\\temp\\chrome_debug"
# Edge (Chromium 기반이므로 동일한 CDP 지원)
start msedge --remote-debugging-port=9222 --user-data-dir="c:\\temp\\edge_debug"
2단계: CDP 라이브러리 설치
npm install chrome-remote-interface
3단계: 기본 연결 테스트
const CDP = require('chrome-remote-interface');
async function connectToBrowser() {
// 새 탭 생성
const tab = await CDP.New({port: 9222});
console.log('새 탭 생성됨:', tab.id);
// 탭에 연결
const client = await CDP({tab});
const {Page, Runtime} = client;
// 프로토콜 활성화
await Page.enable();
await Runtime.enable();
// 페이지 로드
await Page.navigate({url: 'https://example.com'});
// JavaScript 실행
const result = await Runtime.evaluate({
expression: 'document.title'
});
console.log('페이지 제목:', result.result.value);
client.close();
}
connectToBrowser();
실전 예제: 벽돌깨기 게임 스크립트
벽돌깨기 게임을 스크립트로 자동 플레이하도록 만들어봤다.게임 제어 스크립트
const CDP = require('chrome-remote-interface');
async function autoPlayBreakout() {
try {
// 브라우저 연결
const tab = await CDP.New({port: 9222});
const client = await CDP({tab});
const {Page, Runtime} = client;
await Page.enable();
await Runtime.enable();
// 게임 페이지 로드
const gameUrl = 'file:///path/to/breakout-game.html';
await Page.navigate({url: gameUrl});
// 페이지 로드 완료 대기
await new Promise(resolve => {
Page.loadEventFired(resolve);
});
console.log('게임 로드 완료');
// 게임 제어 스크립트 주입
await Runtime.evaluate({
expression: `
let autoPlayInterval;
function startAutoPlay() {
autoPlayInterval = setInterval(() => {
if (typeof ball !== 'undefined' && typeof paddle !== 'undefined') {
// 공의 위치를 추적해서 패들 이동
const targetX = ball.x - paddle.width / 2;
if (targetX > paddle.x + 10) {
// 우측으로 이동
const rightEvent = new KeyboardEvent('keydown', {
code: 'ArrowRight',
key: 'ArrowRight'
});
document.dispatchEvent(rightEvent);
} else if (targetX < paddle.x - 10) {
// 좌측으로 이동
const leftEvent = new KeyboardEvent('keydown', {
code: 'ArrowLeft',
key: 'ArrowLeft'
});
document.dispatchEvent(leftEvent);
}
}
}, 50);
}
startAutoPlay();
'게임 제어 스크립트 시작됨!';
`
});
// 게임 상태 모니터링
const monitorInterval = setInterval(async () => {
try {
const gameState = await Runtime.evaluate({
expression: `
JSON.stringify({
score: document.getElementById('scoreValue')?.textContent || '0',
gameOver: document.getElementById('gameOver')?.style.display !== 'none',
ballPosition: typeof ball !== 'undefined' ?
{x: Math.round(ball.x), y: Math.round(ball.y)} : null
});
`
});
const state = JSON.parse(gameState.result.value);
console.log(`점수: ${state.score}, 공 위치: (${state.ballPosition?.x}, ${state.ballPosition?.y})`);
// 게임 오버 시 재시작
if (state.gameOver) {
console.log('게임 오버! 재시작...');
await Runtime.evaluate({
expression: `
const spaceEvent = new KeyboardEvent('keydown', {
code: 'Space',
key: ' '
});
document.dispatchEvent(spaceEvent);
`
});
}
} catch (error) {
console.error('모니터링 에러:', error.message);
}
}, 1000);
// 30초 후 종료
setTimeout(() => {
clearInterval(monitorInterval);
console.log('게임 제어 종료');
client.close();
}, 30000);
} catch (error) {
console.error('CDP 에러:', error.message);
}
}
autoPlayBreakout();
고급 기능들
DOM 요소 직접 조작
// 페이지의 모든 링크 색상 변경
await Runtime.evaluate({
expression: `
document.querySelectorAll('a').forEach(link => {
link.style.color = '#ff0080';
link.style.textShadow = '0 0 10px #ff0080';
});
`
});
// DOM 노드 직접 조작
const {root} = await DOM.getDocument();
const {nodeId} = await DOM.querySelector({
selector: '#target-element',
nodeId: root.nodeId
});
await DOM.setAttributeValue({
nodeId,
name: 'style',
value: 'background: linear-gradient(45deg, #ff0080, #00ff80);'
});
네트워크 요청 모니터링
// 네트워크 모니터링 시작
await Network.enable();
Network.responseReceived(({response}) => {
console.log(`응답: ${response.status} ${response.url}`);
});
Network.requestWillBeSent(({request}) => {
console.log(`요청: ${request.method} ${request.url}`);
});
스크린샷 캡처
// 페이지 전체 스크린샷
const screenshot = await Page.captureScreenshot({
format: 'png',
quality: 90
});
require('fs').writeFileSync('screenshot.png', screenshot.data, 'base64');
console.log('스크린샷 저장됨: screenshot.png');
실전 활용 사례
1. 웹 스크래핑 제어
// 무한 스크롤 페이지 전체 로드
await Runtime.evaluate({
expression: `
async function loadAllContent() {
while (true) {
const scrollHeight = document.documentElement.scrollHeight;
window.scrollTo(0, scrollHeight);
await new Promise(resolve => setTimeout(resolve, 1000));
if (document.documentElement.scrollHeight === scrollHeight) break;
}
}
loadAllContent();
`
});
2. 폼 제어
await Runtime.evaluate({
expression: `
document.querySelector('#username').value = 'testuser';
document.querySelector('#password').value = 'testpass';
document.querySelector('#login-form').submit();
`
});
3. 성능 테스트
// 성능 메트릭 수집
await Performance.enable();
const metrics = await Performance.getMetrics();
metrics.metrics.forEach(metric => {
console.log(`${metric.name}: ${metric.value}`);
});
주의사항
보안 고려사항
- CDP는 매우 강력한 도구이므로 신뢰할 수 있는 환경에서만 사용
- 디버깅 포트가 열린 브라우저는 외부 접근 차단 필요
- 프로덕션 환경에서는 별도의 브라우저 인스턴스 사용 권장
성능 최적화
- 불필요한 이벤트 리스너 제거
- 메모리 누수 방지를 위한 적절한 정리
- 동시 연결 수 제한
// 정리 함수 예시
async function cleanup(client) {
try {
await client.close();
} catch (error) {
console.error('정리 중 에러:', error);
}
}
process.on('SIGINT', () => cleanup(client));
다른 브라우저 지원
Chromium 기반 브라우저들
- Chrome: 네이티브 지원
- Edge: 완전 호환
- Brave: 동일한 API
- Opera: Chromium 기반이므로 지원
다른 브라우저들
- Firefox: Firefox DevTools Protocol (다른 API)
- Safari: WebKit Remote Debugging Protocol
참고 자료
공식 문서
대안 도구들
- Puppeteer: CDP 기반 high-level API
- Playwright: 멀티 브라우저 제어
- Selenium: WebDriver 기반 (다른 프로토콜)
댓글