NỘI DUNG BÀI HỌC

✅ Đọc data từ Properties file để làm giá trị config toàn cục
    🔆 Xây dựng class tĩnh để tạo biến data config
    🔆 Sử dụng config data trong BaseTest và các class khác
✅ Ghi data vào Properties file

Trong Java, file .properties là một loại tệp cấu hình đặc trưng được hỗ trợ trực tiếp bởi Java dạng văn bản đơn giản chứa các cặp key-value (khóa-giá trị), thường dùng để lưu:

  • Cấu hình ứng dụng (ví dụ: đường dẫn file, địa chỉ server, thông tin kết nối CSDL, v.v.)

  • Thông tin người dùng, tùy chọn khởi chạy, v.v.


Nếu đang làm automation (như Selenium, Appium...) thì file .properties rất tiện để lưu thông tin như URL, thông tin đăng nhập, config đa môi trường, v.v.

✅ Đọc data từ Properties file để làm giá trị config toàn cục

1. Tạo tệp có phần mở rộng là .properties

Trong dự án của bạn, nhấp chuột phải vào src/test và tạo directory tên resources, tạo thêm thư mục con tên configs. Sau đó tạo một tệp mới với phần mở rộng là .properties ví dụ config.properties trong thư mục configs.

Lưu ý: nên lưu file properties ở package test (src/test/)

Tiếp theo khởi tạo giá trị trong file properties theo cấu trúc "key"="value" như bên dưới.

#Config enable Appium Server on local machine
APPIUM_DRIVER_LOCAL_SERVICE = true
#Set timeout for appium service
TIMEOUT_SERVICE = 60
#Set timeout for Explicit wait
TIMEOUT_EXPLICIT_DEFAULT = 10
#Set data/config/report path
JSON_CONFIG_FILE_PATH = src/test/resources/configs/device.json
JSON_DATA_FILE_PATH = src/test/resources/test_data/data.json
EXCEL_DATA_FILE_PATH =
TEST_DATA_FOLDER_PATH = src/test/resources/test_data
LOCATE = en
SCREENSHOT_FAIL = true
SCREENSHOT_PASS = true
SCREENSHOT_ALL = true
SCREENSHOT_PATH = exports/screenshots
RECORD_VIDEO = true
RECORD_VIDEO_PATH = exports/videos
EXTENT_REPORT_PATH = exports/reports/ExtentReport
ALLURE_REPORT_PATH = exports/reports/AllureReport

 

2. Tạo một class Java để xây dựng hàm Get và Set properties file

Tạo một class SystemHelpers đặt trong package helpers để lưu các hàm xử lý bổ trợ cho các class khác.

package com.anhtester.helpers;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.Normalizer;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.regex.Pattern;

public class SystemHelpers {

    private static final Pattern NONLATIN = Pattern.compile("[^\\w-]");
    private static final Pattern WHITESPACE = Pattern.compile("[\\s]");

    public static String makeSlug(String input) {
        if (input == null)
            throw new IllegalArgumentException();

        String noWhiteSpace = WHITESPACE.matcher(input).replaceAll("_");
        String normalized = Normalizer.normalize(noWhiteSpace, Normalizer.Form.NFD);
        String slug = NONLATIN.matcher(normalized).replaceAll("");
        return slug.toLowerCase(Locale.ENGLISH);
    }

    /**
     * @return Get the path to your source directory with a / at the end
     */
    public static String getCurrentDir() {
        String current = System.getProperty("user.dir") + File.separator;
        return current;
    }

    public static boolean createFolder(String path) {
        try {
            File folder = new File(path);

            // Kiểm tra xem đã tồn tại và có phải là folder không
            if (folder.exists() && folder.isDirectory()) {
                System.out.println("Folder đã tồn tại: " + path);
                return false;
            }

            // Tạo folder và các thư mục cha
            boolean created = folder.mkdirs();

            if (created) {
                System.out.println("Tạo folder thành công: " + path);
            } else {
                System.out.println("Tạo folder thất bại: " + path);
            }

            return created;
        } catch (Exception e) {
            System.out.println("Lỗi khi tạo folder: " + e.getMessage());
            return false;
        }
    }

    /**
     * @param str        string to be split based on condition
     * @param valueSplit the character to split the string into an array of values
     * @return array of string values after splitting
     */
    public static ArrayList<String> splitString(String str, String valueSplit) {
        ArrayList<String> arrayListString = new ArrayList<>();
        for (String s : str.split(valueSplit, 0)) {
            arrayListString.add(s);
        }
        return arrayListString;
    }

    public static boolean checkValueInListString(String expected, String listValues[]) {
        boolean found = false;

        for (String s : listValues) {
            if (s.equals(expected)) {
                found = true;
                break;
            }
        }
        return found;
    }

    public static boolean checkValueInListString(String expected, List<String> listValues) {
        boolean found = false;

        for (String s : listValues) {
            if (s.equals(expected)) {
                found = true;
                break;
            }
        }
        return found;
    }

