Programming

Panduan Lengkap Puppeteer untuk Otomatisasi Browser dari Nol

M
MUGHU
38 menit baca
Panduan Lengkap Puppeteer untuk Otomatisasi Browser dari Nol
Daftar isi

Teman-Teman, pernah nggak kamu kepikiran soal gimana cara otomatisasi browser buat ngerjain tugas repetitif kayak scraping data, testing UI, atau generate PDF? Nah, Puppeteer itu jawabannya. Ini library JavaScript yang bikin kamu bisa ngendaliin Chrome atau Firefox pakai kode—tanpa harus klik-klik manual.

Singkatnya, Puppeteer itu ibada kamu nyuruh "robot" buat buka website, klik tombol, isi form, screenshot, sampai extract data—semua otomatis. Dibikin oleh tim Chrome DevTools dari Google, sekarang udah versi 25.3.0 dan dipakai jutaan developer di seluruh dunia.

Artikel ini bakal ngebahas everything yang kamu perlu tahu soal Puppeteer—mulai dari instalasi, konsep dasar, tutorial step-by-step, sampai studi kasus nyata. MUGHU sendiri udah pakai Puppeteer di beberapa proyek, jadi pengalaman dan kendala yang MUGHU alamin bakal ikut dibagikan biar kamu nggak nyangkut di titik yang sama.


Apa Itu Puppeteer dan Kenapa Kamu Perlu Tahu?

Puppeteer adalah library Node.js yang ngasih kamu API tingkat tinggi buat ngontrol Chrome atau Firefox lewat Chrome DevTools Protocol atau WebDriver BiDi. Default-nya jalan di mode headless—alias browser jalan di background tanpa UI yang kelihatan. Tapi kalau mau, kamu juga bisa jalanin mode headed buat debugging.

Puppeteer adalah JavaScript library yang nyediain API tingkat tinggi buat ngontrol Chrome atau Firefox di atas DevTools Protocol atau WebDriver BiDi. Secara default jalan headless (tanpa UI yang kelihatan).

Buat yang masih bingung, analoginya gini: bayangin kamu main game pakai joystick. Nah, Puppeteer itu joystick-nya, browser itu konsol gim-nya, dan kode JavaScript kamu itu tombol-tombol yang kamu pencet. Tiap baris kode = satu perintah yang dikirim ke browser buat dijalanin.

Kenapa Puppeteer Populer Banget?

Beberapa alasan kenapa developer-developer suka banget sama Puppeteer:

  • Headless by default — nggak perlu setup ribet, browser langsung jalan di background.

  • Modern async API — berbasis JavaScript/Node.js, jadi gampang diintegrasikan sama stack modern.

  • Download browser otomatis — sekali npm install puppeteer, Chrome versi yang cocok langsung terpasang.

  • Ekosistem besar — udah dipakai lebih dari 9.900 proyek di npm, jadi community support-nya kuat.

  • Dukungan Firefox — selain Chrome, sekarang Puppeteer juga dukung Firefox lewat WebDriver BiDi.

Puppeteer vs Selenium: Mana yang Pass?

Ini pertanyaan yang sering banget muncul. MUGHU sendiri dulu awalnya pakai Selenium buat testing, lalu pindah ke Puppeteer dan kerasa bedanya langsung. Berikut perbandingannya:

Kriteria

Puppeteer

Selenium

Bahasa/API

JavaScript/Node.js, async modern

Banyak bahasa, tapi sering lebih verbose

Browser Support

Chrome, Chromium, Firefox

Chrome, Firefox, Safari, Edge, dll

Mode Headless

Default, gampang banget

Didukung tapi setup bisa lebih tricky

Kecepatan & Stabilitas

Cepat, stabil buat Chrome

Kadang lebih lambat, bisa flaky

Instalasi

npm install puppeteer selesai

Butuh browser driver, konfigurasi lebih banyak

Paling Cocok Buat

JS-heavy sites, scraping, otomasi Chrome

Cross-browser testing, workflow lama

Kesimpulannya: kalau kamu fokus otomasi Chrome/Chromium dan sudah nyaman sama JavaScript, Puppeteer jelas pilihan yang lebih ringan dan cepat. Tapi kalau butuh testing lintas browser yang luas, Selenium masih punya tempatnya.


Yang Kamu Perlu Siapin Se Mulai (Prerequisites)

Sebelum mulai, pastiin beberapa hal ini udah ready di mesin kamu. MUGHU pernah coba install Puppeteer tanpa Node.js terpasang, dan hasilnya... ya ketemu error halaman pertama. Jadi mari kita pastiin dulu.

1. Node.js (Versi 18 atau Yang Lebih Baru)

Puppeteer butuh Node.js buat jalan. Cek versi yang terpasang dengan perintah:

BASH
node --version

Output yang diharapkan:

BASH
v20.11.0  # atau versi yang lebih baru

Kalau belum terpasang, download dari situs resmi Node.js. Pilih versi LTS biar stabil.

2. NPM atau Package Manager Lain

NPM udah otomatis terpasang bareng Node.js. Tapi kalau kamu prefer pakai Yarn, pnpm, atau Bun, semuanya juga didukung.

3. Editor Kode

Rekomendasi sih Visual Studio Code karena gratis, ringan, dan ada ekstensi JavaScript yang membanru. Tapi editor apa pun juga bisa—bahkan Notepad kalau kamu tipe berani.

4. Pengetahuan Dasar JavaScript

Kamu nggak perlu jago JavaScript banget, tapi setidaknya paham konsep dasar:

  • Variabel dan tipe data

  • Function dan arrow function (() => {})

  • Async/await (ini penting banget karena Puppeteer sepenuhnya async)

  • Cara baca dokumentasi

5. Koneksi Internet yang Stabil

Saat pertama kali install, Puppeteer bakal download Chrome—dan ukurannya rada gede (sekitar 150-200MB). Jadi siapin koneksi yang lumayan.


Step 1: Install Puppeteer di Proyek Kamu

Sekarang kita mulai bagian serunya. Bikin folder baru buat proyek kamu, lalu ikuti langkah-langkah berikut.

Bikin Folder Proyek dan Inisialisasi NPM

BASH
mkdir belajar-puppeteer
cd belajar-puppeteer
npm init -y

Perintah npm init -y bikin file package.json default tanpa perlu jawab pertanyaan-pertanyaan. Praktis.

Output yang diharapkan:

