NỘI DUNG BÀI HỌC

🎒 Phần 1: Tổng quan về TestOptions (use)

🎥 Phần 2: Các chế độ Debug (Modes)

🛡️ Phần 3: Xử lý bảo mật với ignoreHTTPSErrors

👻 Phần 4: Chế độ Headless vs Headed

🚀 Phần 5: Quyền lực khởi động (launchOptions)

🖥️ Phần 6: Giải bài toán Maximize (Window vs Viewport)

📸 Phần 7: Nghệ thuật chụp ảnh (screenshot)

📱 Phần 8: Sức mạnh giả lập thiết bị (devices)

🎒Phần 9: Vị trí lưu trữ (Output directory)



Phần 1: TestOptions là gì?

Hãy tưởng tượng Test Runner là một người lính ra trận.

  • Test Code (spec.ts): Là nhiệm vụ người lính phải làm (Bắn bia A, chạy đến điểm B).

  • TestOptions (use): Chính là chiếc Balo hành trang của người lính đó.

Trong Balo (use) có gì?

  • Có ống nhòm quay phim không? (Video)

  • Có máy ảnh không? (Screenshot)

  • Có bản đồ không? (BaseURL)

  • Đang đóng giả người nước nào? (Locale/Timezone)

  • Đeo kính râm hay để mắt trần? (Headless/Viewport)

👉 Tóm lại: use định nghĩa MÔI TRƯỜNGCÔNG CỤ mà bài test sẽ sử dụng.


Ba tầng cấu hình (Hierarchy)

Playwright cho phép bạn đeo "Balo" ở 3 cấp độ, cái sau sẽ ghi đè (override) cái trước:

  1. Global (Toàn cầu): Cấu hình chung cho cả dự án.

  2. Project (Dự án con): Cấu hình riêng cho từng trình duyệt (Chrome, Mobile...).

  3. Test Level (Cục bộ): Cấu hình riêng cho đúng 1 bài test cụ thể.

Các Config "Quyền Lực" cần chú ý

Dưới đây là 5 nhóm options quan trọng nhất bạn cần thuộc lòng:

Nhóm 1: "Hộp đen" lưu bằng chứng (Artifacts)

Đây là nhóm giúp bạn debug khi test Fail.

  • trace: (Quan trọng nhất) Ghi lại toàn bộ hành trình mạng, console, snapshot.

    • Khuyên dùng: 'retain-on-failure' hoặc 'on-first-retry'.

  • screenshot: Chụp ảnh màn hình.

    • Khuyên dùng: 'only-on-failure'.

  • video: Quay video.

    • Khuyên dùng: 'retain-on-failure' (để tiết kiệm ổ cứng).

Nhóm 2: Mạng & Định vị (Network & Context)

  • baseURL: Đường dẫn gốc.

    • Ví dụ: baseURL: 'https://crm.anhtester.com' -> Khi test chỉ cần viết page.goto('/login').

  • ignoreHTTPSErrors: Bỏ qua lỗi SSL (Màn hình đỏ cảnh báo bảo mật).

    • Khuyên dùng: true (Đặc biệt khi test ở môi trường Dev/Local).

  • storageState: File chứa Cookie đăng nhập. Dùng để bypass login (Login 1 lần, dùng mãi mãi).

Nhóm 3: Hiển thị (Visual)

  • headless: Chạy ẩn hay hiện?

    • true (Mặc định): Chạy ẩn, nhanh, dùng cho CI/CD.

    • false: Hiện trình duyệt lên để mắt thường nhìn thấy.

  • viewport: Kích thước khung hình web.

    • Chuẩn: { width: 1280, height: 720 } hoặc { width: 1920, height: 1080 }.

    • Lưu ý: Nếu muốn Maximize cửa sổ, phải set viewport: null.

Nhóm 4: Ổn định (Timeouts)

  • actionTimeout: Thời gian chờ tối đa cho 1 cú click/type.

    • Khuyên dùng: 10000 (10s). Nếu quá 10s nút không bấm được thì Fail luôn đi, đừng chờ nữa.

  • navigationTimeout: Thời gian chờ load trang (page.goto).

Bài mẫu Config ví dụ

Dưới đây là file playwright.config.ts mẫu mực kết hợp tất cả kiến thức trên.

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  // Thư mục chứa test
  testDir: './tests',
  
  // Tự động retry 1 lần nếu fail (Để kích hoạt mode on-first-retry)
  retries: 1,

  // ==========================================
  // 🎒 CẤU HÌNH CHUNG (GLOBAL USE)
  // ==========================================
  use: {
    // 1. Setup đường dẫn gốc (Best Practice)
    baseURL: 'https://crm.anhtester.com',

    // 2. Debug Artifacts (Tiết kiệm nhưng đủ dùng)
    trace: 'on-first-retry',      // Chỉ lưu trace khi chạy lại
    screenshot: 'only-on-failure', // Chỉ chụp ảnh khi lỗi
    
    // Video cấu hình chi tiết
    video: {
      mode: 'retain-on-failure',     // Chỉ giữ video khi lỗi
      size: { width: 1920, height: 1080 } // Ép size HD cho nét
    },

    // 3. Môi trường & Ổn định
    ignoreHTTPSErrors: true, // Chấp nhận SSL đểu
    actionTimeout: 15 * 1000, // Click nút tối đa 15s

    // 4. Timezone & Locale (Giả lập người dùng VN)
    locale: 'vi-VN',
    timezoneId: 'Asia/Ho_Chi_Minh',
  },

  // ==========================================
  // 💻 CẤU HÌNH THEO PROJECT
  // ==========================================
  projects: [
    {
      name: 'Chromium High Def',
      use: {
        ...devices['Desktop Chrome'], // Lấy cấu hình gốc
        
        // Ghi đè: Màn hình to đẹp
        viewport: { width: 1920, height: 1080 },
      },
    },
    {
      name: 'iPhone 13',
      use: {
        ...devices['iPhone 13'], // Lấy cấu hình Mobile chuẩn
        // Mobile thì không cần video HD 1920x1080, để mặc định theo máy là được
      },
    },
  ],
});


Tổng kết cần nhớ

Khi làm việc với TestOptions (use), hãy nhớ 3 nguyên tắc vàng:

  1. Đừng bật video: 'on' bừa bãi: Trừ khi làm UAT, còn lại hãy dùng 'retain-on-failure' để bảo vệ ổ cứng.

  2. Luôn dùng baseURL: Để code ngắn gọn và dễ chuyển đổi môi trường (Staging -> Prod).

  3. Trace là chân ái: Khi debug CI/CD, trace quan trọng hơn video. Video cho bạn thấy "Cái gì hỏng", Trace cho bạn biết "Tại sao hỏng" (API lỗi 500 hay mạng lag).


Phần 2: Modes(chế độ) 

Hiểu rõ các mode này là chìa khóa để giải quyết bài toán kinh điển: "Làm sao để có đủ dữ liệu debug mà test vẫn chạy nhanh, không tốn ổ cứng?".

Chúng ta sẽ đi qua từng mode, từ đơn giản nhất đến cái "thông minh" nhất là on-first-retry.


Danh sách các Mode chính

Playwright cung cấp các mode sau cho trace, video, và screenshot:

  1. 'off'

  2. 'on'

  3. 'retain-on-failure'

  4. 'on-first-retry'

  5. 'on-all-retries' (Ít dùng hơn)


 Giải thích chi tiết từng Mode

🛑 1. Mode: 'off' (Tắt hoàn toàn)

  • Cơ chế: Không ghi gì cả. Test chạy "mù".

  • Ưu điểm: Tốc độ nhanh nhất (Max Performance). Tốn ít tài nguyên nhất.

  • Nhược điểm: Test Fail là "khóc tiếng Mán" vì không biết tại sao chết (trừ khi nhìn log text).

  • Khi nào dùng: Khi chạy test API (không giao diện), hoặc chạy Unit Test số lượng lớn (hàng nghìn cái).

