Product Management
Đăng nhập
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Wait Strategies — Implicit, Explicit, Fluent

Mở đầu — vì sao bài này quan trọng

Nếu bạn hỏi bất kỳ kỹ sư automation nào có kinh nghiệm rằng "kẻ thù số một của test tự động là gì?", câu trả lời gần như luôn giống nhau: flaky test — những bài test lúc pass lúc fail dù code chẳng thay đổi gì. Và nếu đào sâu hơn nữa để tìm nguyên nhân gốc rễ của flaky test, bạn sẽ chạm tới một thủ phạm quen mặt: wait sai cách.

Lý do rất đơn giản. Trình duyệt hiện đại và các ứng dụng web ngày nay hầu như đều dùng JavaScript để render nội dung động. Khi Selenium báo rằng "page đã load xong" (sự kiện document.readyState = complete), điều đó không có nghĩa là mọi element bạn cần đã sẵn sàng. Một nút "Thanh toán" có thể xuất hiện 800 mili-giây sau đó, khi AJAX call lấy giỏ hàng trả về. Một bảng dữ liệu có thể mất 2 giây để đổ dữ liệu từ API. Nếu test của bạn cố click vào element ngay lập tức, nó sẽ ném ra NoSuchElementException hoặc ElementNotInteractableException — và nó fail một cách ngẫu nhiên, tùy vào việc hôm nay mạng nhanh hay chậm.

Đây chính xác là lý do wait strategy quan trọng đến vậy. Nắm vững ba loại wait — Implicit, Explicit, Fluent — và biết dùng đúng loại vào đúng lúc là ranh giới phân biệt một suite test "chạy được trên máy tôi" với một suite test ổn định trên CI, chạy 100 lần fail 0 lần. Trong bài này chúng ta sẽ đi sâu vào từng loại wait, hiểu cơ chế bên trong, và quan trọng nhất là biết khi nào nên và không nên dùng loại nào.

Khái niệm cốt lõi

Bản chất của mọi wait strategy là để giải quyết một vấn đề: thời điểm test muốn tương tác với element không trùng với thời điểm element thực sự sẵn sàng. Có ba cách tiếp cận, khác nhau về phạm vi và độ linh hoạt.

1. Implicit Wait — cây gậy toàn cục (và vì sao KHÔNG khuyến nghị)

Implicit wait là một cấu hình toàn cục (global). Bạn set nó một lần, và từ đó về sau, mỗi khi Selenium gọi findElement mà không tìm thấy element ngay, nó sẽ tự động thử lại (poll) liên tục cho đến khi hết thời gian timeout bạn đặt.

from selenium import webdriver

driver = webdriver.Chrome() driver.implicitly_wait(10) # đơn vị: giây

Nếu element chưa có, Selenium sẽ đợi tối đa 10 giây rồi mới ném exception

element = driver.find_element(By.ID, "checkout-button")

Nghe có vẻ tiện, nhưng implicit wait có ba nhược điểm nghiêm trọng khiến giới chuyên nghiệp tránh xa:

  • Nó chỉ đợi element "xuất hiện trong DOM", không đợi element "sẵn sàng để tương tác". Một nút có thể đã có trong DOM nhưng đang bị disabled, bị che, hoặc chưa clickable. Implicit wait sẽ trả về element đó và test vẫn fail khi click.
  • Nó áp dụng cho MỌI lời gọi findElement, kể cả những trường hợp bạn cố tình muốn kiểm tra element KHÔNG tồn tại. Ví dụ khi test "thông báo lỗi phải biến mất", implicit wait sẽ khiến mỗi lần check tốn đủ 10 giây timeout một cách vô nghĩa, làm suite chậm khủng khiếp.
  • Nó trộn lẫn nguy hiểm với explicit wait. Đây là lỗi kinh điển: nếu bạn dùng cả implicit wait và explicit wait cùng lúc, thời gian chờ có thể cộng dồn và biến thiên khó lường (theo tài liệu Selenium chính thức, hành vi này là "unpredictable"). Ví dụ, bạn đặt implicit 10s và explicit 15s — trong một số driver, tổng thời gian chờ thực tế có thể vọt lên trên cả hai con số.
