NỘI DUNG BÀI HỌC

✅Các loại Waits khi sử dụng Appium chờ đợi element trên mobile app
✅Cách sử dụng Implicit Wait
✅Cách sử dụng Explicit Wait
✅Cách sử dụng Plugin Wait từ Appium 2x

✅Các loại Waits khi sử dụng Appium chờ đợi element trên mobile app

Trong Appium Java 2.x, có hai loại Waits phổ biến khi làm việc với các phần tử trên ứng dụng:

  1. Implicit Wait (Chờ ngầm định)

    • Là thời gian chờ chung mà WebDriver áp dụng cho tất cả các phần tử.
    • Nếu phần tử không xuất hiện ngay lập tức để thao tác thì WebDriver sẽ thử lại trong khoảng thời gian tối đa đã chỉ định trước khi ném ra lỗi NoSuchElementException.
    • Áp dụng chờ đợi ngay khi chúng ta gọi hàm findElement hoặc findElements.
    • Không khuyến khích sử dụng nếu cần kiểm soát tốt hơn khi chờ phần tử.
  2. Explicit Wait (Chờ rõ ràng)

    • Chờ một điều kiện cụ thể xảy ra trước khi tiếp tục thực hiện hành động.
    • Áp dụng chờ đợi cho từng trường hợp khi chúng ta gọi điều kiện sử dụng cụ thể cho từng element.
    • Sử dụng WebDriverWait hoặc FluentWait để kiểm tra điều kiện một cách linh hoạt.
    • Giúp kiểm soát tốt hơn so với Implicit Wait, chỉ chờ khi cần thiết.


Ngoài ta từ Appium 2x chúng ta còn có thể sử dụng Plugin để cài đặt cơ chế waits thông qua command line khi khởi động Appium Server. Tuy nhiên thì cách này cũng không hay bằng Explicit Wait. Nên vì thế chúng ta sẽ tìm hiểu kĩ và sử dụng Explicit Wait thôi là đủ.


✅Cách sử dụng Implicit Wait

Khai báo một dòng code duy nhất để thiết lập wait toàn cục cho tất cả các câu lệnh tìm kiếm element (findElement/findElements). Chúng ta sẽ đặt dòng code này tại class BaseTest.

// Ví dụ đặt thời gian chờ implicit là 10 giây
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));


Triển khai vào code cho cả Android và iOS:

import io.appium.java_client.AppiumBy;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.android.options.UiAutomator2Options;
import io.appium.java_client.ios.IOSDriver;
import io.appium.java_client.ios.options.XCUITestOptions;
import org.openqa.selenium.WebElement;
import org.testng.annotations.Test;

import java.net.MalformedURLException;
import java.net.URL;
import java.time.Duration;

public class ApplyImplicitWait {
    @Test
    public void applyImplicitWaitAndroid() throws MalformedURLException {
        UiAutomator2Options options = new UiAutomator2Options();
        options.setDeviceName("emulator-5554");
        options.setPlatformName("Android");
        options.setAutomationName("UiAutomator2");
        options.setApp("/path/to/app.apk");

        AndroidDriver driver = new AndroidDriver(new URL("http://localhost:4723"), options);

        // Đặt thời gian chờ implicit là 10 giây
        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));

        // Tìm kiếm phần tử (sẽ chờ tối đa 10 giây nếu không tìm thấy)
        // Nếu tìm thấy tại giây vào thì xử lý ngay tại giây đó
        WebElement button = driver.findElement(AppiumBy.id("com.example:id/button_login"));
        button.click();

        driver.quit();
    }

    @Test
    public void applyImplicitWaitIOS() throws MalformedURLException {
        XCUITestOptions options = new XCUITestOptions();
        options.setDeviceName("iPhone 16");
        options.setPlatformName("iOS");
        options.setAutomationName("XCUITest");
        options.setApp("/path/to/app.ipa");

        IOSDriver driver = new IOSDriver(new URL("http://localhost:4723"), options);

        // Đặt thời gian chờ implicit là 10 giây
        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));

        // Tìm kiếm phần tử (sẽ chờ tối đa 10 giây nếu không tìm thấy)
        // Nếu tìm thấy tại giây vào thì xử lý ngay tại giây đó
        WebElement button = driver.findElement(AppiumBy.id("loginButton"));
        button.click();

        driver.quit();
    }
}


