Đang tải...

Lửng Lọc Lõi

Đeo kính để soi Bug cho rõ, nhíu mày để nhắc Dev sửa cho kỹ. Với tôi, 'chạy được' thôi là chưa đủ! 🧐💻


0

Puppeteer cho fresher: Test web headless không cần framework nặng

Lửng Lọc Lõi
Lửng Lọc Lõi

5 tháng trước · 12 phút đọc

Puppeteer là gì và tại sao fresher nên học trước?

Mình nhớ lần đầu được giao nhiệm vụ "test tự động" khi mới vào nghề. Mở Selenium lên, cài driver, cấu hình... mất cả buổi sáng vẫn chưa chạy được dòng đầu tiên. Rồi một đồng nghiệp bảo: "Thử Puppeteer đi, 5 phút là chạy được."

Puppeteer là thư viện Node.js cho phép điều khiển trình duyệt Chrome/Chromium bằng code. Bạn có thể dùng nó để:

  • Mở trang web, click nút, điền form
  • Chụp screenshot hoặc xuất PDF
  • Scrape dữ liệu từ trang web
  • Kiểm tra luồng người dùng tự động (automation test)

Headless nghĩa là chạy Chrome không có giao diện hiển thị - như Chrome ẩn hình vẫn duyệt web bình thường. Tốc độ nhanh hơn, không chiếm màn hình, chạy được trên server.

343720 Puppeteer điều khiển Chrome ngầm, không cần bạn nhìn thấy gì

Tại sao phù hợp với fresher? Ba lý do đơn giản:

Một: Cài đặt 1 lệnh, không cần driver riêng như Selenium. Puppeteer tự đi kèm Chromium.

Hai: Cú pháp JavaScript thuần, không có abstraction layer phức tạp. Bạn đọc code là hiểu code đang làm gì.

Ba: Cộng đồng lớn. Gặp lỗi Google là ra ngay.

Trong danh sách top công cụ automation testing được cộng đồng QA toàn cầu đánh giá cao của State of Testing 2024-2025, Puppeteer vẫn giữ vị trí top các công cụ automation phổ biến nhất - đặc biệt với team nhỏ và dự án cần bắt đầu nhanh.


Cài đặt Puppeteer trong 3 phút

Bạn cần Node.js phiên bản 18 trở lên. Kiểm tra bằng lệnh:

node --version

Nếu chưa có Node.js, tải tại nodejs.org. Sau đó tạo thư mục project:

mkdir puppeteer-test
cd puppeteer-test
npm init -y

Cài Puppeteer:

npm install puppeteer

343721 Lần đầu cài sẽ mất 1-2 phút vì Puppeteer tự tải Chromium

Gói puppeteer (~170MB) tự tải Chromium về máy. Nếu đường truyền chậm hoặc bạn đã có Chrome, dùng puppeteer-core (nhẹ hơn, nhưng cần chỉ đường dẫn tới Chrome thủ công).

Sau khi cài xong, tạo file test.js với nội dung đơn giản nhất có thể:

const puppeteer = require('puppeteer');

(async () => {
  // Khởi động trình duyệt
  const browser = await puppeteer.launch();
  
  // Mở tab mới
  const page = await browser.newPage();
  
  // Điều hướng tới trang web
  await page.goto('https://example.com');
  
  // Chụp ảnh màn hình
  await page.screenshot({ path: 'result.png' });
  
  // Đóng trình duyệt
  await browser.close();
  
  console.log('Xong! Kiểm tra file result.png');
})();

Chạy thử:

node test.js

Nếu thấy file result.png xuất hiện trong thư mục - chúc mừng, bạn đã chạy Puppeteer thành công.

Một lỗi hay gặp ở bước này: Error: Failed to launch the browser process. Nguyên nhân thường là thiếu thư viện hệ thống (Linux) hoặc sandbox bị chặn. Fix bằng cách thêm option:

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

