Bài viết này dành cho ai?
Bạn là QC manual. Hằng ngày bạn tập qua từng màn hình, log bug, retest, rồi lặp lại. Bạn hiểu sản phẩm rõ hơn bất kỳ ai, nhưng hai từ “tự động hóa” vẫn nghe xa vời. Bài viết này được viết dành riêng cho bạn.
Đọc xong bài này, bạn sẽ cài được Appium, chạy được test đầu tiên, và có lộ trình rõ ràng để đi tiếp.
Tại sao lại chọn Appium?
Trước khi bắt tay vào cài đặt, hãy trả lời câu hỏi hiển nhiên: tại sao Appium mà không phải công cụ khác?
Đa nền tảng ngay từ đầu. Viết một bộ test dùng cho cả Android và iOS. Không cần công cụ riêng hay kỹ năng riêng cho từng nền tảng.
Không cần sửa code ứng dụng. Khác với một số framework yêu cầu nhúng SDK vào trong app, Appium làm việc với chính file APK hoặc IPA mà người dùng tải về. Bạn test đúng thứ thật.
Dùng ngôn ngữ lập trình bất kỳ. Appium sử dụng giao thức WebDriver. Bạn có thể viết test bằng Python, Java, JavaScript, C-sharp, Ruby, hoặc bất kỳ ngôn ngữ nào có WebDriver client. Chọn ngôn ngữ mà team bạn đã quen.
Mã nguồn mở với cộng đồng lớn. Được bảo trợ bởi OpenJS Foundation, Appium có hàng ngàn contributor, diễn đàn hoạt động, và plugin cho hầu hết mọi nhu cầu. Nếu bạn gặp vấn đề, khả năng cao là ai đó đã giải quyết rồi.
Tiêu chuẩn ngành. Hầu hết các tin tuyển dụng QA automation mobile đều đề cập Appium. Học Appium là đầu tư trực tiếp vào sự nghiệp của bạn.
Kiến trúc Appium 2.0
Appium 2.0 giới thiệu một thay đổi kiến trúc lớn. Hiểu điều này sẽ giúp bạn tiết kiệm hàng giờ khi gặp lỗi.
graph TB
A["Test Script<br/>(Python / Java / JS)"] -->|WebDriver Protocol| B["Appium Server<br/>(Node.js)"]
B -->|UiAutomator2 Driver| C["Thiet bi Android<br/>hoac Emulator"]
B -->|XCUITest Driver| D["Thiet bi iOS<br/>hoac Simulator"]
B -->|Plugin System| E["Plugins<br/>(images, gestures, wait)"]
style A fill:#4A90D9,stroke:#2C5F8A,color:#fff
style B fill:#50C878,stroke:#2E8B57,color:#fff
style C fill:#FF8C42,stroke:#CC6F35,color:#fff
style D fill:#FF8C42,stroke:#CC6F35,color:#fff
style E fill:#9B59B6,stroke:#7D3C98,color:#fffNhững khái niệm chính trong Appium 2.0:
- Mô hình Client-Server. Test script của bạn là client. Nó gửi lệnh qua HTTP đến Appium server. Server dịch các lệnh đó thành hành động trên thiết bị.
- Kiến trúc dựa trên Driver. Appium không còn gom driver sẵn nữa. Bạn chỉ cài những gì cần:
uiautomator2cho Android,xcuitestcho iOS,espressocho Android Espresso, và nhiều hơn. - Hệ thống Plugin. Cần so sánh hình ảnh? Cài plugin images. Cần chiến lược wait tùy chỉnh? Có plugin cho việc đó. Server luôn gọn nhẹ.
- Phiên bản tách biệt. Driver và plugin cập nhật độc lập với server. Không cần chờ một bản Appium mới để có bản vá driver.
Cài đặt từ A đến Z
Điều kiện tiên quyết
Bạn cần cài đặt những thứ này trước khi dùng đến Appium:
| Công cụ | Mục đích | Cần trên |
|---|---|---|
| Node.js 18+ | Chạy Appium server | Tất cả |
| JDK 11 hoặc 17 | Android toolchain | Tất cả |
| Android Studio | Android SDK, emulator | Tất cả |
| Xcode 15+ | iOS Simulator, build tools | Chỉ macOS |
| Python 3.10+ | Viết test script | Tất cả |
Bước 1: Cài Node.js
Tải từ nodejs.org hoặc dùng version manager:
# macOS voi Homebrew
brew install node
# Windows voi Chocolatey
choco install nodejs-lts
# Kiem tra
node --version
npm --version
Bước 2: Cài JDK
# macOS
brew install openjdk@17
# Windows -- tai tu https://adoptium.net
# Sau do dat JAVA_HOME trong System Environment Variables
Bước 3: Cài đặt Android SDK
- Tải và cài Android Studio.
- Mở Android Studio, vào SDK Manager.
- Cài Android SDK Platform 34, SDK Build-Tools, và SDK Platform-Tools.
- Tạo emulator qua AVD Manager (nên chọn Pixel 7, API 34).
Bước 4: Biến môi trường (Environment Variables)
Đây là chỗ mà hầu hết người mới bị mắc. Hãy đặt các biến này vĩnh viễn.
macOS / Linux — thêm vào ~/.zshrc hoặc ~/.bashrc:
export JAVA_HOME=$(/usr/libexec/java_home)
export ANDROID_HOME=$HOME/Library/Android/sdk
export PATH=$PATH:$ANDROID_HOME/platform-tools
export PATH=$PATH:$ANDROID_HOME/tools
Windows — đặt trong System Environment Variables:
JAVA_HOME = C:\Program Files\Eclipse Adoptium\jdk-17
ANDROID_HOME = C:\Users\TenBan\AppData\Local\Android\Sdk
PATH += %ANDROID_HOME%\platform-tools
Kiểm tra mọi thứ hoạt động:
java -version
adb devices
echo $ANDROID_HOME # macOS/Linux
echo %ANDROID_HOME% # Windows
Bước 5: Cài Appium 2.x
# Cai Appium server
npm install -g appium
# Kiem tra
appium --version
# Phai hien 2.x.x
# Cai driver Android
appium driver install uiautomator2
# Cai driver iOS (chi macOS)
appium driver install xcuitest
# Liet ke driver da cai
appium driver list --installed
Bước 6: Appium Doctor (Kiểm tra sức khỏe)
# Cai plugin doctor
npm install -g @appium/doctor
# Chay kiem tra
appium-doctor --android
# Tren macOS, chay them:
appium-doctor --ios
Sửa mọi mục đỏ cho đến khi tất cả đều xanh. Xanh hết nghĩa là bạn đã sẵn sàng.
Bước 7: Appium Inspector
Appium Inspector là người bạn tốt nhất của bạn. Nó cho phép bạn khám phá ứng dụng bằng mắt và tìm locator của các element.
- Tải từ github.com/appium/appium-inspector/releases.
- Mở lên và cấu hình remote host:
127.0.0.1, port4723, path/. - Đặt desired capabilities (xem phần tiếp theo).
- Bấm Start Session và bắt đầu click quanh app để xem cây element.
Test Script Đầu Tiên
Chúng ta sẽ viết một test thật sự. Chúng ta sẽ tự động hóa ứng dụng Settings của Android — không cần file APK nào.
Cài Python Client
pip install Appium-Python-Client
Test Script
"""first_appium_test.py -- Test Appium dau tien."""
import unittest
from appium import webdriver
from appium.options import UiAutomator2Options
from appium.webdriver.common.appiumby import AppiumBy
class TestAndroidSettings(unittest.TestCase):
"""Mo Android Settings va kiem tra thanh tim kiem."""
def setUp(self):
options = UiAutomator2Options()
options.platform_name = "Android"
options.device_name = "emulator-5554"
options.app_package = "com.android.settings"
options.app_activity = ".Settings"
options.automation_name = "UiAutomator2"
options.no_reset = True
self.driver = webdriver.Remote(
command_executor="http://127.0.0.1:4723",
options=options,
)
self.driver.implicitly_wait(10)
def test_search_bar_exists(self):
"""Kiem tra thanh tim kiem co hien thi."""
search = self.driver.find_element(
AppiumBy.ACCESSIBILITY_ID,
"Search settings",
)
self.assertTrue(search.is_displayed())
search.click()
# Nhap vao o tim kiem
search_field = self.driver.find_element(
AppiumBy.ID,
"com.android.settings:id/search_src_text",
)
search_field.send_keys("Wi-Fi")
# Kiem tra ket qua hien thi
results = self.driver.find_elements(
AppiumBy.CLASS_NAME,
"android.widget.TextView",
)
self.assertTrue(len(results) > 0)
def tearDown(self):
if self.driver:
self.driver.quit()
if __name__ == "__main__":
unittest.main()
Chạy Test
# Terminal 1: Khoi dong Appium server
appium
# Terminal 2: Khoi dong emulator
emulator -avd Pixel_7_API_34
# Terminal 3: Chay test
python first_appium_test.py
Luồng Thực Thi Test
sequenceDiagram
participant T as Test Script
participant S as Appium Server
participant D as UiAutomator2 Driver
participant E as Android Emulator
T->>S: POST /session (capabilities)
S->>D: Khoi tao driver
D->>E: Cai UiAutomator2 server
E-->>D: San sang
D-->>S: Session da tao
S-->>T: Session ID
T->>S: Tim element (Accessibility ID)
S->>D: Dinh vi element
D->>E: Truy van UiAutomator2
E-->>D: Tham chieu element
D-->>S: Tim thay element
S-->>T: Element ID
T->>S: Click element
S->>D: Thuc hien tap
D->>E: Su kien input
E-->>T: Hoan thanh
T->>S: DELETE /session
S->>D: Don dep
D->>E: Go serverPage Object Model (POM)
Khi test đầu tiên chạy được, hãy áp dụng Page Object Model ngay lập tức. Đây là điều bắt buộc để test có thể bảo trì được.
Tại sao POM?
- Một chỗ để cập nhật. Nếu ID của một nút thay đổi, bạn chỉ sửa một file, không phải 50 test.
- Test đọc được.
login_page.enter_username("john")đọc như tiếng Anh bình thường. - Component tái sử dụng. Cùng một page object dùng được cho nhiều kịch bản test khác nhau.
Cấu trúc POM
graph TB
A["Test Cases<br/>test_login.py<br/>test_search.py"] --> B["Page Objects<br/>LoginPage<br/>SearchPage<br/>HomePage"]
B --> C["Base Page<br/>Hanh dong chung:<br/>click, type, wait, swipe"]
C --> D["Appium Driver<br/>WebDriver instance"]
style A fill:#E74C3C,stroke:#C0392B,color:#fff
style B fill:#3498DB,stroke:#2980B9,color:#fff
style C fill:#2ECC71,stroke:#27AE60,color:#fff
style D fill:#F39C12,stroke:#D68910,color:#fffHiện thực
base_page.py — hành động dùng chung:
"""base_page.py -- Lop co so cho tat ca page objects."""
from appium.webdriver.common.appiumby import AppiumBy
class BasePage:
"""Cung cap cac hanh dong chung cho tat ca cac trang."""
def __init__(self, driver):
self.driver = driver
def find(self, locator_type, locator_value):
return self.driver.find_element(locator_type, locator_value)
def click(self, locator_type, locator_value):
self.find(locator_type, locator_value).click()
def type_text(self, locator_type, locator_value, text):
element = self.find(locator_type, locator_value)
element.clear()
element.send_keys(text)
def is_displayed(self, locator_type, locator_value):
try:
return self.find(locator_type, locator_value).is_displayed()
except Exception:
return False
login_page.py — trang cụ thể:
"""login_page.py -- Page object cho man hinh Dang nhap."""
from appium.webdriver.common.appiumby import AppiumBy
from pages.base_page import BasePage
class LoginPage(BasePage):
"""Dai dien cho man hinh Dang nhap cua ung dung."""
# Locators
USERNAME_FIELD = (AppiumBy.ACCESSIBILITY_ID, "username_input")
PASSWORD_FIELD = (AppiumBy.ACCESSIBILITY_ID, "password_input")
LOGIN_BUTTON = (AppiumBy.ACCESSIBILITY_ID, "login_button")
ERROR_MESSAGE = (AppiumBy.ID, "com.app:id/error_text")
def enter_username(self, username):
self.type_text(*self.USERNAME_FIELD, username)
def enter_password(self, password):
self.type_text(*self.PASSWORD_FIELD, password)
def tap_login(self):
self.click(*self.LOGIN_BUTTON)
def login(self, username, password):
self.enter_username(username)
self.enter_password(password)
self.tap_login()
def get_error_message(self):
return self.find(*self.ERROR_MESSAGE).text
test_login.py — test sạch sẽ:
"""test_login.py -- Cac test cho luong dang nhap."""
import unittest
from pages.login_page import LoginPage
class TestLogin(unittest.TestCase):
"""Cac test tinh nang dang nhap."""
def test_successful_login(self):
login = LoginPage(self.driver)
login.login("testuser", "password123")
# Assert chuyen huong den trang chu
def test_invalid_password(self):
login = LoginPage(self.driver)
login.login("testuser", "wrong")
error = login.get_error_message()
self.assertEqual(error, "Invalid credentials")
Cấu trúc thư mục dự án
project/
pages/
__init__.py
base_page.py
login_page.py
home_page.py
search_page.py
tests/
__init__.py
conftest.py
test_login.py
test_search.py
config/
capabilities.json
requirements.txt
pytest.ini
Chiến lược Locator — Xếp hạng theo độ tin cậy
Không phải locator nào cũng như nhau. Đây là bảng xếp hạng chính thức:
1. Accessibility ID (Tốt nhất)
driver.find_element(AppiumBy.ACCESSIBILITY_ID, "submit_button")
Hoạt động trên cả Android (content-desc) và iOS (accessibilityIdentifier). Nhanh, ổn định, đa nền tảng. Hãy đề nghị developer thêm accessibility label — nó giúp cả việc test lẫn người dùng thật sử dụng screen reader.
2. ID
driver.find_element(AppiumBy.ID, "com.myapp:id/submit_btn")
Resource ID của Android. Tin cậy nhưng chỉ dùng cho một nền tảng.
3. Class Name
driver.find_element(AppiumBy.CLASS_NAME, "android.widget.Button")
Hữu ích để tìm nhóm element tương tự. Quá chung chung cho element đơn lẻ.
4. XPath (Dùng cẩn thận)
# XPath tuong doi -- chap nhan duoc
driver.find_element(
AppiumBy.XPATH,
'//android.widget.Button[@text="Submit"]'
)
# XPath tuyet doi -- KHONG BAO GIO dung
# Se hong khi bat ky element cha nao thay doi
driver.find_element(
AppiumBy.XPATH,
'/hierarchy/android.widget.FrameLayout/...'
)
XPath chậm và dễ gãy. Chỉ dùng khi không còn cách nào khác, và luôn dùng đường dẫn tương đối.
5. Android UIAutomator (Nâng cao)
driver.find_element(
AppiumBy.ANDROID_UIAUTOMATOR,
'new UiSelector().textContains("Submit")'
)
Mạnh mẽ cho các truy vấn phức tạp nhưng chỉ dùng cho Android.
Mẹo: Mở Appium Inspector, bấm vào bất kỳ element nào, và xem những chiến lược locator nào có sẵn. Luôn ưu tiên Accessibility ID.
Tích hợp Dự án Thật: CI/CD với Appium
Chạy test bằng tay không mở rộng được. Đây là cách tích hợp Appium vào pipeline delivery của bạn.
Kiến trúc CI/CD Pipeline
graph LR
A["Developer<br/>Push Code"] --> B["GitHub Actions<br/>/ Jenkins"]
B --> C["Build App<br/>(APK / IPA)"]
C --> D["Appium Tests<br/>trong Docker"]
D --> E{"Tests Pass?"}
E -->|Co| F["Deploy len<br/>Staging"]
E -->|Khong| G["Thong bao Team<br/>Chan Release"]
D --> H["Cloud Devices<br/>BrowserStack<br/>Sauce Labs"]
style A fill:#4A90D9,stroke:#2C5F8A,color:#fff
style B fill:#50C878,stroke:#2E8B57,color:#fff
style C fill:#FF8C42,stroke:#CC6F35,color:#fff
style D fill:#9B59B6,stroke:#7D3C98,color:#fff
style F fill:#2ECC71,stroke:#27AE60,color:#fff
style G fill:#E74C3C,stroke:#C0392B,color:#fff
style H fill:#F39C12,stroke:#D68910,color:#fffVí dụ GitHub Actions
name: Mobile Tests
on:
pull_request:
branches: [main]
jobs:
appium-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Cai dat JDK 17
uses: actions/setup-java@v4
with:
java-version: "17"
distribution: "temurin"
- name: Cai dat Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Cai dat Appium
run: |
npm install -g appium
appium driver install uiautomator2
- name: Khoi dong Android Emulator
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 34
script: |
appium &
sleep 10
python -m pytest tests/ --junitxml=results.xml
- name: Upload ket qua
uses: actions/upload-artifact@v4
with:
name: test-results
path: results.xml
Chạy trên BrowserStack (Thiết bị đám mây)
"""browserstack_config.py -- Chay test tren thiet bi that o dam may."""
from appium.options import UiAutomator2Options
options = UiAutomator2Options()
options.platform_name = "Android"
options.device_name = "Samsung Galaxy S24"
options.platform_version = "14.0"
# Cau hinh rieng cho BrowserStack
options.set_capability("bstack:options", {
"userName": "YOUR_USERNAME",
"accessKey": "YOUR_ACCESS_KEY",
"appUrl": "bs://your-uploaded-app-hash",
"projectName": "My App Tests",
"buildName": "Sprint 42",
})
# Ket noi den BrowserStack hub
driver = webdriver.Remote(
command_executor="https://hub.browserstack.com/wd/hub",
options=options,
)
Cài đặt Docker cho môi trường nhất quán
FROM node:20-slim
RUN npm install -g appium \
&& appium driver install uiautomator2
RUN apt-get update && apt-get install -y \
python3 python3-pip openjdk-17-jdk
COPY requirements.txt .
RUN pip3 install -r requirements.txt
COPY . /tests
WORKDIR /tests
CMD ["python3", "-m", "pytest", "tests/", "-v"]
Lời khuyên cho QC Manual chuyển sang Automation
Phần này là trái tim của bài viết. Tôi đã chứng kiến hàng chục QC tester thực hiện bước chuyển đổi này. Đây là những gì hiệu quả.
Bắt đầu nhỏ, thắng sớm
Đừng cố tự động hóa toàn bộ regression suite trong tuần đầu. Hãy chọn một test happy-path — luồng đăng nhập, hoặc mở màn hình chính. Cho nó xanh. Ăn mừng. Rồi thêm một test nữa.
Làm chủ Appium Inspector trước
Trước khi viết bất kỳ dòng code nào, hãy dành hai tiếng chỉ để click quanh app trong Appium Inspector. Tìm hiểu cây element trông như thế nào. Hiểu tại sao một số element có ID còn số khác thì không. Sự hiểu biết trực quan này đáng giá hơn bất kỳ tutorial nào.
Áp dụng POM từ ngày đầu
Tôi biết nó cảm giác như “thêm việc” khi bạn mới có ba test. Tin tôi đi — đến test thứ 20, bạn hoặc sẽ có POM và làm việc hiệu quả, hoặc sẽ có code rối như mì tôm và phải viết lại hết.
Học Python cơ bản trước khi học Appium
Bạn không cần trở thành kỹ sư phần mềm. Nhưng bạn cần hiểu:
- Biến, chuỗi, danh sách
- Hàm và lớp (cơ bản)
if/for/whileimportvà cấu trúc file- Cách đọc thông báo lỗi
Hai tuần học Python cơ bản trên bất kỳ nền tảng miễn phí nào là đủ.
Kết cặp với Developer
Hãy nhờ một developer trong team pair với bạn một tiếng mỗi tuần. Họ có thể giúp bạn:
- Hiểu cấu trúc ứng dụng
- Đề nghị developer thêm accessibility ID
- Debug test lỗi
- Review code cho test script của bạn
Tự động hóa cái đau nhất
Test nào bạn ghét chạy thủ công nhất? Cái có 47 bước mất 30 phút? Đó chính là ứng viên tự động hóa đầu tiên của bạn sau login.
Giữ danh sách Locator
Duy trì một bảng đơn giản: Màn hình, Element, Locator tốt nhất, Locator dự phòng. Đây sẽ trở thành tài liệu tham khảo và tiết kiệm thời gian khi viết test mới.
Những lỗi thường gặp và cách khắc phục
Test không ổn định (Flaky Tests)
Triệu chứng: Test khi pass khi fail, không có thay đổi code nào.
Nguyên nhân và cách sửa:
- Vấn đề timing. Thay
time.sleep()bằng explicit wait:
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
wait = WebDriverWait(driver, 15)
element = wait.until(
EC.presence_of_element_located(
(AppiumBy.ACCESSIBILITY_ID, "submit_button")
)
)
- Animation gây nhiễu. Tắt animation trên thiết bị: Settings, Developer Options, đặt tất cả animation scale về OFF.
- Phụ thuộc mạng. Mock API response cho các test phụ thuộc dữ liệu backend.
Không tìm thấy Element
Triệu chứng: NoSuchElementException dù bạn thấy element trên màn hình.
Cách sửa:
- Element có thể nằm trong view hierarchy khác (webview vs native). Kiểm tra context:
driver.contexts. - Element có thể cần cuộn. Dùng
driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, 'new UiScrollable(...).scrollIntoView(...)'). - Locator có thể đã thay đổi. Kiểm tra lại với Appium Inspector.
Test chạy chậm
Triệu chứng: Mỗi test mất vài phút để chạy.
Cách sửa:
- Dùng
noReset: Trueđể tránh cài lại app giữa các test. - Dùng
skipServerInstallation: Truenếu UiAutomator2 server đã có trên thiết bị. - Chạy test song song bằng
pytest-xdistvới nhiều emulator. - Ưu tiên Accessibility ID và ID hơn XPath.
Emulator vs Thiết bị thật
| Khía cạnh | Emulator | Thiết bị thật |
|---|---|---|
| Chi phí | Miễn phí | Cần thiết bị hoặc dịch vụ đám mây |
| Tốc độ | Phụ thuộc máy host | Ổn định |
| Độ tin cậy | Thỉnh thoảng treo | Rất ổn định |
| Camera, GPS, Cảm biến | Mô phỏng (hạn chế) | Phần cứng thật |
| Nên dùng cho | Phát triển, CI | Xác nhận cuối cùng |
Thực hành tốt nhất: Phát triển và debug trên emulator. Chạy full suite trên thiết bị thật (hoặc đám mây) trước khi release.
Vấn đề “Máy tôi chạy được mà”
Đây là lý do Docker và cloud device farm tồn tại. Emulator của bạn có thể có cài đặt khác với CI. Giải pháp:
- Dùng Docker container cho môi trường Appium nhất quán.
- Dùng BrowserStack hoặc Sauce Labs để test trên thiết bị thật.
- Khóa phiên bản tất cả dependency trong
requirements.txtvàpackage.json.
Kế hoạch 30 ngày của bạn
| Tuần | Mục tiêu | Hành động |
|---|---|---|
| 1 | Cài đặt và Khám phá | Cài mọi thứ, chạy Appium Inspector, khám phá app |
| 2 | Test đầu tiên | Viết 3 test cho luồng đăng nhập dùng POM |
| 3 | Mở rộng phạm vi | Thêm test cho 2 màn hình nữa, xử lý edge case |
| 4 | Tích hợp CI | Cài đặt GitHub Actions, chạy test mỗi PR |
Tài liệu tham khảo
- Tài liệu chính thức Appium
- Hướng dẫn chuyển đổi Appium 2.0
- Appium Python Client trên PyPI
- Bản phát hành Appium Inspector
- Tài liệu UiAutomator2 Driver
- Tài liệu XCUITest Driver
- Tài liệu Selenium WebDriverWait
- Tích hợp BrowserStack Appium
- Sauce Labs Mobile Testing
- Android Developer - UI Automator
- Page Object Model Pattern
Tự động hóa kiểm thử mobile không phải là đích đến — mà là kỹ năng bạn xây dựng từng test một. Bạn đã hiểu ứng dụng tốt hơn bất kỳ developer nào. Giờ bạn có công cụ để chứng minh điều đó bằng code. Bắt đầu hôm nay, bắt đầu nhỏ, và cứ tiếp tục.