Demo cụ thể đối với My Demo App:

import com.anhtester.common.BaseTest;
import com.anhtester.drivers.DriverManager;
import com.anhtester.keywords.MobileUI;
import io.appium.java_client.AppiumBy;
import org.openqa.selenium.WebElement;
import org.testng.Assert;
import org.testng.annotations.Test;

import java.time.Duration;

public class DemoImplicitWait extends BaseTest {

    @Test
    public void testImplicitWait() {
        //Set implicit wait toàn cục cho tất cả các lệnh findElement tối đa là 3 giây
        DriverManager.getDriver().manage().timeouts().implicitlyWait(Duration.ofSeconds(3));

        DriverManager.getDriver().findElement(AppiumBy.accessibilityId("View menu")).click();

        //Ví dụ chỗ này sai locator, thì sau 3 giây nó sẽ báo lỗi
        DriverManager.getDriver().findElement(AppiumBy.accessibilityId("Login Menu Item 123")).click();

        WebElement headerLoginPage = DriverManager.getDriver().findElement(AppiumBy.id("com.saucelabs.mydemoapp.android:id/loginTV"));
        Assert.assertTrue(headerLoginPage.isDisplayed());
        String headerLoginText = headerLoginPage.getText();
        System.out.println("headerLoginText: " + headerLoginText);

        Assert.assertEquals(headerLoginText, "Login");

        DriverManager.getDriver().findElement(AppiumBy.xpath("//android.widget.EditText[@resource-id=\"com.saucelabs.mydemoapp.android:id/nameET\"]")).clear();
        DriverManager.getDriver().findElement(AppiumBy.xpath("//android.widget.EditText[@resource-id=\"com.saucelabs.mydemoapp.android:id/nameET\"]")).sendKeys("bod@example.com");
        DriverManager.getDriver().findElement(AppiumBy.xpath("//android.widget.EditText[@resource-id=\"com.saucelabs.mydemoapp.android:id/passwordET\"]")).clear();
        DriverManager.getDriver().findElement(AppiumBy.xpath("//android.widget.EditText[@resource-id=\"com.saucelabs.mydemoapp.android:id/passwordET\"]")).sendKeys("10203040");
        DriverManager.getDriver().findElement(AppiumBy.accessibilityId("Tap to login with given credentials")).click();
        MobileUI.sleep(2);

        WebElement headerCatalogPage = DriverManager.getDriver().findElement(AppiumBy.accessibilityId("title"));
        Assert.assertTrue(headerCatalogPage.isDisplayed());
        String headerCatalogText = headerCatalogPage.getText();
        System.out.println("headerCatalogText: " + headerCatalogText);

        Assert.assertEquals(headerCatalogText, "Products");

        System.out.println("Login success.");
    }

}


✅Cách sử dụng Explicit Wait

Chúng ta sẽ sử dụng class WebDriverWait để khai báo đối tượng class và thiết lặp thời gian chờ, khai báo cụ thể trong từng class tại vị trí sử dụng, thay vì class chung BaseTest.

WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));

 

Tiếp theo thì gọi các điều kiện chờ cụ thể, trong Appium 2.x, WebDriverWait hỗ trợ nhiều điều kiện chờ (ExpectedConditions) giúp bạn kiểm soát quá trình chờ một cách hiệu quả hơn. Dưới đây là danh sách các hàm phổ biến và cách sử dụng chúng.

1. Chờ phần tử xuất hiện (visibilityOfElementLocated)