🎥 2. Mode: 'on' (Luôn luôn bật)

  • Cơ chế: Cứ chạy test là Ghi.

    • Test Pass (Xanh) -> LƯU file.

    • Test Fail (Đỏ) -> LƯU file.

  • Ưu điểm: Dữ liệu đầy đủ nhất. Dùng làm bằng chứng nghiệm thu (UAT) cho khách hàng.

  • Nhược điểm:

    • Rất chậm: Máy phải gồng mình render video/trace liên tục.

    • Rất tốn ổ cứng: 1000 test case = 1000 video (dù 999 cái Pass).

  • Khi nào dùng: Debug cục bộ 1 bài test cụ thể, hoặc khi Sếp/Khách hàng yêu cầu bằng chứng.

♻️ 3. Mode: 'retain-on-failure' (Giữ lại khi lỗi - Phổ biến nhất)

  • Cơ chế:

    • Playwright LUÔN LUÔN GHI vào bộ nhớ tạm (RAM/Temp).

    • Nếu Test Pass -> Xóa bộ nhớ tạm (Không xuất file).

    • Nếu Test Fail -> Xuất dữ liệu từ bộ nhớ tạm ra file.

  • Ưu điểm: Ổ cứng sạch sẽ (chỉ chứa file lỗi). Không bao giờ bị mất dấu vết của bug.

  • Nhược điểm: Vẫn Tốn CPU/RAM (vì nó vẫn phải âm thầm ghi hình đằng sau, dù sau đó có thể xóa đi). Test chạy sẽ chậm hơn so với 'off'.

  • Khi nào dùng: Môi trường CI/CD tiêu chuẩn. Cân bằng giữa việc debug và dọn dẹp rác.

🚀 4. Mode: 'on-first-retry' (Chỉ bật khi thử lại - Tối ưu nhất)

Đây là mode "thông minh" dành cho các hệ thống lớn, chỉ hoạt động khi bạn cài đặt retries > 0.

  • Cơ chế:

    • Lần chạy 1 (First Run): Tương đương 'off'. Không ghi hình, không Trace. Chạy tốc độ tối đa.

      • Nếu Pass: Xong luôn. (Siêu nhanh).

      • Nếu Fail: Kích hoạt Retry.

    • Lần chạy 2 (First Retry): Tương đương 'on'. Bắt đầu bật máy quay và ghi Trace.

      • Dù lần 2 này Pass hay Fail, nó cũng sẽ LƯU lại dữ liệu của lần 2 này.

  • Ưu điểm:

    • Nhanh nhất: Vì 90% test case thường sẽ Pass ngay lần 1, nên ta không tốn tài nguyên để ghi hình chúng.

    • Đúng trọng tâm: Chỉ tốn công sức cho những thằng "có vấn đề" (phải chạy lại).

  • Nhược điểm: Mất dữ liệu của lần lỗi đầu tiên (Lần 1 fail không có video, chỉ có video của lần 2).

  • Khi nào dùng: Các dự án lớn (Regression Test) chạy hàng đêm, cần tiết kiệm thời gian chạy tối đa.


📊 Bảng so sánh tổng hợp

Mode Lần 1 (Pass) Lần 1 (Fail) Lần 2 (Retry) Hiệu năng (Speed) Dung lượng ổ cứng
'off' ❌ Không ❌ Không ❌ Không ⚡️ Nhanh nhất 🟢 Thấp nhất
'on' LƯU LƯU LƯU 🐢 Chậm nhất 🔴 Cao nhất
'retain-on-failure' 🎥 Quay rồi xóa LƯU LƯU 🐢 Chậm 🟢 Thấp (chỉ lưu lỗi)
'on-first-retry' ❌ Không ❌ Không LƯU 🚀 Rất Nhanh 🟢 Thấp


🛠 Ví dụ Config thực tế


📝 Bước 1: Chuẩn bị File Test (Chung cho cả 4 trường hợp)

Bạn tạo file tests/video-demo.spec.ts.

Trong bài này, mình cố tình code để có thể DỄ DÀNG CHỈNH PASS/FAIL (bằng cách comment/uncomment dòng code cuối).

import { test, expect } from '@playwright/test';

test('Demo Video Modes trên CRM Anh Tester', async ({ page }) => {
  // 1. Vào trang Login
  await page.goto('https://crm.anhtester.com/admin/authentication');

  // 2. Đăng nhập
  await page.fill('input[name="email"]', 'admin@example.com');
  await page.fill('input[name="password"]', '123456');
  await page.click('button[type="submit"]');

  // 3. Chờ vào Dashboard
  await page.waitForURL(/.*admin/);
  console.log('✅ Đã vào Dashboard!');

  // --- QUYẾT ĐỊNH SỐ PHẬN PASS HAY FAIL Ở ĐÂY ---
  
  // Trường hợp PASS: Tiêu đề đúng
  await expect(page).toHaveTitle(/Dashboard/); 

  // Trường hợp FAIL (Mở comment dòng dưới để test Fail)
  // await expect(page).toHaveTitle('Tiêu đề sai bét nhè'); 
});


⚙️ Bước 2: Thử nghiệm 4 Cấu hình (Config)

Bạn mở file playwright.config.ts và thay đổi phần use tương ứng với từng trường hợp dưới đây. Mình đã kẹp sẵn cả TraceScreenshot như bạn yêu cầu.

1️⃣ Trường hợp: video: 'off' (Tiết kiệm nhất)

Dành cho lúc chạy hàng nghìn test API hoặc test ổn định.

// playwright.config.ts
export default defineConfig({
  use: {
    // 📸 Ảnh & Trace: Vẫn giữ lại nếu Fail để debug
    trace: 'retain-on-failure',
    screenshot: 'only-on-failure',
    
    // 🎥 Video: TẮT HẾT
    video: 'off', 
  },
});

👉 Kết quả:

  • Nếu Test Pass: Thư mục sạch trơn.

  • Nếu Test Fail: Có Ảnh + Trace. KHÔNG CÓ VIDEO.


2️⃣ Trường hợp: video: 'on' (Đầy đủ nhất)

Dành cho UAT, cần bằng chứng gửi khách hàng.

// playwright.config.ts
export default defineConfig({
  use: {
    trace: 'retain-on-failure',
    screenshot: 'only-on-failure', // Ảnh thì tiết kiệm
    
    // 🎥 Video: QUAY TẤT CẢ (Lúc nào cũng lưu)
    video: {
      mode: 'on',
      size: { width: 1920, height: 1080 } // Ép nét căng
    }
  },
});

👉 Kết quả:

  • Nếu Test Pass: CÓ VIDEO (quay cảnh login thành công) + Không có ảnh/trace.

  • Nếu Test Fail: CÓ VIDEO (quay cảnh bị lỗi) + Có Ảnh + Có Trace.


3️⃣ Trường hợp: video: 'retain-on-failure' (Khuyên dùng 🏆)

Cân bằng nhất. Chỉ lưu video khi cần thiết.

// playwright.config.ts
export default defineConfig({
  use: {
    trace: 'retain-on-failure',
    screenshot: 'only-on-failure',

    // 🎥 Video: CHỈ GIỮ LẠI NẾU FAIL
    video: {
      mode: 'retain-on-failure',
      size: { width: 1920, height: 1080 }
    }
  },
});

👉 Kết quả:

  • Nếu Test Pass: Thư mục sạch trơn (Video quay xong tự xóa).

  • Nếu Test Fail: CÓ VIDEO + Có Ảnh + Có Trace.


4️⃣ Trường hợp: video: 'on-first-retry' (Tối ưu hiệu năng)

Dành cho hệ thống CI/CD lớn. Lần đầu chạy nhanh, nếu lỗi chạy lại mới quay.

 
// playwright.config.ts
export default defineConfig({
  // ⚠️ BẮT BUỘC: Phải cho phép chạy lại (Retry) ít nhất 1 lần
  retries: 1, 

  use: {
    trace: 'on-first-retry',      // Trace cũng chỉ bật khi retry
    screenshot: 'only-on-failure',

    // 🎥 Video: CHỈ QUAY KHI RETRY
    video: {
      mode: 'on-first-retry',
      size: { width: 1920, height: 1080 }
    }
  },
});
 

