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ự):
-
Hô "Nhảy đi!" (Lệnh
click). -
Vận động viên nhảy cái "Vèo!".
-
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):-
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). -
Hô "Nhảy đi!" (Lệnh
click). -
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.alltrả 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
-
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).
-
-
Đó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.
-