Thấy khó ở bước này thì bình thường - mình cũng mất 20 phút mày mò lần đầu. Cứ copy lỗi paste vào Google là ra cách fix ngay.


Scrape dữ liệu web: Ví dụ thực tế với trang tin tức

Scraping là lấy dữ liệu từ trang web bằng code - thay vì tay bạn copy từng cái, Puppeteer làm thay. Dưới đây mình hướng dẫn scrape danh sách tiêu đề bài viết từ một trang tin tức.

Trước khi code, cần biết CSS selector để chỉ cho Puppeteer biết "lấy phần tử này". Mở trang web, bấm chuột phải vào tiêu đề, chọn Inspect để xem HTML. Ví dụ thấy <h2 class="article-title">, selector sẽ là .article-title.

343722 Inspect Element giúp tìm đúng selector - bước quan trọng nhất khi scrape

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  
  // Trỏ tới trang cần lấy dữ liệu
  await page.goto('https://vnexpress.net/kinh-doanh', {
    waitUntil: 'networkidle2' // Chờ trang load xong hoàn toàn
  });
  
  // Lấy tất cả tiêu đề bài viết
  const titles = await page.evaluate(() => {
    // Hàm này chạy TRONG trình duyệt, không phải Node.js
    const elements = document.querySelectorAll('h3.title-news a');
    return Array.from(elements).map(el => el.textContent.trim());
  });
  
  console.log('Tìm thấy:', titles.length, 'bài viết');
  titles.slice(0, 5).forEach((title, i) => {
    console.log(`${i + 1}. ${title}`);
  });
  
  await browser.close();
})();

Hàm page.evaluate() là điểm đặc biệt của Puppeteer. Code bên trong hàm này chạy trong ngữ cảnh trình duyệt - bạn có thể dùng document, window, các DOM API bình thường. Code bên ngoài chạy trong Node.js. Hai vùng này tách biệt hoàn toàn.

Lưu ý về scraping: Luôn kiểm tra robots.txt của trang web và điều khoản dịch vụ trước khi scrape. Dùng delay giữa các request để không làm quá tải server. Scrape cho mục đích học tập hoặc dữ liệu cá nhân thì ổn, nhưng cần cẩn thận khi dùng thương mại.


Template script test e-commerce Việt Nam

Mình hay dùng Puppeteer để kiểm tra luồng mua hàng - thứ mà nếu lỗi sẽ thiệt hại trực tiếp về doanh thu. Dưới đây là template kiểm tra 3 điểm quan trọng nhất của một trang e-commerce:

  1. Trang sản phẩm load được và hiển thị đúng giá
  2. Nút "Thêm vào giỏ hàng" hoạt động
  3. Giỏ hàng cập nhật đúng số lượng

Tải template đầy đủ tại đây: Template test e-commerce với Puppeteer

const puppeteer = require('puppeteer');

// Cấu hình - thay bằng URL thật của bạn
const CONFIG = {
  baseUrl: 'https://your-ecommerce-site.vn',
  timeout: 30000, // 30 giây timeout
  headless: false // false = hiện trình duyệt để debug, true = headless
};

async function testProductPage(page, productUrl) {
  console.log('🔍 Kiểm tra trang sản phẩm...');
  
  await page.goto(productUrl, { waitUntil: 'networkidle2', timeout: CONFIG.timeout });
  
  // Kiểm tra giá hiển thị
  const price = await page.$eval('.product-price', el => el.textContent);
  
  if (!price || price.trim() === '') {
    throw new Error('LỖI: Giá sản phẩm không hiển thị');
  }
  console.log('✅ Giá hiển thị:', price.trim());
}