👉 Kết quả:

  • Lần 1 (Chạy Fail): KHÔNG CÓ VIDEO (Chạy rất nhanh).

  • Lần 2 (Tự động chạy lại do Fail): CÓ VIDEO + Có Trace.


    Cái on-first-retry này "khó nhằn" ở chỗ: Làm sao để giả lập tình huống "Chạy lần 1 chết, Chạy lần 2 sống" (Flaky Test) để mà kiểm chứng?

    Chứ nếu test cứ chạy là Pass, hoặc cứ chạy là Fail mãi thì khó thấy tác dụng của nó.

    Đừng lo, mình có "Tuyệt chiêu testInfo.retry". Mình sẽ viết một đoạn code "Tự sát ở lần 1, Hồi sinh ở lần 2" để ép Playwright phải quay video cho bạn xem.

    Làm theo đúng 3 bước này nhé:

    ⚙️ Bước 1: Cấu hình playwright.config.ts

    Bắt buộc phải bật retries lên thì chế độ này mới hoạt động như trên


    🧪 Bước 2: Viết Code "Giả vờ Flaky"

    Tạo file tests/retry-demo.spec.ts.

    Ta dùng logic: "Nếu đây là lần chạy đầu tiên (retry === 0) -> Ném lỗi Fail. Nếu là lần chạy lại (retry === 1) -> Cho chạy tiếp."

     
    import { test, expect } from '@playwright/test';
    
    test('Demo Video chỉ quay khi Retry', async ({ page }, testInfo) => {
      
      console.log(`🔄 Đang chạy lần thứ: ${testInfo.retry + 1} (Retry Index: ${testInfo.retry})`);
    
      // 1. Vào trang Login
      await page.goto('/admin/authentication');
      await page.fill('input[name="email"]', 'admin@example.com');
      await page.fill('input[name="password"]', '123456');
    
      // ============== KỊCH BẢN GIẢ LẬP =================
      // Nếu là lần chạy đầu tiên (retry = 0)
      if (testInfo.retry === 0) {
          console.log('💥 Lần 1: Cố tình đánh Fail để kích hoạt Retry...');
          
          // Giả vờ expect sai để gây lỗi
          // Playwright thấy lỗi -> Sẽ hủy lần này (KHÔNG LƯU VIDEO) -> Tự động Retry
          expect(true).toBe(false); 
      }
      
      // ============== LẦN CHẠY THỨ 2 ===================
      // Nếu code chạy xuống được đây, nghĩa là đang ở lần Retry (retry = 1)
      console.log('✅ Lần 2: Đã Retry! Lúc này Video đang được quay...');
      
      await page.click('button[type="submit"]');
      await expect(page).toHaveTitle(/Dashboard/);
    });

    🎬 Bước 3: Chạy và Xem Kết Quả (Quan trọng)

    Chạy lệnh test:

    npx playwright test tests/retry-demo.spec.ts
    

    Bạn sẽ thấy console báo kiểu:

    1. x Demo Video... (Lần 1 Fail)

    2. Retry #1 (Playwright tự chạy lại)

    3. Passed (Lần 2 Pass)

    Sau đó mở Report:

    npx playwright show-report
    

    👀 Cách soi trong Report:

    Khi mở bài test đó ra, bạn sẽ thấy ở trên cùng có các Tabs (Tab con):

    1. Tab Attempt #1 (Lần thử 1):

      • Trạng thái: Failed.

      • Kéo xuống dưới: KHÔNG CÓ VIDEO. (Đúng như cam kết, chạy lần đầu fail thì kệ nó, cho nhanh).

    2. Tab Retry #1 (Lần thử lại):

      • Trạng thái: Passed.

      • Kéo xuống dưới: CÓ VIDEO xuất hiện! 🎥

    🎯 Ý nghĩa thực tế

    Bạn thấy đấy, với chế độ này:

    • Nếu hệ thống ổn định (chạy phát ăn ngay), bạn tiết kiệm được thời gian quay và dung lượng ổ cứng cho lần 1.

    • Chỉ khi nào mạng lag, server chập chờn khiến test phải Retry, Playwright mới "bật máy quay" để bạn xem lại: "Tại sao lần 2 lại chạy được? Có phải do mạng không?".

     

🧐 Cách soi kết quả (Trace + Screenshot + Video) chung

Sau khi chạy xong test với lệnh: npx playwright test

Bạn mở báo cáo lên:

npx playwright show-report

Bạn sẽ thấy giao diện "3 trong 1" cực kỳ xịn xò khi test bị Fail:

  1. Errors: Log lỗi chi tiết (text).

  2. Screenshots: Ảnh chụp ngay lúc Fail (do screenshot: 'only-on-failure').

  3. Video: Trình phát video nằm ở cuối (do video: 'retain-on-failure').

  4. Traces: Biểu tượng cái icon "mạch điện" hoặc tab Trace. Bấm vào đó, bạn sẽ tua lại được từng cú click chuột, từng API call.

💡 Lời khuyên cuối cùng cho bạn

Khi mới học hoặc làm dự án cá nhân, bạn hãy dùng cấu hình số 3 (retain-on-failure) cho tất cả (Video, Trace, Screenshot). Nó giúp bạn không bao giờ bỏ lỡ khoảnh khắc lỗi mà ổ cứng vẫn sạch sẽ.


Phần 3: ignoreHTTPSErrors là gì?

Thực ra ignoreHTTPSErrors không hẳn là một "mode" (chế độ) phức tạp như Video hay Trace, mà nó là một Cờ (Flag) bật/tắt đơn giản: true hoặc false.


Bản chất vấn đề (Tại sao cần nó?)

Khi bạn truy cập một trang web https://..., trình duyệt sẽ kiểm tra "Căn cước công dân" (Chứng chỉ SSL/TLS) của trang web đó.

  • Môi trường Production (Web thật): Có chứng chỉ xịn, được công nhận -> Trình duyệt cho vào.

  • Môi trường Dev/Staging/Local: Dev thường lười mua chứng chỉ xịn, họ tự tạo ra chứng chỉ "pha-ke" (Self-signed certificate).

    • 👉 Trình duyệt thấy chứng chỉ lạ -> Nó chặn lại và hiện Màn hình đỏ (Your connection is not private).

    • 👉 Hậu quả: Playwright không thể vào được trang web -> Test Fail ngay lập tức.


Cách hoạt động của ignoreHTTPSErrors

Tùy chọn này giống như việc bạn đưa cho Playwright một chiếc "Thẻ bài miễn tử".

  • ignoreHTTPSErrors: false (Mặc định):

    • Playwright hành xử như người dùng khó tính.

    • Gặp lỗi SSL -> Dừng lại, Báo lỗi, Test Fail.

  • ignoreHTTPSErrors: true (Khuyên dùng cho Dev/Staging):

    • Playwright hành xử như một người dễ tính (hoặc bấm nút "Advanced -> Proceed to...").

    • Gặp lỗi SSL -> Kệ nó, cứ truy cập tiếp vào bên trong.

    • Test chạy bình thường như không có chuyện gì xảy ra.


Ví dụ cấu hình

Bạn thường đặt cái này trong use của playwright.config.ts.

import { defineConfig } from '@playwright/test';

export default defineConfig({
  use: {
    baseURL: 'https://staging.crm.anhtester.com', // Server test hay bị lỗi SSL
    
    // 👇 BẬT CÁI NÀY LÊN
    ignoreHTTPSErrors: true, 
  },
});​


4. Khi nào NÊN và KHÔNG NÊN dùng?

Trường hợp Setting Lý do
Chạy trên Localhost / Staging true Server nội bộ thường dùng chứng chỉ tự ký (self-signed). Nếu không bật, bạn không test được gì cả.
Chạy trên Production false Trên môi trường thật, nếu SSL bị lỗi thì đó là BUG BỰ. Bạn cần test phải fail để báo Dev sửa ngay (hết hạn chứng chỉ, cấu hình sai...).

🎯 Tóm lại