🔹Chờ một phần tử hiển thị trên màn hình (có trong DOM và có thể nhìn thấy).
👉 Dùng khi: Cần đợi một phần tử xuất hiện trước khi thao tác.

WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));

WebElement element = wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("com.example:id/button_login")));

Sau đó có thể lấy element.click()

Triển khai vào code cho cả Android và iOS:

import io.appium.java_client.AppiumDriver;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.android.options.UiAutomator2Options;
import io.appium.java_client.ios.IOSDriver;
import io.appium.java_client.ios.options.XCUITestOptions;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.annotations.Test;

import java.net.MalformedURLException;
import java.net.URL;
import java.time.Duration;

public class ApplyExplicitWait {
    @Test
    public void applyExplicitWaitAndroid() throws MalformedURLException {
        UiAutomator2Options options = new UiAutomator2Options();
        options.setDeviceName("emulator-5554");
        options.setPlatformName("Android");
        options.setAutomationName("UiAutomator2");
        options.setApp("/path/to/app.apk");

        AppiumDriver driver = new AndroidDriver(new URL("http://localhost:4723"), options);

        // Khởi tạo WebDriverWait
        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));

        // Chờ nút Login xuất hiện và có thể click
        WebElement loginButton = wait.until(ExpectedConditions.elementToBeClickable(By.id("com.example:id/button_login")));
        loginButton.click();

        driver.quit();
    }

    @Test
    public void applyExplicitWaitIOS() throws MalformedURLException {
        XCUITestOptions options = new XCUITestOptions();
        options.setDeviceName("iPhone 15");
        options.setPlatformName("iOS");
        options.setAutomationName("XCUITest");
        options.setApp("/path/to/app.app");

        IOSDriver driver = new IOSDriver(new URL("http://localhost:4723"), options);

        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));

        // Chờ label "Welcome" hiển thị
        WebElement welcomeLabel = wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("welcome_label")));
        System.out.println(welcomeLabel.getText());

        driver.quit();
    }
}

 

2. Chờ phần tử biến mất (invisibilityOfElementLocated)

🔹 Chờ một phần tử biến mất khỏi màn hình (không hiển thị hoặc không còn trong DOM).
👉 Dùng khi: Chờ một màn hình loading hoặc một dialog cụ thể biến mất trước khi tiếp tục.

wait.until(ExpectedConditions.invisibilityOfElementLocated(By.id("com.example:id/loading_spinner")));

 

3. Chờ phần tử có thể click (elementToBeClickable)

🔹 Chờ phần tử có thể click được (hiển thị và có thể tương tác).
👉 Dùng khi: button nào đó có thể sẵn sàng để click vào vì nhiều khi nó đang bị disable, tránh lỗi "Element Not Clickable".

WebElement button = wait.until(ExpectedConditions.elementToBeClickable(By.id("com.example:id/button_login")));
button.click();

 

4. Chờ phần tử tồn tại trong DOM (presenceOfElementLocated)

🔹 Chờ một phần tử có trong DOM, nhưng chưa chắc đã hiển thị.
👉 Dùng khi: Cần kiểm tra sự tồn tại của phần tử trong DOM, dù nó có thể đang bị ẩn. (display: none)

WebElement element = wait.until(ExpectedConditions.presenceOfElementLocated(By.id("com.example:id/hidden_element")));

 

5. Chờ phần tử có text cụ thể (textToBePresentInElement)

🔹 Chờ đến khi phần tử hiển thị và có chứa text cụ thể.
👉 Dùng khi: Kiểm tra các text của các message trạng thái như "Success", "Completed", "Failed"...

wait.until(ExpectedConditions.textToBePresentInElementLocated(By.id("com.example:id/status"), "Login Success."));

 

6. Chờ một phần tử có attribute cụ thể (attributeToBe)

🔹 Chờ đến khi phần tử có một giá trị thuộc tính cụ thể.
👉 Dùng khi: Chờ một nút chuyển từ disabled sang enabled.