async function testAddToCart(page) {
  console.log('🛒 Kiểm tra thêm vào giỏ hàng...');
  
  // Click nút thêm vào giỏ
  await page.click('.btn-add-to-cart');
  
  // Chờ giỏ hàng cập nhật (tối đa 5 giây)
  await page.waitForSelector('.cart-count', { timeout: 5000 });
  
  // Lấy số lượng trong giỏ
  const cartCount = await page.$eval('.cart-count', el => el.textContent);
  console.log('✅ Giỏ hàng:', cartCount, 'sản phẩm');
  
  return parseInt(cartCount) > 0;
}

async function runTest() {
  const browser = await puppeteer.launch({ headless: CONFIG.headless });
  const page = await browser.newPage();
  
  // Set viewport như màn hình desktop
  await page.setViewport({ width: 1366, height: 768 });
  
  try {
    await testProductPage(page, `${CONFIG.baseUrl}/san-pham/ao-thun-trang`);
    const cartOk = await testAddToCart(page);
    
    if (cartOk) {
      console.log('\n✅ TẤT CẢ TEST PASSED - Luồng mua hàng hoạt động bình thường');
    } else {
      console.log('\n❌ FAILED - Giỏ hàng không cập nhật sau khi thêm sản phẩm');
    }
  } catch (err) {
    console.error('\n❌ LỖI:', err.message);
    // Chụp ảnh màn hình khi lỗi để debug
    await page.screenshot({ path: 'error-screenshot.png', fullPage: true });
    console.log('📸 Đã lưu ảnh lỗi tại error-screenshot.png');
  } finally {
    await browser.close();
  }
}

runTest();

343723 Chụp screenshot khi lỗi là thói quen tốt - tiết kiệm nhiều thời gian debug

Có một điểm mình thấy nhiều fresher hay bỏ qua: luôn chụp screenshot khi test fail. Trong block catch, thêm page.screenshot() ngay. Bug không dễ reproduce - ảnh chụp lúc lỗi xảy ra giúp bạn và dev hiểu được vấn đề mà không cần reproduce lại.

Ngoài ra, đặt headless: false khi đang viết script mới. Bạn sẽ thấy Chrome tự mở, tự click - dễ phát hiện script sai chỗ nào. Khi script chạy ổn rồi, chuyển về true để chạy background.


Puppeteer vs Selenium: Chọn cái nào khi mới bắt đầu?

Câu hỏi này mình nghe rất nhiều. Câu trả lời ngắn: Bắt đầu với Puppeteer, học Selenium sau khi đi làm.

Lý do:

Tiêu chí Puppeteer Selenium
Cài đặt 1 lệnh npm Cài driver riêng, cấu hình nhiều bước
Tốc độ học 1-2 ngày chạy được 3-5 ngày mới ổn định
Hỗ trợ browser Chủ yếu Chrome/Chromium Multi-browser (Chrome, Firefox, Safari...)
Dùng trong team Phổ biến ở startup, team JS Phổ biến ở enterprise, Java/.NET team
Phù hợp học Rất phù hợp fresher Phù hợp khi đã có nền tảng

Selenium mạnh hơn khi cần test đa trình duyệt hoặc làm việc trong môi trường Java/.NET. Nhưng với fresher học tự động hóa lần đầu, overhead cài đặt và cấu hình Selenium thường khiến nhiều bạn nản trước khi viết được dòng code đầu tiên.

343724 Puppeteer và Selenium phù hợp cho các giai đoạn khác nhau trong career

Playwright - anh em ruột của Puppeteer do cùng team Microsoft phát triển - hiện đang nổi lên như một lựa chọn mạnh hơn về multi-browser. 67% growth in adoption since 2024 (Browserbase) cho thấy Playwright đang tăng trưởng nhanh. Nhưng cú pháp Playwright và Puppeteer rất giống nhau, nên học Puppeteer xong chuyển Playwright rất dễ.

Nếu bạn đang học JavaScript cơ bản, mình gợi ý ghé qua khóa Lập Trình JavaScript Cơ Bản trước - miễn phí, cover đủ async/await và Promise mà Puppeteer dùng nhiều.


