OpenSource-Hub

puppeteer

라이브러리

puppeteer/puppeteer

Chrome 및 Firefox 브라우저를 제어하는 JavaScript API.

개요

Puppeteer는 DevTools 프로토콜 또는 WebDriver BiDi를 통해 Chrome 또는 Firefox를 제어하는 고급 API를 제공합니다. 기본적으로 헤드리스로 실행되며 브라우저 자동화, 테스트 및 웹 크롤링에 널리 사용됩니다. Node.js를 지원하며 스크린샷, PDF 생성, 폼 상호작용 등의 기능을 제공합니다.

README 미리보기

# Puppeteer\n\n[](https://github.com/puppeteer/puppeteer/actions/workflows/ci.yml)\n[](https://npmjs.org/package/puppeteer)\n\n\n\n> Puppeteer is a JavaScript library which provides a high-level API to control\n> Chrome or Firefox over the\n> [DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/) or [WebDriver BiDi](https://pptr.dev/webdriver-bidi).\n> Puppeteer runs in the headless (no visible UI) by default\n\n## [Get started](https://pptr.dev/docs) | [API](https://pptr.dev/api) | [FAQ](https://pptr.dev/faq) | [Contributing](https://pptr.dev/contributing) | [Troubleshooting](https://pptr.dev/troubleshooting)\n\n## Installation\n\n```bash npm2yarn\nnpm i puppeteer # Downloads compatible Chrome during installation.\nnpm i puppeteer-core # Alternatively, install as a library, without downloading Chrome.\n```\n\n:::note\n\nModern package managers (including npm (see the [RFC](https://github.com/npm/rfcs/pull/868)), pnpm, Yarn, Bun, and Deno) block dependency install scripts by default. If the install script is blocked, Puppeteer will not download the browser during installation, leading to runtime errors.\n\nYou can manually download the required browsers after installation by running:\n\n```bash npm2yarn\nnpx puppeteer browsers install\n```\n\nAlternatively, you can configure your package manager to allow the install script to run (for example, with npm, by adding `"puppeteer"` to `"allowScripts"` in your `package.json`).\n\n:::\n\n## MCP\n\nInstall [`chrome-devtools-mcp`](https://github.com/ChromeDevTools/chrome-devtools-mcp),\na Puppeteer-based MCP server for browser automation and debugging.\n\nPuppeteer also supports the experimental [WebMCP](https://pptr.dev/guides/webmcp) API.\n\n## Example\n\n```ts\nimport puppeteer from 'puppeteer';\n// Or import puppeteer from 'puppeteer-core';\n\n// Launch the browser and open a new blank page.\nconst browser = await puppeteer.launch();\nconst page = await browser.newPage();\n\n// Navigate the page to a UR

FAQ (5)

문제 해결
localhost에서 browserWSEndpoint를 통해 Puppeteer를 Chrome에 연결할 때 발생하는 ECONNREFUSED ::1 오류를 해결하는 방법은?

이 문제는 Node.js 18+가 localhost를 IPv6 주소 ::1로 해석하지만, Chrome의 DevTools 서버는 IPv4 127.0.0.1에서만 수신 대기하기 때문에 발생합니다. http://localhost:9222/json/version이 반환하는 webSocketDebuggerUrl에는 ws://localhost:...가 포함되어 있으므로 ::1:9222로의 연결이 거부됩니다(이 문제는 Puppeteer 17.0.0에서 발생하기 시작했습니다). **해결 방법:** 연결하기 전에 엔드포인트에서 localhost를 127.0.0.1로 바꾸세요:

const { webSocketDebuggerUrl } = await fetch('http://127.0.0.1:9222/json/version').then(r => r.json());
const browser = await puppeteer.connect({
  browserWSEndpoint: webSocketDebuggerUrl.replace('localhost', '127.0.0.1')
});

또는 dns.setDefaultResultOrder('ipv4first')를 사용해 Node의 DNS 결과 순서를 IPv4 우선으로 설정하거나, --dns-result-order=ipv4first 옵션으로 Node를 실행하세요.

원본 Issue #8873
문제 해결
Puppeteer 전체 페이지 스크린샷에서 삽입된 동영상 썸네일이 비어 있는 이유는 무엇이며 어떻게 해결할 수 있나요?

임베디드 비디오와 iframe은 종종 지연 로드(lazy-load)되기 때문에 page.screenshot({ fullPage: true })가 페이지를 캡처하기 전에 렌더링되지 않습니다. 해결 방법: 먼저 페이지를 스크롤하여 지연 로딩을 트리거한 다음, 다시 맨 위로 스크롤하고 스크린샷을 찍으세요.

방법 1 — 마우스 휠로 스크롤 시뮬레이션:

await page.goto('https://example.com', { waitUntil: 'networkidle0' });

for (let i = 0; i < 10; i++) {
  await page.mouse.wheel({ deltaY: 400 });
  await new Promise(resolve => setTimeout(resolve, 1000));
}

await page.evaluate(() => window.scrollTo(0, 0));
await new Promise(resolve => setTimeout(resolve, 3000));

await page.screenshot({ fullPage: true, path: 'thumbnails.png' });