BASH
Wrote to /path/ke/belajar-puppeteer/package.json:
{
  "name": "belajar-puppeteer",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

Karena kita bakal pakai ES modules (import/export), ubah package.json sedikit. Tambahin "type": "module":

JSON
{
  "name": "belajar-puppeteer",
  "version": "1.0.0",
  "type": "module",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

Kenapa pakai ES modules? Karena dokumentasi resmi Puppeteer sekarang pakai format import, dan ini juga lebih konsisten sama praktik JavaScript modern.

Install Puppeteer

BASH
npm install puppeteer

Perintah ini bakal ngelakuin dua hal sekaligus:

  1. Download dan install library Puppeteer

  2. Download versi Chrome yang kompatibel (ini bagian yang butuh internet)

Output yang diharapkan:

BASH
added 395 packages in 45s

Kalau pakai package manager modern kayak pnpm atau Yarn yang block install scripts by default, Puppeteer mungkin nggak download browser-nya. Kamu bisa download manual setelah install:

BASH
npx puppeteer browsers install

Atau alternatif lain, konfigurasikan package manager kamu buat allow script-nya jalan. Buat npm, tambahin "puppeteer" ke "allowScripts" di package.json:

JSON
{
  "allowScripts": ["puppeteer"]
}

Puppeteer vs Puppeteer-Core: Apa Bedanya?

Ini penting buat dipahami. Ada dua package yang bisa kamu install:

Aspek

puppeteer

puppeteer-core

Download Browser

Ya, sekali install langsung download Chrome

Tidak, kamu harus punya Chrome sendiri

Ukuran Install

Lebih besar (~200MB total)

Lebih kecil (just the library)

Cocok Buat

Proyek baru, testing, scraping umum

CI/CD, environment yang sudah punya browser

Update Browser

Otomatis, bundling versi yang kompatibel

Manual, kamu yang atur

Cara Install

npm i puppeteer

npm i puppeteer-core

MUGHU pribadi hampir selalu pakai puppeteer (versi penuh) pas development. Tapi di production atau CI/CD, puppeteer-core lebih masuk akal karena nggak perlu download browser yang udah ada.


Step 2: Skrip Pertama—Launch Browser dan Buka Halaman

Sekarang kita buat skrip pertama. Ini kayak "Hello World"-nya Puppeteer.

Bikin file index.js dan isi kayak gini:

JAVASCRIPT
import puppeteer from 'puppeteer';

// Launch browser di mode headless (default)
const browser = await puppeteer.launch();
console.log('Browser berhasil dijalankan!');

// Buka halaman baru
const page = await browser.newPage();

// Navigasi ke URL tujuan
await page.goto('https://example.com');

// Ambil judul halaman
const title = await page.title();
console.log('Judul halaman:', title);

// Tutup browser
await browser.close();
console.log('Browser ditutup. Selesai!');

Jalanin skrip-nya:

BASH
node index.js

Output yang diharapkan:

BASH
Browser berhasil dijalankan!
Judul halaman: Example Domain
Browser ditutup. Selesai!

Gampang banget, kan? Tapi di balik kesederhanaan ini, ada beberapa hal yang penting buat dipahami.

Kenapa Tiap Baris Itu Penting

puppeteer.launch() — Ini fungsi yang memulai browser. Secara default, browser jalan headless. Kalau kamu mau lihat browser-nya keluar ke layar (mode headed), tinggal ubah jadi:

JAVASCRIPT
const browser = await puppeteer.launch({ headless: false });

MUGHU sering pakai mode headed waktu разработки—biar bisa liat apa yang browser lakukan. Soalnya kadang script jalan tapi elemen yang mau diklik nggak muncul, dan cuma dengan lihat langsung kita bisa tahu masalahnya.

browser.newPage() — Ini bikin tab baru, kayak kamu Ctrl+T di browser. Page object inilah yang kamu pakai buat berinteraksi sama halaman web.

page.goto() — Ini kayak ketik URL di address bar dan tekan Enter. Ada beberapa option yang bisa kamu kasih:

JAVASCRIPT
await page.goto('https://example.com', {
  waitUntil: 'networkidle2',  // Tunggu sampai network activity minim
  timeout: 30000  // Timeout 30 detik
});

waitUntil: 'networkidle2' itu penting buat halaman yang dinamis (SPA), karena dia bakal nunggu sampai nggak ada network request lebih dari 2 dalam 500ms. Ini ngebatasin masalah halaman yang belum ke-load semua tapi script udah mau ngambil data.

browser.close() — Selalu, selalu tutup browser setelah selesai. Kalau nggak, browser jalan terus di background dan makan memory. MUGHU pernah lupa close dan laptop jadi panas bangeeet karena ada 47 instance Chrome jalan di background. Belajaran mahal.


Step 3: Berinteraksi Sama Halaman—Klik, Ketik, dan Ambil Data

Sekarang kita masuk ke bagian yang lebih seru: bikin browser buat "bertindak" kayak manusia.

Mengambil Data dari Halaman

JAVASCRIPT
import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
const page = await browser.newPage();

await page.goto('https://example.com');

// Ambil teks dari elemen
const headingText = await page.$eval('h1', el => el.textContent);
console.log('Heading:', headingText);

// Ambil semua teks paragraf
const paragraphs = await page.$$eval('p', els =>
  els.map(el => el.textContent.trim())
);
console.log('Paragraf:', paragraphs);

// Ambil URL dari semua link
const links = await page.$$eval('a', els =>
  els.map(el => ({ text: el.textContent.trim(), href: el.href }))
);
console.log('Link:', links);

await browser.close();

Output yang diharapkan:

BASH
Heading: Example Domain
Paragraf: [ 'This domain is for use in illustrative examples in documents...' ]
Link: [ { text: 'More information...', href: 'https://www.iana.org/domains/example' } ]

Fungsi $eval dan $$eval itu penting buat dipahami:

  • page.$eval(selector, fn) — ambil satu elemen pertama yang match sama selector, lalu jalankan fungsi padanya.

  • page.$$eval(selector, fn) — ambil semua elemen yang match, lalu jalankan fungsi ke array-nya.

Selector yang dipakai adalah CSS selector standar—sama kayak yang kamu pakai di CSS atau document.querySelector() di browser.

Mengetik di Input Field

JAVASCRIPT
import puppeteer from 'puppeteer';

const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();

await page.goto('https://duckduckgo.com');

// Klik search box dan ketik query
await page.type('#searchbox_input', 'Puppeteer automation', { delay: 100 });

// Tunggu sebentar biar keliatan
await new Promise(r => setTimeout(r, 1000));

// Tekan Enter
await page.keyboard.press('Enter');

// Tunggu hasil pencarian muncul
await page.waitForSelector('[data-result]');

// Ambil judul hasil pertama
const firstResult = await page.$eval('[data-result] h2', el => el.textContent);
console.log('Hasil pertama:', firstResult);

await browser.close();

Output yang diharapkan:

BASH
Hasil pertama: Puppeteer | Puppeteer

Perhatiin { delay: 100 } di page.type(). Ini bikin ketikannya ada delay 100ms per karakter—kayak orang ngetik asli. Kenapa penting? Karena beberapa website punya deteksi bot yang curiga sama input yang ke-isi instan (0ms delay). Dengan delay, otomasi kita terlihat lebih natural.

Menggunakan Locator API (Cara Modern)

Puppeteer versi terbaru ngenalin Locator API yang lebih ekspresif dan robust. Ini cara yang direkomendasikan di dokumentasi resmi Puppeteer:

JAVASCRIPT
import puppeteer from 'puppeteer';

const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();

await page.goto('https://developer.chrome.com/');

// Set ukuran viewport
await page.setViewport({ width: 1080, height: 1024 });

// Buka menu search dengan keyboard
await page.keyboard.press('/');

// Ketik di search box pakai locator ARIA
await page.locator('::-p-aria(Search)').fill('automate beyond recorder');

// Klik hasil pertama
await page.locator('.devsite-result-item-link').click();

// Cari elemen yang ngandung teks tertentu
const textSelector = await page
  .locator('::-p-text(Customize and automate)')
  .waitHandle();

const fullTitle = await textSelector?.evaluate(el => el.textContent);
console.log('Judul blog post:', fullTitle);

await browser.close();

Locator API ini punya beberapa selector khusus yang powerful banget:

  • ::-p-aria(name) — nyari elemen berdasarkan accessible name (ARIA label). Tahan sama perubahan struktur DOM.

  • ::-p-text(text) — nyari elemen yang ngandung teks tertentu.

  • ::-p-xpath(xpath) — buat kamu yang lebih suka XPath.

Kenapa Locator API lebih bagus? Karena dia otomatis nunggu elemen muncul (auto-wait), jadi kamu nggak perlu manual waitForSelector() tiap kali. Praktis dan lebih sedikit bug.


Step 4: Screenshot dan Generate PDF

Salah satu use case Puppeteer yang paling populer adalah mengambil screenshot dan generate PDF halaman web. Ini dipakai buat report, dokumentasi, sampai preview website.

Screenshot Halaman Penuh

JAVASCRIPT
import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
const page = await browser.newPage();

// Set viewport biar konsisten
await page.setViewport({ width: 1920, height: 1080 });

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

// Screenshot halaman penuh
await page.screenshot({
  path: 'screenshot.png',
  fullPage: true
});

console.log('Screenshot tersimpan!');

await browser.close();

Output yang diharapkan:

BASH
Screenshot tersimpan!

Cek folder proyek kamu, dan file screenshot.png bakal ada di sana.

Screenshot Elemen Spesifik

Kadang kamu cuma butuh screenshot bagian tertentu dari halaman, bukan semuanya. Ini contohnya:

JAVASCRIPT
import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
const page = await browser.newPage();

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

// Screenshot hanya elemen <h1>
const h1Element = await page.$('h1');
await h1Element.screenshot({ path: 'heading.png' });

console.log('Screenshot elemen tersimpan!');

await browser.close();

Generate PDF dari Halaman Web

Ini fitur yang MUGHU pakai banget buat generate report otomatis dari dashboard:

JAVASCRIPT
import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
const page = await browser.newPage();

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

// Generate PDF
await page.pdf({
  path: 'halaman.pdf',
  format: 'A4',
  printBackground: true,
  margin: {
    top: '20px',
    right: '20px',
    bottom: '20px',
    left: '20px'
  }
});

console.log('PDF tersimpan!');

await browser.close();

Output yang diharapkan:

BASH
PDF tersimpan!

Option printBackground: true itu penting kalau halaman punya background color atau image yang ingin kamu pertahankan di PDF. Kalau nggak diset, background bakal ke-skip dan PDF keliatan kosong/hitam-putih doang.


Step 5: Network Interception—Block dan Modifikasi Request

Ini salah satu fitur Puppeteer yang MUGHU paling suka. Kamu bisa intercept, block, atau modifikasi network request yang dilakuin browser. Manfaatnya banyak banget:

  • Block iklan dan tracker buat testing yang bersih

  • Modifikasi API response buat testing edge case

  • Block resource berat (gambar, video) biar loading lebih cepat

  • Mock API response buat testing tanpa backend real

Block Semua Gambar (Buat Loading Lebih Cepat)

JAVASCRIPT
import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
const page = await browser.newPage();

// Aktifkan request interception
await page.setRequestInterception(true);

page.on('request', request => {
  // Block gambar
  if (request.resourceType() === 'image') {
    request.abort();
  } else {
    request.continue();
  }
});

const startTime = Date.now();
await page.goto('https://news.ycombinator.com', {
  waitUntil: 'domcontentloaded'
});
const loadTime = Date.now() - startTime;

console.log(`Halaman ke-load dalam ${loadTime}ms (tanpa gambar)`);

await browser.close();

Output yang diharapkan:

BASH
Halaman ke-load dalam 1423ms (tanpa gambar)

Waktu loading bisa beda-beda, tapi intinya: dengan block image, loading bisa 40-60% lebih cepat tergantung halaman. Ini berguna banget kalau kamu lagi scrape ratusan halaman dan cuma butuh teksnya doang.

Modifikasi Response API

JAVASCRIPT
import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
const page = await browser.newPage();

await page.setRequestInterception(true);

page.on('request', request => {
  // Kalau request ke API tertentu, kasih response palsu
  if (request.url().includes('/api/user')) {
    request.respond({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({
        id: 1,
        name: 'Mughu Test User',
        email: '[email protected]'
      })
    });
  } else {
    request.continue();
  }
});

await page.goto('https://example.com');
console.log('Mock response berhasil dikirim!');

await browser.close();

Kenapa ini berguna? Bayangin kamu lagi develop frontend tapi backend belum siap. Daripada nunggu backend, kamu bisa mock API response-nya pakai Puppeteer. Tim MUGHU dulu sering banget pakai teknik ini waktu develop fitur yang bergantung sama API yang belum ada.


Step 6: Menangani Multiple Pages dan Tabs

Browser nyata sering punya banyak tab. Puppeteer juga bisa ngatur ini.

JAVASCRIPT
import puppeteer from 'puppeteer';

const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();

await page.goto('https://example.com');

// Bikin tab baru
const page2 = await browser.newPage();
await page2.goto('https://example.org');

// Bikin tab baru lagi
const page3 = await browser.newPage();
await page3.goto('https://iana.org');

// List semua tab yang aktif
const pages = await browser.pages();
console.log(`Jumlah tab aktif: ${pages.length}`);

// Tutup tab kedua
await page2.close();

const remainingPages = await browser.pages();
console.log(`Tab tersisa: ${remainingPages.length}`);

await browser.close();

Output yang diharapkan:

BASH
Jumlah tab aktif: 4
Tab tersisa: 3

Angka 4 karena ada 1 tab default (about:blank) + 3 tab yang kita bikin. Setelah close page2, tersisa 3 tab.

Menangani Popup Window

Kadang klik link di halaman bikin popup window baru. Ini cara handle-nya:

JAVASCRIPT
import puppeteer from 'puppeteer';

const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();

await page.goto('https://example.com');

// Listen untuk tab/popup baru
const popupPromise = new Promise(resolve =>
  browser.once('targetcreated', target => resolve(target.page()))
);

// Klik link yang buka tab baru (kalau ada)
// await page.click('a[target="_blank"]');

// await popupPromise;
// console.log('Popup window terbuka!');

await browser.close();

Step 7: Handle Login dan Session (Cookies)

Banyak halaman butuh login dulu sebelum bisa diakses. Puppeteer bisa ngelakuin ini, dan yang lebih penting—bisa nyimpan session biar nggak login ulang tiap kali.

Login Otomatis

JAVASCRIPT
import puppeteer from 'puppeteer';

const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();

await page.goto('https://quotes.toscrape.com/login');

// Isi form login
await page.type('#username', 'admin');
await page.type('#password', 'admin');

// Klik tombol login
await page.click('input[type="submit"]');

// Tunggu navigasi selesai
await page.waitForNavigation();

console.log('Login berhasil!');
console.log('URL sekarang:', page.url());

// Cek apakah login berhasil dengan cari elemen logout
const logoutButton = await page.$('a[href="/logout"]');
if (logoutButton) {
  console.log('Session login aktif!');
} else {
  console.log('Login mungkin gagal.');
}

await browser.close();

Output yang diharapkan:

BASH
Login berhasil!
URL sekarang: https://quotes.toscrape.com/
Session login aktif!

Site quotes.toscrape.com itu sandbox yang dibikin khusus buat latihan scraping. Username dan passwordnya admin/admin. Aman buat belajar.

Nyimpan dan Muat Ulang Cookies

Biar nggak login ulang tiap kali jalanin script, simpan cookies-nya:

JAVASCRIPT
import puppeteer from 'puppeteer';
import fs from 'fs';

const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();

await page.goto('https://quotes.toscrape.com/login');
await page.type('#username', 'admin');
await page.type('#password', 'admin');
await page.click('input[type="submit"]');
await page.waitForNavigation();

// Simpan cookies
const cookies = await page.cookies();
fs.writeFileSync('cookies.json', JSON.stringify(cookies, null, 2));
console.log('Cookies tersimpan!');

await browser.close();

Lalu di script lain, muat cookies yang udah tersimpan:

JAVASCRIPT
import puppeteer from 'puppeteer';
import fs from 'fs';

const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();

// Muat cookies yang udah disimpan
const cookies = JSON.parse(fs.readFileSync('cookies.json', 'utf-8'));
await page.setCookie(...cookies);

// Sekarang langsung akses halaman yang butuh login
await page.goto('https://quotes.toscrape.com/');

const logoutButton = await page.$('a[href="/logout"]');
console.log(logoutButton ? 'Session masih aktif!' : 'Session expired.');

await browser.close();

Output yang diharapkan:

BASH
Cookies tersimpan!
BASH
Session masih aktif!

Teknik ini hemat banget waktu kalau kamu butuh scrape multiple page yang semuanya butuh login. Tapi ingat, cookies punya expiry date. Kalau kadaluarsa, kamu harus login ulang.


Step 8: Wait Strategies—Nunggu yang Benar

Ini topik yang sering bikin orang nyangkut. Halaman web itu async—elemen muncul, ilang, dan berubah. Kalau script kamu langsung ngambil elemen yang belum muncul, bakal error.

Jenis Wait di Puppeteer

Strategi Wait

Kegunaan

Contoh

page.waitForSelector(sel)

Tunggu elemen muncul di DOM

await page.waitForSelector('#result')

page.waitForNavigation()

Tunggu navigasi halaman selesai

await page.waitForNavigation()

page.waitForFunction(fn)

Tunggu kondisi custom terpenuhi

await page.waitForFunction(() => window.dataLoaded)

page.waitForTimeout(ms)

Tunggu fix waktu (deprecated, hindari)

page.locator(sel).wait()

Auto-wait dari Locator API

await page.locator('#btn').wait()

Contoh: Tunggu Elemen Muncul

JAVASCRIPT
import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
const page = await browser.newPage();

await page.goto('https://quotes.toscrape.com/');

// Tunggu elemen quote muncul
await page.waitForSelector('.quote');

// Ambil semua quote
const quotes = await page.$$eval('.quote .text', els =>
  els.map(el => el.textContent.trim())
);

console.log(`Total quote di halaman: ${quotes.length}`);
console.log('Quote pertama:', quotes[0]);

await browser.close();

Output yang diharapkan:

BASH
Total quote di halaman: 10
Quote pertama: "The world as we have created it is a process of our thinking..."

Contoh: Tunggu Kondisi Custom

Bayangin kamu lagi nunggu data dimuat ke window.myData:

JAVASCRIPT
await page.waitForFunction(
  () => window.myData && window.myData.length > 0,
  { timeout: 10000 }
);

Ini nunggu maksimal 10 detik sampai variable window.myData ada dan isinya lebih dari 0.

Kesalahan yang Sering Terjadi Soal Wait

MUGHU dulu sering banget pakai page.waitForTimeout(5000) buat "nunggu" halaman load. Ini keliru karena:

  • Fix delay nggak efisien—kadang cuma butuh 1 detik tapi kamu nunggu 5 detik.

  • Kadang butuh lebih dari 5 detik dan script tetap fail.

Solusi yang benar: pakai waitForSelector atau waitForFunction yang langsung check kondisinya. Lebih cepat dan reliable.


Step 9: Scraping Data dari Halaman dengan Pagination

Sekarang kita masuk ke skenario yang lebih realistis: scraping data dari halaman yang punya banyak page.

JAVASCRIPT
import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
const page = await browser.newPage();

let allQuotes = [];
let currentPage = 1;
let hasNextPage = true;

while (hasNextPage) {
  await page.goto(`https://quotes.toscrape.com/page/${currentPage}/`);
  await page.waitForSelector('.quote');

  // Ambil quote di halaman ini
  const quotes = await page.$$eval('.quote', els =>
    els.map(el => ({
      text: el.querySelector('.text')?.textContent.trim(),
      author: el.querySelector('.author')?.textContent.trim(),
      tags: Array.from(el.querySelectorAll('.tag')).map(t => t.textContent.trim())
    }))
  );

  allQuotes = allQuotes.concat(quotes);
  console.log(`Halaman ${currentPage}: ${quotes.length} quotes`);

  // Cek apakah masih ada halaman berikutnya
  const nextButton = await page.$('.next > a');
  if (nextButton) {
    currentPage++;
  } else {
    hasNextPage = false;
  }
}

console.log(`\nTotal quote terkumpul: ${allQuotes.length}`);
console.log('Contoh quote:', allQuotes[0]);

await browser.close();

Output yang diharapkan:

BASH
Halaman 1: 10 quotes
Halaman 2: 10 quotes
Halaman 3: 10 quotes
...
Halaman 10: 10 quotes

Total quote terkumpul: 100
Contoh quote: { text: '"The world as we have created it...", author: 'Albert Einstein', tags: ['change', 'deep-thoughts', 'world'] }

Script di atas bakal scrape semua quote dari 10 halaman quotes.toscrape.com—total 100 quote dengan teks, author, dan tag-nya.

Kenapa loop ini aman? Karena kita cek dulu apakah tombol "Next" ada. Kalau udah nggak ada, berarti kita udah di halaman terakhir dan loop berhenti. Ini lebih reliable dibanding hard-code jumlah halaman (yang bisa berubah sewaktu-waktu).


Step 10: Mengatur User-Agent dan Hindari Deteksi Bot

Beberapa website punya sistem deteksi bot yang bakal block request dari browser yang "mencurigakan." Puppeteer secara default ngasih tanda-tanda tertentu yang bisa ketahuan.

Set Custom User-Agent

JAVASCRIPT
import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
const page = await browser.newPage();

// Set User-Agent yang terlihat kayak browser biasa
await page.setUserAgent(
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36'
);

await page.goto('https://httpbin.org/user-agent');

const bodyText = await page.$eval('body', el => el.textContent);
console.log('Response server:', bodyText);

await browser.close();

Output yang diharapkan:

BASH
Response server: {
  "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
}

Set Viewport dan Language

Untuk bikin browser Puppeteer terlihat lebih natural, set beberapa hal tambahan:

JAVASCRIPT
await page.setViewport({
  width: 1920,
  height: 1080,
  deviceScaleFactor: 1,
});

await page.setExtraHTTPHeaders({
  'Accept-Language': 'id-ID,id;q=0.9,en-US;q=0.8,en;q=0.7'
});

// Emulasi locale
await page.evaluateOnNewDocument(() => {
  Object.defineProperty(navigator, 'language', { get: () => 'id-ID' });
  Object.defineProperty(navigator, 'languages', { get: () => ['id-ID', 'id', 'en'] });
});

Di Puppeteer v25.2.0, ada fitur baru page locale emulation yang lebih gampang:

JAVASCRIPT
await page.emulateLocale({ locale: 'id-ID' });

Ini cara yang lebih bersih dan native. Pakai yang ini kalau kamu udah di versi 25.2.0+.


Studi Kasus: Otomatisasi Report Dashboard Harian

Sekarang MUGHU mau kasih contoh nyata dari pengalaman pribadi. Ini kasus yang bikin MUGHU jatuh cinta sama Puppeteer (dan juga kadang berabe).

Background

Di salah satu proyek, MUGHU tiap pagi harus buka 4 dashboard berbeda, screenshot masing-masing, lalu susun jadi report harian buat klien. Proses manual ini makan 45-60 menit tiap hari. Bosen banget.

Challenge

  • Dashboard butuh login (session-based)

  • 1 dashboard pakai SPA berat yang load-nya lama

  • Screenshot harus konsisten ukurannya (klien butuh layout yang sama tiap hari)

  • Kadang dashboard element nggak muncul tepat waktu

Approach

MUGHU bikin script Puppeteer yang otomatis ngelakuin semua itu. Dengan flow kayak gini: launch browser → login ke masing-masing dashboard → tunggu load → screenshot → tutup browser → satukan screenshot jadi PDF report.

Implementation

JAVASCRIPT
import puppeteer from 'puppeteer';
import fs from 'fs';

const dashboards = [
  {
    name: 'Analytics',
    url: 'https://analytics.example.com/dashboard',
    loginUrl: 'https://analytics.example.com/login',
    usernameSelector: '#email',
    passwordSelector: '#password',
    submitSelector: '#submit',
    username: '[email protected]',
    password: 'secret',
    screenshotSelector: '#main-dashboard',
    waitSelector: '.chart-loaded'
  },
  {
    name: 'Sales',
    url: 'https://sales.example.com/overview',
    loginUrl: 'https://sales.example.com/login',
    usernameSelector: '#username',
    passwordSelector: '#password',
    submitSelector: 'button[type="submit"]',
    username: '[email protected]',
    password: 'secret',
    screenshotSelector: '#dashboard-container',
    waitSelector: '.sales-chart'
  }
];

async function captureDashboard(page, config) {
  console.log(`Memproses dashboard: ${config.name}...`);

  // Login
  await page.goto(config.loginUrl, { waitUntil: 'networkidle2' });
  await page.type(config.usernameSelector, config.username);
  await page.type(config.passwordSelector, config.password);
  await page.click(config.submitSelector);

  // Tunggu navigasi dan dashboard ter-load
  await page.waitForNavigation({ waitUntil: 'networkidle2' });
  await page.goto(config.url, { waitUntil: 'networkidle2' });
  await page.waitForSelector(config.waitSelector, { timeout: 30000 });

  // Beri tambahan waktu buat chart render
  await new Promise(r => setTimeout(r, 3000));

  // Screenshot elemen spesifik
  const element = await page.$(config.screenshotSelector);
  const filename = `${config.name.toLowerCase()}-${Date.now()}.png`;
  await element.screenshot({ path: filename });
  console.log(`Screenshot ${config.name} tersimpan: ${filename}`);

  return filename;
}

const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.setViewport({ width: 1920, height: 1080 });

const screenshots = [];
for (const config of dashboards) {
  try {
    const filename = await captureDashboard(page, config);
    screenshots.push(filename);
  } catch (error) {
    console.error(`Gagal capture ${config.name}:`, error.message);
  }
}

await browser.close();

console.log('\n=== Ringkasan ===');
console.log(`Total dashboard berhasil: ${screenshots.length}/${dashboards.length}`);
console.log('File screenshot:', screenshots);

Results

Setelah script ini live, waktu yang sebelumnya 45-60 menit jadi 3-5 menit saja—dan itu sebagian besar cuma waktu browser jalan, bukan waktu MUGHU yang aktif. MUGHU cukup jalanin script, bikin kopi, dan pas kopi udah dingin, report-nya udah siap.

Sebulan berjalan, penghematan waktunya jadi sekitar 20 jam per bulan yang sebelumnya dipakai buat copy-paste manual. Buat klien, report-nya lebih konsisten karena ukuran screenshot selalu sama—nggak ada lagi "screenshot hari ini kepotong di bagian bawah."

Key Learnings

  • Login paling njlimet. Dashboard yang pake SSO (Single Sign-On) bikin script jadi lebih rumit. Solusinya: simpan cookies setelah login manual pertama, lalu muat ulang.

  • Wait strategy itu krusial. Dashboard SPA berat bikin networkidle2 kadang nggak cukup. MUGHU akhirnya pakai kombinasi waitForSelector + delay tambahan buat nunggu chart render.

  • Error handling itu wajib. Kalau satu dashboard down, script harus tetap jalan buat yang lain. Try-catch per dashboard, buat satu blok untuk semua.

  • Screenshot element > screenshot halaman penuh. Screenshot elemen spesifik (#dashboard-container) lebih konsisten dibanding halaman penuh yang bisa kepotong.


Error Umum dan Troubleshooting

Sekarang kita bahas error-error yang sering banget muncul waktu pake Puppeteer. MUGHU udah kena semuanya, jadi kamu nggak perlu.

Error 1: "No usable sandbox"

BASH
Error: Failed to launch the browser process!
[ERROR:zygote_host_impl_linux.cc(89)] Running without renderer sandbox.

Penyebab: Linux environment (terutama di Docker atau CI/CD) biasanya butuh sandbox di-disable.

Solusi:

JAVASCRIPT
const browser = await puppeteer.launch({
  args: ['--no-sandbox', '--disable-setuid-sandbox']
});

Kenapa butuh --no-sandbox? Chrome secara default pakai sandbox buat security. Tapi di environment yang nggak punya akses kernel namespace (kayak container Docker yang locked-down), ini jadi masalah. --no-sandbox menonaktifkan sandbox tersebut.

Peringatan: Jangan pakai --no-sandbox di environment production yang menghadap publik, karena ini kurangi security. Buat CI/CD internal sih aman-aman aja.

Error 2: "Navigation Timeout Exceeded"

BASH
Error: Navigation timeout of 30000 ms exceeded

Penyebab: Halaman butuh waktu lebih lama dari 30 detik (default timeout) buat load.

Solusi:

JAVASCRIPT
// Naikin timeout global
await page.setDefaultNavigationTimeout(60000); // 60 detik

// Atau per-request
await page.goto('https://example.com', {
  timeout: 60000,
  waitUntil: 'networkidle2'
});

Kalau halaman emang berat banget, coba ganti waitUntil ke 'domcontentloaded' yang lebih cepat. networkidle2 nunggu network activity berhenti, yang bisa lama kalau halaman punya websocket atau polling.

Error 3: "Cannot find element" atau "Selection is not pointing to a Node"

BASH
Error: Execution context was destroyed, most likely because of a navigation.

Penyebab: Kamu ngakses elemen yang sudah nggak ada karena halaman navigasi atau re-render.

Solusi:

JAVASCRIPT
// Wrong — elemen mungkin sudah ilang pas dievaluate
const element = await page.$('#button');
await page.click('#nav-link');  // navigasi terjadi
await element.click();  // ERROR: element already detached

// Correct — pakai selector langsung
await page.click('#nav-link');
await page.waitForSelector('#button');
await page.click('#button');

Jangan simpan reference elemen terlalu lama. Kalau ada kemungkinan halaman berubah, pakai selector ulang.

Error 4: "Protocol Error: Target Closed"

BASH
Error: Protocol error: Target closed.

Penyebaban: Browser telah ditutup (atau crash) tapi script masih coba ngakses page.

Solusi:

JAVASCRIPT
try {
  await page.goto('https://example.com');
  // ... operasi lain
} catch (error) {
  console.error('Browser error:', error.message);
} finally {
  if (browser) {
    await browser.close().catch(() => {});
  }
}

Selalu bungkus operasi dalam try-catch dan pastiin browser.close() di block finally biar nggak ada process yang nyangkut.

Error 5: "puppeteer not found" atau Module Error

BASH
Error: Cannot find module 'puppeteer'

Penyebab: Puppeteer belum ter-install atau kamu jalanin script dari folder yang salah.

Solusi:

BASH
# Pastiin di folder yang ada package.json-nya
ls package.json  # harus ada file ini

# Install ulang kalau perlu
npm install puppeteer

# Jangan pakai global install — selalu local

Error 6: Headless Mode nggak Muncul Browser

Kalau kamu set headless: false tapi browser nggak muncul:

BASH
# Di Linux, butuh Xvfb atau display lain
# Ubuntu/Debian:
sudo apt-get install xvfb
xvfb-run node index.js

# Atau di Docker, pakai image yang sudah include dependencies

Server Linux rata-rata nggak punya GUI. Mode headed butuh display server (kayak X11). Solusinya: pakai Xvfb (virtual framebuffer) atau tetap di headless mode.


Tips Lanjutan: Performa dan Best Practices

Setelah kamu nyaman sama dasar-dasarnya, ini tips buat bikin script Puppeteer yang lebih efisien dan robust.

1. Gunakan waitUntil yang Tepat

Mode

Kapan Dipakai

'load'

Halaman statis sederhana

'domcontentloaded'

DOM udah parse, nggak perlu tunggu resource

'networkidle0'

Tidak ada network request sama sekali selama 500ms

'networkidle2'

Maksimal 2 network request selama 500ms (buat SPA)

MUGHU paling sering pakai 'networkidle2' buat halaman modern dan 'domcontentloaded' buat halaman sederhana. 'networkidle0' terlalu ketat dan sering timeout di halaman yang punya polling atau websocket.

2. Block Resource yang Nggak Perlu

Buat scraping yang cuma butuh teks, block gambar, font, dan stylesheet:

JAVASCRIPT
await page.setRequestInterception(true);

const blockedTypes = ['image', 'font', 'stylesheet', 'media'];

page.on('request', request => {
  if (blockedTypes.includes(request.resourceType())) {
    request.abort();
  } else {
    request.continue();
  }
});

Ini bisa ngurangin waktu load sampai 50-70%. MUGHU pernah scrape 500 halaman dalam 3 menit dengan teknik ini, padahal tanpa block resource butuh 15 menit.

3. Pakai puppeteer-core di Production

Di environment server yang sudah punya Chrome terpasang, pakai puppeteer-core dan arahkan ke Chrome yang sudah ada:

JAVASCRIPT
import puppeteer from 'puppeteer-core';

const browser = await puppeteer.launch({
  executablePath: '/usr/bin/google-chrome',
  headless: true,
  args: ['--no-sandbox']
});

Ini lebih hemat space (nggak download browser tambahan) dan lebih gampang di-manage versinya.

4. Reuse Browser Instance

Daripada launch browser baru tiap request, buat satu instance dan reuse:

JAVASCRIPT
import puppeteer from 'puppeteer';

let browserInstance = null;

async function getBrowser() {
  if (!browserInstance || !browserInstance.connected) {
    browserInstance = await puppeteer.launch({
      headless: true,
      args: ['--no-sandbox']
    });
  }
  return browserInstance;
}

async function scrapePage(url) {
  const browser = await getBrowser();
  const page = await browser.newPage();
  await page.goto(url, { waitUntil: 'domcontentloaded' });
  const data = await page.content();
  await page.close();
  return data;
}

// Jangan lupa close saat shutdown
process.on('exit', async () => {
  if (browserInstance) await browserInstance.close();
});

Launch browser itu operasi yang berat. Dengan reuse, kamu hemat 2-3 detik per request. Buat high-volume scraping, ini beda banget.

5. Handle Rate Limiting

Jangan pelor semua halaman sekaligus. Kasih delay antar request buat sopan sama server:

JAVASCRIPT
const pages = [1, 2, 3, 4, 5];

for (const pageNum of pages) {
  await page.goto(`https://example.com/page/${pageNum}`);
  // ... scrape data

  // Delay 1-2 detik antar halaman
  await new Promise(r => setTimeout(r, 1500));
}

Beberapa website bakal block IP kamu kalau request terlalu cepat. Delay 1-2 detik biasanya aman buat site kebanyakan.

6. Handle Dynamic Content (Lazy Load)

Halaman dengan infinite scroll atau lazy load butuh scroll biar konten muncul:

JAVASCRIPT
async function scrapeAllContent(page) {
  let lastHeight = await page.evaluate(() => document.body.scrollHeight);

  while (true) {
    // Scroll ke bawah
    await page.evaluate(() => {
      window.scrollTo(0, document.body.scrollHeight);
    });

    // Nunggu konten baru
    await new Promise(r => setTimeout(r, 1500));

    // Cek apakah height berubah
    const newHeight = await page.evaluate(() => document.body.scrollHeight);
    if (newHeight === lastHeight) {
      break; // Udah nggak ada konten baru
    }
    lastHeight = newHeight;
  }

  // Sekarang ambil semua data
  return await page.$$eval('.item', els =>
    els.map(el => el.textContent.trim())
  );
}

Puppeteer untuk Testing: Dasar UI Testing

Selain scraping dan otomatisasi, Puppeteer juga keren banget buat UI testing. MUGHU pakai ini buat ngecek flow kritikal kayak checkout, registration, dan login secara berkala.

Test Sederhana: Cek Page Title dan Elemen

JAVASCRIPT
import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
const page = await browser.newPage();

await page.goto('https://example.com');

// Test 1: Page title benar
const title = await page.title();
if (title === 'Example Domain') {
  console.log('✅ Test 1 PASSED: Title benar');
} else {
  console.log(`❌ Test 1 FAILED: Title = "${title}"`);
}

// Test 2: H1 lebih dari satu
const h1Count = await page.$$eval('h1', els => els.length);
if (h1Count > 0) {
  console.log(`✅ Test 2 PASSED: Ditemukan ${h1Count} H1`);
} else {
  console.log('❌ Test 2 FAILED: Nggak ada H1');
}

// Test 3: Link ada
const linkCount = await page.$$eval('a', els => els.length);
if (linkCount > 0) {
  console.log(`✅ Test 3 PASSED: Ditemukan ${linkCount} link`);
} else {
  console.log('❌ Test 3 FAILED: Nggak ada link');
}

await browser.close();

Output yang diharapkan:

BASH
✅ Test 1 PASSED: Title benar
✅ Test 2 PASSED: Ditemukan 1 H1
✅ Test 3 PASSED: Ditemukan 1 link

Test Flow: Input → Submit → Validate

JAVASCRIPT
import puppeteer from 'puppeteer';

const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();

await page.goto('https://quotes.toscrape.com/login');

// Input credentials
await page.type('#username', 'admin');
await page.type('#password', 'admin');
await page.click('input[type="submit"]');

// Tunggu navigasi
await page.waitForNavigation();

// Validasi: pastiin kita di halaman utama (bukan login lagi)
const url = page.url();
if (url === 'https://quotes.toscrape.com/') {
  console.log('✅ Login test PASSED: Berhasil redirect ke home');
} else {
  console.log(`❌ Login test FAILED: URL = ${url}`);
}

// Validasi: logout button muncul
const hasLogout = await page.$('a[href="/logout"]');
if (hasLogout) {
  console.log('✅ Logout button test PASSED: Tombol logout tersedia');
} else {
  console.log('❌ Logout button test FAILED: Tombol logout nggak ada');
}

await browser.close();

Output yang diharapkan:

BASH
✅ Login test PASSED: Berhasil redirect ke home
✅ Logout button test PASSED: Tombol logout tersedia

Buat yang lebih serius soal testing, MUGHU rekomendasiin kombinasi Puppeteer dengan framework testing kayak Jest atau Vitest. Tapi itu materi tersendiri yang layak dibahas lebih dalam di artikel terpisah.


Eksplorasi Lanjutan: Web Workers dan MCP

Puppeteer versi terbaru (25.x) ngenalin beberapa fitur eksperimental yang menarik buat di-explore.

Web Workers Support

Di versi 25.2.0, Puppeteer nambahin waitForFunction buat Web Workers. Ini artinya kamu bisa evaluate function di dalam Web Worker context:

JAVASCRIPT
// Akses web worker di halaman
const worker = await page.worker();

// Evaluate function di worker context
const result = await worker.evaluate(() => {
  return self.workerData;
});

console.log('Worker data:', result);

Ini berguna kalau kamu lagi test halaman yang pakai Web Workers buat background processing (kayak crypto operations, data processing, dll).

WebMCP Support

Puppeteer sekarang mendukung WebMCP (Model Context Protocol) secara eksperimental. Ini ngasih kemampuan buat ekspos browser tools ke AI agent. Kalau kamu tertarik dengan AI + browser automation, ini worth buat dijajal:

JAVASCRIPT
// Enable WebMCP di browser
const browser = await puppeteer.launch({
  webMCP: true
});

Fitur ini masih eksperimental, jadi expect breaking changes di versi mendatang. Tapi konsepnya menarik banget—bayangin AI agent yang bisa langsung browse website, extract data, dan buat keputusan secara real-time.


Puppeteer di Environment Berbeda

Lokal (Laptop/PC)

Ini setup yang paling gampang. Install Node.js, npm install puppeteer, jalanin. Selesai.

BASH
node index.js

Docker

Puppeteer di Docker butuh beberapa dependencies. Berikut Dockerfile minimal:

DOCKERFILE
FROM node:20-slim

# Install Chromium dependencies
RUN apt-get update && apt-get install -y \
  chromium \
  fonts-liberation \
  libasound2 \
  libatk-bridge2.0-0 \
  libatk1.0-0 \
  libcups2 \
  libdbus-1-3 \
  libdrm2 \
  libgbm1 \
  libgtk-3-0 \
  libnspr4 \
  libnss3 \
  libxcomposite1 \
  libxdamage1 \
  libxfixes3 \
  libxkbcommon0 \
  libxrandr2 \
  xdg-utils \
  --no-install-recommends \
  && rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .

CMD ["node", "index.js"]

Dan di kode, arahkan ke Chromium yang sudah terpasang:

JAVASCRIPT
const browser = await puppeteer.launch({
  executablePath: '/usr/bin/chromium',
  args: ['--no-sandbox', '--disable-setuid-sandbox'],
  headless: true
});

CI/CD (GitHub Actions)

Berikut config GitHub Actions buat jalanin Puppeteer:

YAML
name: Scrape Test
on: [push]

jobs:
  scrape:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'

      - run: npm ci
      - run: npm install puppeteer

      - run: node index.js
        env:
          PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 'true'
          PUPPETEER_EXECUTABLE_PATH: '/usr/bin/google-chrome'

Ubuntu runner GitHub Actions sudah punya Chrome terpasang, jadi kamu bisa skip download dan langsung pakai yang ada.


Pros dan Cons Puppeteer: Review Jujur

Sebagai someone yang udah pakai Puppeteer di production, MUGHU mau kasih review yang jujur dan balanced.

Pros

  • Setup gampang banget. npm install puppeteer selesai. Chrome langsung ke-download otomatis. Nggak perlu install driver terpisah kayak Selenium.

  • Headless mode default. Cocok buat server, CI/CD, environment tanpa display.

  • API yang clean dan modern. Async/await-based, gampang dibaca.

  • Screenshot dan PDF generation built-in. Nggak butuh library tambahan.

  • Locator API yang robust. Auto-wait, ARIA-based selector, text-based selector—semua built-in.

  • Community yang aktif. Repository GitHub Puppeteer udah ada 95 ribu stars dan ribuan issue yang udah ter-answer.

  • Update reguler. Versi 25.3.0 terbit Juli 2026, jadi tool ini actively maintained.

Cons

  • Download browser saat install berat. 150-200MB buat Chrome. Di koneksi lambat ini bisa jadi masalah.

  • Hanya Chrome dan Firefox. Kalau butuh test Safari atau Edge (non-Chromium), Puppeteer nggak bisa. Selenium masih lebih unggul di sini.

  • Anti-bot detection bisa jadi masalah. Cloudflare, reCAPTCHA, dan anti-bot lainnya bisa detect Puppeteer. Butuh teknik tambahan (proxy rotation, stealth plugin, dll).

  • Memory usage lumayan. Tiap browser instance butuh 100-200MB RAM. Buat parallel scraping, ini bisa jadi bottleneck.

  • Beberapa API masih berubah antar versi. MUGHU pernah upgrade dari v22 ke v23 dan beberapa deprecated function berhenti jalan. Always test setelah upgrade.

Siapa yang Cocok Pakai Puppeteer?

Profil

Cocok?

Kenapa

Developer JS yang butuh otomasi browser

✅ Banget

API-nya natural buat JS developer

Tim QA yang test Chrome primary

Headless mode + screenshot = testing yang efisien

Scraper yang target sitenya JS-heavy

Browser beneran dirender, nggak ada masalah JS

Tim yang butuh cross-browser testing (Safari, dll)

⚠️ Pertimbangin

Cuma dukung Chrome dan Firefox

Non-developer yang butuh scrape data sesekali

⚠️ Mungkin overkill

Tools no-code mungkin lebih cocok

Proyek yang butuh mobile testing beneran

❌ Ngga cocok

Emulation bisa, tapi bukan device real


Versi Terbaru: Apa yang Baru di Puppeteer 25.x?

MUGHU selalu recommend buat stay up-to-date dengan versi terbaru. Berikut highlight dari beberapa rilis terbaru:

Puppeteer v25.3.0 (Juli 2026)

  • Support installing extensions untuk browser contexts

  • Fix swap handling tanpa timer

  • Roll ke Firefox 152.0.4

Puppeteer v25.2.0 (Juni 2026)

  • Page locale emulation (page.emulateLocale())

  • waitForFunction di Web Workers

  • Extensions bisa jalan di atas WebSocket

  • Roll ke Chrome 150.0.7871.24

  • Performance: parallelize extension workers fetching, optimize iframe population di accessibility snapshots

  • SHA-256 integrity verification buat browser archives yang di-download

Puppeteer v25.1.0 (Mei 2026)

  • Roll ke Chrome 149.0.7827.2

  • Roll ke Firefox 151.0

  • Improve progress bar dan install concurrency

  • Ganti cosmiconfig dengan lilconfig (lebih ringan)

Fitur paling berguna menurut MUGHU di 25.2.0 adalah page locale emulation. Dulu buat emulate locale harus pakai evaluateOnNewDocument yang rada hacky. Sekarang tinggal page.emulateLocale({ locale: 'id-ID' })—bersih dan resmi.


Panduan Memilih: Puppeteer vs Alternatif Lain

Selain Selenium, ada beberapa tools lain yang sering dibandingin sama Puppeteer. MUGHU mau kasih perbandingan yang lebih luas:

Kriteria

Puppeteer

Playwright

Cypress

Selenium

Ecosystem

Node.js

Node.js, Python, .NET, Java

JavaScript

Multi-bahasa

Browser

Chrome, Firefox

Chrome, Firefox, Safari

Chrome, Firefox, Edge

Semua major

Mobile Emulation

⚠️ Limited

⚠️

API Style

Async/await

Async/await

Sync-like

Multi-style

Test Runner

❌ (butuh Jest/Vitest)

✅ Built-in

✅ Built-in

Auto-wait

⚠️ Manual (Locator API improving)

✅ Built-in

✅ Built-in

Parallelisme

Manual

✅ Built-in

✅ (Grid)

Scraping (non-testing)

Learning Curve

Rendah

Rendah-Menengah

Menengah

Menengah-Tinggi

Rekomendasi Berdasarkan Use Case

Buat otomasi browser murni (scraping, PDF, screenshot): MUGHU recommend Puppeteer. API-nya clean, headless default, dan nggak butuh test runner. Kalau butuh scrape ribuan halaman, Puppeteer + resource blocking = performansi yang lumayan.

Buat E2E testing serius dengan multi-browser: Playwright lebih cocok. Built-in test runner, auto-wait yang lebih baik, dan support Safari (WebKit) natively.

Buat team QA yang sudah invest di JavaScript testing: Cypress punya DX (developer experience) yang excellent dan community yang kuat. Tapi Cypress nggak cocok buat scraping non-testing.

Buat organisasi yang butuh testing lintas platform dan bahasa: Selenium masih jadi pilihan, terutama kalau team kamu punya codebase Java, Python, atau C#.

MUGHU personally pakai Puppeteer buat scraping dan otomasi non-testing, lalu Playwright buat E2E testing. Dua-duanya punya tempatnya masing-masing di toolchain MUGHU.


Contoh Lengkap: Scrapping Produk dari Toko Online

Sekarang kita masuk ke contoh yang lebih lengkap. MUGHU mau tunjukin cara scrape produk dari toko online (kita pakai books.toscrape.com yang dibuat khusus buat latihan) dengan pagination, data extraction, dan error handling.

JAVASCRIPT
import puppeteer from 'puppeteer';
import fs from 'fs';

async function scrapeBooks() {
  const browser = await puppeteer.launch({
    headless: true,
    args: ['--no-sandbox']
  });

  const page = await browser.newPage();
  await page.setViewport({ width: 1280, height: 800 });

  // Block resource yang nggak perlu buat hemat bandwidth
  await page.setRequestInterception(true);
  page.on('request', request => {
    const type = request.resourceType();
    if (['image', 'font', 'stylesheet'].includes(type)) {
      request.abort();
    } else {
      request.continue();
    }
  });

  const allBooks = [];
  let pageNum = 1;
  let hasNext = true;

  while (hasNext) {
    const url = pageNum === 1
      ? 'http://books.toscrape.com/catalogue/category/books_1/index.html'
      : `http://books.toscrape.com/catalogue/category/books_1/page-${pageNum}.html`;

    console.log(`Scraping halaman ${pageNum}...`);

    try {
      await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 20000 });
      await page.waitForSelector('.product_pod', { timeout: 10000 });

      const books = await page.$$eval('.product_pod', els =>
        els.map(el => {
          const titleEl = el.querySelector('h3 a');
          const priceEl = el.querySelector('.price_color');
          const stockEl = el.querySelector('.instock, .outofstock');

          return {
            title: titleEl?.getAttribute('title') || titleEl?.textContent.trim(),
            price: priceEl?.textContent.trim() || 'N/A',
            stock: stockEl?.textContent.trim() || 'N/A',
            url: titleEl?.href || ''
          };
        })
      );

      allBooks.push(...books);
      console.log(`  Ditemukan ${books.length} buku`);

      // Cek halaman berikutnya
      const nextButton = await page.$('.next a');
      if (nextButton) {
        pageNum++;
      } else {
        hasNext = false;
      }
    } catch (error) {
      console.error(`Error di halaman ${pageNum}: ${error.message}`);
      hasNext = false;
    }
  }

  // Simpan ke file
  fs.writeFileSync('books.json', JSON.stringify(allBooks, null, 2));
  console.log(`\n=== Selesai ===`);
  console.log(`Total buku: ${allBooks.length}`);
  console.log(`File: books.json`);

  await browser.close();
  return allBooks;
}

scrapeBooks().catch(console.error);

Output yang diharapkan:

BASH
Scraping halaman 1...
  Ditemukan 20 buku
Scraping halaman 2...
  Ditemukan 20 buku
...
Scraping halaman 8...
  Ditemukan 4 buku

=== Selesai ===
Total buku: 991
File: books.json

Script ini ngebatasin beberapa hal yang udah kita bahas:

  • Error handling per halaman — kalau satu halaman error, script tetap lanjut

  • Resource blocking — block gambar, font, dan CSS buat hemat bandwidth

  • Wait strategydomcontentloaded + waitForSelector buat pastiin konten muncul

  • Pagination otomatis — cek tombol "Next" dan loop sampai habis

  • Output ke file — simpan hasil ke JSON buat diproses lebih lanjut


Kesalahan yang Sering Dilakuin dan Cara Ngindarin

MUGHU mau tutup dengan list kesalahan-kesalahan yang sering banget dilakuin orang pas pakai Puppeteer—termasuk yang MUGHU sendiri pernah lakuin.

1. Lupa Tutup Browser

Ini yang paling sering. Browser jalan di background dan makin menumpuk. Gejala: laptop lemot, RAM kepenuhan, task manager penuh proses Chrome.

Solusi: Selalu await browser.close() di block finally:

JAVASCRIPT
const browser = await puppeteer.launch();
try {
  // ... operasi
} finally {
  await browser.close();
}

2. Pakai waitForTimeout Buat Nunggu

Fix delay itu nggak reliable dan nggak efisien.

Solusi: Pakai waitForSelector atau waitForFunction yang langsung ngecek kondisi real.

3. Nggak Handle Error Sama Sekali

Script tanpa error handling bakal crash di tengah jalan kalau ada satu halaman yang bermasalah.

Solusi: Bungkus operasi per-page dalam try-catch dan log error-nya:

JAVASCRIPT
for (const url of urls) {
  try {
    await page.goto(url);
    // ... scrape
  } catch (err) {
    console.error(`Gagal scrape ${url}: ${err.message}`);
    continue;  // Lanjut ke URL berikutnya
  }
}

4. Lupa Set Viewport

Tanpa set viewport, halaman di-render dalam ukuran default (800x600) yang bisa bikin layout berbeda dari yang diharapkan.

Solusi: Selalu set viewport eksplisit:

JAVASCRIPT
await page.setViewport({ width: 1920, height: 1080 });

5. Pakai Selector yang Rapuh

Selector kayak .bg-blue-500 > div > div:nth-child(3) bakal break kalau developer ubah struktur HTML atau CSS class.

Solusi: Pakai selector yang stabil—ARIA label, data-testid, atau minimal selector yang bermakna kayak .product-card atau #checkout-button.

JAVASCRIPT
// ❌ Rapuh
await page.click('.grid > div > div:nth-child(2) > button');

// ✅ Stabil
await page.click('[data-testid="add-to-cart"]');
await page.locator('::-p-aria(Add to cart)').click();

KESIMPULAN
Sepanjang artikel ini, kita udah ngelaksin Puppeteer dari yang paling dasar—install, bikin script pertama, navigasi, sampai teknik advanced kayak resource blocking, pagination otomatis, dan error handling yang scalable. Yang bikin Puppeteer beda dari tool scraping lain adalah dia menjalankan browser *real* dalam mode headless, jadi JavaScript-heavy site kayak SPA (Single Page Application) bukan masalah lagi. Tapi power ini datang dengan tanggung jawab: setiap `browser.launch()` harus diimbangi dengan `browser.close()`, setiap `waitForTimeout` harus diganti sama wait strategy yang condong ke event-driven, dan setiap selector harus dipilih dengan ketahanan jangka panjang.

Dari pengalaman pribadi MUGHU, kesalahan-kesalahan yang kita bahas di atas bukan teori—itu beneran terjadi di production, bikin script berhenti di tengah jalan, atau bikin server tiba-tiba kehabisan RAM karena ada belasan instance Chrome yang lupa dimati. Pola yang paling ampuh buat ngindarin semua itu simpel aja: **selalu bungkus operasi browser dalam try-finally, pakai wait strategy yang ngecek kondisi nyata, dan pilih selector yang nggak gampang break**. Kalau tiga hal itu jadi habit, script Puppeteer lo bakal jauh lebih stabil dan gampang di-maintain.

Puppeteer. (2026). Puppeteer: Automate Chrome or Firefox with the DevTools Protocol and WebDriver BiDi.

GitHub. (2026). Puppeteer: JavaScript API for Chrome and Firefox browser automation.

Chrome for Developers. (2026). Puppeteer: Automating Chrome and Firefox with Node.js.

Puppeteer. (2026). Getting started with Puppeteer.

npm. (2026). Puppeteer: A high-level API to control headless Chrome over the DevTools Protocol.

Merriam-Webster. (2026). Puppeteer: Definition and meaning.

Thunderbit. (2026). What is Puppeteer? A comprehensive guide for beginners.

GitHub. (2026). Puppeteer releases: Version history and changelog.

Wikipedia. (2026). Puppeteer: The art and history of puppet manipulation.

TutorialsPoint. (2026). Puppeteer tutorial: Automation and frontend testing guide.

Komentar (0)

Belum ada komentar. Jadilah yang pertama berbagi pendapat!

Tinggalkan komentar