NỘI DUNG BÀI HỌC

🧠 Xử lý multi tab

🛠️ xử lý popup window





Nguyên lý cốt lõi: "Cái bẫy thời gian" (Race Condition)

Hãy tưởng tượng bạn là một Nhiếp ảnh gia (Playwright) muốn chụp lại khoảnh khắc một vận động viên (Trình duyệt) nhảy qua xà (Mở tab mới).

  • Cách làm sai (Code tuần tự):

    1. Hô "Nhảy đi!" (Lệnh click).

    2. Vận động viên nhảy cái "Vèo!".

    3. Sau đó bạn mới đưa máy ảnh lên ngắm (Lệnh waitForEvent).

    • Kết quả: Bạn chụp được... khoảng không. Vận động viên đã tiếp đất rồi. Code báo lỗi Timeout vì chờ mãi không thấy sự kiện mở tab (do nó đã xảy ra rồi).

  • Cách làm đúng (Promise.all - Giăng lưới):

    1. Bạn dựng chân máy, chỉnh tiêu cự, ngón tay đặt sẵn lên nút chụp (Thiết lập waitForEvent).

    2. Hô "Nhảy đi!" (Lệnh click).

    3. Ngay khi vận động viên nhúc nhích, máy tự động "Tách!" (Bắt được sự kiện).

Đó chính là lý do tại sao chúng ta phải dùng Promise.all.


Phần 1: Xử lý Tab mới (Handling New Tabs)

Trong Playwright, một tab mới (target="_blank") được coi là một trang giấy mới được rút ra từ BrowserContext (như rút tờ giấy mới từ cùng một tập hồ sơ).

Kịch bản thực tế

Bạn đang ở trang danh sách sản phẩm. Bạn click vào một iPhone 15, nó mở ra tab mới hiển thị chi tiết. Bạn cần verify giá tiền ở tab mới đó.

Code chi tiết và Phân tích

test('Xử lý Tab mới chuẩn chỉ', async ({ context, page }) => {
  await page.goto('https://demo-website.com');

  // --- BẮT ĐẦU PHA GIĂNG LƯỚI ---
  
  // Promise.all nhận vào một mảng các hành động cần thực hiện SONG SONG
  const [newPage] = await Promise.all([
    // Hành động 1: "Đặt cái bẫy" - Lắng nghe sự kiện một trang (page) mới được tạo ra trong context
    context.waitForEvent('page'), 
    
    // Hành động 2: "Thả mồi" - Click vào link để kích hoạt mở tab
    page.click('a[target="_blank"]') 
  ]);
  
  // --- KẾT THÚC PHA GIĂNG LƯỚI ---

  // Lúc này, newPage đã nằm gọn trong tay ta.
  // Nhưng cẩn thận: Tab mới mở ra có thể trắng trơn chưa kịp load nội dung.
  // Luôn luôn chờ nó load xong.
  await newPage.waitForLoadState(); // Mặc định là chờ trạng thái 'load'

  // Bây giờ newPage là một đối tượng Page hoàn chỉnh.
  // Bạn muốn làm gì với nó thì làm, y hệt như biến 'page' cũ.
  console.log(await newPage.title()); 
  
  // Ví von: Bạn đang cầm 2 cái điều khiển TV trên 2 tay.
  // Tay trái điều khiển 'page' (Tab cũ), tay phải điều khiển 'newPage' (Tab mới).
  await newPage.click('#buy-now-btn'); // Click bên tab mới
  await page.bringToFront();           // Quay lại nhìn tab cũ
});​

 


Giải thích cú pháp const [newPage] = ...

Tại sao lại có dấu ngoặc vuông []?

  • Promise.all trả về một mảng kết quả theo thứ tự bạn truyền vào.

  • Cái đầu tiên bạn truyền là waitForEvent -> nó trả về cái Tab mới.

  • Cái thứ hai bạn truyền là click -> nó trả về void (không có gì).

  • const [newPage] là cách viết tắt (Destructuring) để lấy ngay phần tử đầu tiên của mảng kết quả và gán cho biến tên là newPage.


Phần 2: Xử lý Popup Window

Popup khác Tab ở chỗ nào?

  • Về mặt người dùng: Tab là một trang to đùng. Popup thường là một cửa sổ nhỏ, bay lơ lửng, thường dùng để Login bằng Facebook/Google hoặc hiển thị quảng cáo/thông báo.

  • Về mặt Playwright: Cả hai đều là Page. Tuy nhiên, Popup thường được kích hoạt bởi JavaScript (window.open) từ trang hiện tại, chứ không hẳn là từ Context.

Ví von

Nếu Tab mới là "Xây thêm một căn phòng mới" trong ngôi nhà, thì Popup giống như "Dán một tờ giấy nhớ (sticky note)" lên tường. Nó nhỏ hơn, phụ thuộc vào cái tường, nhưng vẫn là một bề mặt để viết (tương tác).

Code chi tiết

 
test('Xử lý Popup Window', async ({ page }) => {
  await page.goto('https://demo-website.com/login');

  // Kịch bản: Click nút "Login with Google" sẽ bật ra một popup nhỏ
  
  const [popup] = await Promise.all([
    // Lưu ý: Ở đây ta lắng nghe sự kiện 'popup' trên đối tượng 'page' (trang hiện tại)
    // thay vì 'context'. Vì popup thường được sinh ra từ hành động của trang hiện tại.
    page.waitForEvent('popup'),
    
    // Click nút mở popup
    page.click('#login-google-btn')
  ]);

  // Chờ popup tải nội dung
  await popup.waitForLoadState();

  // Tương tác với popup
  // Ví dụ: Nhập email vào ô input trong popup
  await popup.fill('input[type="email"]', 'user@gmail.com');
  
  // Giả sử sau khi login xong, popup tự đóng lại.
  // Ta nên chờ cho popup biến mất để tránh lỗi nếu ta cố tương tác tiếp.
  await popup.waitForEvent('close');
  
  // Quay lại trang chính verify xem đã login thành công chưa
  await expect(page.locator('#user-profile')).toBeVisible();
});

Tổng kết sự khác biệt quan trọng

  1. Nguồn gốc sự kiện:

    • Tab mới: Thường bắt từ context.waitForEvent('page'). (Vì nó là một trang ngang hàng).

    • Popup: Thường bắt từ page.waitForEvent('popup'). (Vì nó là con/phụ thuộc của trang hiện tại).

  2. Đóng lại:

    • Popup thường tự đóng sau khi xong việc (ví dụ login xong).

    • Tab mới thường giữ nguyên cho đến khi user tắt.

 

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