방법 2 — 각 비디오/iframe을 직접 화면에 보이도록 스크롤:

await page.evaluate(async () => {
  for (const el of document.querySelectorAll('video, iframe')) {
    el.scrollIntoView();
    await new Promise(r => setTimeout(r, 200));
  }
  window.scrollTo(0, 0);
});
await page.screenshot({ path: 'fullpage.png', fullPage: true });

두 가지 접근 방식 모두 캡처 전에 지연 로드된 썸네일이 렌더링되도록 강제합니다. Puppeteer 22.13.1에서 작동을 확인했습니다.

원본 Issue #12778
문제 해결
왜 Puppeteer에서 dedicated worker의 가로채기된 요청이 절대 처리되지 않는가 (request.respond/continue/abort가 아무런 효과가 없는가)?

이는 Puppeteer v25.12.0까지의 버전에 존재하는 알려진 버그입니다. 전용 워커(dedicated worker)가 fetch()를 호출하면 Chrome은 페이지 세션에서는 Fetch.requestPaused를 발생시키지만 Network.requestWillBeSent는 워커 세션에서 발생시킵니다. 경합(race)으로 인해 Puppeteer는 일시 중지된 이벤트를 세션 정보 없이 저장한 다음, Fetch 도메인이 없는 워커 세션으로 Fetch.fulfillRequest(또는 continue/abort)를 전송합니다. Chrome은 이를 'Fetch.fulfillRequest' wasn't found 오류로 거부하고, Puppeteer는 이 오류를 무시해 버리며, 워커 요청은 영원히 멈춥니다. 해결 방법: PR #15605가 포함된 버전(25.12.0 이후 main에서 수정됨)으로 Puppeteer를 업그레이드하세요. 예: npm install puppeteer@latest. 이는 세션 라우팅 경합 조건(race condition)이므로 영향을 받는 버전에서는 신뢰할 수 있는 우회 방법이 없으며, requestWillBeSent가 우연히 먼저 도착하는 경우에만 요청이 성공합니다.

원본 Issue #15497
문제 해결
Puppeteer fullPage 스크린샷에서 fixed/sticky 헤더가 잘못된 위치(중간 또는 하단)에 표시되는 이유는 무엇이며, 어떻게 해결할 수 있나요?

원인: page.screenshot({ fullPage: true })는 기본적으로 captureBeyondViewport: true를 사용하며, 이는 스크롤 위치를 초기화하지 않고 전체 문서를 캡처합니다. 코드가 페이지를 스크롤하는 경우(예: 지연 로딩을 트리거하기 위해 하단까지 스크롤하는 autoScroll 헬퍼), window.scrollY가 하단에 그대로 남게 됩니다. 그러면 position: fixed/sticky 요소나 스크롤 기반 JS가 페이지 상단이 아닌 현재 뷰포트 위치에 렌더링됩니다.

해결 방법 1 — 캡처하기 전에 상단으로 다시 스크롤:

await autoScroll(page);
await page.evaluate(() => window.scrollTo(0, 0));
await page.waitForTimeout(300); // sticky 요소가 안정될 때까지 대기
await page.screenshot({ path: 'example.png', fullPage: true });

해결 방법 2 — 주입된 CSS를 사용하여 헤더를 absolute 위치로 강제하거나 완전히 숨기기:

await page.addStyleTag({ content: 'header, .navbar, .sticky-nav { position: absolute !important; }' });
// 또는 완전히 제거하려면:
// await page.addStyleTag({ content: 'header { display: none !important; }' });
await page.screenshot({ path: 'example.png', fullPage: true });

선택적으로 page.screenshot({ fullPage: true, captureBeyondViewport: false })를 사용하면 캡처 동작이 변경되지만, 스크롤 위치를 초기화하는 것이 가장 확실한 해결 방법입니다.

원본 Issue #8191
문제 해결
왜 puppeteer.connect()는 browserURL에서 경로 접두사를 제거하고 /t/token/json/version 대신 /json/version을 호출하나요?

이 문제는 Puppeteer가 new URL('/json/version', browserURL)을 사용하기 때문에 발생합니다. WHATWG URL API에서 /로 시작하는 경로는 절대 경로이며 기본 URL의 전체 pathname을 대체합니다. 이 문제는 Puppeteer PR #15435에서 기존 pathname에 json/version을 추가하는 방식으로 수정되었습니다. 해당 수정이 포함된 릴리스로 puppeteer-core 또는 puppeteer를 업그레이드하세요.

임시 해결 방법: 접두사를 유지하면서 버전 엔드포인트를 직접 확인한 후 browserWSEndpoint로 연결하세요:

const versionURL = new URL(browserURL);
versionURL.pathname = versionURL.pathname.replace(/\/?$/, '/') + 'json/version';

const response = await fetch(versionURL.href);
const { webSocketDebuggerUrl } = await response.json();

const browser = await puppeteer.connect({
  browserWSEndpoint: webSocketDebuggerUrl,
});

이렇게 하면 http://host:9222/t/token이 http://host:9222/t/token/json/version으로 올바르게 변경됩니다.

원본 Issue #15250