Kết luận: implicit wait chỉ nên dùng khi bạn không có lựa chọn nào khác, hoặc trong các script throwaway đơn giản. Với framework nghiêm túc, hãy đặt implicitly_wait(0) (mặc định) và chỉ dùng explicit wait.

2. Explicit Wait — đợi có điều kiện, chính xác và khuyến nghị

Explicit wait là cách tiếp cận đúng đắn. Thay vì đợi mù quáng "tối đa N giây cho mọi thứ", bạn nói với Selenium: "hãy đợi cho đến khi điều kiện cụ thể này trở thành đúng, tối đa N giây".

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By

wait = WebDriverWait(driver, 10) # timeout 10 giây

Đợi đến khi nút checkout CLICKABLE (visible + enabled), không chỉ tồn tại

button = wait.until(EC.element_to_be_clickable((By.ID, "checkout-button"))) button.click()

Điểm mạnh của explicit wait nằm ở expected conditions — thư viện các điều kiện có sẵn cho phép bạn mô tả chính xác trạng thái mình cần:

  • presence_of_element_located — element có trong DOM (chưa chắc thấy được)
  • visibility_of_element_located — element hiển thị (visible)
  • element_to_be_clickable — element hiển thị VÀ enabled → sẵn sàng click
  • text_to_be_present_in_element — text mong đợi đã xuất hiện
  • invisibility_of_element_located — element đã biến mất (rất hữu ích cho spinner loading)
  • alert_is_present — popup alert đã xuất hiện
Cơ chế bên trong: WebDriverWait poll điều kiện theo chu kỳ (mặc định 500ms một lần). Ngay khi điều kiện đúng, nó trả về ngay lập tức — không lãng phí thời gian thừa. Nếu hết timeout mà điều kiện vẫn sai, nó ném TimeoutException. Đây là lý do explicit wait vừa nhanh vừa đáng tin.

3. Fluent Wait — explicit wait có thể tùy biến sâu

Fluent wait là phiên bản linh hoạt nhất, cho phép bạn tùy chỉnh ba thứ: timeout tổng, chu kỳ poll, và danh sách exception cần bỏ qua trong lúc chờ. Thực ra trong Python, WebDriverWait đã chính là một dạng fluent wait — bạn truyền thêm tham số:

from selenium.common.exceptions import StaleElementReferenceException