    public static void killProcessOnPort(String port) {
        String command = "";

        // Check OS to set command to find and kill process
        if (System.getProperty("os.name").toLowerCase().contains("win")) {
            command = "cmd /c netstat -ano | findstr :" + port;
        } else {
            command = "lsof -i :" + port;
        }

        try {
            Process process = Runtime.getRuntime().exec(command);
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line;
            while ((line = reader.readLine()) != null) {
                String[] tokens = line.trim().split("\\s+");
                String pid = tokens[1];  // PID position may vary by OS
                if (System.getProperty("os.name").toLowerCase().contains("win")) {
                    Runtime.getRuntime().exec("taskkill /F /PID " + pid);
                } else {
                    Runtime.getRuntime().exec("kill -9 " + pid);
                }
            }
            reader.close();
            process.waitFor();
            System.out.println("####### Kill process on port " + port + " successfully.");
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void startAppiumWithPlugins(String server, String port) {
        ProcessBuilder processBuilder = new ProcessBuilder(
                "appium",
                "-a", server,
                "-p", port,
                "-ka", "800",
                "--use-plugins", "appium-reporter-plugin,element-wait,gestures,device-farm,appium-dashboard",
                "-pa", "/",
                "--plugin-device-farm-platform", "android"
        );

        // Redirect error and output streams
        processBuilder.redirectErrorStream(true);

        try {
            // Start the process
            Process process = processBuilder.start();
            System.out.println("Appium server started with plugins.");

            // Optional: Read the output (if needed for debugging)
            new Thread(() -> {
                try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                    String line;
                    while ((line = reader.readLine()) != null) {
                        System.out.println(line);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }).start();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}


Dùng File.separator thay thế cho ký tự đường dẫn trong máy tính, nó tự hiểu Window, MacOSLinux luôn.

public static String getCurrentDir() {
    String current = System.getProperty("user.dir") + File.separator;
    return current;
}


Tiếp theo tạo class tên PropertiesHelpers đặt cùng package helpers.

package com.anhtester.helpers;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.LinkedList;
import java.util.Properties;

public class PropertiesHelpers {

    private static Properties properties;
    private static String linkFile;
    private static FileInputStream file;
    private static FileOutputStream out;
    private static String relPropertiesFilePathDefault = "src/test/resources/configs/config.properties";

    public static Properties loadAllFiles() {
        LinkedList<String> files = new LinkedList<>();
        // Add tất cả file Properties vào đây theo mẫu
        files.add("src/test/resources/configs/config.properties");
        // files.add("src/test/resources/configs/local.properties");
        // files.add("src/test/resources/configs/production.properties");

        try {
            properties = new Properties();

            for (String f : files) {
                Properties tempProp = new Properties();
                linkFile = SystemHelpers.getCurrentDir() + f;
                file = new FileInputStream(linkFile);
                tempProp.load(file);
                properties.putAll(tempProp);
            }
            return properties;
        } catch (IOException ioe) {
            return new Properties();
        }
    }

    public static void setFile(String relPropertiesFilePath) {
        properties = new Properties();
        try {
            linkFile = SystemHelpers.getCurrentDir() + relPropertiesFilePath;
            file = new FileInputStream(linkFile);
            properties.load(file);
            file.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void setDefaultFile() {
        properties = new Properties();
        try {
            linkFile = SystemHelpers.getCurrentDir() + relPropertiesFilePathDefault;
            file = new FileInputStream(linkFile);
            properties.load(file);
            file.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static String getValue(String key) {
        String value = null;
        try {
            if (file == null) {
                properties = new Properties();
                linkFile = SystemHelpers.getCurrentDir() + relPropertiesFilePathDefault;
                file = new FileInputStream(linkFile);
                properties.load(file);
                file.close();
            }
            // Lấy giá trị từ file đã Set
            value = properties.getProperty(key);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        return value;
    }

    public static void setValue(String key, String keyValue) {
        try {
            if (file == null) {
                properties = new Properties();
                file = new FileInputStream(SystemHelpers.getCurrentDir() + relPropertiesFilePathDefault);
                properties.load(file);
                file.close();
                out = new FileOutputStream(SystemHelpers.getCurrentDir() + relPropertiesFilePathDefault);
            }
            //Ghi vào cùng file Prop với file lấy ra
            out = new FileOutputStream(linkFile);
            System.out.println(linkFile);
            properties.setProperty(key, keyValue);
            properties.store(out, null);
            out.close();
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

    public static void setValue(String filePropertiesRelativePath, String key, String keyValue) {
        try {

            Properties properties = new Properties();
            FileInputStream file = new FileInputStream(SystemHelpers.getCurrentDir() + filePropertiesRelativePath);
            properties.load(file);
            file.close();

            properties.setProperty(key, keyValue);
            FileOutputStream out = new FileOutputStream(SystemHelpers.getCurrentDir() + filePropertiesRelativePath);
            properties.store(out, null);
            out.close();
            System.out.println("Set value '" + keyValue + "' to file " + filePropertiesRelativePath);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

}

 

- Hàm loadAllFiles() để khởi tạo giá trị cho nhiều file properties setup sẵn trong hàm.

- Hàm
setFile() để khởi tạo giá trị cho đối tượng properties của class Properties với đường dẫn đến file configs.properties trên.

- Hàm setDefaultFile() để khởi tạo giá trị cho đối tượng properties của class Properties với đường dẫn có sẵn trong class.

- Hàm getValue() để đọc file đã setup bên trên và lấy giá trị ra theo Key trong file đã tạo.

- Hàm setValue() để gán ngược giá trị với key tương ứng vào lại file properties trên.

Chúng ta có thể chỉ định file properties default để khi nếu không có chỉ định cụ thể thì nó sẽ hiểu là lấy ra file default này:

private static String relPropertiesFilePathDefault = "src/test/resources/configs/config.properties";


Rồi tiếp theo là gọi hàm ra sử dụng để làm giá trị config toàn cục.

3. Cách đọc giá trị từ properties file

Các bạn nhìn thấy các hàm trong class PropertiesHelpers có trạng thái là static nên khi mình gọi thì lấy luôn cái tên class để chấm gọi chứ không cần khởi tạo đối tượng của class.

Muốn sử dụng Properties file để đọc ghi giá trị thì đầu tiên phải gọi hàm loadAllFiles() để khởi tạo tất cả các file properties chỉ định trong chính hàm này.

PropertiesHelpers.loadAllFiles();

Còn nếu muốn chỉ định ở file cụ thể thì gọi hàm setFile(file path).

Hiện tại đang trong TestNG Famework nên là cái này chúng ta để ở @BeforeClass hoặc @BeforeTest hay @BeforeSuite gì cũng được. Nghĩa là cho nó chạy trước cái hàm getValue() là được.

Tiếp theo muốn ĐỌC giá trị từ file properties thì gọi hàm getValue()

PropertiesHelpers.getValue("APPIUM_DRIVER_LOCAL_SERVICE")
PropertiesHelpers.getValue("TIMEOUT_SERVICE")
PropertiesHelpers.getValue("TIMEOUT_EXPLICIT_DEFAULT")

Cái giá trị "APPIUM_DRIVER_LOCAL_SERVICE" là cái key trong file properties. Cụ thể giá trị của key "APPIUM_DRIVER_LOCAL_SERVICE" là "true" đã tạo bên trên.

Cái tên key có PHÂN BIỆT HOA THƯỜNG trong Java.

Chú ý: tất cả giá trị trong Properties file sẽ trả về kết quả kiểu String thôi nhé, nếu có số thì cần convert sang number. Ví dụ giá trị PORT hoặc TIMEOUT. Còn giá trị true/false thì cũng là dạng String.


🔆 Xây dựng class tĩnh để tạo biến data config

Thay vì mỗi lần gọi giá trị từ Properties file các bạn phải gọi lại key thì giờ chúng ta cho nó lưu vào biến tĩnh static trong một class cố định để gọi đi gọi lại sử dụng cho tiện hơn. Không cần nhớ tên Key mỗi lần gọi.

🚀 Lợi ích khi sử dụng biến tĩnh lưu data config:

  1. Tối ưu hiệu suất

    • Chỉ cần đọc cấu hình một lần khi class được tải lần đầu tiên.

    • Tiết kiệm thời gian và tài nguyên khi chạy các test case.

  2. Tăng tính bảo trì

    • Khi cấu hình cần thay đổi, chỉ cần chỉnh sửa một lần duy nhất tại file properties hoặc JSON.

    • Code của em sẽ sạch, rõ ràng và dễ dàng quản lý.

  3. Độ linh hoạt cao

    • Dễ dàng chuyển đổi môi trường test (như platform, device, IP, Port) bằng cách chỉnh sửa thông số trong properties hoặc JSON mà không ảnh hưởng đến code chính.

  4. Giảm rủi ro trong quá trình phát triển và vận hành

    • Do các cấu hình được load sớm, việc thiếu hoặc sai sót cấu hình sẽ sớm được phát hiện, giảm thiểu rủi ro runtime.

  5. Code dễ đọc và rõ ràng hơn

    • Các giá trị cấu hình được tập trung tại một nơi, giúp code các test script dễ hiểu và minh bạch hơn.

 

❌ Nhược điểm khi dùng 1 config chung

Với cách sử dụng các biến static trong class ConfigData như trên thì thực sự không phù hợp cho việc chạy song song (parallel execution) với nhiều thiết bị cùng lúc, lý do là các biến static sẽ là biến toàn cục và dùng chung giữa các luồng (threads).


⚠️ Lý do không chạy được song song

  • Biến static là dùng chung giữa các threads.

  • Khi một thread thay đổi biến, thread khác cũng bị ảnh hưởng theo.

  • Dẫn tới xung đột dữ liệu (data conflict), sai lệch kết quả test.


Nên chỗ này chỉ tạo biến tĩnh đối với các giá trị chung như timeout, đường dẫn, bật tắt chụp hình, video, report path,...
Không tạo các biến giá trị riêng như Device name, Host, Port,...Vì mấy này cần độc lập và riêng cho mỗi test cases khi truyền vào thì nó mới chạy song song được nhiều loại thiết bị khác nhau.

⭐️ Triển khai class chung lưu biến tĩnh

Tạo class tên ConfigData trong package constants (src/main/java/com/anhtester/constants) để khai báo các biến tĩnh tương ứng với các key trong properties file.



package com.anhtester.constants;

import com.anhtester.helpers.PropertiesHelpers;
import com.anhtester.helpers.SystemHelpers;

import java.io.File;
import java.io.IOException;

public class ConfigData {

    private ConfigData() {
        // Ngăn chặn khởi tạo class
    }

    // Load all properties files
    static {
        PropertiesHelpers.loadAllFiles();
    }

    public static final String PROJECT_PATH = SystemHelpers.getCurrentDir();
    public static final String EXCEL_DATA_FILE_PATH = PropertiesHelpers.getValue("EXCEL_DATA_FILE_PATH");
    public static final String JSON_DATA_FILE_PATH = PropertiesHelpers.getValue("JSON_DATA_FILE_PATH");
    public static final String JSON_CONFIG_FILE_PATH = PropertiesHelpers.getValue("JSON_CONFIG_FILE_PATH");
    public static final String TEST_DATA_FOLDER_PATH = PropertiesHelpers.getValue("TEST_DATA_FOLDER_PATH");
    public static final String LOCATE = PropertiesHelpers.getValue("LOCATE");
    public static final String TIMEOUT_SERVICE = PropertiesHelpers.getValue("TIMEOUT_SERVICE");
    public static final String TIMEOUT_EXPLICIT_DEFAULT = PropertiesHelpers.getValue("TIMEOUT_EXPLICIT_DEFAULT");
    public static final String APPIUM_DRIVER_LOCAL_SERVICE = PropertiesHelpers.getValue("APPIUM_DRIVER_LOCAL_SERVICE");

}


Hàm xây dựng để private là mục đích không cho khởi tạo đối tượng class. Thay vào đó muốn gọi sử dụng thì lấy tên class chấm gọi. (ConfigData.TIMEOUT_SERVICE)

 

🔆 Gọi ConfigData class sử dụng tại class BaseTest

package com.anhtester.common;

import com.anhtester.constants.ConfigData;
import com.anhtester.drivers.DriverManager;
import com.anhtester.helpers.SystemHelpers;
import com.anhtester.keywords.MobileUI;
import io.appium.java_client.AppiumBy;
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 io.appium.java_client.service.local.AppiumDriverLocalService;
import io.appium.java_client.service.local.AppiumServiceBuilder;
import io.appium.java_client.service.local.flags.GeneralServerFlag;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Optional;
import org.testng.annotations.Parameters;

import java.net.URL;
import java.time.Duration;
import java.util.Objects;

public class BaseTest {

    private AppiumDriverLocalService service;
    private String HOST = "127.0.0.1";
    private String PORT = "4723";
    private int TIMEOUT_SERVICE = 60;

    /**
     * Chạy Appium server với host và port được chỉ định.
     *
     * @param host Địa chỉ host của Appium server
     * @param port Port của Appium server
     */
    public void runAppiumServer(String host, String port) {
        System.out.println("HOST: " + host);
        System.out.println("PORT: " + port);

        //Set host and port
        if (host == null || host.isEmpty()) {
            host = HOST;
        } else {
            HOST = host;
        }

        if (port == null || port.isEmpty()) {
            port = PORT;
        } else {
            PORT = port;
        }

        TIMEOUT_SERVICE = Integer.parseInt(ConfigData.TIMEOUT_SERVICE);

        //Kill process on port
        SystemHelpers.killProcessOnPort(PORT);

        //Build the Appium service
        AppiumServiceBuilder builder = new AppiumServiceBuilder();
        builder.withIPAddress(HOST);
        builder.usingPort(Integer.parseInt(PORT));
        builder.withArgument(GeneralServerFlag.LOG_LEVEL, "info"); // Set log level (optional)
        builder.withTimeout(Duration.ofSeconds(TIMEOUT_SERVICE));

        //Start the server with the builder
        service = AppiumDriverLocalService.buildService(builder);
        service.start();

        if (service.isRunning()) {
            System.out.println("##### Appium server started on " + HOST + ":" + PORT);
        } else {
            System.out.println("Failed to start Appium server on LOCAL.");
        }

    }

    /**
     * Thiết lập (khởi tạo và lưu trữ) AppiumDriver cho luồng hiện tại.
     *
     * @param platformName         Tên platform (Android/iOS)
     * @param platformVersion      Phiên bản platform
     * @param deviceName           Tên thiết bị
     * @param udid                 UDID của thiết bị (quan trọng cho parallel)
     * @param automationName       Tên automation engine (UiAutomator2/XCUITest)
     * @param appPackage           Package của app Android
     * @param appActivity          Activity của app Android
     * @param noReset              Không reset app trước khi chạy
     * @param fullReset            Reset app trước khi chạy
     * @param autoGrantPermissions Tự động cấp quyền cho app
     * @param host                 Địa chỉ host của Appium server
     * @param port                 Port của Appium server
     * @param bundleId             Bundle ID của app iOS
     * @param wdaLocalPort         Port WDA (iOS parallel)
     * @param systemPort           Port System (Android parallel)
     */
    @BeforeMethod(alwaysRun = true)
    @Parameters({"platformName", "platformVersion", "deviceName", "udid", "automationName", "appPackage", "appActivity", "noReset", "fullReset", "autoGrantPermissions", "host", "port", "bundleId", "wdaLocalPort", "systemPort"})
    public void setUpDriver(String platformName, String platformVersion, String deviceName, @Optional String udid, @Optional String automationName, @Optional String appPackage, @Optional String appActivity, boolean noReset, boolean fullReset, boolean autoGrantPermissions, String host, String port, @Optional String bundleId, @Optional String wdaLocalPort, @Optional String systemPort) {
        //Khởi động Appium server dưới máy local
        if (ConfigData.APPIUM_DRIVER_LOCAL_SERVICE.trim().equalsIgnoreCase("true")) {
            System.out.println("Khởi động Appium server LOCAL: " + host + ":" + port);
            runAppiumServer(host, port);
        } else {
            System.out.println("Chạy Appium server từ xa hoặc đã bật sẵn.");
        }

        //Print tất cả các thông số
        System.out.println("platformName: " + platformName);
        System.out.println("platformVersion: " + platformVersion);
        System.out.println("deviceName: " + deviceName);
        System.out.println("udid: " + udid);
        System.out.println("automationName: " + automationName);
        System.out.println("appPackage: " + appPackage);
        System.out.println("appActivity: " + appActivity);
        System.out.println("noReset: " + noReset);
        System.out.println("fullReset: " + fullReset);
        System.out.println("autoGrantPermissions: " + autoGrantPermissions);
        System.out.println("host: " + host);
        System.out.println("port: " + port);
        System.out.println("bundleId: " + bundleId);
        System.out.println("wdaLocalPort: " + wdaLocalPort);
        System.out.println("systemPort: " + systemPort);

        AppiumDriver driver = null;

        try {
            if (platformName.equalsIgnoreCase("Android")) {
                UiAutomator2Options options = new UiAutomator2Options();
                options.setPlatformName(platformName);
                options.setPlatformVersion(platformVersion);
                options.setDeviceName(deviceName);
                if (udid != null && !udid.isEmpty()) {
                    options.setUdid(udid);
                }
                if (appPackage != null && !appPackage.isEmpty()) {
                    options.setAppPackage(appPackage);
                }
                if (appActivity != null && !appActivity.isEmpty()) {
                    options.setAppActivity(appActivity);
                }
                // options.setApp("/path/to/your/app.apk");
                options.setAutomationName(Objects.requireNonNullElse(automationName, "UiAutomator2"));
                options.setNoReset(noReset);
                options.setFullReset(fullReset);
                if (systemPort != null && !systemPort.isEmpty()) {
                    options.setSystemPort(Integer.parseInt(systemPort));
                }

                driver = new AndroidDriver(new URL("http://" + host + ":" + port), options);
                System.out.println("Khởi tạo AndroidDriver cho thread: " + Thread.currentThread().getId() + " trên thiết bị: " + deviceName);


            } else if (platformName.equalsIgnoreCase("iOS")) {
                XCUITestOptions options = new XCUITestOptions();
                options.setPlatformName(platformName);
                options.setPlatformVersion(platformVersion);
                options.setDeviceName(deviceName);
                // options.setApp("/path/to/your/app.app or .ipa");
                if (bundleId != null && !bundleId.isEmpty()) {
                    options.setBundleId(bundleId);
                }
                options.setAutomationName(Objects.requireNonNullElse(automationName, "XCUITest"));
                options.setNoReset(false);
                options.setFullReset(false);
                if (wdaLocalPort != null && !wdaLocalPort.isEmpty()) {
                    options.setWdaLocalPort(Integer.parseInt(wdaLocalPort));
                }
                // options.setXcodeOrgId("YOUR_TEAM_ID");
                // options.setXcodeSigningId("iPhone Developer");

                driver = new IOSDriver(new URL("http://" + host + ":" + port), options);
                System.out.println("Khởi tạo IOSDriver cho thread: " + Thread.currentThread().getId() + " trên thiết bị: " + deviceName);

            } else {
                throw new IllegalArgumentException("Platform không hợp lệ: " + platformName);
            }

            // Lưu driver vào ThreadLocal
            DriverManager.setDriver(driver);

        } catch (Exception e) {
            System.err.println("❌Lỗi nghiêm trọng khi khởi tạo driver cho thread " + Thread.currentThread().getId() + " trên device " + deviceName + ": " + e.getMessage());
            // Có thể ném lại lỗi để TestNG biết test setup thất bại
            throw new RuntimeException("❌Không thể khởi tạo Appium driver ", e);
        }

    }

    @AfterMethod(alwaysRun = true)
    public void tearDownDriver() {
        if (DriverManager.getDriver() != null) {
            DriverManager.quitDriver();
            System.out.println("##### Driver quit and removed.");
        }

        //Dừng Appium server LOCAL nếu đã khởi động
        if (ConfigData.APPIUM_DRIVER_LOCAL_SERVICE.trim().equalsIgnoreCase("true")) {
            stopAppiumServer();
        }
    }

    /**
     * Stop Appium server.
     */
    public void stopAppiumServer() {
        if (service != null && service.isRunning()) {
            service.stop();
            System.out.println("##### Appium server stopped on " + HOST + ":" + PORT);
        }
        //Kill process on port
        SystemHelpers.killProcessOnPort(PORT);
    }

    /**
     * Tải xuống dữ liệu từ server. Chỉ dành cho Taurus App.
     *
     * @param dataNumber Số thứ tự của dữ liệu cần tải xuống
     */
    public void downloadDataFromServer(int dataNumber) {
        //Navigate to config to download database demo
        DriverManager.getDriver().findElement(AppiumBy.accessibilityId("Config")).click();
        DriverManager.getDriver().findElement(AppiumBy.accessibilityId("Server database")).click();
        MobileUI.sleep(2);
        DriverManager.getDriver().findElement(AppiumBy.xpath("//android.view.View[contains(@content-desc,'Data " + dataNumber + "')]/android.widget.Button")).click();
        DriverManager.getDriver().findElement(AppiumBy.accessibilityId("Replace")).click();
        MobileUI.sleep(1);

        //Handle Alert Message, check displayed hoặc getText/getAttribute để kiểm tra nội dung message
        if (DriverManager.getDriver().findElement(AppiumBy.accessibilityId("Downloaded")).isDisplayed()) {
            System.out.println("Database demo downloaded.");
        } else {
            System.out.println("Warning!! Can not download Database demo.");
        }
        MobileUI.sleep(2);
        DriverManager.getDriver().findElement(AppiumBy.accessibilityId("Back")).click();
    }
}


Trong class BaseTest thì chúng ta chú ý thầy có dùng biến config tại hàm setUpDriver, tearDownDriver:

//Khởi động Appium server dưới máy local
if (ConfigData.APPIUM_DRIVER_LOCAL_SERVICE.trim().equalsIgnoreCase("true")) {
    System.out.println("Khởi động Appium server LOCAL: " + host + ":" + port);
    runAppiumServer(host, port);
} else {
    System.out.println("Chạy Appium server từ xa hoặc đã bật sẵn.");
}
//Dừng Appium server LOCAL nếu đã khởi động
if (ConfigData.APPIUM_DRIVER_LOCAL_SERVICE.trim().equalsIgnoreCase("true")) {
    stopAppiumServer();
}

Biến TIMEOUT_SERVICE

TIMEOUT_SERVICE = Integer.parseInt(ConfigData.TIMEOUT_SERVICE);


🔆 Gọi ConfigData class sử dụng tại các class khác như MobileUI, Report, Mail,...sau này dùng thêm bổ sung từ từ.

Sử dụng biến timeout trong class MobileUI

package com.anhtester.keywords;

import com.anhtester.constants.ConfigData;
import com.anhtester.drivers.DriverManager;
import org.openqa.selenium.*;
import org.openqa.selenium.NoSuchElementException;
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 org.testng.Assert;

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

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

public class MobileUI {

    private static final int DEFAULT_TIMEOUT = Integer.parseInt(ConfigData.TIMEOUT_EXPLICIT_DEFAULT);
    private static final double STEP_ACTION_TIMEOUT = Double.parseDouble(ConfigData.STEP_ACTION_TIMEOUT);

    public static void sleep(double second) {
        System.out.println("[MobileUI] Sleeping for " + second + " seconds.");
        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) {
        sleep(STEP_ACTION_TIMEOUT);
        System.out.println("[MobileUI] Executing swipe from (" + startX + "," + startY + ") to (" + endX + "," + endY + ") with duration " + durationMillis + "ms.");
        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() {
        sleep(STEP_ACTION_TIMEOUT);
        System.out.println("[MobileUI] Executing swipeLeft.");
        Dimension size = getDriver().manage().window().getSize();
        int startX = (int) (size.width * 0.8);
        int startY = (int) (size.height * 0.3);
        int endX = (int) (size.width * 0.2);
        int endY = startY;
        int duration = 200;
        swipe(startX, startY, endX, endY, duration);
    }

    public static void swipeRight() {
        sleep(STEP_ACTION_TIMEOUT);
        System.out.println("[MobileUI] Executing swipeRight.");
        Dimension size = getDriver().manage().window().getSize();
        int startX = (int) (size.width * 0.2);
        int startY = (int) (size.height * 0.3);
        int endX = (int) (size.width * 0.8);
        int endY = startY;
        int duration = 200;
        swipe(startX, startY, endX, endY, duration);
    }

    private static Point getCenterOfElement(Point location, Dimension size) {
        // No log needed for private helper, logging happens in the calling public method
        return new Point(location.getX() + size.getWidth() / 2,
                location.getY() + size.getHeight() / 2);
    }

    public static void tap(WebElement element) {
        sleep(STEP_ACTION_TIMEOUT);
        System.out.println("[MobileUI] Executing tap on element: " + 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))) // Note: Default pause is 500ms here
                .addAction(finger.createPointerUp(PointerInput.MouseButton.LEFT.asArg()));

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

    public static void tap(int x, int y) {
        sleep(STEP_ACTION_TIMEOUT);
        System.out.println("[MobileUI] Executing tap at coordinates (" + x + "," + y + ") with 200ms pause.");
        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 milliSecondDuration) {
        sleep(STEP_ACTION_TIMEOUT);
        System.out.println("[MobileUI] Executing tap at coordinates (" + x + "," + y + ") with pause " + milliSecondDuration + "ms.");
        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(milliSecondDuration))); //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) {
        sleep(STEP_ACTION_TIMEOUT);
        System.out.println("[MobileUI] Executing zoom on element: " + element + " with approximate scale factor: " + scale + " (Note: Implementation may need review for accurate scaling)");
        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));

        // Simplified movement - Actual scaling might need more complex radial movement logic
        int moveDuration = 50;
        int steps = 10;
        int startDist1X = centerX - distance;
        int startDist2X = centerX + distance;
        int endDist1X, endDist2X;

        if (scale > 1) { // Phóng to - Move fingers further apart
            System.out.println("[MobileUI] Zooming In");
            endDist1X = centerX - (int) (distance * scale); // Example: move further left
            endDist2X = centerX + (int) (distance * scale); // Example: move further right
        } else { // Thu nhỏ - Move fingers closer
            System.out.println("[MobileUI] Zooming Out");
            endDist1X = centerX - (int) (distance * scale); // Example: move closer to center
            endDist2X = centerX + (int) (distance * scale); // Example: move closer to center
        }

        for (int i = 1; i <= steps; i++) {
            int currentX1 = startDist1X + (endDist1X - startDist1X) * i / steps;
            int currentX2 = startDist2X + (endDist2X - startDist2X) * i / steps;
            zoom.addAction(finger1.createPointerMove(Duration.ofMillis(moveDuration), PointerInput.Origin.viewport(), currentX1, centerY));
            zoom2.addAction(finger2.createPointerMove(Duration.ofMillis(moveDuration), PointerInput.Origin.viewport(), currentX2, 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) {
        sleep(STEP_ACTION_TIMEOUT);
        System.out.println("[MobileUI] Executing scroll from (" + startX + "," + startY + ") to (" + endX + "," + endY + ") with duration " + durationMillis + "ms.");
        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() {
        sleep(STEP_ACTION_TIMEOUT);
        // Scroll gesture cho Android
        Map<String, Object> scrollParams = new HashMap<>();
        scrollParams.put("left", 670);
        scrollParams.put("top", 500);
        scrollParams.put("width", 200);
        scrollParams.put("height", 2000);
        scrollParams.put("direction", "down");
        scrollParams.put("percent", 1);

        System.out.println("[MobileUI] Executing scrollGesture command with params: " + scrollParams);
        // Thực hiện scroll gesture
        getDriver().executeScript("mobile: scrollGesture", scrollParams);
    }

    public static void clickElement(By locator, int second) {
        sleep(STEP_ACTION_TIMEOUT);
        System.out.println("[MobileUI] Clicking element located by: " + locator + " within " + second + "s.");
        waitForElementToBeClickable(locator, second).click();
    }

    public static void clickElement(By locator) {
        sleep(STEP_ACTION_TIMEOUT);
        System.out.println("[MobileUI] Clicking element located by: " + locator + " within default timeout (" + DEFAULT_TIMEOUT + "s).");
        waitForElementToBeClickable(locator).click();
    }

    public static void clickElement(WebElement element, int second) {
        sleep(STEP_ACTION_TIMEOUT);
        System.out.println("[MobileUI] Clicking element: " + element + " within " + second + "s.");
        waitForElementToBeClickable(element, second).click();
    }

    public static void clickElement(WebElement element) {
        sleep(STEP_ACTION_TIMEOUT);
        System.out.println("[MobileUI] Clicking element: " + element + " within default timeout (" + DEFAULT_TIMEOUT + "s).");
        waitForElementToBeClickable(element).click();
    }

    public static void setText(By locator, String text) {
        sleep(STEP_ACTION_TIMEOUT);
        System.out.println("[MobileUI] Setting text '" + text + "' on element located by: " + locator + " with default timeout.");
        WebElement element = waitForElementVisible(locator);
        element.click(); // Often needed before clear/sendKeys
        element.clear();
        element.sendKeys(text);
        System.out.println("[MobileUI] Set text completed for locator: " + locator);
    }

    public static void setText(By locator, String text, int second) {
        sleep(STEP_ACTION_TIMEOUT);
        System.out.println("[MobileUI] Setting text '" + text + "' on element located by: " + locator + " with timeout " + second + "s.");
        WebElement element = waitForElementVisible(locator, second);
        element.click();
        element.clear();
        element.sendKeys(text);
        System.out.println("[MobileUI] Set text completed for locator: " + locator);
    }

    public static void setText(WebElement element, String text) {
        sleep(STEP_ACTION_TIMEOUT);
        System.out.println("[MobileUI] Setting text '" + text + "' on element: " + element + " with default timeout.");
        WebElement elm = waitForElementVisible(element);
        elm.click();
        elm.clear();
        elm.sendKeys(text);
        System.out.println("[MobileUI] Set text completed for element: " + element);

    }

    public static void setText(WebElement element, String text, int second) {
        sleep(STEP_ACTION_TIMEOUT);
        System.out.println("[MobileUI] Setting text '" + text + "' on element: " + element + " with timeout " + second + "s.");
        WebElement elm = waitForElementVisible(element, second);
        elm.click();
        elm.clear();
        elm.sendKeys(text);
        System.out.println("[MobileUI] Set text completed for element: " + element);
    }

    public static void clearText(By locator) {
        sleep(STEP_ACTION_TIMEOUT);
        System.out.println("[MobileUI] Clearing text on element located by: " + locator + " with default timeout.");
        WebElement element = waitForElementVisible(locator);
        element.click();
        element.clear();
        System.out.println("[MobileUI] Clear text completed for locator: " + locator);
    }

    public static void clearText(By locator, int second) {
        sleep(STEP_ACTION_TIMEOUT);
        System.out.println("[MobileUI] Clearing text on element located by: " + locator + " with timeout " + second + "s.");
        WebElement element = waitForElementVisible(locator, second);
        element.click();
        element.clear();
        System.out.println("[MobileUI] Clear text completed for locator: " + locator);
    }

    public static void clearText(WebElement element) {
        sleep(STEP_ACTION_TIMEOUT);
        System.out.println("[MobileUI] Clearing text on element: " + element + " with default timeout.");
        WebElement elm = waitForElementVisible(element);
        elm.click();
        elm.clear();
        System.out.println("[MobileUI] Clear text completed for element: " + element);
    }

    public static void clearText(WebElement element, int second) {
        sleep(STEP_ACTION_TIMEOUT);
        System.out.println("[MobileUI] Clearing text on element: " + element + " with timeout " + second + "s.");
        WebElement elm = waitForElementVisible(element, second);
        elm.click();
        elm.clear();
        System.out.println("[MobileUI] Clear text completed for element: " + element);
    }

    public static String getElementText(By locator) {
        sleep(STEP_ACTION_TIMEOUT);
        System.out.println("[MobileUI] Getting text from element located by: " + locator + " with default timeout.");
        WebElement element = waitForElementVisible(locator);
        String text = element.getText();
        System.out.println("[MobileUI] Retrieved text: '" + text + "'");
        return text;
    }

    public static String getElementText(By locator, int second) {
        sleep(STEP_ACTION_TIMEOUT);
        System.out.println("[MobileUI] Getting text from element located by: " + locator + " with timeout " + second + "s.");
        WebElement element = waitForElementVisible(locator, second);
        String text = element.getText();
        System.out.println("[MobileUI] Retrieved text: '" + text + "'");
        return text;
    }

    public static String getElementText(WebElement element) {
        sleep(STEP_ACTION_TIMEOUT);
        System.out.println("[MobileUI] Getting text from element: " + element + " with default timeout.");
        WebElement elm = waitForElementVisible(element);
        String text = elm.getText();
        System.out.println("[MobileUI] Retrieved text: '" + text + "'");
        return text;
    }

    public static String getElementText(WebElement element, int second) {
        sleep(STEP_ACTION_TIMEOUT);
        System.out.println("[MobileUI] Getting text from element: " + element + " with timeout " + second + "s.");
        WebElement elm = waitForElementVisible(element, second);
        String text = elm.getText();
        System.out.println("[MobileUI] Retrieved text: '" + text + "'");
        return text;
    }

    public static String getElementAttribute(By locator, String attribute) {
        sleep(STEP_ACTION_TIMEOUT);
        System.out.println("[MobileUI] Getting attribute '" + attribute + "' from element located by: " + locator + " with default timeout.");
        WebElement element = waitForElementVisible(locator);
        String value = element.getAttribute(attribute);
        System.out.println("[MobileUI] Retrieved attribute value: '" + value + "'");
        return value;
    }

    public static String getElementAttribute(By locator, String attribute, int second) {
        sleep(STEP_ACTION_TIMEOUT);
        System.out.println("[MobileUI] Getting attribute '" + attribute + "' from element located by: " + locator + " with timeout " + second + "s.");
        WebElement element = waitForElementVisible(locator, second);
        String value = element.getAttribute(attribute);
        System.out.println("[MobileUI] Retrieved attribute value: '" + value + "'");
        return value;
    }

    public static String getElementAttribute(WebElement element, String attribute) {
        sleep(STEP_ACTION_TIMEOUT);
        System.out.println("[MobileUI] Getting attribute '" + attribute + "' from element: " + element + " with default timeout.");
        WebElement elm = waitForElementVisible(element);
        String value = elm.getAttribute(attribute);
        System.out.println("[MobileUI] Retrieved attribute value: '" + value + "'");
        return value;
    }

    public static String getElementAttribute(WebElement element, String attribute, int second) {
        sleep(STEP_ACTION_TIMEOUT);
        System.out.println("[MobileUI] Getting attribute '" + attribute + "' from element: " + element + " with timeout " + second + "s.");
        WebElement elm = waitForElementVisible(element, second);
        String value = elm.getAttribute(attribute);
        System.out.println("[MobileUI] Retrieved attribute value: '" + value + "'");
        return value;
    }

    public static boolean isElementPresentAndDisplayed(WebElement element) {
        System.out.println("[MobileUI] Checking if element is present and displayed: " + element);
        boolean result;
        try {
            result = element != null && element.isDisplayed();
            System.out.println("[MobileUI] Element present and displayed check result: " + result);
            return result;
        } catch (NoSuchElementException e) {
            System.out.println("[MobileUI] Element not found during presence/display check: " + element + " - " + e.getMessage());
            return false;
        } catch (Exception e) {
            System.out.println("[MobileUI] An error occurred checking presence/display for element: " + element + " - " + e.getMessage());
            return false;
        }
    }

    public static boolean isElementPresentAndDisplayed(By locator) {
        System.out.println("[MobileUI] Checking if element is present and displayed: " + locator);
        boolean result;
        try {
            WebElement element = getDriver().findElement(locator); // Find first, then check display
            result = element != null && element.isDisplayed();
            System.out.println("[MobileUI] Element present and displayed check result: " + result + " for locator: " + locator);
            return result;
        } catch (NoSuchElementException e) {
            System.out.println("[MobileUI] Element not found during presence/display check: " + locator + " - " + e.getMessage());
            return false;
        } catch (Exception e) {
            System.out.println("[MobileUI] An error occurred checking presence/display for locator: " + locator + " - " + e.getMessage());
            return false;
        }
    }

    public static boolean isElementEnabled(WebElement element) {
        System.out.println("[MobileUI] Checking if element is enabled: " + element);
        boolean result;
        try {
            result = element != null && element.isEnabled();
            System.out.println("[MobileUI] Element enabled check result: " + result);
            return result;
        } catch (Exception e) {
            System.out.println("[MobileUI] An error occurred checking enabled status for element: " + element + " - " + e.getMessage());
            return false;
        }
    }

    public static boolean isElementEnabled(By locator) {
        System.out.println("[MobileUI] Checking if element is enabled: " + locator);
        boolean result;
        try {
            WebElement element = waitForElementVisible(locator); // Ensure it's visible before checking enabled
            result = element != null && element.isEnabled();
            System.out.println("[MobileUI] Element enabled check result: " + result + " for locator: " + locator);
            return result;
        } catch (Exception e) {
            System.out.println("[MobileUI] An error occurred checking enabled status for locator: " + locator + " - " + e.getMessage());
            return false;
        }
    }

    public static boolean isElementSelected(WebElement element) {
        System.out.println("[MobileUI] Checking if element is selected: " + element);
        boolean result;
        try {
            result = element != null && element.isSelected();
            System.out.println("[MobileUI] Element selected check result: " + result);
            return result;
        } catch (Exception e) {
            System.out.println("[MobileUI] An error occurred checking selected status for element: " + element + " - " + e.getMessage());
            return false;
        }
    }

    public static boolean isElementSelected(By locator) {
        System.out.println("[MobileUI] Checking if element is selected: " + locator);
        boolean result;
        try {
            WebElement element = waitForElementVisible(locator); // Ensure it's visible before checking selected
            result = element != null && element.isSelected();
            System.out.println("[MobileUI] Element selected check result: " + result + " for locator: " + locator);
            return result;
        } catch (Exception e) {
            System.out.println("[MobileUI] An error occurred checking selected status for locator: " + locator + " - " + e.getMessage());
            return false;
        }
    }


    // Các hàm verify (sử dụng Assert và gọi lại các hàm is)

    public static void verifyElementPresentAndDisplayed(WebElement element, String message) {
        System.out.println("[MobileUI] Verifying element is present and displayed: " + element + ". Message if failed: " + message);
        Assert.assertTrue(isElementPresentAndDisplayed(element), message);
    }

    public static void verifyElementPresentAndDisplayed(By locator, String message) {
        System.out.println("[MobileUI] Verifying element is present and displayed: " + locator + ". Message if failed: " + message);
        Assert.assertTrue(isElementPresentAndDisplayed(locator), message);
    }

    public static void verifyElementEnabled(WebElement element, String message) {
        System.out.println("[MobileUI] Verifying element is enabled: " + element + ". Message if failed: " + message);
        Assert.assertTrue(isElementEnabled(element), message);
    }

    public static void verifyElementEnabled(By locator, String message) {
        System.out.println("[MobileUI] Verifying element is enabled: " + locator + ". Message if failed: " + message);
        Assert.assertTrue(isElementEnabled(locator), message);
    }

    public static void verifyElementSelected(WebElement element, String message) {
        System.out.println("[MobileUI] Verifying element is selected: " + element + ". Message if failed: " + message);
        Assert.assertTrue(isElementSelected(element), message);
    }

    public static void verifyElementSelected(By locator, String message) {
        System.out.println("[MobileUI] Verifying element is selected: " + locator + ". Message if failed: " + message);
        Assert.assertTrue(isElementSelected(locator), message);
    }

    public static void verifyElementText(WebElement element, String expectedText, String message) {
        System.out.println("[MobileUI] Verifying text of element: " + element + " equals '" + expectedText + "'. Message if failed: " + message);
        Assert.assertEquals(getElementText(element), expectedText, message);
    }

    public static void verifyElementText(By locator, String expectedText, String message) {
        System.out.println("[MobileUI] Verifying text of element: " + locator + " equals '" + expectedText + "'. Message if failed: " + message);
        Assert.assertEquals(getElementText(locator), expectedText, message);
    }

    public static void verifyElementAttribute(WebElement element, String attribute, String expectedValue, String message) {
        System.out.println("[MobileUI] Verifying attribute '" + attribute + "' of element: " + element + " equals '" + expectedValue + "'. Message if failed: " + message);
        Assert.assertEquals(getElementAttribute(element, attribute), expectedValue, message);
    }

    public static void verifyElementAttribute(By locator, String attribute, String expectedValue, String message) {
        System.out.println("[MobileUI] Verifying attribute '" + attribute + "' of element: " + locator + " equals '" + expectedValue + "'. Message if failed: " + message);
        Assert.assertEquals(getElementAttribute(locator, attribute), expectedValue, message);
    }

    public static void assertTrueCondition(boolean condition, String message) {
        System.out.println("[MobileUI] Asserting condition: " + condition + ". Message if failed: " + message);
        Assert.assertTrue(condition, message);
        System.out.println("[MobileUI] Assertion passed for condition: " + condition);
    }


    // --- Wait Methods ---

    public static WebElement waitForElementToBeClickable(By locator, int timeout) {
        System.out.println("[MobileUI] Waiting up to " + timeout + "s for element to be clickable: " + locator);
        WebDriverWait wait = new WebDriverWait(DriverManager.getDriver(), Duration.ofSeconds(timeout));
        return wait.until(ExpectedConditions.elementToBeClickable(locator));
    }

    public static WebElement waitForElementToBeClickable(By locator) {
        System.out.println("[MobileUI] Waiting up to " + DEFAULT_TIMEOUT + "s (default) for element to be clickable: " + locator);
        WebDriverWait wait = new WebDriverWait(DriverManager.getDriver(), Duration.ofSeconds(DEFAULT_TIMEOUT));
        return wait.until(ExpectedConditions.elementToBeClickable(locator));
    }

    public static WebElement waitForElementToBeClickable(WebElement element, int timeout) {
        System.out.println("[MobileUI] Waiting up to " + timeout + "s for element to be clickable: " + element);
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(timeout));
        return wait.until(ExpectedConditions.elementToBeClickable(element));
    }

    public static WebElement waitForElementToBeClickable(WebElement element) {
        System.out.println("[MobileUI] Waiting up to " + DEFAULT_TIMEOUT + "s (default) for element to be clickable: " + element);
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(DEFAULT_TIMEOUT));
        return wait.until(ExpectedConditions.elementToBeClickable(element));
    }

    public static WebElement waitForElementVisible(By locator, int timeout) {
        System.out.println("[MobileUI] Waiting up to " + timeout + "s for element to be visible: " + locator);
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(timeout));
        return wait.until(ExpectedConditions.visibilityOfElementLocated(locator));
    }

    public static WebElement waitForElementVisible(By locator) {
        System.out.println("[MobileUI] Waiting up to " + DEFAULT_TIMEOUT + "s (default) for element to be visible: " + locator);
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(DEFAULT_TIMEOUT));
        return wait.until(ExpectedConditions.visibilityOfElementLocated(locator));
    }

    public static WebElement waitForElementVisible(WebElement element, int timeout) {
        System.out.println("[MobileUI] Waiting up to " + timeout + "s for element to be visible: " + element);
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(timeout));
        return wait.until(ExpectedConditions.visibilityOf(element));
    }

    public static WebElement waitForElementVisible(WebElement element) {
        System.out.println("[MobileUI] Waiting up to " + DEFAULT_TIMEOUT + "s (default) for element to be visible: " + element);
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(DEFAULT_TIMEOUT));
        return wait.until(ExpectedConditions.visibilityOf(element));
    }

    public static boolean waitForElementInvisible(By locator, int timeout) {
        System.out.println("[MobileUI] Waiting up to " + timeout + "s for element to be invisible: " + locator);
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(timeout));
        return wait.until(ExpectedConditions.invisibilityOfElementLocated(locator));
    }

    public static boolean waitForElementInvisible(By locator) {
        System.out.println("[MobileUI] Waiting up to " + DEFAULT_TIMEOUT + "s (default) for element to be invisible: " + locator);
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(DEFAULT_TIMEOUT));
        return wait.until(ExpectedConditions.invisibilityOfElementLocated(locator));
    }

    public static boolean waitForElementInvisible(WebElement element, int timeout) {
        System.out.println("[MobileUI] Waiting up to " + timeout + "s for element to be invisible: " + element);
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(timeout));
        return wait.until(ExpectedConditions.invisibilityOf(element));
    }

    public static boolean waitForElementInvisible(WebElement element) {
        System.out.println("[MobileUI] Waiting up to " + DEFAULT_TIMEOUT + "s (default) for element to be invisible: " + element);
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(DEFAULT_TIMEOUT));
        return wait.until(ExpectedConditions.invisibilityOf(element));
    }

    public static WebElement waitForElementPresent(By locator, int timeout) {
        System.out.println("[MobileUI] Waiting up to " + timeout + "s for element to be present in DOM: " + locator);
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(timeout));
        return wait.until(ExpectedConditions.presenceOfElementLocated(locator));
    }

    public static WebElement waitForElementPresent(By locator) {
        System.out.println("[MobileUI] Waiting up to " + DEFAULT_TIMEOUT + "s (default) for element to be present in DOM: " + locator);
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(DEFAULT_TIMEOUT));
        return wait.until(ExpectedConditions.presenceOfElementLocated(locator));
    }

    public static boolean waitForTextToBePresent(By locator, String text, int timeout) {
        System.out.println("[MobileUI] Waiting up to " + timeout + "s for text '" + text + "' to be present in element: " + locator);
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(timeout));
        return wait.until(ExpectedConditions.textToBePresentInElementLocated(locator, text));
    }

    public static boolean waitForTextToBePresent(By locator, String text) {
        System.out.println("[MobileUI] Waiting up to " + DEFAULT_TIMEOUT + "s (default) for text '" + text + "' to be present in element: " + locator);
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(DEFAULT_TIMEOUT));
        return wait.until(ExpectedConditions.textToBePresentInElementLocated(locator, text));
    }

