Programming
Panduan Lengkap Puppeteer untuk Otomatisasi Browser dari Nol
Daftar isi
- Apa Itu Puppeteer dan Kenapa Kamu Perlu Tahu?
- Kenapa Puppeteer Populer Banget?
- Puppeteer vs Selenium: Mana yang Pass?
- Yang Kamu Perlu Siapin Se Mulai (Prerequisites)
- 1. Node.js (Versi 18 atau Yang Lebih Baru)
- 2. NPM atau Package Manager Lain
- 3. Editor Kode
- 4. Pengetahuan Dasar JavaScript
- 5. Koneksi Internet yang Stabil
- Step 1: Install Puppeteer di Proyek Kamu
- Bikin Folder Proyek dan Inisialisasi NPM
- Install Puppeteer
- Puppeteer vs Puppeteer-Core: Apa Bedanya?
- Step 2: Skrip Pertama—Launch Browser dan Buka Halaman
- Kenapa Tiap Baris Itu Penting
- Step 3: Berinteraksi Sama Halaman—Klik, Ketik, dan Ambil Data
- Mengambil Data dari Halaman
- Mengetik di Input Field
- Menggunakan Locator API (Cara Modern)
- Step 4: Screenshot dan Generate PDF
- Screenshot Halaman Penuh
- Screenshot Elemen Spesifik
- Generate PDF dari Halaman Web
- Step 5: Network Interception—Block dan Modifikasi Request
- Block Semua Gambar (Buat Loading Lebih Cepat)
- Modifikasi Response API
- Step 6: Menangani Multiple Pages dan Tabs
- Menangani Popup Window
- Step 7: Handle Login dan Session (Cookies)
- Login Otomatis
- Nyimpan dan Muat Ulang Cookies
- Step 8: Wait Strategies—Nunggu yang Benar
- Jenis Wait di Puppeteer
- Contoh: Tunggu Elemen Muncul
- Contoh: Tunggu Kondisi Custom
- Kesalahan yang Sering Terjadi Soal Wait
- Step 9: Scraping Data dari Halaman dengan Pagination
- Step 10: Mengatur User-Agent dan Hindari Deteksi Bot
- Set Custom User-Agent
- Set Viewport dan Language
- Studi Kasus: Otomatisasi Report Dashboard Harian
- Background
- Challenge
- Approach
- Implementation
- Results
- Key Learnings
- Error Umum dan Troubleshooting
- Error 1: "No usable sandbox"
- Error 2: "Navigation Timeout Exceeded"
- Error 3: "Cannot find element" atau "Selection is not pointing to a Node"
- Error 4: "Protocol Error: Target Closed"
- Error 5: "puppeteer not found" atau Module Error
- Error 6: Headless Mode nggak Muncul Browser
- Tips Lanjutan: Performa dan Best Practices
- 1. Gunakan waitUntil yang Tepat
- 2. Block Resource yang Nggak Perlu
- 3. Pakai puppeteer-core di Production
- 4. Reuse Browser Instance
- 5. Handle Rate Limiting
- 6. Handle Dynamic Content (Lazy Load)
- Puppeteer untuk Testing: Dasar UI Testing
- Test Sederhana: Cek Page Title dan Elemen
- Test Flow: Input → Submit → Validate
- Eksplorasi Lanjutan: Web Workers dan MCP
- Web Workers Support
- WebMCP Support
- Puppeteer di Environment Berbeda
- Lokal (Laptop/PC)
- Docker
- CI/CD (GitHub Actions)
- Pros dan Cons Puppeteer: Review Jujur
- Pros
- Cons
- Siapa yang Cocok Pakai Puppeteer?
- Versi Terbaru: Apa yang Baru di Puppeteer 25.x?
- Puppeteer v25.3.0 (Juli 2026)
- Puppeteer v25.2.0 (Juni 2026)
- Puppeteer v25.1.0 (Mei 2026)
- Panduan Memilih: Puppeteer vs Alternatif Lain
- Rekomendasi Berdasarkan Use Case
- Contoh Lengkap: Scrapping Produk dari Toko Online
- Kesalahan yang Sering Dilakuin dan Cara Ngindarin
- 1. Lupa Tutup Browser
- 2. Pakai waitForTimeout Buat Nunggu
- 3. Nggak Handle Error Sama Sekali
- 4. Lupa Set Viewport
- 5. Pakai Selector yang Rapuh
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 |
| 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:
node --version
Output yang diharapkan:
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
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:
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":
Baca juga OpenCode Free: Agen AI Coding Gratis dan Open Source
{
"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
npm install puppeteer
Perintah ini bakal ngelakuin dua hal sekaligus:
-
Download dan install library Puppeteer
-
Download versi Chrome yang kompatibel (ini bagian yang butuh internet)
Output yang diharapkan:
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:
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:
{
"allowScripts": ["puppeteer"]
}
Puppeteer vs Puppeteer-Core: Apa Bedanya?
Ini penting buat dipahami. Ada dua package yang bisa kamu install:
Aspek |
|
|
|---|---|---|
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 |
|
|
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:
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:
node index.js
Output yang diharapkan:
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:
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:
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
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:
Baca juga ClinePass: Langganan Model Open Weight untuk Coding
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
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:
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:
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
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:
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:
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:
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:
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)
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:
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
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.
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:
Baca juga Kiro Dev: IDE Agentic AWS untuk Spec-Driven Development
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:
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
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:
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:
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:
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:
Cookies tersimpan!
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 |
|---|---|---|
| Tunggu elemen muncul di DOM |
|
| Tunggu navigasi halaman selesai |
|
| Tunggu kondisi custom terpenuhi |
|
| Tunggu fix waktu (deprecated, hindari) | — |
| Auto-wait dari Locator API |
|
Contoh: Tunggu Elemen Muncul
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:
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:
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.
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:
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.
Baca juga Codex CLI dari OpenAI: Panduan Lengkap Ngoding di Terminal
Set Custom User-Agent
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:
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:
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:
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
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
networkidle2kadang nggak cukup. MUGHU akhirnya pakai kombinasiwaitForSelector+ 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"
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:
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"
Error: Navigation timeout of 30000 ms exceeded
Penyebab: Halaman butuh waktu lebih lama dari 30 detik (default timeout) buat load.
Solusi:
// 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"
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:
// 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.
Baca juga CodeBuddy: Editor Kode AI untuk Coding Lebih Cepat
Error 4: "Protocol Error: Target Closed"
Error: Protocol error: Target closed.
Penyebaban: Browser telah ditutup (atau crash) tapi script masih coba ngakses page.
Solusi:
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
Error: Cannot find module 'puppeteer'
Penyebab: Puppeteer belum ter-install atau kamu jalanin script dari folder yang salah.
Solusi:
# 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:
# 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 |
|---|---|
| Halaman statis sederhana |
| DOM udah parse, nggak perlu tunggu resource |
| Tidak ada network request sama sekali selama 500ms |
| 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:
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:
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:
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:
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:
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
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:
Baca juga GitHub Copilot: Cara Mempercepat Kerja Tim
✅ Test 1 PASSED: Title benar
✅ Test 2 PASSED: Ditemukan 1 H1
✅ Test 3 PASSED: Ditemukan 1 link
Test Flow: Input → Submit → Validate
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:
✅ 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:
// 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:
// 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.
node index.js
Docker
Puppeteer di Docker butuh beberapa dependencies. Berikut Dockerfile minimal:
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:
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:
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 puppeteerselesai. 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#.
Baca juga Mengenal Astro 7.1: Framework JavaScript Ringan untuk Website
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.
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:
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 strategy —
domcontentloaded+waitForSelectorbuat 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:
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:
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:
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.
// ❌ 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.
Baca juga 9Router v0.5.35: Solusi Rate Limit AI Coding
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