ignoreHTTPSErrors: true chính là lệnh: "Thấy màn hình đỏ cảnh báo bảo mật thì cứ phớt lờ và đi tiếp cho tao!"


Phần 4: Headless là gì?

Headless là gì? (Chế độ "Ma")

  • Headed (Có đầu - headless: false):

    • Là chế độ bình thường như khi bạn lướt web.

    • Trình duyệt bật lên, bạn thấy nút bấm, hình ảnh, chuột di chuyển.

    • Giống như: Một con người bằng xương bằng thịt đang ngồi máy tính.

  • Headless (Không đầu - headless: true):

    • Trình duyệt vẫn chạy, vẫn tải HTML, CSS, vẫn click, vẫn login... nhưng nó Tàng hình.

    • Nó không vẽ giao diện ra màn hình (No UI). Nó chạy ngầm trong background.

    • Giống như: Một "con ma" đang lướt web. Nó làm mọi thứ nhưng bạn không nhìn thấy nó.



Tại sao headless: true lại làm page.pause() trở nên "phế"?

Hàm await page.pause() có chức năng là Đóng băng thời gian để bạn debug.

Khi bạn gọi lệnh này, Playwright sẽ bật lên một cái cửa sổ gọi là Playwright Inspector (Bộ thanh tra).

  • Trường hợp 1: Chạy headless: false (Có giao diện)

    • Cửa sổ Chrome hiện ra trang web.

    • Cửa sổ Inspector hiện ra bên cạnh.

    • 👉 Bạn nhìn thấy trang web đang đứng im. Bạn có thể thò chuột vào web bấm thử, soi Element. Rất sướng.

  • Trường hợp 2: Chạy headless: true (Tàng hình)

    • Cửa sổ Inspector hiện ra.

    • NHƯNG Cửa sổ Chrome chứa trang web thì KHÔNG THẤY ĐÂU. (Vì nó đang tàng hình mà).

    • 👉 Bạn dừng code lại, nhưng bạn không nhìn thấy trang web đang hiển thị cái gì, không thấy lỗi nằm đâu. Bạn chỉ nhìn thấy mỗi cái Inspector trơ trọi.

👉 Kết luận: Để dùng page.pause() hiệu quả, bạn BẮT BUỘC phải tắt Headless (tức là dùng headless: false).


3. Khi nào dùng cái nào?

Chế độ headless: true (Mặc định) headless: false
Hình tượng 👻 Con ma (Tàng hình) 🧑 Người thật (Hiện hình)
Tốc độ 🚀 Siêu nhanh (Không tốn sức vẽ hình) 🐢 Chậm hơn (Phải vẽ giao diện)
Mục đích Chạy trên CI/CD, Server, Jenkins. Chạy test số lượng lớn. Debug, viết code, xem page.pause().
page.pause() Chạy được nhưng không thấy web đâu. Hoàn hảo.

Cách chuyển đổi nhanh (Mẹo cho Pro)

Bạn không cần sửa file playwright.config.ts liên tục đâu. Hãy dùng dòng lệnh (Terminal) để điều khiển cho linh hoạt.

Cách 1: Config cứng trong code (Không khuyên dùng lắm)

// playwright.config.ts
use: {
  headless: false, // Luôn luôn hiện hình
}

Cách 2: Dùng cờ khi chạy lệnh (Khuyên dùng 🌟)

Bạn cứ để mặc định trong config là headless: true (để lên Server nó chạy nhanh).

Khi nào bạn cần debug ở máy mình, hãy chạy lệnh:

# Cách này ép bật giao diện lên
npx playwright test --headed

# Hoặc cách này (Vừa bật giao diện, vừa bật sẵn Inspector để Debug)
npx playwright test --debug

💡 Lưu ý quan trọng

Khi bạn chạy lệnh npx playwright test --debug, Playwright thông minh đến mức nó sẽ Tự động ép headless: false cho bạn luôn. Vì nó biết bạn đang muốn debug thì chắc chắn bạn cần nhìn thấy trình duyệt! Nhưng chỉ ép ở tầng playwright.config.ts thôi

🎯 Tóm lại

  • Headless: Là trình duyệt chạy ngầm (tàng hình), dành cho robot/server.

  • Muốn dùng page.pause(): Phải cần "nhìn thấy" web -> Phải dùng Headed (headless: false).

Mẹo: Đừng sửa config, hãy dùng lệnh npx playwright test --headed khi cần soi lỗi.


Phần 5: LAUNCH OPTIONS - QUYỀN LỰC KHỞI ĐỘNG (PHẦN CƠ BẢN)


launchOptions là gì?

Nếu use là cài đặt cho "Trang web" (Website), thì launchOptions là cài đặt cho chính "Phần mềm Trình duyệt" (File .exe của Chrome/Edge).

Nó quy định trạng thái của trình duyệt ngay tại thời điểm bạn click đúp chuột mở nó lên:

  • Mở lên có chạy ngầm không?

  • Mở lên có bật sẵn F12 không?

  • Mở lên chạy nhanh hay chạy chậm?

  • Dùng Chrome xịn hay Chromium lõi?


Viết nó ở đâu trong Config?

Nó nằm lồng bên trong khối use của file playwright.config.ts.

import { defineConfig } from '@playwright/test';

export default defineConfig({
  use: {
    baseURL: 'https://crm.anhtester.com',
    
    // 👇 NÓ NẰM Ở ĐÂY
    launchOptions: {
      // Các lệnh điều khiển trình duyệt nằm trong này
      slowMo: 500,
      devtools: true,
    },
  },
});


Top 4 cấu hình launchOptions cơ bản nhất

Đây là những món "đồ chơi" bạn cần biết để điều khiển trình duyệt theo ý muốn:

🐢 A. slowMo (Slow Motion - Quay chậm)

Mặc định, Robot chạy siêu nhanh (mắt thường không nhìn kịp). slowMo giúp kìm hãm con Robot lại.

  • Giá trị: Số mili-giây (ms).

  • Tác dụng: Giúp bạn debug, nhìn rõ từng cú click, từng ký tự được gõ. Cực kỳ hữu ích khi demo sản phẩm cho sếp/khách hàng xem.

launchOptions: {
  slowMo: 1000, // Chậm lại 1 giây giữa mỗi hành động
}


🛠 B. devtools (Developer Tools)

Bình thường khi trình duyệt mở lên, bạn phải bấm F12 thủ công để soi lỗi. Option này tự động làm việc đó.

  • Giá trị: true / false.

  • Tác dụng: Tự động bật cửa sổ Inspect Element (F12) ngay khi trình duyệt khởi động.

launchOptions: {
  devtools: true, // Tự bật F12 để soi Console/Network
}


📺 C. channel (Chọn kênh trình duyệt)

Playwright mặc định dùng Chromium (phiên bản mã nguồn mở, nhẹ). Nhưng nếu bạn muốn test trên trình duyệt thực tế người dùng đang cài trên máy?

  • Giá trị: 'chrome', 'msedge', 'chrome-beta', ...

  • Tác dụng: Bắt Playwright dùng đúng file .exe của Google Chrome hoặc Microsoft Edge trên máy tính của bạn.

launchOptions: {
  channel: 'chrome', // Test trên Google Chrome thật
  // channel: 'msedge', // Test trên Microsoft Edge thật
}


🏳️ D. args (Arguments - Các lá cờ)

Đây là "túi thần kỳ". Nó chứa danh sách các câu lệnh khởi động (Command line switches) của chính Google Chrome. Playwright chỉ là người chuyển phát các lệnh này cho Chrome.

  • Giá trị: Một mảng các chuỗi ký tự ['--cờ-1', '--cờ-2'].

  • Ví dụ phổ biến:

    • '--start-maximized': (Sẽ học kỹ ở bài sau) Ra lệnh mở full màn hình.

    • '--incognito': Ép mở chế độ ẩn danh (giao diện tối).

    • '--disable-notifications': Tắt sạch các popup thông báo của trình duyệt.

launchOptions: {
  args: ['--start-maximized', '--disable-notifications']
}


Phân biệt uselaunchOptions 

Rất nhiều bạn nhầm lẫn nhét cấu hình sai chỗ. Hãy nhớ bảng này:

Tên Thuộc về ai? Ví dụ cấu hình
launchOptions Cái Máy (Trình duyệt) Tốc độ chạy (slowMo), Loại trình duyệt (channel), Cửa sổ to/nhỏ (args), Bật tắt F12 (devtools).
use (Cấp ngoài) Cái Trang (Page/Context) Đường dẫn (baseURL), Quay phim (video), Chụp ảnh (screenshot), Múi giờ (timezone), Cookie (storageState).

🎯 Tóm lại

Khi bạn muốn can thiệp vào hành vi khởi động của trình duyệt (trước cả khi nó load trang web), hãy tìm đến launchOptions.


Phần 6: GIẢI MÃ BÀI TOÁN MAXIMIZE (WINDOW VS VIEWPORT)


Phân biệt Window Size và Viewport Size

Để hiểu tại sao code lại phức tạp, bạn phải tách biệt 2 khái niệm này. Hãy nhìn vào trình duyệt của bạn:

🪟 A. Window Size (Kích thước Cửa Sổ)

  • Là gì: Là toàn bộ cái khung phần mềm trình duyệt (Chrome/Edge).

  • Bao gồm: Thanh tiêu đề, nút tắt/bật (❌), thanh địa chỉ (Address Bar), thanh Bookmarks, đường viền xung quanh... và cả phần nội dung bên trong.

  • Người quản lý: Hệ điều hành (Windows/macOS) và launchOptions.

📄 B. Viewport Size (Kích thước Vùng Hiển Thị)

  • Là gì: Là phần "Giấy vẽ" (Canvas) màu trắng bên trong, nơi trang web (HTML/CSS) thực sự hiển thị.

  • Đặc điểm: Nó nằm lọt thỏm bên trong Window.

  • Người quản lý: Playwright (use.viewport).


 "Xung đột quyền lực" gây ra vấn đề

Mặc định, Playwright được thiết kế để giả lập thiết bị.

  • Nó sẽ ép Viewport phải là 1280x720 (hoặc size bạn set), bất chấp cái Window bên ngoài to hay nhỏ.

👉 Kịch bản lỗi thường gặp:

  1. Bạn dùng lệnh args: ['--start-maximized']. -> Window bung to hết cỡ (Full HD 1920x1080).

  2. Nhưng bạn quên chỉnh Viewport. -> Viewport vẫn bị Playwright khóa cứng ở 1280x720.

  3. Hậu quả: Cửa sổ thì to, nhưng trang web chỉ hiển thị một góc bé tí. Phần còn lại là khoảng trắng vô nghĩa.


Công thức "Combo Huyền Thoại"

Để Maximize hoàn hảo (Trang web tràn viền màn hình), chúng ta phải thực hiện đồng thời 2 bước:

  1. Bước 1 (Vỏ): Ra lệnh cho Chrome bung to cửa sổ (launchOptions).

  2. Bước 2 (Ruột): Ra lệnh cho Playwright tháo xích, cho phép nội dung tràn ra theo cửa sổ (viewport: null).

💻 Code Cấu Hình Chuẩn 

// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  use: {
    // --- BƯỚC 1: Cấu hình VỎ (Window) ---
    launchOptions: {
      // 'args' là nơi truyền lệnh cho file .exe của Chrome
      // '--start-maximized': Lệnh của Google Chrome để mở full màn hình
      args: ['--start-maximized'] 
    },

    // --- BƯỚC 2: Cấu hình RUỘT (Viewport) ---
    // viewport: null nghĩa là "Hủy bỏ kích thước mặc định".
    // "Cái vỏ to bao nhiêu thì cái ruột giãn ra bấy nhiêu".
    viewport: null, 

    // (Tùy chọn) BƯỚC 3: Giữ độ nét cho màn hình xịn (Retina/2K)
    // Xóa bỏ tỷ lệ scale mặc định để hình ảnh nét căng
    deviceScaleFactor: undefined,
  },
});


 Giải thích chi tiết từng dòng (Deep Dive)

Tại sao lại là null mà không phải số cụ thể?

  • Nếu viewport: { width: 1920, height: 1080 }:

    • Bạn đang ép cứng nội dung.

    • Nếu màn hình máy bạn nhỏ hơn 1920x1080 -> Xuất hiện thanh cuộn (Scrollbar) ngang/dọc rất xấu.

    • Nếu màn hình máy bạn to hơn (4K) -> Web bị bé lại.

  • Nếu viewport: null:

    • Đây là chế độ Responsive tự nhiên.

    • Playwright thả lỏng hoàn toàn. Kích thước trang web sẽ phụ thuộc 100% vào kích thước cửa sổ (--start-maximized).

⚠️ Cảnh báo: "Cái bẫy" Headless Mode

Combo trên CHỈ HOẠT ĐỘNG khi bạn chạy có giao diện (headless: false).

Khi bạn chạy ẩn (headless: true - ví dụ trên CI/CD):

  1. Lệnh --start-maximized bị vô hiệu hóa (Vì server không có màn hình vật lý để mà Maximize).

  2. Chrome tự reset về size mặc định (800x600).

  3. viewport: null, trang web cũng bị co lại còn 800x600.

  4. Kết quả: Test Fail vì không tìm thấy nút bấm (do web bị vỡ giao diện).

Bạn cần hiểu cơ chế của lệnh --start-maximized:

  1. Khi có giao diện (headless: false):

    • Chrome khởi động -> Nó hỏi Hệ điều hành (Windows): "Ê, cái màn hình vật lý đang to bao nhiêu?"

    • Windows trả lời: "1920x1080".

    • Chrome bung cửa sổ ra đúng 1920x1080.

    • viewport: null, trang web cũng giãn ra 1920x1080.

    • 👉 Thành công.

  2. Khi chạy ẩn (headless: true):

    • Chrome khởi động -> Nó hỏi Hệ điều hành: "Màn hình đâu?"

    • Hệ điều hành trả lời: "Mày đang chạy ngầm, làm gì có màn hình vật lý nào!"

    • Chrome bối rối: "Thế lệnh Maximize giờ tính sao?" -> Lệnh này bị VÔ HIỆU HÓA.

    • Chrome quay về kích thước "xuất xưởng" mặc định (Default Window Size) -> Chính là 800x600.

    • Lúc này viewport lại đang là null (tức là Playwright không được phép can thiệp chỉnh sửa kích thước).

    • 👉 Kết quả: Trang web bé tẹo teo 800x600.

Minh họa dễ hiểu

Hãy tưởng tượng bạn đang may áo (Render Web).

  • headless: false (Có người mẫu): Bạn bảo "May áo bó sát người mẫu". Áo sẽ to đẹp tùy theo người mẫu cao to.

  • headless: true (Không người mẫu): Bạn vẫn bảo "May áo bó sát người mẫu" nhưng... không có người mẫu nào đứng đó cả.

Kết quả: Bạn may một cái áo theo kích thước mặc định (Size S) cho an toàn.


Bạn hãy copy file này vào và chạy thử (cả mode Headless và Headed) để thấy sự thật bất ngờ.

import { test } from '@playwright/test';

test('Kiểm tra kích thước thật sự (Viewport vs Window)', async ({ page }) => {
  // 1. Vào đại một trang nào đó để trình duyệt khởi tạo xong
  await page.goto('https://crm.anhtester.com/admin/authentication');

  // 2. Dùng page.evaluate để chạy lệnh JS bên trong trình duyệt
  // Đây là cách duy nhất để hỏi Chrome: "Mày đang to bao nhiêu?"
  const sizes = await page.evaluate(() => {
    return {
      // 1. Viewport: Kích thước phần nội dung web (Cái quan trọng nhất)
      viewport: {
        width: window.innerWidth,
        height: window.innerHeight
      },
      
      // 2. Window: Kích thước tổng thể cửa sổ (Bao gồm thanh địa chỉ, viền...)
      window: {
        width: window.outerWidth,
        height: window.outerHeight
      },
      
      // 3. Screen: Kích thước màn hình vật lý (Monitor của bạn hoặc Server)
      screen: {
        width: window.screen.width,
        height: window.screen.height
      }
    };
  });

  // 3. In ra Console để soi
  console.log('--------------------------------------------------');
  console.log('🖥️  SCREEN (Màn hình):   ', `${sizes.screen.width} x ${sizes.screen.height}`);
  console.log('🪟 WINDOW (Cửa sổ):     ', `${sizes.window.width} x ${sizes.window.height}`);
  console.log('📄 VIEWPORT (Nội dung): ', `${sizes.viewport.width} x ${sizes.viewport.height}`);
  console.log('--------------------------------------------------');
  
  // Logic check nhanh cho bạn đỡ phải nhìn số
  if (sizes.viewport.width <= 800) {
    console.log('🚨 CẢNH BÁO: Viewport đang bé tí (Mặc định). Headless Mode có vấn đề!');
  } else {
    console.log('✅ Viewport to đẹp. Config ổn!');
  }
});


