Selenium Webdriver như đã giới thiệu ở bài số 3 rồi thì nó là thư viện để tương tác với browser, nhưng khi chạy code có thể với 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 liên tục gặp phải Exception.
Để khắc phục điều này thì Selenium Webdriver cung cấp 3 loại Wait để giúp cho test script trở nên ổn định hơn, đồng bộ hóa giữa những lần run test.
Cái chúng ta sử dụng nhiều hổm rài là Thread.sleep(timeout) thì các bạn cũng biết nó ý nghĩa gì rồi đó. Nó dùng để chờ đợi chính xác cụ thể thời gian cho từng dòng lệnh thực thi.
Thread.sleep thuộc về Java và sẽ khó chờ đợi ngay cả sau khi phần tử được hiển thị. Ít ai dùng thằng này khi xây dựng thành hình Framework. Vì nó không chính xác cho chung cả hệ thống.
Và giờ chúng ta tìm hiểu phần chính là Wait trong Selenium
Dịch ra tiếng việt là “chờ đợi ngầm”, có nghĩa là nó sẽ luôn tìm kiếm Element trong 1 khoảng thời gian ngầm định trước khi văng ra No Such Element Exception.
Cú pháp:
Selenium 3
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
Selenium 4 thì cú pháp mới cho phần đơn vị thời gian:
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
Ví dụ:
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
Ví dụ trên mình sẽ luôn tìm kiếm 1 element trong khoảng thời gian là 10s. Sau 10s mà không tìm thấy thì sẽ văng ra Exception lỗi.
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.annotations.Test;
import java.time.Duration;
public class TestWait {
WebDriver driver;
@Test(priority = 1)
public void demoImplicitWait() throws InterruptedException {
driver = new ChromeDriver();
driver.manage().window().maximize();
//Set timeout for implicitlyWait
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
driver.get("https://hrm.anhtester.com/");
driver.findElement(By.id("iusername")).sendKeys("admin_example");
driver.findElement(By.id("ipassword")).sendKeys("123456");
driver.findElement(By.xpath("//button[@type='submit']")).click();
//Click menu dự án
driver.findElement(By.xpath("//span[contains(text(),'Projects')]")).click();
Thread.sleep(2000);
driver.quit();
}
}
Và có thể bổ sung thêm 2 câu lệnh dưới đây để chờ đợi Trang load xong và Script load xong:
driver.manage().timeouts().scriptTimeout(Duration.ofSeconds(30));
driver.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(60));
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ả:
Và hậu quả của việc này là:
Vì vậy để sử dụng hiệu quả thì An khuyên các bạn:
@AfterTest
public void tearDown(){
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(0));
driver.quit();
}
Dich tiếng việt là “cố tình đợi”, Explicit wait được sử dụng để tạm dừng việc thực thi script cho đến khi một điều kiện cụ thể được chỉ định được đáp ứng hoặc thời gian tối đa đã trôi qua.
Khác với Implicit wait, Explicit wait chỉ được áp dụng cho một trường hợp cụ thể thay vì dùng cho toàn bộ các lệnh trong script.
Nó đợi theo trạng thái của Element và Page thay vì phụ thuộc vào thời gian (timeout).
Nếu chúng ta đang sử dụng kết hợp cả hai loại Wait trên thì có 2 trường hợp như sau:
Explicit Wait nằm trong pagekage org.openqa.selenium.support.ui
với 2 package con:
import org.openqa.selenium.support.ui.ExpectedConditions
import org.openqa.selenium.support.ui.WebDriverWait
Khởi tạo Object cho class WebDriverWait
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
Tạo một biến tham chiếu “wait” cho lớp WebDriverWait và khởi tạo nó bằng cách sử dụng biến thể WebDriver và khai báo thời gian chờ tối đa để quá trình thực thi tạm dừng. Thời gian chờ tối đa này được tính bằng "giây" (trong ví dụ này đang set là 10s)
Ví dụ:
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//h3[contains(text(),'Website Testing')]")));
driver.findElement(By.xpath("//h3[contains(text(),'Website Testing')]")).click();
Lớp ExpectedConditions
cung cấp một bộ trợ giúp tuyệt vời để giải quyết các tình huống trong đó chúng ta phải xác định điều kiện xảy ra trước khi thực thi các step test thực tế.
Lớp ExpectedConditions
đi kèm với một loạt các điều kiện mong đợi có thể được truy cập với sự trợ giúp của biến tham chiếu WebDriverWait
và phương thức until()
.
#1) elementToBeClickable() – Điều kiện mong đợi là chờ một phần tử có thể click được, tức là phần tử đó phải hiện diện / được hiển thị trên màn hình và phải enabled (có thể click).
Code:
wait.until(ExpectedConditions.elementToBeClickable(By.xpath("//div[contains(text(),'Anh Tester')]")));
#2) textToBePresentInElement() – Điều kiện mong đợi là chờ một phần tử chứa đoạn text được chỉ định.
Code:
wait.until(ExpectedConditions.textToBePresentInElement(By.xpath("//div[@id= 'forgotPass'"), "text to be found"));
#3) alertIsPresent() – Điều kiện mong đợi là chờ một hộp cảnh báo xuất hiện.
Code:
wait.until(ExpectedConditions.alertIsPresent()) !=null);
#4) titleIs() – Điều kiện mong đợi là chờ một trang có tiêu đề cụ thể.
Code:
wait.until(ExpectedConditions.titleIs("Anh Tester - Automation Testing"));
#5) frameToBeAvailableAndSwitchToIt() – Điều kiện mong đợi là chờ một khung có sẵn (available) và ngay sau khi có khung, điều khiển sẽ tự động chuyển sang nó.
Code:
wait.until(ExpectedConditions.frameToBeAvailableAndSwitchToIt(By.id("newframe")));
#6) visibilityOfElementLocated() - được sử dụng để kiểm tra xem một phần tử có tồn tại trong DOM của một trang và hiển thị hay không. Có nghĩa là nó sử dụng đối tượng By thay vì đối tượng WebElement với chức năng có thể gọi để tìm phần tử đó trước rồi kiểm tra phần tử đó có hiển thị hay không. Chứ nó không tìm kiếm liền.
Code:
wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//h3[contains(text(),'Website Testing')]")));
driver.findElement(By.xpath("//h3[contains(text(),'Website Testing')]")).click();
#7) visibilityOf() - được sử dụng để kiểm tra xem một phần tử hiện diện trên DOM của một trang có hiển thị hay không. Có nghĩa là bạn đã tìm thấy Element đó rồi và chỉ kiểm tra nó có hiển thị hay chưa. Nên tham số nó kiểm tra là 1 đối tượng WebElement chứ không phải đối tượng By.
Code:
wait.until(ExpectedConditions.visibilityOf(driver.findElement(By.xpath("//h3[contains(text(),'Website Testing')]"))));
driver.findElement(By.xpath("//h3[contains(text(),'Website Testing')]")).click();
#8) presenceOfElementLocated
Code:
wait.until(ExpectedConditions.presenceOfElementLocated(By.xpath("//h3[contains(text(),'Website Testing')]")));
driver.findElement(By.xpath("//h3[contains(text(),'Website Testing')]")).getAttribute("value");
Ngoài ra còn rất nhiều các hàm hỗ trợ khác các bạn tìm hiểu thêm nhé, nếu không rõ thì hỏi An.
https://www.selenium.dev/selenium/docs/api/java/org/openqa/selenium/support/ui/ExpectedConditions.html
package anhtester.com.testcode;
import io.github.bonigarcia.wdm.WebDriverManager;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.annotations.Test;
import java.time.Duration;
public class TestWait {
WebDriver driver;
@Test(priority = 2)
public void ExplicitWaitDemo() throws InterruptedException {
WebDriverManager.chromedriver().setup();
driver = new ChromeDriver();
System.out.println("Created Driver");
driver.manage().window().maximize();
driver.get("https://app.hrsale.com/");
//Set timeout
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
driver.findElement(By.id("iusername")).sendKeys("frances.burns");
driver.findElement(By.id("ipassword")).sendKeys("frances.burns");
driver.findElement(By.xpath("//button[@type='submit']")).click();
//Click menu dự án
wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//span[contains(text(),'Projects')]")));
driver.findElement(By.xpath("//span[contains(text(),'Projects')]")).click();
Thread.sleep(2000);
driver.quit();
}
}
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 ý:
Với Selenium 4 cải tiến tích hợp phần thời gian vòng lặp trong Explicit Wait luôn:
WebDriverWait wait = new WebDriverWait(DriverManager.getDriver(), Duration.ofSeconds(10), Duration.ofMillis(500));
An khuyên các bạn dùng cách cải tiến trên nhé. Để custom được khoảng thời gian 500ms đó tuỳ ý.
Selenium đã viết rất nhiều các condition khác nhau, lựa chọn sử dụng cho phù hợp. Bạn tìm hiểu thêm nhé.
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.
Trong phần code các bài về sau thì An chỉ sử dụng Explicit Wait vì tránh lãng phí thời gian khi tìm element. Và build Famework nó ổn định hơn.
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>;
Cách dùng:
Wait<WebDriver> wait = new FluentWait<WebDriver>(driver)
.withTimeout(Duration.ofSeconds(30))
.pollingEvery(Duration.ofSeconds(5))
.ignoring(NoSuchElementException.class);
// Chờ 30 giây để một phần tử hiện diện trên trang
// Và sẽ thực hiện lặp lại mỗi 5 giây nếu chưa tìm thấy phần tử đó
Sau đó, bạn vẫn sử dụng như Wait của Explicit Wait thôi
wait.until(ExpectedConditions.elementToBeClickable(linkToClick));
package anhtester.com.testcode;
import io.github.bonigarcia.wdm.WebDriverManager;
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.FluentWait;
import org.openqa.selenium.support.ui.Wait;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.annotations.Test;
import java.time.Duration;
public class TestWait {
WebDriver driver;
@Test(priority = 3)
public void FluentWaitDemo() throws InterruptedException {
WebDriverManager.chromedriver().setup();
driver = new ChromeDriver();
System.out.println("Created Driver");
driver.manage().window().maximize();
//Set timeout for FluentWait
Wait<WebDriver> wait = new FluentWait<WebDriver>(driver)
.withTimeout(Duration.ofSeconds(30))
.pollingEvery(Duration.ofSeconds(5))
.ignoring(NoSuchElementException.class);
driver.get("https://app.hrsale.com/");
driver.findElement(By.id("iusername")).sendKeys("frances.burns");
driver.findElement(By.id("ipassword")).sendKeys("frances.burns");
driver.findElement(By.xpath("//button[@type='submit']")).click();
//Click menu dự án
wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//span[contains(text(),'Projects')]")));
driver.findElement(By.xpath("//span[contains(text(),'Projects')]")).click();
Thread.sleep(2000);
driver.quit();
}
}
Các hàm bổ trợ còn lại nghiên cứu thêm tại đây:
https://www.selenium.dev/selenium/docs/api/java/org/openqa/selenium/support/ui/FluentWait.html
Ví dụ hàm withMessage(java.lang.String message) sẽ hỗ trợ ghi ra câu gì đó khi không tìm thấy chẵn hạn.
Bạn có thể viết dạng này cho 1 đối tượng WebElement:
(Thêm này dô import java.util.function.Function;
)
WebElement loginButton = wait.until(new Function<WebDriver, WebElement>() {
public WebElement apply(WebDriver driver) {
return driver.findElement(By.id("btn-login"));
}
});
loginButton.click();
Severity: Notice
Message: Undefined variable: new
Filename: post/post_detail.php
Line Number: 384
Backtrace:
File: /home/anhtest2/public_html/application/views/frontend/post/post_detail.php
Line: 384
Function: _error_handler
File: /home/anhtest2/public_html/application/views/frontend/layout/layout_view.php
Line: 370
Function: view
File: /home/anhtest2/public_html/application/core/MY_Controller.php
Line: 34
Function: view
File: /home/anhtest2/public_html/application/controllers/frontend/Post.php
Line: 59
Function: render
File: /home/anhtest2/public_html/index.php
Line: 315
Function: require_once
Severity: Notice
Message: Trying to get property 'slug' of non-object
Filename: post/post_detail.php
Line Number: 384
Backtrace:
File: /home/anhtest2/public_html/application/views/frontend/post/post_detail.php
Line: 384
Function: _error_handler
File: /home/anhtest2/public_html/application/views/frontend/layout/layout_view.php
Line: 370
Function: view
File: /home/anhtest2/public_html/application/core/MY_Controller.php
Line: 34
Function: view
File: /home/anhtest2/public_html/application/controllers/frontend/Post.php
Line: 59
Function: render
File: /home/anhtest2/public_html/index.php
Line: 315
Function: require_once
Anh Tester
Đường dẫu khó chân vẫn cần bước đi
Đời dẫu khổ tâm vẫn cần nghĩ thấu