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ông visibility: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ợ (ShiftControlAltMeta) 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ùng force như 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: EnterTabEscapeBackspaceDelete, các phím mũi tên, HomeEndF1...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 ShiftControlAltMeta.

 

// 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 (clickfillpress) đã 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 waitForTimeout thủ 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ống mouse.down()/up() khi thư viện phức tạp.
  • Điền form dùng fill(); cần bắn event dùng press(); input đặc biệt dùng pressSequentially().
  • Hiếm khi cần focus() tường minh, trừ khi test riêng hành vi onFocus/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 fillpresspressSequentially, 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.

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