wait.until(ExpectedConditions.attributeToBe(By.id("com.example:id/button"), "enabled", "true"));

 

7. Chờ số lượng phần tử nhất định (numberOfElementsToBe)

🔹 Chờ đến khi một danh sách phần tử đạt số lượng mong muốn.
👉 Dùng khi: Chờ danh sách items xuất hiện trên danh sách.

wait.until(ExpectedConditions.numberOfElementsToBe(By.className("android.widget.TextView"), 5));

 

8. Chờ URL chứa đoạn text cụ thể (urlContains)

🔹 Chờ URL chứa một phần cụ thể (dành cho ứng dụng web/hybrid). Native app/Flutter app không cần đến.
👉 Dùng khi: Kiểm tra app đã chuyển đến trang mong muốn.

wait.until(ExpectedConditions.urlContains("dashboard"));

 

9. Chờ cửa sổ thứ hai xuất hiện (numberOfWindowsToBe)

🔹 Chờ đến khi ứng dụng mở thêm một cửa sổ mới (dùng cho web/hybrid). Native app/Flutter app không cần đến.
👉 Dùng khi:Chờ app mở popup hoặc tab mới.

wait.until(ExpectedConditions.numberOfWindowsToBe(2));

 

10. Chờ JavaScript hoàn thành (jsReturnsValue)

🔹 Chờ một đoạn JavaScript trả về giá trị (áp dụng cho hybrid/web). Native app/Flutter app không cần đến.
👉 Dùng khi:Chờ trang web load xong.

wait.until(ExpectedConditions.jsReturnsValue("return document.readyState === 'complete'"));

 

Ngoài ra còn rất nhiều hàm khác chúng ta tìm hiểu và sử dụng thêm từ class ExpectedConditions hỗ trợ.

Xem thêm tại đây: https://www.selenium.dev/selenium/docs/api/java/org/openqa/selenium/support/ui/ExpectedConditions.html

Cần chú ý các hàm cùng tên khác tham số (đa hình trong Java) thì có 2 dạng là kiểu ByWebElement.

Ví dụ:

elementToBeClickable(By locator)
🔹 Chờ phần tử có thể click dựa trên locator từ đầu luôn.
🔹 Appium tự động tìm và kiểm tra nếu phần tử sẵn sàng click.

WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));

// Chờ nút login có thể click
WebElement loginButton = wait.until(ExpectedConditions.elementToBeClickable(By.id("com.example:id/button_login")));
loginButton.click();


elementToBeClickable(WebElement element)
🔹 Dùng khi bạn đã tìm thấy phần tử ở bước trước đó, nhưng nó chưa sẵn sàng để click.
🔹 Appium sẽ chờ đến khi phần tử có thể tương tác được.

WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));

// Tìm phần tử trước
WebElement loginButton = driver.findElement(By.id("com.example:id/button_login"));

// Chờ phần tử này có thể click
wait.until(ExpectedConditions.elementToBeClickable(loginButton)).click();

 

Demo cụ thể đối với My Demo App:

import com.anhtester.common.BaseTest;
import com.anhtester.drivers.DriverManager;
import com.anhtester.keywords.MobileUI;
import io.appium.java_client.AppiumBy;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.Assert;
import org.testng.annotations.Test;

import java.time.Duration;

public class DemoExplicitWait extends BaseTest {