    public static boolean waitForTextToBePresent(WebElement element, String text, int timeout) {
        System.out.println("[MobileUI] Waiting up to " + timeout + "s for text '" + text + "' to be present in element: " + element);
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(timeout));
        return wait.until(ExpectedConditions.textToBePresentInElement(element, text));
    }

    public static boolean waitForTextToBePresent(WebElement element, String text) {
        System.out.println("[MobileUI] Waiting up to " + DEFAULT_TIMEOUT + "s (default) for text '" + text + "' to be present in element: " + element);
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(DEFAULT_TIMEOUT));
        return wait.until(ExpectedConditions.textToBePresentInElement(element, text));
    }

    public static boolean waitForAttributeToBe(By locator, String attribute, String value, int timeout) {
        System.out.println("[MobileUI] Waiting up to " + timeout + "s for attribute '" + attribute + "' to be '" + value + "' in element: " + locator);
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(timeout));
        return wait.until(ExpectedConditions.attributeToBe(locator, attribute, value));
    }

    public static boolean waitForAttributeToBe(By locator, String attribute, String value) {
        System.out.println("[MobileUI] Waiting up to " + DEFAULT_TIMEOUT + "s (default) for attribute '" + attribute + "' to be '" + value + "' in element: " + locator);
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(DEFAULT_TIMEOUT));
        return wait.until(ExpectedConditions.attributeToBe(locator, attribute, value));
    }

    public static boolean waitForAttributeToBe(WebElement element, String attribute, String value, int timeout) {
        System.out.println("[MobileUI] Waiting up to " + timeout + "s for attribute '" + attribute + "' to be '" + value + "' in element: " + element);
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(timeout));
        return wait.until(ExpectedConditions.attributeToBe(element, attribute, value));
    }

    public static boolean waitForAttributeToBe(WebElement element, String attribute, String value) {
        System.out.println("[MobileUI] Waiting up to " + DEFAULT_TIMEOUT + "s (default) for attribute '" + attribute + "' to be '" + value + "' in element: " + element);
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(DEFAULT_TIMEOUT));
        return wait.until(ExpectedConditions.attributeToBe(element, attribute, value));
    }

    public static List<WebElement> waitForNumberOfElements(By locator, int expectedCount, int timeout) {
        System.out.println("[MobileUI] Waiting up to " + timeout + "s for number of elements to be " + expectedCount + " for locator: " + locator);
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(timeout));
        return wait.until(ExpectedConditions.numberOfElementsToBe(locator, expectedCount));
    }

    public static List<WebElement> waitForNumberOfElements(By locator, int expectedCount) {
        System.out.println("[MobileUI] Waiting up to " + DEFAULT_TIMEOUT + "s (default) for number of elements to be " + expectedCount + " for locator: " + locator);
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(DEFAULT_TIMEOUT));
        return wait.until(ExpectedConditions.numberOfElementsToBe(locator, expectedCount));
    }

    public static boolean waitForUrlContains(String text, int timeout) {
        System.out.println("[MobileUI] Waiting up to " + timeout + "s for URL to contain: '" + text + "'");
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(timeout));
        return wait.until(ExpectedConditions.urlContains(text));
    }

    public static boolean waitForUrlContains(String text) {
        System.out.println("[MobileUI] Waiting up to " + DEFAULT_TIMEOUT + "s (default) for URL to contain: '" + text + "'");
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(DEFAULT_TIMEOUT));
        return wait.until(ExpectedConditions.urlContains(text));
    }

    public static boolean waitForNumberOfWindows(int expectedWindows, int timeout) {
        System.out.println("[MobileUI] Waiting up to " + timeout + "s for number of windows to be: " + expectedWindows);
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(timeout));
        return wait.until(ExpectedConditions.numberOfWindowsToBe(expectedWindows));
    }

    public static boolean waitForNumberOfWindows(int expectedWindows) {
        System.out.println("[MobileUI] Waiting up to " + DEFAULT_TIMEOUT + "s (default) for number of windows to be: " + expectedWindows);
        WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(DEFAULT_TIMEOUT));
        return wait.until(ExpectedConditions.numberOfWindowsToBe(expectedWindows));
    }

}

 

✅ Ghi data vào Properties file

Chỗ này chúng ta học để biết cách ghi vào file tuy nhiên trong thực tế chỉ nên đọc ra chứ không nên ghi vào kẻo làm lộn xộn data mặc định đã setup cho cấu hình toàn cục.

Có ghi thì nên ghi vào file mới, không ghi vào file config mặc định (config.properties)

Cách ghi thì dùng hàm setValue và truyền file path mới, key và giá trị cần ghi vào.

Nhớ tạo sẵn cái file properties cần set value trước.

Khi này KHÔNG cần gọi hàm loadAllFiles() vì nó set giá trị vào file mới.

PropertiesHelpers.setValue(
    "src/test/resources/test_data/data.properties",
    "platform",
    "Android");


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