NỘI DUNG BÀI HỌC
✅ Xây dựng class xử lý JSON file (đọc/ghi)
✅ Đọc data từ JSON file để làm giá trị config cho device
✅ Thêm thư viện hỗ trợ xử lý JSON file trong Maven project
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.13.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.18.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.18.3</version>
</dependency>
-
Gson (Google):
-
Chuyển đổi giữa Java Object và JSON (
serialize
vàdeserialize
). -
Ví dụ:
new Gson().toJson(obj)
hoặcnew Gson().fromJson(json, MyClass.class)
. -
Nhẹ, đơn giản, dễ dùng, nhưng ít tính năng nâng cao hơn Jackson.
-
-
Jackson Databind (FasterXML):
-
Tương tự Gson, nhưng mạnh hơn về tùy biến, hiệu suất và hỗ trợ sâu.
-
Dùng để ánh xạ giữa JSON ↔ Java object.
-
Ví dụ:
new ObjectMapper().writeValueAsString(obj)
hoặcreadValue(...)
.
-
-
Jackson Core:
-
Là lõi nền tảng JSON parser/streamer cho
jackson-databind
. -
Dùng khi cần thao tác trực tiếp với JSON dạng stream (ít phụ thuộc, hiệu suất cao hơn).
-
Nếu chỉ cần thao tác đơn giản thì Gson là đủ. Nhưng nếu muốn xử lý linh hoạt, nhiều cấu hình (như custom deserializer, ignore field, annotation…), nên dùng Jackson.
Thầy An chơi luôn 2 thư viện 😝
✅ Xây dựng class xử lý JSON file (đọc/ghi)
Cũng tương tự như việc đọc ghi file Properties, các bạn tạo class tên JsonHelpers để chung package helpers để khai báo các hàm đọc ghi JSON như bên dưới:
package com.anhtester.helpers;
import com.anhtester.constants.ConfigData;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
public class JsonHelpers {
// Get value from JSON file with multiple keys
public static String getValueJsonObject(String... keys) {
String value = null;
ObjectMapper objectMapper = new ObjectMapper();
try {
JsonNode node = objectMapper.readTree(new File(ConfigData.JSON_DATA_FILE_PATH));
for (String key : keys) {
node = node.path(key);
}
value = node.asText();
System.out.println("Value: " + value);
} catch (IOException e) {
e.printStackTrace();
}
return value;
}
// Get value from JSON file in new file path with multiple keys
public static String getValueJsonObject_FilePath(String filePath, String... keys) {
String value = null;
ObjectMapper objectMapper = new ObjectMapper();
try {
JsonNode node = objectMapper.readTree(new File(filePath));
for (String key : keys) {
node = node.path(key);
}
value = node.asText();
System.out.println("Value: " + value);
} catch (IOException e) {
e.printStackTrace();
}
return value;
}
// Get value from JSON array
public static String getValueJsonArray(int itemIndex, String... keys) {
String value = null;
ObjectMapper objectMapper = new ObjectMapper();
try {
JsonNode rootNode = objectMapper.readTree(new File(ConfigData.JSON_DATA_FILE_PATH));
// Lấy item tại index từ mảng gốc
JsonNode itemNode = rootNode.get(itemIndex);
if (itemNode == null || !itemNode.isObject()) {
throw new IllegalArgumentException("Item index không hợp lệ hoặc không phải object.");
}
JsonNode current = itemNode;
for (String key : keys) {
current = current.path(key);
}
value = current.asText();
System.out.println("Value: " + value);
} catch (IOException e) {
e.printStackTrace();
}
return value;
}
// Get value from JSON array in new file path
public static String getValueJsonArray_FilePath(String filePath, int itemIndex, String... keys) {
String value = null;
ObjectMapper objectMapper = new ObjectMapper();
try {
JsonNode rootNode = objectMapper.readTree(new File(filePath));
// Lấy item tại index từ mảng gốc
JsonNode itemNode = rootNode.get(itemIndex);
if (itemNode == null || !itemNode.isObject()) {
throw new IllegalArgumentException("Item index không hợp lệ hoặc không phải object.");
}
JsonNode current = itemNode;
for (String key : keys) {
current = current.path(key);
}
value = current.asText();
System.out.println("Value: " + value);
} catch (IOException e) {
e.printStackTrace();
}
return value;
}
public static void updateValueJsonObject(String keyName, Number value) {
Reader reader;
try {
reader = Files.newBufferedReader(Paths.get(ConfigData.JSON_DATA_FILE_PATH));
Gson gson = new Gson();
//Convert Json file to Json Object
JsonObject jsonObject = gson.fromJson(reader, JsonObject.class);
System.out.println("Original JSON: " + jsonObject);
//Update value if exist key
jsonObject.addProperty(keyName, value);
System.out.println("Modified JSON: " + jsonObject);
//Store new Json data to file
File jsonFile = new File(ConfigData.JSON_DATA_FILE_PATH);
OutputStream outputStream = new FileOutputStream(jsonFile);
outputStream.write(gson.toJson(jsonObject).getBytes());
outputStream.flush();
reader.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void updateValueJsonObject(String keyName, String value) {
Reader reader;
try {
reader = Files.newBufferedReader(Paths.get(ConfigData.JSON_DATA_FILE_PATH));
Gson gson = new Gson();
//Convert Json file to Json Object
JsonObject jsonObject = gson.fromJson(reader, JsonObject.class);
System.out.println("Original JSON: " + jsonObject);
if (jsonObject.has(keyName)) {
System.out.println("🔁 Update key: '" + keyName + "'");
} else {
System.out.println("➕ Add new key: '" + keyName + "'");
}
//Update value if exist key
jsonObject.addProperty(keyName, value);
System.out.println("Modified JSON: " + jsonObject);
//Store new Json data to file
File jsonFile = new File(ConfigData.JSON_DATA_FILE_PATH);
OutputStream outputStream = new FileOutputStream(jsonFile);
outputStream.write(gson.toJson(jsonObject).getBytes());
outputStream.flush();
reader.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void updateValueJsonObject(String parentKey, String keyName, Number value) {
Reader reader;
try {
reader = Files.newBufferedReader(Paths.get(ConfigData.JSON_DATA_FILE_PATH));
Gson gson = new Gson();
//Convert Json file to Json Object
JsonObject jsonObject = gson.fromJson(reader, JsonObject.class);
System.out.println("Original JSON: " + jsonObject);
// Đảm bảo parentKey tồn tại
if (!jsonObject.has(parentKey) || !jsonObject.get(parentKey).isJsonObject()) {
jsonObject.add(parentKey, new JsonObject());
}
//Update value if exist key
jsonObject.getAsJsonObject(parentKey).addProperty(keyName, value);
System.out.println("Modified JSON: " + jsonObject);
//Store new Json data to file
File jsonFile = new File(ConfigData.JSON_DATA_FILE_PATH);
OutputStream outputStream = new FileOutputStream(jsonFile);
outputStream.write(gson.toJson(jsonObject).getBytes());
outputStream.flush();
reader.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void updateValueJsonObject(String parentKey, String keyName, String value) {
Reader reader;
try {
reader = Files.newBufferedReader(Paths.get(ConfigData.JSON_DATA_FILE_PATH));
Gson gson = new Gson();
//Convert Json file to Json Object
JsonObject jsonObject = gson.fromJson(reader, JsonObject.class);
System.out.println("Original JSON: " + jsonObject);
// Đảm bảo parentKey tồn tại
if (!jsonObject.has(parentKey) || !jsonObject.get(parentKey).isJsonObject()) {
jsonObject.add(parentKey, new JsonObject());
}
// Gán keyName vào object con
jsonObject.getAsJsonObject(parentKey).addProperty(keyName, value);
System.out.println("Modified JSON: " + jsonObject);
//Store new Json data to file
File jsonFile = new File(ConfigData.JSON_DATA_FILE_PATH);
OutputStream outputStream = new FileOutputStream(jsonFile);
outputStream.write(gson.toJson(jsonObject).getBytes());
outputStream.flush();
reader.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void updateValueJsonObject_FilePath(String filePath, String keyName, String value) {
Reader reader;
try {
reader = Files.newBufferedReader(Paths.get(filePath));
Gson gson = new Gson();
//Convert Json file to Json Object
JsonObject jsonObject = gson.fromJson(reader, JsonObject.class);
System.out.println("Original JSON: " + jsonObject);
// Đảm bảo keyName tồn tại
if (!jsonObject.has(keyName) || !jsonObject.get(keyName).isJsonObject()) {
jsonObject.add(keyName, new JsonObject());
}
//Update value if exist key
jsonObject.addProperty(keyName, value);
System.out.println("Modified JSON: " + jsonObject);
//Store new Json data to new file
File jsonFile = new File(filePath);
OutputStream outputStream = new FileOutputStream(jsonFile);
outputStream.write(gson.toJson(jsonObject).getBytes());
outputStream.flush();
//Close reader
reader.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void updateValueJsonObject_FilePath(String filePath, String keyName, Number value) {
Reader reader;
try {
reader = Files.newBufferedReader(Paths.get(filePath));
Gson gson = new Gson();
//Convert Json file to Json Object
JsonObject jsonObject = gson.fromJson(reader, JsonObject.class);
System.out.println("Original JSON: " + jsonObject);
// Đảm bảo parentKey tồn tại
if (!jsonObject.has(keyName) || !jsonObject.get(keyName).isJsonObject()) {
jsonObject.add(keyName, new JsonObject());
}
//Update value if exist key
jsonObject.addProperty(keyName, value);
System.out.println("Modified JSON: " + jsonObject);
//Store new Json data to new file
File jsonFile = new File(filePath);
OutputStream outputStream = new FileOutputStream(jsonFile);
outputStream.write(gson.toJson(jsonObject).getBytes());
outputStream.flush();
//Close reader
reader.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void updateValueJsonObject_FilePath(String filePath, String parentKey, String keyName, String value) {
Reader reader;
try {
reader = Files.newBufferedReader(Paths.get(filePath));
Gson gson = new Gson();
//Convert Json file to Json Object
JsonObject jsonObject = gson.fromJson(reader, JsonObject.class);
System.out.println("Original JSON: " + jsonObject);
// Đảm bảo parentKey tồn tại
if (!jsonObject.has(parentKey) || !jsonObject.get(parentKey).isJsonObject()) {
jsonObject.add(parentKey, new JsonObject());
}
//Update value if exist key
jsonObject.getAsJsonObject(parentKey).addProperty(keyName, value);
System.out.println("Modified JSON: " + jsonObject);
//Store new Json data to file
File jsonFile = new File(filePath);
OutputStream outputStream = new FileOutputStream(jsonFile);
outputStream.write(gson.toJson(jsonObject).getBytes());
outputStream.flush();
reader.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void updateValueJsonObject_FilePath(String filePath, String parentKey, String keyName, Number value) {
Reader reader;
try {
reader = Files.newBufferedReader(Paths.get(filePath));
Gson gson = new Gson();
//Convert Json file to Json Object
JsonObject jsonObject = gson.fromJson(reader, JsonObject.class);
System.out.println("Original JSON: " + jsonObject);
// Đảm bảo parentKey tồn tại
if (!jsonObject.has(parentKey) || !jsonObject.get(parentKey).isJsonObject()) {
jsonObject.add(parentKey, new JsonObject());
}
//Update value if exist key
jsonObject.getAsJsonObject(parentKey).addProperty(keyName, value);
System.out.println("Modified JSON: " + jsonObject);
//Store new Json data to file
File jsonFile = new File(filePath);
OutputStream outputStream = new FileOutputStream(jsonFile);
outputStream.write(gson.toJson(jsonObject).getBytes());
outputStream.flush();
reader.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void updateValueJsonArray(String value, int index, String... keys) {
if (keys == null || keys.length == 0) {
throw new IllegalArgumentException("Phải cung cấp ít nhất một Key");
}
try (Reader reader = Files.newBufferedReader(Paths.get(ConfigData.JSON_DATA_FILE_PATH))) {
Gson gson = new Gson();
JsonArray jsonArray = gson.fromJson(reader, JsonArray.class);
// Kiểm tra index hợp lệ
if (index < 0 || index >= jsonArray.size()) {
throw new IndexOutOfBoundsException("Tham số index nằm ngoài giới hạn.");
}
JsonObject current = jsonArray.get(index).getAsJsonObject();
// Duyệt từ key[0] đến key[n-2], tạo JsonObject nếu chưa có
for (int i = 0; i < keys.length - 1; i++) {
String key = keys[i];
if (!current.has(key) || !current.get(key).isJsonObject()) {
current.add(key, new JsonObject());
}
current = current.getAsJsonObject(key);
}
// Gán giá trị cho key cuối
current.addProperty(keys[keys.length - 1], value);
// Ghi lại file
try (OutputStream outputStream = new FileOutputStream(ConfigData.JSON_DATA_FILE_PATH)) {
outputStream.write(gson.toJson(jsonArray).getBytes());
outputStream.flush();
}
System.out.println("✅ Updated index " + index + ": " + String.join(" → ", keys) + " = " + value);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void updateValueJsonArray_FilePath(String filePath, String value, int index, String... keys) {
if (keys == null || keys.length == 0) {
throw new IllegalArgumentException("Phải cung cấp ít nhất một Key");
}
try (Reader reader = Files.newBufferedReader(Paths.get(filePath))) {
Gson gson = new Gson();
JsonArray jsonArray = gson.fromJson(reader, JsonArray.class);
// Kiểm tra index hợp lệ
if (index < 0 || index >= jsonArray.size()) {
throw new IndexOutOfBoundsException("Tham số index nằm ngoài giới hạn.");
}
JsonObject current = jsonArray.get(index).getAsJsonObject();
// Duyệt từ key[0] đến key[n-2], tạo JsonObject nếu chưa có
for (int i = 0; i < keys.length - 1; i++) {
String key = keys[i];
if (!current.has(key) || !current.get(key).isJsonObject()) {
current.add(key, new JsonObject());
}
current = current.getAsJsonObject(key);
}
// Gán giá trị cho key cuối
current.addProperty(keys[keys.length - 1], value);
// Ghi lại file
try (OutputStream outputStream = new FileOutputStream(filePath)) {
outputStream.write(gson.toJson(jsonArray).getBytes());
outputStream.flush();
}
System.out.println("✅ Updated index " + index + ": " + String.join(" → ", keys) + " = " + value);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Khai báo các key đường dẫn trỏ đến từng file trong config.properties và khai báo biến vào class ConfigData. (ConfigData.JSON_DATA_FILE_PATH)
🔆 Đọc data từ JSON file
Tạo file data.json theo đường dẫn src/test/resources/test_data/data.json và khai báo data cho các tất cả các test cases theo từng module hoặc mỗi file json cho một module khác nhau.
Ví dụ khai báo JSON object như sau:
{
"platform": "Android",
"device": "Pixel 8",
"account": {
"username": "admin",
"password": "admin"
},
"table": {
"table_name": "Table 1",
"hidden_table": "false"
},
"product": {
"product_name": "Product 1",
"product_price": "1000000",
"hidden_product": "false",
"product_image": "src/test/resources/test_data/product1.png"
}
}
Ví dụ khai báo JSON array như sau:
src/test/resources/test_data/sample_products.json
[
{
"id": 1,
"name": "Laptop Dell XPS 13",
"specs": {
"cpu": "Intel i7",
"ram": "16GB",
"storage": "512GB SSD"
},
"price": {
"amount": 29990000,
"currency": "VND"
},
"available": true
},
{
"id": 2,
"name": "MacBook Pro M2",
"specs": {
"cpu": "Apple M2",
"ram": "16GB",
"storage": "1TB SSD"
},
"price": {
"amount": 38990000,
"currency": "VND"
},
"available": false
},
{
"id": 3,
"name": "Asus ROG Zephyrus G14",
"specs": {
"cpu": "AMD Ryzen 9",
"ram": "32GB",
"storage": "1TB SSD"
},
"price": {
"amount": 42990000,
"currency": "VND"
},
"available": true
},
{
"id": 4,
"name": "HP Spectre x360",
"specs": {
"cpu": "Intel i5",
"ram": "8GB",
"storage": "256GB SSD"
},
"price": {
"amount": 23990000,
"currency": "VND"
},
"available": true
}
]
⭐️ Đọc data từ file JSON
@Test
public void testReadJsonFile() {
//Get value from JSON object
JsonHelpers.getValueJsonObject("platform");
JsonHelpers.getValueJsonObject("device");
JsonHelpers.getValueJsonObject("account", "username");
JsonHelpers.getValueJsonObject("account", "password");
JsonHelpers.getValueJsonObject("product", "product_name");
JsonHelpers.getValueJsonObject("product", "product_price");
JsonHelpers.getValueJsonObject("product", "hidden_product");
JsonHelpers.getValueJsonObject("product", "product_image");
JsonHelpers.getValueJsonObject_FilePath("src/test/resources/configs/device.json", "platforms", "android", "devices", "pixel8", "deviceName");
//Get value from JSON array
JsonHelpers.getValueJsonArray_FilePath("src/test/resources/test_data/sample_products.json", 0, "price", "amount");
}
🔆 Ghi data và cập nhật data vào JSON file
Chỗ này các hàm updateValueJson đã viết sẵn 2 trường hợp:
- Cập nhật giá trị nếu key đã tồn tại (update)
- Add key mới cùng giá trị mới nếu key chưa tồn tại (add)
@Test
public void testUpdateValueToJsonFile() {
//Add new data with root key value
JsonHelpers.updateValueJsonObject(
"database_name", "Data 4");
JsonHelpers.updateValueJsonObject(
"database_index", 4);
//Add new data with parent key is object
JsonHelpers.updateValueJsonObject("database",
"database_name", "Data 1");
JsonHelpers.updateValueJsonObject("database",
"database_index", 1);
//Update value with parent key is object in file path
JsonHelpers.updateValueJsonFile_Object("src/test/resources/test_data/data.json",
"product", "product_name", "Product 3");
JsonHelpers.updateValueJsonFile_Object("src/test/resources/test_data/data.json",
"product", "product_price", 140000);
//Add new data with parent key is object in file path
JsonHelpers.updateValueJsonFile_Object("src/test/resources/test_data/data.json",
"profile", "name", "Anh Tester");
JsonHelpers.updateValueJsonFile_Object("src/test/resources/test_data/data.json",
"profile", "email", "anhtester@example.com");
JsonHelpers.updateValueJsonFile_Object("src/test/resources/test_data/data.json",
"profile", "phone", "0939206009");
JsonHelpers.updateValueJsonFile_Object("src/test/resources/test_data/data.json",
"profile", "address", "HCM");
JsonHelpers.updateValueJsonFile_Object("src/test/resources/test_data/data.json",
"profile", "job_title", "Tester");
//Update value in JSON array
JsonHelpers.updateValueJsonArray_FilePath("src/test/resources/test_data/sample_products.json", "20000", 0, "price", "amount");
}
src/test/resources/test_data/data.json
{
"platform": "Android",
"device": "Pixel 8",
"account": {
"username": "admin",
"password": "admin"
},
"table": {
"table_name": "Table 1",
"hidden_table": "false"
},
"product": {
"product_name": "Product 3",
"product_price": 140000,
"hidden_product": "false",
"product_image": "src/test/resources/test_data/product1.png"
},
"profile": {
"name": "Anh Tester",
"email": "anhtester@example.com",
"phone": "0939206009",
"address": "HCM",
"job_title": "Tester"
},
"database_name": "Data 4",
"database_index": 4,
"database": {
"database_name": "Data 1",
"database_index": 1
}
}
src/test/resources/test_data/sample_products.json
[
{
"id": 1,
"name": "Laptop Dell XPS 13",
"specs": {
"cpu": "Intel i7",
"ram": "16GB",
"storage": "512GB SSD"
},
"price": {
"amount": "20000",
"currency": "VND"
},
"available": true
},
{
"id": 2,
"name": "MacBook Pro M2",
"specs": {
"cpu": "Apple M2",
"ram": "16GB",
"storage": "1TB SSD"
},
"price": {
"amount": 38990000,
"currency": "VND"
},
"available": false
},
{
"id": 3,
"name": "Asus ROG Zephyrus G14",
"specs": {
"cpu": "AMD Ryzen 9",
"ram": "32GB",
"storage": "1TB SSD"
},
"price": {
"amount": 42990000,
"currency": "VND"
},
"available": true
},
{
"id": 4,
"name": "HP Spectre x360",
"specs": {
"cpu": "Intel i5",
"ram": "8GB",
"storage": "256GB SSD"
},
"price": {
"amount": 23990000,
"currency": "VND"
},
"available": true
}
]
✅ Đọc data từ JSON file để làm giá trị config cho device
Chúng ta sẽ đọc thông tin devices từ JSON file để tiện cho việc khai báo các thông tin một lần và gọi ra sử dụng lại thay vì truyền vào XML khá nhiều các fields thông tin.
Tạo file device.json trong mục src/test/resources/configs/device.json và khai báo theo cấu trúc sau:
{
"platforms": {
"android": {
"devices": {
"pixel8": {
"platformName": "Android",
"platformVersion": "15",
"automationName": "UiAutomator2",
"deviceName": "Pixel_8_Pro_API_35_2",
"appPackage": "com.anhtester.mobile_app.taurus",
"appActivity": "com.anhtester.mobile_app.taurus.MainActivity",
"appAndroidPath": "src/test/resources/apps/flutter-app-release.apk",
"noReset": false,
"fullReset": false,
"autoGrantPermissions": true
},
"pixel9": {
"platformName": "Android",
"platformVersion": "14",
"automationName": "UiAutomator2",
"deviceName": "Pixel_9_Pro_XL_API_34",
"appPackage": "com.anhtester.mobile_app.taurus",
"appActivity": "com.anhtester.mobile_app.taurus.MainActivity",
"appAndroidPath": "src/test/resources/apps/flutter-app-release.apk",
"noReset": false,
"fullReset": false,
"autoGrantPermissions": true
}
}
},
"ios": {
}
}
}
Sau này cần thêm fields gì thì cứ khai báo vào rồi đọc ra dùng. Tạm thời các thông tin chính như vậy.
Tiếp theo xây dựng các hàm xử lý chuyên dụng cho cấu trúc JSON mà mình đã thiết lập trong class ConfigData chung với các biến môi trường để gọi dùng chung cho tiện.
Hàm getValueJsonConfig
(String platform, String device, String propertyName) chúng ta chỉ cần truyền 3 tham số platform, device và propertyName để nó get ra đúng giá trị từng field cho từng loại thiết bị android hoặc ios đã thiết lập.
package com.anhtester.constants;
import com.anhtester.helpers.PropertiesHelpers;
import com.anhtester.helpers.SystemHelpers;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
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");
public static final String STEP_ACTION_TIMEOUT = PropertiesHelpers.getValue("STEP_ACTION_TIMEOUT");
public static String getValueJsonConfig(String platform, String device, String propertyName) {
// Initialize Jackson ObjectMapper
ObjectMapper mapper = new ObjectMapper();
// Read JSON file
JsonNode rootNode = null;
try {
rootNode = mapper.readTree(new File(ConfigData.JSON_CONFIG_FILE_PATH));
} catch (IOException e) {
throw new RuntimeException(e);
}
String result = rootNode
.path("platforms")
.path(platform.trim().toLowerCase())
.path("devices")
.path(device.trim().toLowerCase())
.path(propertyName)
.asText();
System.out.println("***" + propertyName + ": " + result);
return result;
}
}
🔆 Truyền giá trị từ device.json vào BaseTest
Cập nhật class BaseTest lại như sau:
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_Json_Device {
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 deviceName Tên thiết bị trong device.json
* @param udid UDID của thiết bị Android (quan trọng cho parallel)
* @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", "deviceName", "udid", "host", "port", "bundleId", "wdaLocalPort", "systemPort"})
public void setUpDriver(String platformName, String deviceName, @Optional String udid, 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: " + ConfigData.getValueJsonConfig(platformName, deviceName, "platformVersion"));
System.out.println("deviceName: " + ConfigData.getValueJsonConfig(platformName, deviceName, "deviceName"));
System.out.println("udid: " + ConfigData.getValueJsonConfig(platformName, deviceName, "udid"));
System.out.println("automationName: " + ConfigData.getValueJsonConfig(platformName, deviceName, "automationName"));
System.out.println("appPackage: " + ConfigData.getValueJsonConfig(platformName, deviceName, "appPackage"));
System.out.println("appActivity: " + ConfigData.getValueJsonConfig(platformName, deviceName, "appActivity"));
System.out.println("noReset: " + ConfigData.getValueJsonConfig(platformName, deviceName, "noReset"));
System.out.println("fullReset: " + ConfigData.getValueJsonConfig(platformName, deviceName, "fullReset"));
System.out.println("autoGrantPermissions: " + ConfigData.getValueJsonConfig(platformName, deviceName, "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(ConfigData.getValueJsonConfig(platformName, deviceName, "platformVersion"));
options.setDeviceName(ConfigData.getValueJsonConfig(platformName, deviceName, "deviceName"));
if (udid != null && !udid.isEmpty()) {
options.setUdid(udid);
}
String appPackage = ConfigData.getValueJsonConfig(platformName, deviceName, "appPackage");
if (appPackage != null && !appPackage.isEmpty()) {
options.setAppPackage(appPackage);
}
String appActivity = ConfigData.getValueJsonConfig(platformName, deviceName, "appActivity");
if (appActivity != null && !appActivity.isEmpty()) {
options.setAppActivity(appActivity);
}
// options.setApp("/path/to/your/app.apk");
options.setAutomationName(Objects.requireNonNullElse(ConfigData.getValueJsonConfig(platformName, deviceName, "automationName"), "UiAutomator2"));
options.setNoReset(Boolean.parseBoolean(ConfigData.getValueJsonConfig(platformName, deviceName, "noReset")));
options.setFullReset(Boolean.parseBoolean(ConfigData.getValueJsonConfig(platformName, deviceName, "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(ConfigData.getValueJsonConfig(platformName, deviceName, "platformVersion"));
options.setDeviceName(ConfigData.getValueJsonConfig(platformName, deviceName, "deviceName"));
// options.setApp("/path/to/your/app.app or .ipa");
if (bundleId != null && !bundleId.isEmpty()) {
options.setBundleId(bundleId);
}
options.setAutomationName(Objects.requireNonNullElse(ConfigData.getValueJsonConfig(platformName, deviceName, "automationName"), "XCUITest"));
options.setNoReset(Boolean.parseBoolean(ConfigData.getValueJsonConfig(platformName, deviceName, "noReset")));
options.setFullReset(Boolean.parseBoolean(ConfigData.getValueJsonConfig(platformName, deviceName, "fullReset")));
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();
}
}
🔆 Tạo suite XML và truyền lại giá trị
Bấy giờ các fields giá trị setup đã giảm đi khá nhiều so với buổi hôm trước.
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
<suite name="Suite Regression Test" verbose="1" parallel="tests">
<test name="Platform Android Test 1 - Taurus App">
<parameter name="platformName" value="Android"/>
<parameter name="deviceName" value="pixel8"/>
<parameter name="udid" value="emulator-5554"/>
<parameter name="host" value="127.0.0.1"/>
<parameter name="port" value="8000"/>
<parameter name="systemPort" value="8201"/>
<classes>
<class name="com.anhtester.Bai22_JSON_File.testcases.LoginTest">
<methods>
<include name="testLoginSuccess"/>
</methods>
</class>
</classes>
</test>
<test name="Platform Android Test 2 - Taurus App">
<parameter name="platformName" value="Android"/>
<parameter name="deviceName" value="pixel9"/>
<parameter name="udid" value="emulator-5556"/>
<parameter name="host" value="127.0.0.1"/>
<parameter name="port" value="9000"/>
<parameter name="systemPort" value="8202"/>
<classes>
<class name="com.anhtester.Bai22_JSON_File.testcases.LoginTest">
<methods>
<include name="testLoginFailWithUsernameInvalid"/>
</methods>
</class>
</classes>
</test>
<!-- <test name="Platform iOS Test - Taurus App">-->
<!-- <parameter name="platformName" value="iOS"/>-->
<!-- <parameter name="deviceName" value="iphone14"/>-->
<!-- <parameter name="bundleId" value=""/>-->
<!-- <parameter name="host" value="127.0.0.1"/>-->
<!-- <parameter name="port" value="6000"/>-->
<!-- <parameter name="wdaLocalPort" value="8101"/>-->
<!-- <classes>-->
<!-- <class name="com.anhtester.Bai21_Config_File.testcases.MenuTest"/>-->
<!-- </classes>-->
<!-- </test>-->
</suite>