    @Test
    public void testExplicitWait() {
        //Set implicit wait về giá trị 0 để vô hiệu hoá
        DriverManager.getDriver().manage().timeouts().implicitlyWait(Duration.ofSeconds(0));

        DriverManager.getDriver().findElement(AppiumBy.accessibilityId("View menu")).click();

        //Khai báo đối tượng wait thuộc WebDriverWait (Explicit)
        WebDriverWait wait = new WebDriverWait(DriverManager.getDriver(), Duration.ofSeconds(2));

        wait.until(ExpectedConditions.visibilityOfElementLocated(AppiumBy.accessibilityId("Login Menu Item 123")));

        //Ví dụ chỗ này sai locator, cần wait locator visible sau đó mới thao tác sau
        //Nếu nó không visible thì báo lỗi, không thao tác bước sau được
        DriverManager.getDriver().findElement(AppiumBy.accessibilityId("Login Menu Item 123")).click();

        //Wait header present trước khi kểm tra displayed
        wait.until(ExpectedConditions.presenceOfElementLocated(AppiumBy.id("com.saucelabs.mydemoapp.android:id/loginTV")));

        WebElement headerLoginPage = DriverManager.getDriver().findElement(AppiumBy.id("com.saucelabs.mydemoapp.android:id/loginTV"));
        Assert.assertTrue(headerLoginPage.isDisplayed());
        String headerLoginText = headerLoginPage.getText();
        System.out.println("headerLoginText: " + headerLoginText);

        Assert.assertEquals(headerLoginText, "Login");

        //Khai báo wait thứ 2 để setup lại thời gian khác wait trước
        WebDriverWait wait2 = new WebDriverWait(DriverManager.getDriver(), Duration.ofSeconds(5));
        //Wait email locator visible trước khi thao tác điền email
        wait2.until(ExpectedConditions.visibilityOfElementLocated(AppiumBy.xpath("//android.widget.EditText[@resource-id=\"com.saucelabs.mydemoapp.android:id/nameET12345\"]")));

        DriverManager.getDriver().findElement(AppiumBy.xpath("//android.widget.EditText[@resource-id=\"com.saucelabs.mydemoapp.android:id/nameET\"]")).clear();
        DriverManager.getDriver().findElement(AppiumBy.xpath("//android.widget.EditText[@resource-id=\"com.saucelabs.mydemoapp.android:id/nameET\"]")).sendKeys("bod@example.com");
        DriverManager.getDriver().findElement(AppiumBy.xpath("//android.widget.EditText[@resource-id=\"com.saucelabs.mydemoapp.android:id/passwordET\"]")).clear();
        DriverManager.getDriver().findElement(AppiumBy.xpath("//android.widget.EditText[@resource-id=\"com.saucelabs.mydemoapp.android:id/passwordET\"]")).sendKeys("10203040");
        DriverManager.getDriver().findElement(AppiumBy.accessibilityId("Tap to login with given credentials")).click();
        MobileUI.sleep(2);

        WebElement headerCatalogPage = DriverManager.getDriver().findElement(AppiumBy.accessibilityId("title"));
        Assert.assertTrue(headerCatalogPage.isDisplayed());
        String headerCatalogText = headerCatalogPage.getText();
        System.out.println("headerCatalogText: " + headerCatalogText);

        Assert.assertEquals(headerCatalogText, "Products");

        System.out.println("Login success.");
    }

}

 

🔆Triển khai các hàm waits từ Explicit thành hàm chung 

Nếu mà mỗi lần wait phải khai báo lại đối tượng wait và các hàm điều kiện thì nó hơi dài dòng, nên chúng ta có thể xây dựng hàm chung cho nó tại class MobileUI hổm rài chúng ta đã từng khai báo. Hoặc một class nào đó bất kỳ, hướng đến xây dựng class keyword chung để dùng lại sau này.

MobileUI.java

package com.anhtester.keywords;

import org.openqa.selenium.By;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.Point;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Pause;
import org.openqa.selenium.interactions.PointerInput;
import org.openqa.selenium.interactions.Sequence;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

import java.time.Duration;
import java.util.*;

import static com.anhtester.drivers.DriverManager.getDriver;