✅ Giải pháp tối thượng (Hybrid Config)

Code này giúp bạn "bất tử": Ở nhà thì Maximize, lên Server thì HD.

import { defineConfig } from '@playwright/test';

// Kiểm tra xem code đang chạy ở đâu?
// process.env.CI thường có sẵn trên Jenkins/GitHub Actions
const isCI = !!process.env.CI; 

export default defineConfig({
  use: {
    // 1. Logic Viewport
    // - Nếu CI: Set cứng 1920x1080 cho ổn định.
    // - Nếu Local: Set null để hưởng thụ Maximize.
    viewport: isCI ? { width: 1920, height: 1080 } : null,

    // 2. Logic Window
    launchOptions: {
      args: isCI 
        ? ['--window-size=1920,1080'] // CI: Giả lập cửa sổ to
        : ['--start-maximized']       // Local: Bung lụa hết cỡ
    },
  },
});


Tại sao cần deviceScaleFactor: undefined

👉 Đây là lệnh để "Hủy cài đặt gốc" từ devices['Desktop Chrome'].

Bạn cần dòng này vì trong file config gốc (playwright.config.ts), bạn thường có dòng:

use: {
  ...devices['Desktop Chrome'], // 👈 THỦ PHẠM NẰM Ở ĐÂY
}

Trong cái gói devices['Desktop Chrome'] này, Playwright đã cài đặt cứng:

  • deviceScaleFactor: 1: Nghĩa là tỷ lệ điểm ảnh chuẩn (Non-Retina).

Vấn đề:

Nếu bạn dùng Laptop màn hình 2K, 4K hoặc Macbook Retina, thì deviceScaleFactor của máy bạn thực tế là 2 hoặc 3.

  • Nếu bạn Maximize cửa sổ mà vẫn để deviceScaleFactor: 1 (do kế thừa từ config gốc) -> Hình ảnh, chữ viết sẽ bị nhòe, vỡ hạt hoặc bé tí không đúng với thực tế máy bạn.

Giải pháp:

  • deviceScaleFactor: undefined: Nghĩa là "Xóa bỏ setting cũ đi".

  • Khi xóa đi, Chrome sẽ tự động nhìn vào hệ điều hành của bạn và thốt lên: "À, máy ông này là màn hình Retina, tôi sẽ tự động render nét căng theo chuẩn Retina!".


🎯 Tóm lại bằng hình tượng đời thường

Hãy tưởng tượng trình duyệt là một Bức tranh treo tường.

  1. args: ['--start-maximized']: Bạn mua cái Khung tranh to nhất có thể (To bằng bức tường).

  2. viewport: null: Bạn vẽ bức tranh tràn viền ra khắp cái khung đó. (Nếu không có lệnh này, bạn chỉ vẽ một hình bé tí giữa cái khung to đùng).

  3. deviceScaleFactor: undefined: Bạn dùng độ phân giải mắt thường của bạn để ngắm tranh. (Nếu không có lệnh này, bạn bị ép đeo một cái kính lão bị mờ để nhìn tranh).

Đó là lý do tại sao bộ 3 này luôn đi cùng nhau để tạo ra trải nghiệm Full Màn Hình & Nét Căng trên máy Local.

 

Phần 7: Làm chủ screenshot


screenshot: 'on' (Chế độ "Ảnh Kỷ Niệm")

Nó CHỈ chụp đúng 1 tấm vào giây phút cuối cùng.

  • Thời điểm chụp: Ngay sau khi dòng code cuối cùng của bài test chạy xong (hoặc khi test bị Fail), nhưng trước khi Playwright đóng trình duyệt (teardown).

  • Số lượng: Duy nhất 1 tấm.

  • Bất kể kết quả:

    • Test Xanh (Pass): Nó chụp lại màn hình lúc thành công (ví dụ: chụp trang Dashboard).

    • Test Đỏ (Fail): Nó chụp lại màn hình lúc bị lỗi (ví dụ: chụp trang Login đang báo lỗi).

👉 Ví dụ: Bài test có 50 bước.

  • Playwright chạy từ bước 1 đến bước 50 không chụp gì cả.

  • Xong bước 50 -> Tách! (Chụp 1 tấm) -> Lưu vào Report -> Đóng trình duyệt.

Tưởng tượng: Giống như đi du lịch. Bạn đi chơi cả ngày không chụp gì, đến lúc chuẩn bị lên xe về nhà mới đứng lại cổng chào chụp một tấm ảnh lưu niệm. Đó là screenshot: 'on'.

screenshot: 'off' (Chế độ "Không Làm Phiền")

  • Cơ chế: Playwright tắt hoàn toàn tính năng tự động chụp.

  • Hành vi:

    • Test Pass: Không chụp gì.

    • Test Fail: Cũng KHÔNG chụp gì luôn (Màn hình tối om, không có bằng chứng hình ảnh).

👉 Khi nào dùng 'off'?

  1. Khi bạn chạy Performance Test (Test hiệu năng): Cần tốc độ tối đa, việc chụp ảnh (dù chỉ 1 tấm) cũng làm chậm ổ cứng.

  2. Khi bạn đã tin tưởng tuyệt đối vào Trace Viewer (Vì Trace đã có sẵn snapshot rồi, chụp thêm ảnh Screenshot nữa thì thừa thãi dung lượng).


Cấu hình chụp Full Page tự động

Bình thường nếu test Fail, Playwright tự chụp ảnh, nhưng nó chỉ chụp phần đang hiển thị (Viewport). Lỡ cái thông báo lỗi nó nằm tuốt dưới chân trang (Footer) thì sao? Bạn sẽ không nhìn thấy.

Để khắc phục, bạn ép Playwright: "Hễ chụp ảnh tự động (do lỗi hoặc do mode on) là phải chụp Full Page cho tao!".

// playwright.config.ts
export default defineConfig({
  use: {
    // Thay vì viết tắt: screenshot: 'only-on-failure'
    // Bạn viết dạng Object đầy đủ:
    screenshot: {
      mode: 'only-on-failure', // Chỉ chụp khi Fail
      fullPage: true,          // 👈 Ép chụp toàn bộ trang (tự cuộn xuống)
    },
  },
});

👉 Tác dụng: Khi test fail, tấm ảnh bằng chứng trong Report sẽ dài ngoằng từ Header đến Footer, giúp bạn soi lỗi toàn diện hơn.


 Cấu hình omitBackground (Nền trong suốt)

Mặc định, nếu trang web của bạn không set màu nền (background-color trong CSS), trình duyệt sẽ hiển thị màu trắng.

Khi chụp ảnh, Playwright cũng chụp ra nền trắng.

Nếu bạn muốn chụp ra ảnh PNG nền trong suốt (Transparent) (thường dùng để ghép ảnh hoặc so sánh UI visual regression), bạn dùng option này.

// playwright.config.ts
export default defineConfig({
  use: {
    screenshot: {
      mode: 'on',
      omitBackground: true, // 👈 Ẩn nền trắng mặc định -> Ra ảnh trong suốt
    },
  },
});

 

🎓 Phần 8: GIẢI MÃ ...devices['Desktop Chrome']


devices
là cái gì?

