Áp dụng Page Object Model (POM)
Áp dụng mẫu Page Object Model để bộ flow lớn dễ bảo trì.
Khi số lượng test tăng lên, một issue xuất hiện rất nhanh: bạn dùng selector trong mọi file flow.
Các id hardcode rải rác trong hàng chục file YAML thì ban đầu rất dễ, nhưng ngay khi developer đổi tên một nút hoặc đổi một nhãn, bạn có thể phải sửa nhiều test chỉ để xử lý một thay đổi UI. Đây là ví dụ của cách tiếp cận không mở rộng được.
Đây chính xác là issue mà Page Object Model (POM) được thiết kế để giải quyết.
POM là một design pattern thêm một lớp trừu tượng giữa logic test và các selector UI. Thay vì tham chiếu trực tiếp id của phần tử trong flow, bạn tham chiếu một đối tượng JavaScript trung tâm. Khi UI thay đổi, bạn cập nhật selector ở một nơi, và mọi test phụ thuộc vào nó vẫn chạy được.
Ý tưởng cốt lõi
Nguyên tắc dẫn đường của POM rất đơn giản:
Tách phần tử là gì khỏi cách test tương tác với nó.
Bạn làm được điều này bằng cách:
- Định nghĩa selector trong các file JavaScript bằng đối tượng
output. - Nạp các định nghĩa đó khi chạy.
- Tham chiếu biến (không phải
idthô) trong flow.
Khi áp dụng mẫu này, flow của bạn mô tả ý định của người dùng, không phải chi tiết triển khai kỹ thuật.
Quy trình cơ bản
Ở mức tổng quan, triển khai POM theo quy trình sau:
- Lưu
id,texthoặc pattern regex trong các file.jsbằngoutput. - Nhóm selector theo logic: theo màn hình, tính năng hoặc nền tảng.
- Dùng
runScripttrực tiếp hoặc một flow loader tập trung để làm selector khả dụng. - Tham chiếu selector bằng
${output...}trongtapOn,inputText,assertVisiblevà các lệnh khác.
Các phần sau mô tả cách áp dụng POM.
1. Định nghĩa page object đầu tiên
Bắt đầu bằng việc tạo một file JavaScript đại diện cho một màn hình trong app, ví dụ màn hình đăng nhập.
// login.js
output.login = {
email: 'email_text',
password: 'password_text',
loginBtn: 'loginButton',
registerBtn: 'registerButton'
}File này đóng vai trò như từ điển cho màn hình Login. Mỗi key mô tả phần tử là gì, còn value xác định cách Astra Scout tìm thấy nó.
Để dùng các selector của phần tử, bạn cần dùng runScript để nạp các biến phần tử của màn hình. Nhờ đó không có ID thô rải rác trong test, và ý định của mỗi hành động rõ ràng ngay lập tức.
# login_flow.yaml
---
- runScript: login.js
- tapOn:
id: ${output.login.email}
- inputText: "simon@maestro.com"
- tapOn:
id: ${output.login.password}
- inputText: ${PASSWORD}
- tapOn:
id: ${output.login.loginBtn}2. Tổ chức màn hình phức tạp bằng object lồng nhau
Khi màn hình phức tạp hơn, một danh sách selector phẳng có thể khó quản lý. Với POM, bạn có thể lồng các object, phản ánh cấu trúc của UI.
Điều này đặc biệt hữu ích cho các thành phần tái sử dụng như card, toolbar hay menu điều hướng. Ví dụ sau cho thấy một object dùng để mô tả một card:
// cards.js
output.cards = {
cardTitle: 'card_title',
virtualCards: {
createBtn: 'btn_create_virtual',
details: {
number: 'card_number',
expiry: 'card_expiry'
}
}
}Cấu trúc phản ánh phân cấp UI. Các phần tử liên quan nằm cùng nhau và tránh được trùng tên.
Cách dùng selector lồng nhau trong flow tương tự như làm việc với một object JavaScript định nghĩa trong file JSON. Bản thân đường dẫn selector đã ghi lại cấu trúc UI, giúp test dễ hiểu và dễ bảo trì hơn.
- runScript: cards.js
- tapOn:
id: ${output.cards.virtualCards.createBtn}
- assertVisible:
id: ${output.cards.virtualCards.details.number}Lợi ích và đánh đổi của POM
Áp dụng POM là lựa chọn chiến lược cho khả năng bảo trì bộ test, nhưng đi kèm một đánh đổi cụ thể:
Bảo trì tập trung: nếu
btn_create_virtualđổi thànhfab_add, bạn cập nhật một file, không phải hàng chục test.Namespace: object lồng nhau ngăn trùng tên giữa các tên phổ biến như
title,submithaybutton.Hỗ trợ regex: Astra Scout mặc định coi chuỗi selector là biểu thức chính quy. Bạn có thể lưu các pattern động như:
javascriptoutput.welcomeBanner = 'Welcome back, .*'và tái sử dụng chúng nhất quán qua các test.
Error dễ đọc: khi test fail, tên biến như
loginBtncung cấp nhiều thông tin hơn hẳn ID thô.
Dù POM giảm công bảo trì, nó có thể làm giảm đôi chút khả năng đọc của test. Cách trực tiếp thì dễ đọc nhưng mong manh:
- tapOn: Add to BasketTrong khi cách POM thì trừu tượng nhưng vững chắc:
- tapOn: ${output.itemPage.addButton}Hãy chọn POM khi tính ổn định và khả năng mở rộng của bộ automation quan trọng hơn lợi ích của việc đọc văn bản thô trong file YAML.
Cấu trúc thư mục khuyến nghị
Giữ selector tách khỏi logic test sẽ cải thiện tổ chức. Một thư mục elements riêng giúp việc tách biệt này rõ ràng.
Với app đa nền tảng, tổ chức theo nền tảng cũng được khuyến nghị mạnh mẽ. Test của bạn giữ nguyên độc lập nền tảng, còn selector riêng theo từng nền tảng.
android/
elements/
login.js
home.js
loadElements.yaml
tests/
login_flow.yaml
ios/
elements/
login.js
home.js
loadElements.yaml
tests/
login_flow.yamlChiến lược loader
Khi project lớn lên, việc gọi runScript thủ công trong mọi flow trở nên lặp lại và dễ sai sót. Để giải quyết, bạn có thể dùng một Loader Flow. Nhiệm vụ duy nhất của loader là nạp mọi page object một lần, như ví dụ sau:
# elements/loadElements.yaml
appId: com.example.app
---
- runScript: cards.js
- runScript: login.js
- runScript: home.js
- runScript: nav.jsNếu dùng loader như một subflow, nó khởi tạo toàn bộ schema selector ngay đầu run test. Nhờ vậy mọi test bắt đầu từ một trạng thái đã biết, nhất quán, và không selector nào bị quên vô tình.
---
- runFlow: elements/loadElements.yaml
- launchApp
# Mọi selector giờ đã dùng được qua ${output...}Xử lý khác biệt giữa các nền tảng
Android và iOS thường dùng ID khác nhau cho cùng một phần tử chức năng. POM giúp việc này dễ quản lý bằng cách giữ nguyên tên biến và hoán đổi phần triển khai.
- runFlow:
when:
platform: Android
commands:
- runScript: android/elements/login.js
- runFlow:
when:
platform: iOS
commands:
- runScript: ios/elements/login.jsVới cách này, mỗi nền tảng nạp selector riêng của mình, nhưng logic test không bao giờ thay đổi.
- tapOn:
id: ${output.login.loginBtn}Nội dung liên quan
Để nắm vững logic dùng trong Page Object Model, hãy khám phá các bài chuyên sâu sau:
- JavaScript: tìm hiểu cách dùng JavaScript để quản lý dữ liệu dùng chung và cấu trúc phức tạp.
- Tham số và hằng số: tìm hiểu cách đối tượng
outputtồn tại xuyên suốt các flow. - Điều kiện: nắm vững mệnh đề
whencho logic riêng theo nền tảng.
