Chắc hẳn các bạn làm Auto UI sẽ đã hoặc sẽ gặp trong quá trình làm việc. Có thể là bạn đã được dạy ở khóa học nào đó hoặc copy code trên stackoverflow, nhưng trong bài này mình sẽ đi sâu hơn 1 chút để chúng ta thật hiểu cách browser và selenium works trong TH này.
Nội dung bài viết
I. Cách browser render 1 page
Khi user gõ URL vào browser –> server sẽ trả lại 1 document HTML
Ví dụ: https://giangtester.com/
Browser sẽ đọc thông tin của file HTML đó, build DOM và display.
Trong quá trình đọc HTML để build DOM tree, nếu browser bắt gặp 1 đường link js hoặc css thì nó sẽ down file đó trước, rồi nó sẽ run file js luôn.
Bạn đừng thắc mắc vì sao nó lại ko download theo thứ tự vì thực tế chrome support multiple threads để download đống này song song nhau, mình nhớ ko nhầm thì là 6 luồng song song.
Nếu nó gặp in-line js hoặc file js thì browser sẽ run js đó trước, run xong lại build DOM tiếp. Vì sao lại vậy? vì trong file js có thể sẽ thay đổi (thêm, bớt) các element của HTML, nếu để đến cuối mới render thì sẽ tốn thời gian hơn. Tất nhiên, điều này vẫn phải phụ thuộc vào người code, họ để js đó ở đâu trong file HTML, thông thường họ sẽ đặt ở <head>
, load trước khi build <body>
của HTML.
Với người code UI Automation Test như chúng ta thì chỉ cần quan tâm, thế lúc nào DOM build xong. DOM xong thì mới thực hiện findElement()
được.
II. Selenium hỗ trợ thế nào
Khi khởi tạo browser instance để run test, thì Selenium sẽ có option pageLoadStrategy
với 3 tùy chọn: complete, interactive, Any.
Theo lời của selenium document thì
- chỉ dành cho method
get()
, có nghĩa là open trang web mới và - không áp dụng cho các trang web dạng Single Page Applications (SPA) vì các trang web này sẽ dùng js để thay đổi DOM.
- không áp dụng cho các method khác như
click()
vào element haysubmit()
form.
Selenium sẽ dùng query Document.readyState
để check trạng thái của trang web, nhưng họ lại nói complete: waits for all resources to download
, mình đọc lại trang web của mdn web docs thì có vẻ không phải như vậy.
complete
The document and all sub-resources have finished loading. The state indicates that the
load
event is about to fire.https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState
The
load
event is fired when the whole page has loaded, including all dependent resources such as stylesheets, scripts, iframes, and imageshttps://developer.mozilla.org/en-US/docs/Web/API/Window/load_event
Trạng thái của trang web khi complete
là loaded
, không phải downloaded
, có nghĩa là nó đã build DOM hoàn thành.
III. Check page load khi click button
Như đã nói ở trên, cơ chế mặc định của selenium chỉ dành cho việc mở 1 URL mới, nếu click 1 button để open 1 link nào thì sao? Bạn hi vọng là “selenium sẽ phải đợi đến khi page load hoàn thành sau khi click” nhưng thực tế lại khác như vậy.
Selenium không có cách này để báo được là cái link mà bạn vừa click là:
- URL thật
- URL trỏ đúng vào trang web hiện tại
- chỉ là 1 link ảo để gọi Javascript change UI.
Nên khi bạn query document.readyState
thì rất có thể nó trả lại trạng thái của page trước hoặc nó sẽ return true
trước khi Javascript change UI được chạy xong.
void waitForLoad(WebDriver driver) { new WebDriverWait(driver, 30).until((ExpectedCondition<Boolean>) wd -> ((JavascriptExecutor) wd).executeScript("return document.readyState").equals("complete")); }
Hoặc nếu là Ajax call thì nó có thể trả lại trạng thái complete
trước khi ajax call hoàn thành và nhận được hết data. Một solution trên stackoverflow được dùng để check xem Ajax đã hoàn thành hay chưa.
ExpectedCondition<Boolean> jQueryLoad = new ExpectedCondition<Boolean>() { @Override public Boolean apply(WebDriver driver) { try { return ((Long)executeJavaScript("return jQuery.active") == 0); } catch (Exception e) { return true; } } };
Solution này sẽ work trong 1 số TH và 1 số thì không (ví dụ như web viết bằng react) và có người nói là phải add thêm Thread.sleep()
vào giữa đoạn click()
vào wait.until(...)
nếu ko thì jQuery sẽ không active lúc test time và ko thực hiện requery được.
Một số cách khác có thể work khi kiểm tra page đã load xong hay chưa:
- Dựa vào việc khi refresh DOM thì sẽ có element bị stale nếu chúng ta vẫn giữ reference đến element đó. Nếu chúng ta đợi được 1 element nào đó bị stale thì cũng có nghĩa là đoạn DOM đó đã được refresh rồi,
wait.until(stalenessOf(WebElement element))
, đoạn này để cho chắc thì mình nghĩ là cứThread.sleep
thêm 200-300ms. Đọc thêm ở đây. - Hoặc wait cho đến khi nào pageSource được thay đổi. Một cách khá trần trụi nhưng to the point. haha
public void waitForJavascript(int maxWaitMillis, int pollDelimiter) { double startTime = System.currentTimeMillis(); while (System.currentTimeMillis() < startTime + maxWaitMillis) { String prevState = webDriver.getPageSource(); Thread.sleep(pollDelimiter); // <-- would need to wrap in a try catch if (prevState.equals(webDriver.getPageSource())) { return; } } }
source: https://stackoverflow.com/a/43794026/7574461
IV. Tổng kết
Làm việc với UI Automation thật khó, để đợi được đến đến trạng thái cần thiết trước khi tương tác vào elment cũng rất tốn công sức, có khi phải kết hợp vài điều kiện vào với nhau thì mới được. Hi vọng là qua bài này, bạn không chỉ học thêm được vài dòng code mèo cào mà còn hiểu hơn về cách browser làm việc để có thể “đoán” ra vấn đề và có solution hợp lý. Đừng quên cho người viết bài 1 like nhá. ^^