wait = WebDriverWait( driver, timeout=30, # tối đa 30 giây poll_frequency=2, # kiểm tra mỗi 2 giây ignored_exceptions=[StaleElementReferenceException] # bỏ qua stale element trong khi chờ ) element = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, ".realtime-price")))

Trong Java, sự phân biệt rõ ràng hơn với class FluentWait:

Wait<WebDriver> wait = new FluentWait<>(driver)
    .withTimeout(Duration.ofSeconds(30))
    .pollingEvery(Duration.ofSeconds(2))
    .ignoring(NoSuchElementException.class);

WebElement price = wait.until(d -> d.findElement(By.cssSelector(".realtime-price")));

Fluent wait tỏa sáng trong các tình huống đặc biệt: một giá cổ phiếu cập nhật real-time, một bảng dữ liệu re-render liên tục khiến element bị "stale" (tham chiếu cũ không còn hợp lệ), hay khi bạn muốn poll thưa hơn để tránh làm nghẽn một trang nặng.

Tình huống thực tế

Ví dụ 1 — Tiki và nút "Thêm vào giỏ" xuất hiện muộn

Một nhóm QA làm việc cho một sàn thương mại điện tử lớn kiểu Tiki gặp tình huống oái oăm: test tự động cho luồng "thêm sản phẩm vào giỏ" pass đều đặn trên máy dev, nhưng lên CI (chạy trong Docker, cấu hình yếu hơn) thì fail khoảng 30% số lần. Nguyên nhân: trang chi tiết sản phẩm render nút "Thêm vào giỏ" sau khi một AJAX call kiểm tra tồn kho trả về. Trên máy dev mạng nhanh, nút hiện sau ~200ms; trên CI mạng và CPU chậm, có khi mất tới 1,5 giây.

Đội này ban đầu "chữa cháy" bằng time.sleep(2) cứng. Kết quả: suite 400 test chậm thêm 13 phút vì mỗi test đợi thừa, mà thỉnh thoảng vẫn fail khi CI lag hơn 2 giây. Sau đó họ thay bằng explicit wait element_to_be_clickable, timeout 10 giây. Tỷ lệ fail rơi về 0%, và vì wait trả về ngay khi nút sẵn sàng, tổng thời gian suite còn giảm so với dùng sleep. Bài học: sleep cứng vừa chậm vừa không đáng tin — explicit wait với đúng điều kiện giải quyết cả hai vấn đề cùng lúc.

Ví dụ 2 — Spinner loading của một ví điện tử làm test click nhầm

Một startup fintech ở TP.HCM xây app thanh toán. Khi người dùng nhấn "Xác nhận giao dịch", một spinner loading (overlay che toàn màn hình) hiện lên trong lúc gọi API cổng thanh toán, rồi biến mất khi có kết quả. Test tự động cố click nút "Xong" ngay sau khi màn hình kết quả hiện, nhưng đôi khi spinner vẫn còn đó và chặn click, khiến Selenium ném ElementClickInterceptedException.

Kỹ sư SDET giải bài toán này bằng hai bước wait tuần tự: đầu tiên đợi spinner biến mất bằng invisibility_of_element_located, sau đó mới đợi nút "Xong" clickable.

wait.until(EC.invisibility_of_element_located((By.CLASS_NAME, "loading-overlay")))
wait.until(EC.element_to_be_clickable((By.ID, "done-btn"))).click()

Chỉ sau thay đổi này, một lớp flaky test dai dẳng suốt hai sprint biến mất hoàn toàn. Bài học: đừng chỉ đợi element bạn muốn xuất hiện — đôi khi bạn phải đợi thứ khác biến mất trước.

Ví dụ 3 — Bảng giá real-time và Fluent Wait

Một công ty làm nền tảng giao dịch cho một sàn ở Singapore có màn hình hiển thị giá cập nhật mỗi giây qua WebSocket. Test cần verify giá thay đổi, nhưng element bảng bị re-render liên tục nên tham chiếu cũ trở nên "stale", ném StaleElementReferenceException ngẫu nhiên. Explicit wait thông thường không đủ vì exception xảy ra ngay trong lúc poll.

Giải pháp là fluent wait với ignored_exceptions=[StaleElementReferenceException] và poll mỗi 1 giây. Wait sẽ âm thầm bỏ qua stale exception, thử lại cho đến khi lấy được element ổn định. Bài học: fluent wait dành cho những trang động, nơi exception tạm thời là chuyện bình thường và cần được bỏ qua thay vì làm fail test.

Hướng dẫn từng bước

Đây là quy trình chuẩn để áp dụng wait đúng cách trong dự án của bạn:

  • Tắt implicit wait (giữ mặc định 0). Đừng bao giờ trộn implicit và explicit. Chọn explicit làm chiến lược chính.
  • Tạo một WebDriverWait dùng chung. Khởi tạo một lần khi setup driver, tái sử dụng trong toàn bộ test. Đặt timeout hợp lý — thường 10 giây cho web nội bộ, 15–20 giây cho hệ thống có API chậm.
self.wait = WebDriverWait(self.driver, 10)
  • Chọn đúng expected condition cho từng hành động:
- Sắp click → dùng element_to_be_clickable - Sắp đọc text → dùng visibility_of_element_located - Chỉ cần element tồn tại (ví dụ đọc attribute ẩn) → presence_of_element_located - Đợi spinner/overlay tắtinvisibility_of_element_located

  • Bọc wait vào tầng helper hoặc Page Object (chúng ta sẽ học sâu về POM ở Bài 9). Đừng rải WebDriverWait(...) khắp nơi trong test; hãy tạo hàm như click_element(locator) tự động wait bên trong.
def click(self, locator):
    self.wait.until(EC.element_to_be_clickable(locator)).click()
  • Với trang động phức tạp, nâng cấp lên fluent wait — thêm poll_frequencyignored_exceptions khi gặp stale element hay nội dung re-render.
  • Đặt timeout tập trung ở một chỗ config, không hardcode rải rác. Khi CI chậm, bạn chỉ cần đổi một biến.

Lỗi thường gặp & mẹo

  • Dùng time.sleep() cứng. Đây là tội lỗi phổ biến nhất. sleep(3) vừa lãng phí thời gian (element có thể sẵn sàng sau 0,3s) vừa không an toàn (nếu chậm hơn 3s vẫn fail). Chỉ dùng sleep khi thực sự không có điều kiện nào để wait, và ngay cả khi đó cũng nên nghĩ lại.
  • Trộn implicit và explicit wait. Như đã nói, thời gian chờ trở nên khó đoán. Quy tắc: chọn một, và với framework nghiêm túc luôn là explicit.
  • Dùng presence khi thực ra cần visibility hoặc clickable. Đây là nguyên nhân của rất nhiều ElementNotInteractableException. Element có trong DOM ≠ element click được. Hãy chọn điều kiện khớp đúng với hành động sắp làm.
  • Timeout quá ngắn trên CI. Máy CI thường yếu và mạng ảo. Timeout 3 giây chạy ngon trên laptop có thể fail lác đác trên CI. Tăng lên 10–15 giây; explicit wait sẽ không làm chậm nếu element sẵn sàng sớm.
  • Timeout quá dài che giấu bug. Ngược lại, timeout 60 giây có thể "chịu đựng" một trang thật sự bị lỗi hiệu năng, biến test thành vô dụng. Cân bằng hợp lý.
  • Quên xử lý stale element trên trang động. Nếu thấy StaleElementReferenceException xuất hiện lẻ tẻ, đó là dấu hiệu cần fluent wait với ignored_exceptions.
  • Mẹo hay: đặt tên hằng số cho timeout — SHORT_WAIT = 5, DEFAULT_WAIT = 10, LONG_WAIT = 20 — và dùng đúng ngữ cảnh. Code vừa dễ đọc vừa dễ chỉnh.

Bài tập thực hành

  • Chuyển đổi sleep sang explicit wait. Lấy một đoạn test có time.sleep(5) trước khi click một nút, thay bằng WebDriverWait với element_to_be_clickable. Đo và so sánh thời gian chạy trước/sau.
  • Đợi spinner biến mất. Tìm (hoặc mô phỏng) một trang có overlay loading. Viết wait đợi overlay invisibility_of_element_located rồi mới thao tác với nội dung bên dưới. Cố tình bỏ bước này để tự chứng kiến ElementClickInterceptedException.
  • Thử nghiệm fluent wait. Trên một trang có nội dung re-render (ví dụ đồng hồ đếm, giá cập nhật), viết một fluent wait với poll_frequency=1ignored_exceptions=[StaleElementReferenceException]. Quan sát cách nó bỏ qua stale exception.
  • Đo hại của implicit wait. Đặt implicitly_wait(10), sau đó viết một assertion kiểm tra element KHÔNG tồn tại. Đo thời gian bị lãng phí. Đây là bài học trực quan về vì sao implicit wait nguy hiểm cho test negative.

Tóm tắt

Wait strategy là kỹ năng nền tảng phân biệt suite test ổn định với suite test hay flaky. Ba điểm cốt lõi cần nhớ:

  • Implicit wait là cấu hình toàn cục, tiện nhưng thô — chỉ đợi element "tồn tại", dễ gây timeout thừa và trộn lẫn nguy hiểm với explicit. Với framework nghiêm túc, hãy để mặc định 0 và tránh dùng.
  • Explicit wait (WebDriverWait + expected conditions) là lựa chọn khuyến nghị: đợi đúng điều kiện cụ thể, trả về ngay khi sẵn sàng, vừa nhanh vừa đáng tin. Chọn đúng condition — clickable để click, visibility để đọc, invisibility để đợi spinner tắt.
  • Fluent wait là explicit wait nâng cao, tùy biến được chu kỳ poll và exception bỏ qua — dành cho trang động, stale element, nội dung re-render.
Và trên tất cả: đừng bao giờ dùng time.sleep() cứng làm chiến lược wait. Nó là dấu hiệu số một của một suite test sẽ sớm trở nên flaky. Nắm vững wait đúng cách, bạn đã loại bỏ được nguồn flaky test lớn nhất trước cả khi bắt đầu viết Page Object ở bài sau.

Học xong bài này rồi? Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu, và học hết khóa thì có chứng chỉ. Lưu tiến độ của tôi