--- 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 기반 (다른 프로토콜)

정리

CDP는 브라우저의 모든 기능에 접근할 수 있는 강력한 도구. 단순한 제어부터 복잡한 테스팅까지, 기존 도구들로는 불가능했던 세밀한 제어가 가능하다. 실제로 벽돌깨기 게임에서 스크립트가 인간보다 정확하게 패들을 조작하는 걸 보면 CDP의 강력함을 체감할 수 있음.