Playwright & Cơ chế Recording
Playwright là trái tim của hệ thống, chịu trách nhiệm tương tác với website. Tại sao chúng ta chọn Playwright thay vì Selenium hay Puppeteer? Và cơ chế "ghi hình" hoạt động như thế nào?
1. Tại sao chọn Playwright?
| Tính năng | Playwright | Selenium | Puppeteer |
|---|---|---|---|
| Tốc độ | Rất nhanh (WebSocket) | Trung bình (HTTP JSON Wire) | Nhanh |
| Độ ổn định | Cao (Auto-wait) | Thấp (Flaky tests) | Cao |
| Đa trình duyệt | Chromium, Firefox, WebKit | Tất cả | Chỉ Chromium |
| Codegen | Tích hợp sẵn, rất mạnh | Cần plugin | Không có sẵn |
| Ngôn ngữ | Python, JS, Java, C# | Tất cả | JS/TS |
🏆 Lựa chọn: Playwright thắng thế nhờ tính năng Codegen (tự sinh code) và cơ chế Auto-wait (tự đợi element xuất hiện), giúp giảm thiểu code xử lý
sleephaywait_until.
2. Cơ chế Recording (Codegen)
Playwright có một tính năng gọi là codegen. Khi kích hoạt, nó sẽ mở ra một trình duyệt đặc biệt và một cửa sổ Inspector.
Cách hoạt động:
- Playwright inject một đoạn JavaScript vào mọi trang web được mở.
- Script này lắng nghe các sự kiện DOM:
click,input,change,submit. - Khi sự kiện xảy ra, nó tính toán Selector tốt nhất (ưu tiên ID, Text, CSS, XPath).
- Nó bắn sự kiện về process Python để ghi thành code.
Trong guide_ai.py:
Chúng ta sử dụng subprocess để gọi lệnh playwright codegen và lưu output vào file tạm:
# Pseudocode logic
def start_recording(url, output_file):
cmd = [
"playwright", "codegen",
url,
"--target", "python",
"--output", output_file
]
subprocess.run(cmd)3. Cơ chế Replay & Snapshot
Sau khi có file script, hệ thống parse và replay từng action riêng lẻ thay vì chạy script trực tiếp.
Kỹ thuật: Parse → Execute → Capture
Trong src/replayer.py, quy trình như sau:
def _replay_with_browser(browser, actions, images_dir, upload_enabled, upload_fn):
context = browser.new_context(viewport={'width': 1280, 'height': 800})
page = context.new_page()
for i, action in enumerate(actions):
step_num = i + 1
# 1. Execute action
execute_action(page, action)
# 2. Wait for stabilization
page.wait_for_timeout(800)
page.wait_for_load_state('domcontentloaded', timeout=3000)
# 3. Capture screenshot
screenshot_path = images_dir / f"step_{step_num}.png"
page.screenshot(path=str(screenshot_path))
# 4. Upload if enabled
if upload_enabled and upload_fn:
remote_url = upload_fn(str(screenshot_path))Logging chi tiết (Professional UX)
Hệ thống hiển thị progress chi tiết:
🌐 Đang khởi tạo trình duyệt...
✓ Trình duyệt sẵn sàng (1280x800)
📋 Bắt đầu thực thi 10 thao tác...
──────────────────────────────────────────────────
[01/10] 🎯 Điều hướng → https://portal.inet.vn
✓ Thực thi thành công (1250ms)
⏳ Đang chờ trang ổn định...
📸 Đã chụp màn hình
☁️ Đang tải lên cloud...
✓ Tải lên thành công4. Xử lý các vấn đề khó
4.1. Fallback Selector Strategy
Trong src/replayer.py, hàm _find_element_with_fallback thử nhiều cách tìm element:
def _find_element_with_fallback(page, code):
# 1. Thử selector gốc (get_by_role, get_by_text...)
# 2. Nếu không tìm thấy, thử CSS selector
# 3. Fallback: tìm theo tag + text
for tag in ['button', 'a', 'span', 'div', 'li', 'label']:
fallback = page.locator(f'{tag}:has-text("{text}")').first
if fallback.is_visible(timeout=1000):
return fallbackƯu điểm
- ✅ Resilient: Không fail ngay khi selector gốc không hoạt động
- ✅ Flexible: Hỗ trợ nhiều loại selector
Nhược điểm
- ❌ Performance: Thử nhiều selector tốn thời gian
- ❌ False positive: Có thể click nhầm element
4.2. Action Type Detection
Hệ thống tự động nhận diện loại action để hiển thị log đẹp:
def _get_action_type_display(code: str) -> str:
if '.goto(' in code:
return f"Điều hướng → {url}"
if '.fill(' in code:
return f"Nhập liệu → {field}"
if '.click()' in code:
return f"Nhấn chuột → {name}"
# ...4.3. Anti-Bot & Headless Detection
- Giải pháp hiện tại: Chạy
headless=Truecho tốc độ - Nếu bị chặn: Đổi sang
headless=Falsehoặc dùngplaywright-stealth
5. Best Practices khi Record
Để có script sạch và dễ replay:
- Thao tác chậm rãi: Đợi trang load xong hãy click.
- Tránh click thừa: Đừng bôi đen text hay click lung tung, Playwright sẽ ghi lại hết.
- Dùng phím Tab: Để chuyển giữa các ô input (giúp Playwright hiểu flow tốt hơn).
- Kết thúc rõ ràng: Đăng xuất hoặc đóng tab khi xong việc.