public class MobileUI {
    public static void sleep(double second) {
        try {
            Thread.sleep((long) (1000 * second));
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    public static void swipe(int startX, int startY, int endX, int endY, int durationMillis) {
        PointerInput finger = new PointerInput(PointerInput.Kind.TOUCH, "finger");
        Sequence swipe = new Sequence(finger, 1);
        swipe.addAction(finger.createPointerMove(Duration.ofMillis(0), PointerInput.Origin.viewport(), startX, startY));
        swipe.addAction(finger.createPointerDown(0));
        swipe.addAction(finger.createPointerMove(Duration.ofMillis(durationMillis), PointerInput.Origin.viewport(), endX, endY));
        swipe.addAction(finger.createPointerUp(0));
        getDriver().perform(Collections.singletonList(swipe));
    }

    public static void swipeLeft() {
        Dimension size = getDriver().manage().window().getSize();
        int startX = (int) (size.width * 0.8);
        //int startY = size.height / 2; // Ở chính giữa màn hình
        int startY = (int) (size.height * 0.3); // 1/3 bên trên màn hình
        int endX = (int) (size.width * 0.2);
        int endY = startY; // Giữ nguyên độ cao
        int duration = 200;

        swipe(startX, startY, endX, endY, duration);
    }

    public static void swipeRight() {
        Dimension size = getDriver().manage().window().getSize();
        int startX = (int) (size.width * 0.2);
        //int startY = size.height / 2; // Ở chính giữa màn hình
        int startY = (int) (size.height * 0.3); // 1/3 bên trên màn hình
        int endX = (int) (size.width * 0.8);
        int endY = startY; // Giữ nguyên độ cao
        int duration = 200;

        swipe(startX, startY, endX, endY, duration);
    }

    private static Point getCenterOfElement(Point location, Dimension size) {
        return new Point(location.getX() + size.getWidth() / 2,
                location.getY() + size.getHeight() / 2);
    }

    public static void tap(WebElement element) {
        Point location = element.getLocation();
        Dimension size = element.getSize();
        Point centerOfElement = getCenterOfElement(location, size);
        PointerInput finger = new PointerInput(PointerInput.Kind.TOUCH, "finger");
        Sequence sequence = new Sequence(finger, 1)
                .addAction(finger.createPointerMove(Duration.ZERO, PointerInput.Origin.viewport(), centerOfElement))
                .addAction(finger.createPointerDown(PointerInput.MouseButton.LEFT.asArg()))
                .addAction(new Pause(finger, Duration.ofMillis(500)))
                .addAction(finger.createPointerUp(PointerInput.MouseButton.LEFT.asArg()));

        getDriver().perform(Collections.singletonList(sequence));
    }

    public static void tap(int x, int y) {
        PointerInput finger = new PointerInput(PointerInput.Kind.TOUCH, "finger");
        Sequence tap = new Sequence(finger, 1);
        tap.addAction(finger.createPointerMove(Duration.ZERO, PointerInput.Origin.viewport(), x, y));
        tap.addAction(finger.createPointerDown(PointerInput.MouseButton.LEFT.asArg()));
        tap.addAction(new Pause(finger, Duration.ofMillis(200))); //Chạm nhẹ nhanh
        tap.addAction(finger.createPointerUp(PointerInput.MouseButton.LEFT.asArg()));
        getDriver().perform(Arrays.asList(tap));
    }

    public static void tap(int x, int y, int second) {
        PointerInput finger = new PointerInput(PointerInput.Kind.TOUCH, "finger");
        Sequence tap = new Sequence(finger, 1);
        tap.addAction(finger.createPointerMove(Duration.ZERO, PointerInput.Origin.viewport(), x, y));
        tap.addAction(finger.createPointerDown(PointerInput.MouseButton.LEFT.asArg()));
        tap.addAction(new Pause(finger, Duration.ofMillis(second))); //Chạm vào với thời gian chỉ định
        tap.addAction(finger.createPointerUp(PointerInput.MouseButton.LEFT.asArg()));
        getDriver().perform(Arrays.asList(tap));
    }

    public static void zoom(WebElement element, double scale) {
        int centerX = element.getLocation().getX() + element.getSize().getWidth() / 2;
        int centerY = element.getLocation().getY() + element.getSize().getHeight() / 2;
        int distance = 100; // Khoảng cách giữa hai ngón tay

        PointerInput finger1 = new PointerInput(PointerInput.Kind.TOUCH, "finger1");
        PointerInput finger2 = new PointerInput(PointerInput.Kind.TOUCH, "finger2");

        Sequence zoom = new Sequence(finger1, 1);
        zoom.addAction(finger1.createPointerMove(Duration.ofMillis(0), PointerInput.Origin.viewport(), centerX - distance, centerY));
        zoom.addAction(finger1.createPointerDown(0));

        Sequence zoom2 = new Sequence(finger2, 1);
        zoom2.addAction(finger2.createPointerMove(Duration.ofMillis(0), PointerInput.Origin.viewport(), centerX + distance, centerY));
        zoom2.addAction(finger2.createPointerDown(0));

        if (scale > 1) { // Phóng to
            for (int i = 0; i < 10; i++) {
                zoom.addAction(finger1.createPointerMove(Duration.ofMillis(50), PointerInput.Origin.viewport(), centerX - distance / 2, centerY));
                zoom2.addAction(finger2.createPointerMove(Duration.ofMillis(50), PointerInput.Origin.viewport(), centerX + distance / 2, centerY));
            }
        } else { // Thu nhỏ
            for (int i = 0; i < 10; i++) {
                zoom.addAction(finger1.createPointerMove(Duration.ofMillis(50), PointerInput.Origin.viewport(), centerX - distance * 2, centerY));
                zoom2.addAction(finger2.createPointerMove(Duration.ofMillis(50), PointerInput.Origin.viewport(), centerX + distance * 2, centerY));
            }
        }

        zoom.addAction(finger1.createPointerUp(0));
        zoom2.addAction(finger2.createPointerUp(0));

        getDriver().perform(Arrays.asList(zoom, zoom2));
    }

    public static void scroll(int startX, int startY, int endX, int endY, int durationMillis) {
        PointerInput finger = new PointerInput(PointerInput.Kind.TOUCH, "finger");
        Sequence swipe = new Sequence(finger, 1);
        swipe.addAction(finger.createPointerMove(Duration.ofMillis(0), PointerInput.Origin.viewport(), startX, startY));
        swipe.addAction(finger.createPointerDown(0));
        swipe.addAction(finger.createPointerMove(Duration.ofMillis(durationMillis), PointerInput.Origin.viewport(), endX, endY));
        swipe.addAction(finger.createPointerUp(0));
        getDriver().perform(Collections.singletonList(swipe));
    }

    public static void scrollGestureCommand() {
        // Scroll gesture cho Android
        Map<String, Object> scrollParams = new HashMap<>();
        scrollParams.put("left", 670); //vị trí mép trái vùng cuộn cách mép trái màn hình
        scrollParams.put("top", 500); //xác định mép trên của vùng cuộn
        scrollParams.put("width", 200); //chiều ngang của vùng kéo
        scrollParams.put("height", 2000); //chiều dài của vùng kéo
        scrollParams.put("direction", "down"); //Scroll theo chiều từ trên xuống dưới (up, down, left, right)
        scrollParams.put("percent", 1); //Scroll 100% của vùng kéo được chỉ định (width, height)

        // Thực hiện scroll gesture
        getDriver().executeScript("mobile: scrollGesture", scrollParams);
    }

    // Chờ phần tử có thể click (dùng By locator)
    public static WebElement waitForElementToBeClickable(By locator, int timeout) {
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(timeout));
        return wait.until(ExpectedConditions.elementToBeClickable(locator));
    }

    // Chờ phần tử có thể click (dùng WebElement)
    public static WebElement waitForElementToBeClickable(WebElement element, int timeout) {
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(timeout));
        return wait.until(ExpectedConditions.elementToBeClickable(element));
    }