Hãy tưởng tượng devices giống như một Cuốn từ điển khổng lồ (hoặc một cuốn Menu).

Trong cuốn từ điển này, đội ngũ Playwright đã viết sẵn thông số kỹ thuật chuẩn của hàng trăm loại thiết bị: Từ iPhone, Samsung, iPad cho đến Desktop.

Bạn có thể xem thử "ruột gan" của nó bằng cách giữ phím Ctrl + Click chuột trái vào chữ devices trong VS Code. Bạn sẽ thấy nó là một Object khổng lồ như thế này:

 
// Đây là code nguồn của Playwright (Minh họa)
const devices = {
  'Desktop Chrome': {
    browserName: 'chromium',
    viewport: { width: 1280, height: 720 },
    userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...',
    deviceScaleFactor: 1,
    isMobile: false,
    hasTouch: false,
  },
  'iPhone 13': {
    browserName: 'webkit',
    viewport: { width: 390, height: 844 },
    userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X)...',
    deviceScaleFactor: 3,
    isMobile: true,
    hasTouch: true,
  },
  // ... hàng trăm thiết bị khác
};

Toán tử ba chấm ... (Spread Syntax) là gì?

Trong JavaScript/TypeScript, dấu ... gọi là Spread Operator (Toán tử rải/bung).

Nó có tác dụng: "Lấy tất cả ruột gan bên trong cái túi kia, đổ ra bàn cho tôi".

Hay nói cách khác dễ hiểu hơn: Nó chính là lệnh COPY - PASTE.

Ví dụ minh họa:

Khi bạn viết:

use: {
  baseURL: 'https://crm.anhtester.com',
  ...devices['Desktop Chrome'], // 👈 Dòng thần thánh
}

Thì thực chất, Playwright sẽ hiểu (compile) thành như sau:

use: {
  baseURL: 'https://crm.anhtester.com',
  
  // 👇 Nó tự động PASTE toàn bộ đống ở trên vào đây:
  browserName: 'chromium',
  viewport: { width: 1280, height: 720 },
  userAgent: 'Mozilla/5.0 ...',
  deviceScaleFactor: 1,
  isMobile: false,
  // ...
}

👉 Tại sao phải viết ...?

Để cho code nó ngắn! Thay vì bạn phải tự tay gõ lại 5-6 dòng cấu hình (UserAgent, Viewport, BrowserName...), bạn chỉ cần gõ 1 dòng ...devices['Desktop Chrome'] là xong.


Phân tích browserName

Trong cái gói Desktop Chrome đó, có một thuộc tính quan trọng nhất là browserName.

Playwright chỉ có 3 "động cơ" (Engine) chính để chạy web:

  1. chromium: Dùng để chạy Google Chrome, Microsoft Edge, Opera.

  2. firefox: Dùng để chạy Mozilla Firefox.

  3. webkit: Dùng để chạy Apple Safari.

Khi bạn chọn devices['Desktop Chrome'], bên trong nó đã được cài sẵn:

browserName: 'chromium'

⚠️ Sự nhầm lẫn tai hại: browserName vs channel

Rất nhiều bạn hỏi: "Tại sao browserName là 'chromium' mà nó lại gọi là 'Desktop Chrome'? Sao không phải browserName là 'chrome'?"

  • browserName: 'chromium': Nghĩa là dùng MÃ NGUỒN MỞ Chromium để chạy test. (Đây là cái trình duyệt mà Playwright tự tải về, không phải Google Chrome xịn bạn cài trên máy).

  • Tên gọi 'Desktop Chrome': Đây chỉ là cái TÊN GỌI (Label) cho bộ cấu hình đó thôi. Nó ám chỉ: "Đây là cấu hình giả lập giống như Chrome trên máy tính (Màn hình 1280x720, UserAgent của Chrome)".

Nếu bạn muốn chạy bằng Google Chrome thật trên máy tính, bạn phải thêm dòng channel: 'chrome' (như bài launchOptions mình đã dạy).


Tại sao lại viết theo kiểu Config từng Project?

Thường bạn sẽ thấy đoạn code này nằm trong mảng projects:

// playwright.config.ts
export default defineConfig({
  projects: [
    // Project 1: Giả lập Desktop Chrome
    {
      name: 'Chrome PC',
      use: { 
        ...devices['Desktop Chrome'], // Bung cấu hình Chrome ra
        // Bạn có thể ghi đè (Override) nếu thích:
        viewport: { width: 1920, height: 1080 } 
      },
    },

    // Project 2: Giả lập iPhone 13
    {
      name: 'iPhone 13',
      use: { 
        ...devices['iPhone 13'], // Bung cấu hình iPhone ra
        // Tự động có: browserName: webkit, isMobile: true, màn hình bé...
      },
    },
  ],
});

Lợi ích tuyệt đối:

  1. Chuẩn hóa: Bạn không cần nhớ iPhone 13 màn hình rộng bao nhiêu pixel. Playwright nhớ hộ bạn rồi.

  2. User Agent: Đây là cái quan trọng nhất. Khi dùng ...devices['iPhone 13'], Playwright tự đổi User Agent thành iPhone. Server sẽ tưởng bạn là điện thoại thật và trả về giao diện mobile.

  3. Ghi đè linh hoạt: Bạn dùng ... để lấy cấu hình gốc, sau đó viết thêm dòng bên dưới để sửa lại những gì bạn muốn (ví dụ sửa lại viewport).

🎯 Tóm lại

  1. devices: Là cái kho chứa cấu hình mẫu.

  2. ['Desktop Chrome']: Là tên một bộ cấu hình trong kho.

  3. ... (Spread): Là lệnh Copy toàn bộ thuộc tính của bộ cấu hình đó dán vào use.

  4. browserName: Là cái lõi trình duyệt (chromium, firefox, webkit) được định nghĩa sẵn trong bộ cấu hình đó.


Để thấy rõ sự khác biệt một trời một vực giữa việc chỉ khai báo browserName (trần trụi) và dùng ...devices (full đồ), chúng ta sẽ làm một bài test so sánh trực tiếp.

Chúng ta sẽ tạo 2 Projects trong config:

  1. Project "Trần trụi": Chỉ set browserName: 'chromium'.

  2. Project "Full Giáp": Dùng ...devices['Desktop Chrome'].

📝 Bước 1: File Config (playwright.config.ts)

Bạn copy đoạn này vào file config. Chúng ta định nghĩa 2 môi trường chạy khác nhau.

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  // Tắt chạy song song để log in ra thứ tự cho dễ nhìn
  fullyParallel: false, 
  workers: 1,

  projects: [
    // 🔴 CASE 1: Cấu hình "Nhà nghèo" (Chỉ có browserName)
    {
      name: '1. Only BrowserName',
      use: {
        browserName: 'chromium', 
        // Không có viewport, không có UserAgent xịn...
      },
    },

    // 🟢 CASE 2: Cấu hình "Đại gia" (Dùng gói devices có sẵn)
    {
      name: '2. Use Devices Standard',
      use: {
        ...devices['Desktop Chrome'], // Bung lụa toàn bộ cấu hình chuẩn ra
      },
    },
    
    // 🟣 CASE 3: Bonus thêm iPhone để thấy sự biến hình
    {
      name: '3. Use iPhone 13',
      use: {
        ...devices['iPhone 13'], 
      },
    },
  ],
});


📝 Bước 2: File Test soi thông tin (tests/check-device.spec.ts)

Chúng ta sẽ dùng page.evaluate() để moi móc thông tin nội tạng của trình duyệt ra in.

import { test } from '@playwright/test';