Các kỹ thuật Puppeteer hay dùng khi test thực tế

Viết được script chạy là một chuyện. Viết script chạy ổn định qua nhiều lần là chuyện khác. Đây là 4 kỹ thuật mình dùng mỗi ngày:

1. Chờ đúng cách - không dùng setTimeout

Nhiều người mới hay làm:

// ❌ Tránh - chờ cứng 3 giây, không biết trang load xong chưa
await new Promise(r => setTimeout(r, 3000));

Làm đúng:

// ✅ Chờ element xuất hiện mới tiếp tục
await page.waitForSelector('.product-list', { timeout: 10000 });

// ✅ Chờ navigation (chuyển trang) xong
await Promise.all([
  page.waitForNavigation(),
  page.click('.submit-btn')
]);

2. Screenshot theo tên file có timestamp

const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
await page.screenshot({ 
  path: `screenshots/test-${timestamp}.png`,
  fullPage: true 
});

Lưu theo timestamp giúp bạn không bị ghi đè file cũ, dễ tìm ảnh lỗi tương ứng với lần chạy nào.

3. Test trên màn hình mobile

// Giả lập iPhone 13
await page.emulate(puppeteer.KnownDevices['iPhone 13']);
await page.goto('https://your-site.vn');

4. Chặn request không cần thiết để test nhanh hơn

// Chặn ảnh và font để load nhanh hơn
await page.setRequestInterception(true);
page.on('request', (req) => {
  if (['image', 'font'].includes(req.resourceType())) {
    req.abort();
  } else {
    req.continue();
  }
});

343725 Chặn ảnh và font có thể giúp script chạy nhanh hơn đáng kể

Mình từng chạy test kiểm tra giá 50 sản phẩm mà không chặn resource, mất 4 phút. Sau khi chặn ảnh và font, còn khoảng 50 giây. Cùng kết quả, nhanh hơn 5 lần.

Nếu bạn đang học Node.js để hiểu sâu hơn về môi trường chạy Puppeteer, khóa Node & ExpressJS của F8 cover tốt phần này - cũng miễn phí.


Bước tiếp theo sau khi thành thạo Puppeteer cơ bản

Bạn đã biết cài Puppeteer, scrape dữ liệu, và test luồng mua hàng. Bước tiếp theo làm gì?

Tích hợp vào CI/CD pipeline. Chạy test tự động mỗi khi có code mới push lên - GitHub Actions hỗ trợ tốt. Script Puppeteer của bạn sẽ chạy như một bước trong pipeline, báo lỗi ngay nếu có gì đó sai.

Kết hợp với Jest để có báo cáo test đẹp hơn. Puppeteer thuần chỉ log ra console. Dùng kèm Jest hoặc Mocha sẽ có report tổng hợp pass/fail, tiện trình bày với team.

Học Playwright sau khi quen Puppeteer. Như đã nói, cú pháp tương đồng nhưng Playwright hỗ trợ thêm Firefox và Safari. Chromium (Chrome, Edge), Firefox, WebKit (Safari) (TestDino Blog)

343726 Lộ trình automation testing từ Puppeteer lên Playwright và CI/CD

Download checklist kiểm tra trước khi đưa script Puppeteer vào production tại đây: Tải checklist Puppeteer production-ready

Testing không khó. Khó là giữ thói quen viết test case tỉ mỉ, không bỏ qua edge case, không chạy test cho xong. Mình tin nếu bạn đọc đến đây, bạn đã hiểu: automation test không phải làm cho có, mà là bảo vệ sản phẩm khỏi lỗi.

Test kỹ 1 lần tốt hơn fix lỗi nhiều lần sau production. Script Puppeteer viết hôm nay có thể cứu cả team khỏi một sự cố nghiêm trọng ngày mai. Bắt đầu từ trang sản phẩm đơn giản nhất bạn đang test - chuyển nó thành script tự động hóa. Xong comment bên dưới cho mình biết nhé!