    // Chờ phần tử xuất hiện trên màn hình
    public static WebElement waitForElementVisibe(By locator, int timeout) {
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(timeout));
        return wait.until(ExpectedConditions.visibilityOfElementLocated(locator));
    }

    public static WebElement waitForElementVisibe(WebElement element, int timeout) {
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(timeout));
        return wait.until(ExpectedConditions.visibilityOf(element));
    }

    // Chờ phần tử biến mất khỏi màn hình
    public static boolean waitForElementInvisibe(By locator, int timeout) {
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(timeout));
        return wait.until(ExpectedConditions.invisibilityOfElementLocated(locator));
    }

    // Chờ phần tử tồn tại trong DOM
    public static WebElement waitForElementPresent(By locator, int timeout) {
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(timeout));
        return wait.until(ExpectedConditions.presenceOfElementLocated(locator));
    }

    // Chờ phần tử có một text cụ thể
    public static boolean waitForTextToBePresent(By locator, String text, int timeout) {
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(timeout));
        return wait.until(ExpectedConditions.textToBePresentInElementLocated(locator, text));
    }

    public static boolean waitForTextToBePresent(WebElement element, String text, int timeout) {
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(timeout));
        return wait.until(ExpectedConditions.textToBePresentInElement(element, text));
    }

    // Chờ phần tử có một thuộc tính cụ thể
    public static boolean waitForAttributeToBe(By locator, String attribute, String value, int timeout) {
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(timeout));
        return wait.until(ExpectedConditions.attributeToBe(locator, attribute, value));
    }

    public static boolean waitForAttributeToBe(WebElement element, String attribute, String value, int timeout) {
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(timeout));
        return wait.until(ExpectedConditions.attributeToBe(element, attribute, value));
    }

    // Chờ số lượng phần tử trong danh sách
    public static List<WebElement> waitForNumberOfElements(By locator, int expectedCount, int timeout) {
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(timeout));
        return wait.until(ExpectedConditions.numberOfElementsToBe(locator, expectedCount));
    }

    // Chờ đến khi URL chứa một đoạn text cụ thể (dành cho hybrid/web app)
    public static boolean waitForUrlContains(String text, int timeout) {
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(timeout));
        return wait.until(ExpectedConditions.urlContains(text));
    }

    // Chờ đến khi số lượng cửa sổ mở ra đạt một số lượng nhất định (dành cho hybrid/web app)
    public static boolean waitForNumberOfWindows(int expectedWindows, int timeout) {
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(timeout));
        return wait.until(ExpectedConditions.numberOfWindowsToBe(expectedWindows));
    }


}


