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:#fff

Nhữ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: uiautomator2 cho Android, xcuitest cho iOS, espresso cho 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 đíchCần trên
Node.js 18+Chạy Appium serverTất cả
JDK 11 hoặc 17Android toolchainTất cả
Android StudioAndroid SDK, emulatorTất cả
Xcode 15+iOS Simulator, build toolsChỉ macOS
Python 3.10+Viết test scriptTấ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

  1. Tải và cài Android Studio.
  2. Mở Android Studio, vào SDK Manager.
  3. Cài Android SDK Platform 34, SDK Build-Tools, và SDK Platform-Tools.
  4. 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.

  1. Tải từ github.com/appium/appium-inspector/releases.
  2. Mở lên và cấu hình remote host: 127.0.0.1, port 4723, path /.
  3. Đặt desired capabilities (xem phần tiếp theo).
  4. 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 server

Page 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:#fff

Hiệ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:#fff

Ví 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 / while
  • import và 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: True nếu UiAutomator2 server đã có trên thiết bị.
  • Chạy test song song bằng pytest-xdist với nhiều emulator.
  • Ưu tiên Accessibility ID và ID hơn XPath.

Emulator vs Thiết bị thật

Khía cạnhEmulatorThiế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ậyThỉnh thoảng treoRất ổn định
Camera, GPS, Cảm biếnMô phỏng (hạn chế)Phần cứng thật
Nên dùng choPhát triển, CIXá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.txtpackage.json.

Kế hoạch 30 ngày của bạn

TuầnMục tiêuHành động
1Cài đặt và Khám pháCài mọi thứ, chạy Appium Inspector, khám phá app
2Test đầu tiênViết 3 test cho luồng đăng nhập dùng POM
3Mở rộng phạm viThêm test cho 2 màn hình nữa, xử lý edge case
4Tích hợp CICài đặt GitHub Actions, chạy test mỗi PR

Tài liệu tham khảo


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.

Xuất nội dung

Bình luận