NỘI DUNG BÀI HỌC
Hiểu rõ 'Actionability' ⏳✅
Từ click() đến dragTo() 🖱️🖐️
Gửi phím tắt hiệu quả ⌨️⚡
🧠 Phần 1: Actionability - nền tảng của mọi hành động
Trước khi tìm hiểu các hành động chuột và bàn phím, cần hiểu vì sao test Playwright ổn định đến vậy. Bí mật nằm ở cơ chế tự động chờ mang tên Actionability.
Trước khi thực thi một hành động con trỏ, Playwright không hành động ngay mà tự chạy một chuỗi kiểm tra để chắc chắn phần tử thật sự sẵn sàng.
- Phần tử có mặt trong DOM.
- Phần tử hiển thị (không
display:none, khôngvisibility:hidden, không phải 0x0). - Phần tử ngừng di chuyển (kết thúc transition/animation).
- Tự cuộn phần tử vào tầm nhìn.
- Phần tử nhận được pointer events, không bị phần tử khác che tại điểm click.
- Thử lại toàn bộ nếu phần tử bị detach khỏi DOM.
Chỉ khi mọi điều kiện thỏa mãn thì hành động mới chạy. Nhờ vậy bạn hiếm khi phải dùng waitForTimeout để chờ thủ công.
🖱️ Phần 2: Các hành động với chuột
Playwright mô phỏng thao tác chuột giống người dùng thật, và mỗi hành động đều tự động chạy qua Actionability ở trên.
🔹 1. Click, double click, right click
// Click trái tiêu chuẩn
await page.getByRole('button', { name: 'Đăng nhập' }).click();
await page.getByText('Bắt đầu').click();
// Click đúp để mở mục hoặc vào chế độ chỉnh sửa
await page.getByText('Item 1').dblclick();
// Click chuột phải để mở context menu
await page.locator('#my-element').click({ button: 'right' });
await page.getByText('Copy').click(); // chọn mục trong menu vừa hiện ra
🔹 2. Click với options: modifiers và tọa độ
Bạn có thể giữ phím bổ trợ (Shift, Control, Alt, Meta) khi click, hoặc click vào một tọa độ cụ thể bên trong phần tử - rất hữu ích với map, chart, canvas.
// Giữ Shift + Click để chọn nhiều mục
await page.getByText('Mục 1').click();
await page.getByText('Mục 5').click({ modifiers: ['Shift'] });
// Giữ Control + Click để mở link trong tab mới
await page.getByRole('link', { name: 'Trang chủ' }).click({ modifiers: ['Control'] });
// Click vào vị trí cụ thể trong phần tử (mặc định click vào tâm)
await page.locator('#heatmap').click({ position: { x: 150, y: 100 } });
⚠️ Lưu ý về force click:
click({ force: true })bỏ qua toàn bộ kiểm tra Actionability. Đây là anti-pattern vì test không còn phản ánh người dùng thật và có thể che giấu lỗi thật (phần tử bị che). Hãy sửa locator hoặc chờ phần tử che khuất biến mất trước; chỉ dùngforcenhư giải pháp cuối cùng.
🔹 3. Hover và kéo thả
Với kéo thả, ưu tiên hàm cấp cao dragTo() vì nó tự chạy Actionability cho cả phần tử nguồn và đích.
// Hover để làm hiện dropdown hoặc tooltip
await page.getByRole('button', { name: 'Sản phẩm' }).hover();
await page.getByRole('link', { name: 'Laptop' }).click();
// Kéo thả cấp cao - cách nên dùng
const source = page.locator('#item-to-drag');
const target = page.locator('#drop-zone');
await source.dragTo(target);
await expect(target).toContainText('Đã thả!');
Khi dragTo() không hoạt động với thư viện drag-drop phức tạp, bạn có thể xuống API chuột cấp thấp:
await page.locator('#item-to-drag').hover(); // 1. di đến nguồn
await page.mouse.down(); // 2. nhấn giữ chuột trái
await page.locator('#drop-zone').hover(); // 3. di đến đích
await page.mouse.up(); // 4. thả chuột
⌨️ Phần 3: Mô phỏng bàn phím
Phương thức locator.press() mô phỏng chính xác một keystroke: nó tự focus vào phần tử và nhận tên phím logic (giống keyboardEvent.key) hoặc tổ hợp phím.
🔹 1. Phím chức năng và ký tự đơn
Các phím phổ biến: Enter, Tab, Escape, Backspace, Delete, các phím mũi tên, Home, End, F1...F12.
// Enter để submit form
await page.getByText('Submit').press('Enter');
// Tab để chuyển focus sang phần tử tiếp theo
await page.locator('#username').press('Tab');
// Escape để đóng hộp thoại
await page.locator('.modal-content').press('Escape');
// Gõ một ký tự đơn
await page.getByRole('textbox').press('$');
🔹 2. Tổ hợp phím tắt
Kết hợp modifier với phím bằng dấu +. Modifier gồm Shift, Control, Alt, Meta.
// Di con trỏ sang phải 1 từ
await page.getByRole('textbox').press('Control+ArrowRight');
// Chọn tất cả văn bản
await page.getByRole('textbox').press('Control+A');
// Gõ chữ A hoa
await page.getByRole('textbox').press('Shift+A');
// Lưu file (trên Mac dùng Meta/Cmd)
await page.locator('body').press('Meta+S');
🔤 Phần 4: press() vs fill() vs pressSequentially()
Đây là phần quan trọng nhất khi nhập liệu: chọn đúng phương thức giúp test vừa nhanh vừa đúng ý đồ.
🔹 1. fill() - dùng cho 99% trường hợp
fill() xóa input rồi đặt thẳng giá trị, không mô phỏng từng phím. Nhanh và ổn định nhất khi bạn chỉ quan tâm tới kết quả text cuối cùng.
// Cách tốt nhất để điền form
await page.locator('#email').fill('example@gmail.com');
🔹 2. press() - khi cần kích hoạt event
Dùng khi cần kích hoạt event listener, phím tắt, submit bằng Enter, hoặc ô auto-suggest phản ứng theo onKeyDown.
🔹 3. pressSequentially() - gõ từng ký tự như người thật
Dùng khi fill() không hoạt động, thường gặp với code editor hoặc input bị mask định dạng theo từng keystroke.
// Gõ chậm từng chữ, có thể thêm delay giữa mỗi phím
await page.locator('input').pressSequentially('Hello');
await page.locator('input').pressSequentially('Hello', { delay: 100 });
🔹 4. focus() - khi nào thực sự cần?
Các hành động cấp cao (click, fill, press) đã tự focus, nên hiếm khi cần gọi focus() tường minh. Nó chỉ hữu ích khi bạn muốn kiểm tra hành vi xảy ra riêng lúc phần tử nhận focus, ví dụ event onFocus.
const input = page.locator('#password');
const hint = page.locator('#password-hint');
await expect(hint).not.toBeVisible(); // ban đầu hint bị ẩn
await input.focus(); // chỉ focus, chưa gõ gì
await expect(hint).toBeVisible(); // hint xuất hiện nhờ event onFocus
await expect(hint).toContainText('Mật khẩu phải có ít nhất 8 ký tự');
📌 Phần 5: Ghi nhớ nhanh cho Tester
- Actionability là lý do test Playwright ổn định; hãy tin nó thay vì chèn
waitForTimeoutthủ công. - Dùng
click({ modifiers })cho phím bổ trợ vàclick({ position })cho map/chart/canvas. - Tránh
force: true- nó che giấu lỗi thật; ưu tiên sửa locator. - Kéo thả nên dùng
dragTo(); chỉ xuốngmouse.down()/up()khi thư viện phức tạp. - Điền form dùng
fill(); cần bắn event dùngpress(); input đặc biệt dùngpressSequentially(). - Hiếm khi cần
focus()tường minh, trừ khi test riêng hành vionFocus/onBlur.
✅ Kết luận: Bài 4 giúp bạn mô phỏng mọi thao tác chuột và bàn phím đúng như người dùng thật. Khi hiểu rõ Actionability và chọn đúng giữa fill, press, pressSequentially, bạn sẽ viết được các bước tương tác vừa sát thực tế vừa ít gãy vặt.
