Вчера снова пришлось допиливать E2E-тест, который падал из-за timing issue. Знакомый сценарий: page.waitForSelector не срабатывает, скриншоты пустые, а в логах — мутные ошибки про detached DOM. В такие моменты Puppeteer одновременно и спасает, и бесит. Давайте разберёмся, где этот инструмент действительно выручает, а где проще найти альтернативу.
Headless — не значит headache-free
Puppeteer позиционируется как “high-level API to control Chrome/Chromium”, но на практике это скорее набор строительных блоков, чем готовое решение. Вот типичные грабли, на которые наступают новички:
- Timing hell. Казалось бы, что может быть проще:
await page.goto('https://example.com');
await page.click('#submit');
Но в реальности между этими строчками нужно вставить waitForNavigation, waitForSelector или waitForFunction, иначе тест упадёт с ошибкой про detached element. Особенно весело, когда страница использует client-side routing.
- Селекторная рулетка.
page.$('button')часто находит не тот элемент, потому что:
- Кнопка ещё не отрендерена
- Она скрыта под другим элементом
- На странице несколько кнопок с одинаковыми классами
Лайфхак: используем page.waitForSelector с visible: true:
await page.waitForSelector('button:not([disabled])', { visible: true });
- Мемори лики. Headless Chrome жрёт память как не в себя. На CI-сервере с ограниченными ресурсами это может привести к OOM-kill. Фиксится ручным вызовом
browser.close(), но забыть его — проще простого.
Скрапинг без нервотрёпки
Несмотря на сложности в тестировании, для веб-скрапинга Puppeteer — один из лучших вариантов. Вот почему:
- SPA-friendly: не нужно реверсить API, когда данные подгружаются по скроллу
- Stealth mode: можно эмулировать человеческое поведение (задержки, движение мыши)
- PDF/скриншоты: встроенный рендеринг без лишних зависимостей
Пример скрипта для сбора данных с lazy-loaded страницы:
const results = [];
let shouldScroll = true;
while (shouldScroll) {
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
await page.waitForTimeout(1000); // Даём время на подгрузку
const newItems = await page.$$eval('.item', nodes =>
nodes.map(n => n.innerText)
);
if (newItems.length === 0) shouldScroll = false;
else results.push(...newItems);
}
Но и здесь есть подводные камни:
- Cloudflare и другие анти-бот системы детектят headless-браузер
- Большие страницы могут тормозить даже на мощных машинах
- Капчи никто не отменял (хотя есть плагины типа
puppeteer-extra-plugin-stealth)
Puppeteer vs Playwright: что выбрать в 2024?
Playwright — это форк Puppeteer от Microsoft, который развивается быстрее оригинала. Вот их ключевые отличия:
| Критерий | Puppeteer | Playwright |
|---|---|---|
| Браузеры | Chrome, Firefox | Chrome, FF, WebKit |
| Скорость | Медленнее | Быстрее |
| API | Базовое | Расширенное |
| Поддержка | Community | Microsoft |
Личный опыт: Playwright лучше подходит для комплексного тестирования, но если вам нужен быстрый скрапинг или PDF-генерация, Puppeteer пока проще в настройке.
Куда двигаться дальше
Если застряли на проблеме с Puppeteer, попробуйте:
- Добавить больше
waitFor*с кастомными таймаутами - Включить
headless: falseи посмотреть, что происходит в реальном времени - Использовать
page.on('console')для отладки сообщений из браузера
Для серьёзных проектов стоит рассмотреть:
- Миграцию на Playwright, если нужна мультибраузерность
- Обёртки типа
jest-puppeteerдля интеграции с тест-раннерами puppeteer-clusterдля параллельного выполнения задач
Puppeteer — не silver bullet, но один из немногих инструментов, который позволяет автоматизировать браузер без необходимости писать на C++ или разбираться с DevTools Protocol. Главное — принять его причуды и всегда держать под рукой page.screenshot({ path: 'debug.png' }) для отладки.
Источник: https://github.com/puppeteer/puppeteer