🚀 Sử dụng các hàm wait từ class MobileUI

Lấy tên class MobileUI chấm gọi tên hàm trực tiếp mà không cần khởi tạo đối tượng class.

//Ví dụ 1: Chờ một nút có thể click và bấm vào nó
WebElement loginButton = MobileUI.waitForElementToBeClickable(By.id("com.example:id/button_login"), 5);
loginButton.click();

//Ví dụ 2: Chờ cho phần tử hiển thị rồi lấy text
WebElement welcomeText = MobileUI.waitForVisibility(By.id("com.example:id/welcome_message"), 7);
System.out.println("Welcome message: " + welcomeText.getText());

Teacher

Teacher

Anh Tester

Software Quality Engineer

Đườ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

Cộng đồng Automation Testing Việt Nam:

🌱 Telegram Automation Testing:   Cộng đồng Automation Testing
🌱 
Facebook Group Automation: Cộng đồng Automation Testing Việt Nam
🌱 
Facebook Fanpage: Cộng đồng Automation Testing Việt Nam - Selenium
🌱 Telegram
Manual Testing:   Cộng đồng Manual Testing
🌱 
Facebook Group Manual: Cộng đồng Manual Testing Việt Nam

Chia sẻ khóa học lên trang

Bạn có thể đăng khóa học của chính bạn lên trang Anh Tester để kiếm tiền

Danh sách bài học