test('Soi cấu hình Browser', async ({ page }, testInfo) => {
  // Lấy thông tin thực tế từ trình duyệt
  const info = await page.evaluate(() => {
    return {
      userAgent: navigator.userAgent, // Căn cước công dân của trình duyệt
      viewport: { 
        width: window.innerWidth, 
        height: window.innerHeight 
      },
      pixelRatio: window.devicePixelRatio, // Độ nét màn hình
      platform: navigator.platform,        // Hệ điều hành nó nhận diện
      isMobile: navigator.maxTouchPoints > 0 // Có phải màn cảm ứng không?
    };
  });

  console.log(`\n==================================================`);
  console.log(`🚀 PROJECT: ${testInfo.project.name}`);
  console.log(`==================================================`);
  console.log(`- 🆔 User Agent:  ${info.userAgent}`);
  console.log(`- 📏 Viewport:    ${info.viewport.width} x ${info.viewport.height}`);
  console.log(`- 📱 Is Mobile?:  ${info.isMobile}`);
  console.log(`- 🖥️ Platform:    ${info.platform}`);
  console.log(`- ✨ Pixel Ratio: ${info.pixelRatio}`);
  console.log(`\n`);
});

🚀 Bước 3: Chạy và Xem Kết Quả

Bạn chạy lệnh:

npx playwright test tests/check-device.spec.ts

📊 Phân tích kết quả (Sự khác biệt)

Dưới đây là kết quả bạn sẽ thấy (mình phân tích sẵn):

🔴 Case 1: Only BrowserName

Playwright dùng cấu hình mặc định (Fallback).

  • User Agent: Thường chứa chữ HeadlessChrome (rất dễ bị các trang web chặn vì biết là bot).

  • Viewport: Mặc định là 1280 x 720.

  • Is Mobile: false.

  • Platform: Nhận theo máy thật của bạn (ví dụ Win32).

🟢 Case 2: Use Devices Standard

Playwright áp dụng cấu hình chuẩn của Google Chrome.

  • User Agent: Giống hệt Chrome thật (Mozilla/5.0 ... Chrome/118...). Rất khó bị chặn.

  • Viewport: 1280 x 720 (Chuẩn Desktop Chrome mà Playwright quy định).

  • Is Mobile: false.

🟣 Case 3: Use iPhone 13 (Sự biến hình rõ rệt)

  • User Agent: Có chữ iPhone; CPU iPhone OS 15.... Web server nhìn thấy cái này sẽ trả về giao diện Mobile.

  • Viewport: 390 x 664 (Bé tí tẹo như điện thoại thật).

  • Is Mobile: true (Màn hình cảm ứng).

  • Pixel Ratio: 3 (Màn hình Retina siêu nét).


🎯 Kết luận

Câu lệnh ...devices['Desktop Chrome'] không chỉ set browserName. Nó âm thầm làm 2 việc quan trọng:

  1. Fake User Agent: Để trang web tin bạn là người dùng Chrome xịn, không phải Bot.

  2. Set Viewport & Screen: Để giao diện hiển thị đúng chuẩn Desktop/Mobile.

👉 Lời khuyên: Luôn dùng ...devices[...], đừng bao giờ dùng browserName trần trụi trừ khi bạn đang config cái gì đó siêu đặc biệt.


Phần 9: VỊ TRÍ LƯU TRỮ (OUTPUT DIRECTORY)

Trả lời nhanh cho bạn:

  1. Mặc định: Các file tự động (Video, Trace, Screenshot khi Fail) sẽ chui vào thư mục test-results (Nằm ngang hàng với package.json).

  2. Cấu hình: Bạn dùng outputDir để đổi vị trí (Cái này nằm ở cấp cao nhất, KHÔNG NẰM TRONG use {}).

Chúng ta đi chi tiết nhé:


Phân biệt 2 loại file được sinh ra

🤖 Loại 1: Hàng "Tự động" (Artifacts)

Bao gồm:

  • Video (do video: 'on' hoặc 'retain-on-failure').

  • Trace (do trace: 'on').

  • Screenshot tự chụp khi Fail (do screenshot: 'only-on-failure').

👉 Nơi lưu mặc định: Thư mục test-results/.

Playwright sẽ tự động tạo thư mục con bên trong theo quy tắc:

tên-bài-test-tên-project-tên-trình-duyệt

(Ví dụ: test-results/demo-login-Chromium/)


✋ Loại 2: Hàng "Thủ công" (Manual)

Bao gồm:

  • Những tấm ảnh bạn code tay: await page.screenshot({ path: '...' }).

👉 Nơi lưu mặc định: Thư mục ROOT (Gốc dự án).

Nếu bạn không chỉ định thư mục (ví dụ path: 'anh1.png'), nó sẽ nằm chình ình ngay cạnh file package.json.


Cách đổi vị trí lưu (Cấu hình outputDir)

Nếu bạn ghét cái tên test-results, bạn muốn đổi nó thành artifacts hay evidence chẳng hạn.

⚠️ Lưu ý quan trọng: outputDir là cấu hình cấp cao (Top-level), nó nằm ngang hàng với use, chứ KHÔNG nằm bên trong use.

 
// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  // 👇 CẤU HÌNH VỊ TRÍ LƯU (Nằm ở ngoài use)
  outputDir: 'my-artifacts', // Đổi tên thư mục mặc định từ 'test-results' thành 'my-artifacts'

  use: {
    // Trong này chỉ cấu hình CÓ lưu hay KHÔNG (on/off)
    video: 'retain-on-failure',
    trace: 'on-first-retry',
  },
});

Sau khi cấu hình như trên:

  • Video/Trace sẽ lưu vào: my-artifacts/.


Tại sao cấu trúc thư mục trong test-results lại lằng nhằng thế?

Nếu bạn mở thư mục test-results ra, bạn sẽ thấy tên thư mục con rất dài và kỳ quặc, ví dụ:

tests-login-Login-success-Chrome-win32

Lý do:

Playwright chạy Song Song (Parallel).

  • Nếu bạn có 2 bài test cùng tên "Login", hoặc cùng 1 bài test chạy trên 3 trình duyệt (Chrome, Firefox, Safari).

  • Nếu lưu chung 1 chỗ -> File sẽ bị ghi đè (Override) nhau loạn xạ.

  • 👉 Playwright phải sinh ra cái tên thư mục dài ngoằng (kèm hash) để đảm bảo Mỗi lần chạy của mỗi worker có một chỗ ở riêng biệt.


Tổng kết vị trí lưu

Loại file Config Vị trí lưu mặc định Vị trí sau khi chỉnh
Video video: 'on' test-results/<tên-folder-dài>/video.webm <outputDir>/<tên-folder-dài>/video.webm
Trace trace: 'on' test-results/<tên-folder-dài>/trace.zip <outputDir>/<tên-folder-dài>/trace.zip
Auto Screenshot screenshot: 'on' test-results/<tên-folder-dài>/test-failed-1.png <outputDir>/<tên-folder-dài>/...
Manual Screenshot page.screenshot() Thư mục Gốc (./) Theo đường dẫn path bạn viết trong code.


Mẹo dọn rác (.gitignore)

Vì folder test-results (hoặc outputDir bạn đặt) sẽ phình to rất nhanh sau mỗi lần chạy test, và nó chứa file rác không cần commit lên Git.

Hãy nhớ thêm nó vào file .gitignore:Plaintext

# .gitignore
node_modules/
test-results/       <-- Thêm dòng này
playwright-report/  <-- Folder chứa báo cáo HTML
blob-report/

🎯 Tóm lại

  • use {}: Quyết định lưu hay không.

  • outputDir: Quyết định lưu Ở ĐÂU. (Mặc định là test-results).

  • Hai cái này nằm ở 2 vị trí khác nhau trong file config.

Teacher

Teacher

Nguyên Hoàng

Automation Engineer

With 7+ years of hands-on experience across multiple languages and frameworks. I'm here to share knowledge, helping you turn complex processes into simple and effective solutions.

Cộng đồng Automation Testing Việt Nam:

🌱 Telegram Automation Testing:   Cộng đồng Automation Testing
🌱 
Facebook Group Automation: Cộng đồng Automation Testing Việt Nam
🌱 
Facebook Fanpage: Cộng đồng Automation Testing Việt Nam - Selenium
🌱 Telegram
Manual Testing:   Cộng đồng Manual Testing
🌱 
Facebook Group Manual: Cộng đồng Manual Testing Việt Nam

Chia sẻ khóa học lên trang

Bạn có thể đăng khóa học của chính bạn lên trang Anh Tester để kiếm tiền

Danh sách bài học