Chắc bài này sẽ chẳng có ai đọc mấy, nhưng mà mình vẫn phải viết vì để tránh việc các bạn phải switch sang page khác kiếm thông tin về những cái này.
Selenium Webdriver là library để tương tác với browser, nhưng vì nhiều nguyên nhân mà mỗi lần chạy lại khác nhau, trang web có thể load lâu hơn, nhanh hơn, khiến cho test script của bạn phải liên tục gặp phải Exception, ví dụ NoSuchElementException, hoặc StaleElementReferenceException… Để khắc phục những brittle/flaky test ở trên, Selenium Webdriver cung cấp 3 loại Wait để giúp synchronization, nói nôm na là làm cho test script trở nên stable hơn, “đồng bộ hóa” giữa những lần run test.
Nội dung bài viết
I. Implicit Wait
Dịch ra tiếng việt là “đợi ngầm”, có nghĩa là nó sẽ luôn tìm kiếm Element trong 1 khoảng thời gian trước khi văng ra No Such Element Exception.
Cú pháp:
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
Ví dụ: Mình sẽ luôn tìm kiếm 1 element trong khoảng thời gian timeout là 10s. Sau 10s mà không tìm thấy thì sẽ văng ra Exception.
Lưu ý:
Phương thức Wait này nó là static setting, có nghĩa là nó sẽ áp dụng cho tất cả các trường hợp có sử dụng method findElement(). Điều này dẫn đến 2 kết quả:
- Bạn chỉ cần viết 1 dòng này duy nhất ở trong project, không cần viết đến dòng thứ 2. Thi thoảng mình vẫn thấy các bạn fresher mới học viết dòng này tràn lan ở bất kỳ chỗ nào mà bạn cho là phải đợi.
- Giả sử bạn viết dòng này ở trong test1() thì nó vẫn sẽ có tác dụng test2(), test3()….
Và hậu quả của việc này là:
- Như đã nói ở trên, thời gian run test vì nhiều nguyên nhân nên chẳng có lần nào giống lần nào. Khi page load nhanh, bạn để timeout là 5s, khi page load chậm, bạn bị lỗi, phải tăng lên 10s. Sau 1 hồi sửa đi sửa lại, có thể bạn sẽ setup timeout là 30s và đặt nó ở trong @BeforeAll cũng nên. Và thế là bạn tự làm chậm test automation, cái mà được coi là tiết kiệm thời gian cho manual.
- Có những khi Web thay đổi UI, thay vì test fail ngay lập tức thì nó sẽ phải đợi 1 khoảng thời gian để báo fail. Nếu 1,2 test thì cũng không vấn đề nhưng nếu có 100 tests thì thời gian chờ lãng phí rất là nhiều.
Vì vậy để sử dụng hiệu quả thì nhiều test expert khuyên:
- Không nên sử dụng Implicit Wait vì nó lãng phí thời gian hơn là lợi ích nó mang lại.
- Nếu có sử dụng Implicit Wait trong 1 test nào đó thì khi clean up ở @AfterEach nên reset lại thời gian timeouts, để nó không còn ảnh hưởng đến những test khác.
@AfterEach public void tearDown(){ driver.manage().timeouts().implicitlyWait(0, TimeUnit.SECONDS); driver.quit(); }
Update: giải thích chi tiết cách hoạt động của implicit wait.
Selenium 4 thay đổi về cách thức selenium hoạt động, và định nghĩa về việc dùng implicit wait như sau:
A session has an associated session implicit wait timeout that specifies a time to wait in milliseconds for the element location strategy when retrieving elements and when waiting for an element to become interactable when performing element interaction . Unless stated otherwise it is zero milliseconds.
From: https://www.w3.org/TR/webdriver1/#sessions
Khi gọi method driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
thì selenium sẽ gọi 1 HTTP request sang bên Driver server, ví dụ như sau:
Request DefaultHttpRequest(decodeResult: success, version: HTTP/1.1) POST /session/dda13d2363cd838230c4859042a9b459/timeouts HTTP/1.1 User-Agent: selenium/4.10.0 (java windows) Content-Length: 23 Content-Type: application/json; charset=utf-8 host: localhost:46869 accept: */*
Và Server sẽ trả lại response
Response DefaultHttpResponse(decodeResult: success, version: HTTP/1.1) HTTP/1.1 200 OK Content-Length: 14 Content-Type: application/json; charset=utf-8 cache-control: no-cache
Và như thế việc control timeout sẽ nằm hoàn toàn ở Driver server mà ko nằm ở trong selenium code. Khi selenium tìm kiếm element, ví dụ driver.findElement(By.name("q1"))
thì selenium sẽ gửi 1 cái command HTTP request
Request DefaultHttpRequest(decodeResult: success, version: HTTP/1.1) POST /session/dda13d2363cd838230c4859042a9b459/element HTTP/1.1 User-Agent: selenium/4.10.0 (java windows) Content-Length: 56 Content-Type: application/json; charset=utf-8 host: localhost:46869 accept: */*
Trong thời gian 10s này, có vẻ như Driver server sẽ thử tìm trong khoảng 8,9 lần gì đó, mỗi lần cách nhau 1s.
Và sau 10s nếu ko tìm thấy thì Driver server sẽ trả lại 404 Not Found và Selenium sẽ văng ra NoSuchElementException.
Response DefaultHttpResponse(decodeResult: success, version: HTTP/1.1) HTTP/1.1 404 Not Found Content-Length: 967 Content-Type: application/json; charset=utf-8 cache-control: no-cache
II. Explicit Wait
Dich tiếng việt là “cố tình đợi”, nói đến đây là buồn cười, kiểu này giống như anh chàng nào đó tán tỉnh cô gái nhưng công khai trước bàn dân thiên hạ, thay vì kiểu “đợi ngầm” như ở trên. Và chả biết là công khai mặt dày ở ngoài đời thì có hiệu quả thật không, nhưng mà trong Selenium thì nó work tốt hơn nhiều. Lý do
Nó đợi theo trạng thái (state) của Element và Page thay vì phụ thuộc vào thời gian (timeout).
Explicit Wait nằm trong pagekage org.openqa.selenium.support.ui cùng package với LoadableComponent, có nói ở bài trước. Nó hoạt động theo cơ chế:
Nó sẽ bảo WebDriver là đợi cho đến khi điều kiện (ExpectedCondition) được thỏa mãn hoặc hết thời gian timeouts, nó sẽ bắn ra exception tùy theo điều kiện, ví dụ như ElementNotVisibleException, ElementNotInteractableException, TimeoutException…
Có 2 điểm lưu ý:
- Nếu trong thời gian đợi, nếu có exception NotFoundException thì exception cũng sẽ bị bỏ qua.
- Và WebDriver sẽ check điều kiện có được thỏa mãn không sau 1 khoảng interval là 500ms
Ví dụ:
Bạn hoàn toàn có thể dùng WeDriverWait cùng với Page Object, đọc thêm bài 14 basic để biết cách implement. Selenium đã viết rất nhiều các condition khác nhau, thoải mái để lựa chọn. Bạn tự tìm hiểu thêm.
Tuy nhiên, vì mục tiêu viết code để dễ hiểu, phù hợp hơn với business domain, hoặc muốn 1 cái điều kiện mà Selenium chưa cung cấp thì ta hoàn toàn có thể tự viết custom condition cho riêng test của mình. Mình sẽ viết 1 bài về custom Expected condition sau.
Cảnh báo: Không dùng Explicit Wait và Implicit Wait cho cùng 1 test case, nó sẽ thay đổi thời gian timeouts và bạn sẽ không cách nào biết trước test của bạn sẽ fail vì cái gì đâu.
Warning: Do not mix implicit and explicit waits. Doing so can cause unpredictable wait times. For example, setting an implicit wait of 10 seconds and an explicit wait of 15 seconds could cause a timeout to occur after 20 seconds.
From https://www.selenium.dev/documentation/webdriver/waits/
III. Fluent Wait
Mục tiêu của Fluent Wait là cung cấp 1 cơ chế Wait chung, có thể ứng dụng được nhiều chỗ, không chỉ ứng dụng cho mỗi WebDriver, và nó chính là cha của Explicit Wait phía trên. Nói chính xác hơn thì
public class WebDriverWait extends FluentWait<WebDriver>;
Vậy thì ta làm gì với Fluent Wait bây giờ, khi mà những người tạo nên Selenium đã hỗ trợ ta viết riêng WebDriverWait cho Webdriver rồi. =)))) mình không biết nữa. Đùa thôi, ta vẫn có thể làm được 1 chút với Fluent Wait, đó là config con số interval và sẽ ignore Exception nào. Điểm này rất hữu dụng vì WebDriverWait chỉ ignore mỗi NotFoundException, trong khi đó mình có thể gặp các Exception khác mà mình cũng muốn ignore như StaleElementReferenceException.
Wait<WebDriver> wait = new FluentWait<WebDriver>(driver) .withTimeout(Duration.ofSeconds(5)) .pollingEvery(Duration.ofMillis(300)) .ignoring(NoSuchElementException.class, StaleElementReferenceException.class);
Sau đó, bạn vẫn sử dụng như Wait của Explicit Wait thôi
wait.until(ExpectedConditions.elementToBeClickable(linkToClick));
IV. Kết luận
Chốt lại, implicit wait là wait trước khi findElement(), còn explicit wait là wait 1 điều kiện nào đó trước khi đi đến thực hiện 1 việc khác.
Hi vọng sau khi đọc xong bài này, các bạn fresher có thể bớt những dòng code Implicit Wait và những ai vẫn còn phân vân giữa các loại Wait trong Selenium thì sẽ không cần đọc thêm 1 tài liệu nào về phần này nữa. Nếu các bạn thấy bài viết có ích, hãy chia sẻ cho người đồng nghiệp đang chật vật với việc chọn lựa các loại Wait để dùng.
Hết rồi, viết được 1 bài mà hết cả buổi chiều, mệt quá. @@
Có em đọc mà a 😀
skype: kennou27@live.com
Thanks 😀
có những thanh niên mới học test vài ngày đọc rất chăm chú, ví như tui =)))))
Thanks bạn. Hehe
Rất cám ơn anh, bài viết hữu ích, dễ hiểu.
Thanks bạn
Hữu ích lắm anh. Đôi khi ng làm nhiều năm cũng không giải thích rõ như anh đâu ạ.
Thanks em. 😀 bài này anh viết lại kiến thức cũ thôi, quả thực không đóng góp được thêm tí nào.
anh ơi, cho e xin skype để ae mình trao đổi thêm 1 tí nữa được không e, em cảm ơn
Skype mình có ghi ở page About_me
sao bác viết dài dòng thế!!!
Cả 3 đều dùng để tạo ra 1 sự chờ đợi.
Implicit wait: cắm đầu chờ đến khi hết time, trong lúc chờ ko care bất kì điều j sãy ra cả.
Explicit wait: có 2 ý:
Fluent wait: giống y chang Explicit nhưng cái 500ms mình được đổi thành số khác theo ý mình.
Thanks bạn đã đóng góp.
Ủa, mình có bắt bạn đọc bài của mình đâu. :))))
Nhờ a Giang giải thích dài dòng vậy nên mình mới hiểu kỹ hơn về implicit wait và trước giờ mình mắc lỗi y như a Giang đề cập trong bài luôn ạ. Nay tìm được bài này thật sự mở mang hiểu biết của e về các loại wait này 🙂 Thanks a Giang nhiều lắm
Okay em.
Anh ơi, với Implicit wait nếu nó tìm thấy element trước thời gian timeouts thì nó có nhảy sang step tiếp theo luôn không anh, hay nó mặc định chờ hết timeouts vậy ạ
Nếu tìm thấy, nó sẽ ko đợi hết thời gian timeout
Nếu tìm thấy, thì Implicit wait sẽ không đợi hết thời gian Timeout, nghĩa là khi ta cài timeout là 30 nhưng có những thẻ chỉ mất 5-7s để tìm thấy thì nó sẽ thực hiện lệnh tiếp theo. Thì tại sao sử dụng Implicit Wait lại lãng phí thời gian ạ?
Vấn đề nếu fail thì nó ko fail ngay mà sẽ hết thời gian timeout mới fail và chỗ này áp dụng cho tất cả mọi nơi, chứ ko riêng gì ở 1 test.
Cảm ơn anh về bài viết
You’re welcome!
Thanks for sharing. It’s helpful to me.