Web thật đầy cạm bẫy: nội dung tải sau (AJAX), iframe lồng nhau, popup che nút, tải file. Đây là nơi 80% agent gãy. Bài này trang bị cách xử lý từng loại.
1. Chờ đúng cách — kẻ thù số một
Đừng bao giờ sleep(3) mù quáng. Chờ theo điều kiện:
page.wait_for_selector("text=Kết quả") # chờ phần tử xuất hiện
page.wait_for_load_state("networkidle") # chờ mạng lắng
page.get_by_role("button", name="Lưu").wait_for(state="visible")
Playwright có auto-wait cho hầu hết action, nhưng nội dung do JS render sau vẫn cần chờ tường minh.
2. Iframe — nội dung trong khung riêng
Nhiều widget (thanh toán, chat, CAPTCHA) nằm trong iframe. Selector ở page chính không thấy được nó:
frame = page.frame_locator("iframe#payment")
frame.get_by_label("Số thẻ").fill("4242...")
Quy tắc: nếu không tìm thấy phần tử "rành rành trên màn hình", nghi ngờ nó nằm trong iframe.
3. Popup, modal, tab mới
# Bắt tab/popup mới mở
with page.expect_popup() as pi:
page.get_by_text("Mở báo cáo").click()
popup = pi.value
popup.wait_for_load_state()
Modal che nút: agent phải đóng modal (nút X / Esc) trước khi thao tác tiếp — đây là bước observe phải phát hiện được.
4. Upload file
page.get_by_label("Tải CV").set_input_files("cv.pdf")
Không cần mô phỏng hộp thoại hệ điều hành — set_input_files gán trực tiếp, ổn định hơn nhiều so với computer-use click vào dialog OS.
5. Download file
with page.expect_download() as di:
page.get_by_role("button", name="Export").click()
download = di.value
download.save_as("bao-cao-thang-6.csv")
Luôn verify file tồn tại và có nội dung sau khi tải, đừng tin nút "đã bấm".
Các bước xử lý một trang lạ
- Sau khi navigate,
wait_for_load_state("networkidle"). - Kiểm tra có modal/cookie banner che không → đóng trước.
- Nếu thiếu phần tử mong đợi → dò iframe.
- Với action mở cửa sổ mới → bọc trong
expect_popup. - Với tải/đăng file → dùng API chuyên dụng, không dùng dialog OS.
- Verify kết quả bằng tín hiệu khách quan.
Checklist "trang này có bẫy gì"
- [ ] Có cookie banner / modal che nội dung không?
- [ ] Nội dung có load sau (AJAX/infinite scroll) không?
- [ ] Phần tử có nằm trong iframe không?
- [ ] Click này có mở tab/popup mới không?
- [ ] Có CAPTCHA / xác thực 2 lớp chặn không?
- [ ] File tải về đã kiểm tra tồn tại + kích thước chưa?
Ví dụ cụ thể
Agent điền form trợ cấp mãi báo lỗi "không thấy ô Ngày sinh". Nguyên nhân: một cookie banner phủ toàn trang chặn tương tác. Thêm bước observe phát hiện banner và bấm "Đồng ý" trước, agent chạy trơn tru.
CAPTCHA và xác thực 2 lớp — biết điểm dừng
Một số rào cản được thiết kế để chặn tự động hóa: CAPTCHA, OTP qua điện thoại, xác thực sinh trắc. Đừng cố "phá" chúng — vừa vi phạm điều khoản dịch vụ, vừa mong manh. Cách vận hành đúng:
- Phát hiện dấu hiệu (iframe reCAPTCHA, ô nhập OTP) trong bước observe
- DỪNG và escalate cho người xử lý phần xác thực
- Sau khi người vượt qua, agent tiếp tục từ checkpoint
Thiết kế "human-in-the-loop tại điểm chặn" bền vững hơn nhiều so với tự động vượt rào.
Infinite scroll và nội dung tải dần
Nhiều trang chỉ tải thêm khi cuộn. Để lấy đủ dữ liệu:
prev = 0
while True:
page.mouse.wheel(0, 3000)
page.wait_for_load_state("networkidle")
count = page.locator(".item").count()
if count == prev: break # không thêm nữa -> hết
prev = count
Đặt trần số lần cuộn để tránh vòng lặp vô tận trên trang "vô hạn" thật sự.
Sai lầm thường gặp
sleep()cố định: hoặc chờ thừa (chậm) hoặc chờ thiếu (gãy). Luôn chờ theo điều kiện.- Bỏ quên iframe: agent "mù" trước widget thanh toán/CAPTCHA rồi kết luận sai là trang lỗi.
- Mô phỏng dialog OS để upload: cực kỳ mong manh; đã có
set_input_files. - Không đóng modal/cookie banner: mọi click sau đó thất bại khó hiểu.
- Tin "đã tải" mà không kiểm tra file: pipeline chạy tiếp với file rỗng.