Wiki

← Tất cả bài wiki

Keyboard & Mouse Input (Nhập liệu bàn phím và chuột)

automationplaywright

Keyboard & Mouse input là nhóm API của Playwright mô phỏng thao tác bàn phím và chuột ở mức thấp (page.keyboard, page.mouse), bên cạnh các action mức cao như pressSequentially và dragTo. Dùng khi cần kiểm soát chính xác từng phím/từng pixel — ví dụ test phím tắt, nhập liệu giống người thật, hoặc kéo-thả trong app phức tạp.

Khái niệm & Bản chất

  • Hai tầng input: Playwright có 2 lớp — (1) action mức cao gắn với Locator (locator.click(), locator.fill(), locator.press(), locator.dragTo()) tự chờ element sẵn sàng, và (2) input mức thấp theo trang (page.keyboard, page.mouse) bắn sự kiện thô vào tọa độ/focus hiện tại, không auto-wait.
  • page.keyboard mô phỏng bàn phím vật lý: press() (nhấn+thả một phím, hỗ trợ tổ hợp như Control+A), type() (gõ chuỗi ký tự, mỗi ký tự là một keydown/keyup), insertText() (chèn text không phát key event), down()/up() (giữ/thả phím).
  • page.mouse mô phỏng chuột theo tọa độ viewport: move(x, y), click(x, y), dblclick(), down()/up() để giữ kéo, wheel(deltaX, deltaY) để cuộn.
  • pressSequentially (trên Locator) là bản 'gõ từng ký tự' của fill() — phát đủ keydown/keypress/keyup cho từng chữ, hữu ích khi input có listener theo phím (autocomplete, mask số điện thoại).
  • Mọi action mức cao đều đi qua bộ máy auto-wait + actionability check của Playwright; input mức thấp thì không — bạn tự chịu trách nhiệm về timing.

Cách hoạt động & API chính

  • Keyboard: page.keyboard.press('Enter'), page.keyboard.type('text', { delay: 50 }), page.keyboard.down('Shift') + click + up('Shift') để chọn nhiều item, page.keyboard.insertText('') cho nội dung không gõ được.
  • Tên phím theo chuẩn KeyboardEvent.key: Enter, Tab, Escape, ArrowLeft, Backspace, F5, ControlOrMeta (tự map Ctrl trên Win/Linux, Cmd trên macOS — quan trọng khi chạy CI đa nền tảng).
  • Mouse: page.mouse.click(100, 200) bắn vào tọa độ viewport; down()/move()/up() kết hợp để custom drag; wheel() cho infinite scroll / map canvas.
  • Locator actions: locator.press('KeyA') focus vào element rồi press; locator.pressSequentially('0901234567', { delay: 30 }); locator.dragTo(target) cho drag & drop HTML5.
  • Modifier: locator.click({ modifiers: ['Control'] }) để Ctrl+Click; page.click('a', { position: { x, y } }) click lệch tâm.

Ví dụ & Cú pháp thực tế

  • Test phím tắt và gõ từng ký tự: ``ts import { test, expect } from '@playwright/test'; test('shortcut Ctrl+S lưu form', async ({ page }) => { await page.goto('/editor'); const editor = page.getByRole('textbox'); await editor.click(); await page.keyboard.insertText('Noi dung bai viet'); await page.keyboard.press('ControlOrMeta+S'); // Cmd+S trên macOS await expect(page.getByText('Đã lưu')).toBeVisible(); }); ``
  • pressSequentially cho input có mask / autocomplete: ``ts const phone = page.getByLabel('Số điện thoại'); await phone.pressSequentially('0901234567', { delay: 50 }); // gõ chậm để mask xử lý kịp từng ký tự await expect(phone).toHaveValue('090 123 4567'); ``
  • Drag & drop HTML5 bằng dragTo: ``ts await page.getByText('Task A').dragTo(page.locator('#column-done')); // hoặc kéo đến vị trí cụ thể trong target await page.locator('.card').first().dragTo(page.locator('#board'), { targetPosition: { x: 50, y: 200 }, }); ``
  • Kéo chuột thủ công (slider, canvas vẽ): ``ts const box = await page.locator('.slider').boundingBox(); await page.mouse.move(box!.x + 10, box!.y + box!.height / 2); await page.mouse.down(); await page.mouse.move(box!.x + 200, box!.y + box!.height / 2, { steps: 10 }); await page.mouse.up(); ``
  • Giữ Shift để multi-select: ``ts await page.keyboard.down('Shift'); await page.getByRole('row', { name: 'Item 1' }).click(); await page.getByRole('row', { name: 'Item 5' }).click(); await page.keyboard.up('Shift'); ``

Best practices & Khi nào dùng

  • Ưu tiên action mức cao trước: fill()/pressSequentially()/dragTo() đủ cho 90% case; chỉ rớt xuống page.keyboard/page.mouse khi cần tổ hợp phím, giữ phím, hoặc tọa độ thô.
  • Dùng pressSequentially thay fill khi input có keyup/keydown listener (search suggest, tag input, OTP field) — fill() set value một phát không phát key event.
  • Dùng insertText khi chỉ cần đổ text vào (giống paste) mà không quan tâm key event — nhanh và không bị mask cản.
  • Luôn ControlOrMeta thay vì hardcode Control cho shortcut — test chạy local trên macOS lẫn CI Linux vẫn đúng.
  • Drag & drop: thử dragTo() trước; nếu app dùng lib custom (dnd-kit, react-beautiful-dnd) mà dragTo không trigger, fallback sang mouse.down/move/up với steps để mô phỏng quỹ đạo.

Gotchas, So sánh & Lưu ý

  • page.keyboard/page.mouse không auto-wait — gọi page.mouse.click(x, y) khi element chưa render là click vào hư không. Luôn await locator.waitFor() hoặc assert trước.
  • type() vs pressSequentially(): type ở cấp page (gõ vào element đang focus), pressSequentially ở cấp locator (tự focus đúng element) — bản mới khuyên dùng pressSequentially.
  • Focus là vua: page.keyboard.press('Tab') chỉ đúng nếu focus đang ở đúng chỗ; mất focus do re-render là test flake — nên click/focus element trước.
  • HTML5 drag & drop (event dragstart/drop) khác với drag bằng mouse events — dragTo() xử lý được cả hai nhưng một số lib chỉ nghe mouse event, phải dùng mouse thô với steps đủ lớn (ít nhất 2-3 bước) để lib nhận 'drag'.
  • page.mouse.wheel() cuộn cả trang; nếu muốn cuộn 1 container thì dùng locator.evaluate(el => el.scrollTop = ...) hoặc locator.hover() rồi wheel.
  • Trên headless, một số sự kiện IME/compose không đầy đủ — test nhập tiếng Việt có dấu nên dùng insertText hoặc pressSequentially với ký tự đã compose sẵn.