QA 22: Mobil avtomatlashtirish — Maestro, Appium 3, XCUITest va Espresso
🧪 QA Engineer — Zero to Hero
QA 22: Mobil avtomatlashtirish — Maestro, Appium 3, XCUITest va Espresso
Holat: 2026-yil oktabr. Versiyalar: Maestro CLI 2.11.0, Appium 3.8.0 (XCUITest driver 12.16.0, UiAutomator2 driver 8.7.0), WebdriverIO 10.0.2, Xcode 26.5, Detox 20.51.4. WebdriverIO kodlari TypeScript kompilyatorida, XCUITest kodi iOS 26.5 SDK'da tekshirilgan. Maestro va Appium bu dars yozilgan muhitda ishga tushirilmagan — sintaksis rasmiy hujjatlardan olingan.
Bu darsdan keyin siz: mobil ilova uchun to'g'ri avtomatlashtirish vositasini tanlaysiz va birinchi barqaror testlaringizni yozasiz.
- 2026-yilgi mobil avtomatlashtirish vositalari va ularning farqi;
- ilovani testga tayyorlash: identifikatorlar, test rejimi, deep link;
- Maestro bilan YAML flow'lar va CI;
- Appium 3 arxitekturasi, WebdriverIO 10 bilan TypeScript testlar, locator va gesture'lar;
- XCUITest (Swift) va Espresso/Compose (Kotlin) native testlar;
- beqaror (flaky) mobil testlarning sabablari va davosi.
Mundarija
- Mobil avtomatlashtirish xaritasi (2026)
- Testability: ilovani testga tayyorlash
- Maestro: YAML bilan tez start
- Appium 3: arxitektura va o'rnatish
- WebdriverIO 10 + Appium: TypeScript'da birinchi test
- Locator strategiyasi va gesture'lar
- XCUITest: iOS native UI testlar
- Espresso va Compose testing: Android native
- Flaky mobil testlar: sabablar va davolash
- CI va qurilma fermalari
- Qaysi vositani tanlash kerak?
- Amaliy mashqlar
- O'zingizni tekshiring
- Xulosa
- Manbalar
1. Mobil avtomatlashtirish xaritasi (2026) {#xarita}
Mobil UI testlar web testlarga qaraganda sekinroq va qimmatroq. Shuning uchun test piramidasi (QA 4) bu yerda ayniqsa muhim:
- pastda — ko'p unit testlar (Swift Testing/XCTest, JUnit);
- o'rtada — komponent va API testlari (QA 20);
- tepada — kam sonli, lekin muhim E2E ssenariylar: kirish, katalog, savatcha, buyurtma.
| Vosita | Kim yaratgan | Til | Platforma | Turi | Qachon qulay |
|---|---|---|---|---|---|
| XCUITest | Apple | Swift | iOS, iPadOS, macOS, tvOS | Native, alohida jarayonda | iOS jamoasi o'z ilovasini testlaydi |
| Espresso | Kotlin/Java | Android (View) | Native, ilova ichida, UI thread bilan sinxron | Android View ilovalar | |
| Compose UI test | Kotlin | Android (Jetpack Compose) | Native | Compose ekranlar | |
| Appium 3 | Open source (OpenJS) | JS/TS, Java, Python, C# va boshqalar | iOS, Android va boshqalar | Black-box, W3C WebDriver | Ikki platforma, bitta test kodi, device farm'lar |
| Maestro | mobile.dev (open source CLI) | YAML (+ JavaScript) | Android, iOS, Web; RN va Flutter | Black-box, accessibility daraxti orqali | Tez start, QA jamoasi kod yozmasdan |
| Detox | Wix | JavaScript | React Native | Gray-box | React Native ilovalar |
| Patrol / integration_test | LeanCode / Flutter jamoasi | Dart | Flutter | Native + Flutter | Flutter ilovalar (Patrol tizim dialoglari bilan ham ishlaydi) |
🧠 Esda tuting: "Eng yaxshi vosita" yo'q — jamoaga mos vosita bor. Native dasturchilar o'zi test yozsa — XCUITest/Espresso. Alohida QA jamoasi ikki platformani tez qamrab olmoqchi bo'lsa — Maestro yoki Appium.
2. Testability: ilovani testga tayyorlash {#testability}
Avtomatlashtirish kod yozishdan emas, ilovani testga qulay qilishdan boshlanadi. Bu QA va dasturchilarning birgalikdagi ishi.
2.1 Barqaror identifikatorlar
Matn bo'yicha qidirish ("Kirish") til o'zgarganda buziladi. DemoBozor uch tilda ishlaydi — o'zbek, rus, ingliz. Shuning uchun har bir muhim element foydalanuvchiga ko'rinmaydigan identifikatorga ega bo'lishi kerak:
// SwiftUI
TextField("Telefon raqam", text: $phone)
.accessibilityIdentifier("login.phone")
// UIKit
requestCodeButton.accessibilityIdentifier = "login.requestCode"
// Jetpack Compose
OutlinedTextField(
value = phone,
onValueChange = { phone = it },
modifier = Modifier.testTag("login.phone")
)
// Ildiz composable'da bir marta: testTag'lar UiAutomator/Appium/Maestro uchun resource-id bo'lib ko'rinadi
Scaffold(modifier = Modifier.semantics { testTagsAsResourceId = true }) { /* ... */ }
Android View'da esa android:id="@+id/phoneInput" — u avtomatlashtirish vositalarida to'liq uz.demobozor.app:id/phoneInput ko'rinishidagi resource-id bo'lib chiqadi. Compose testTag esa (testTagsAsResourceId yoqilganda) aynan o'zi — login.phone — resource-id bo'ladi.
Nomlash qoidasi (jamoada kelishib oling): ekran.element — login.phone, login.requestCode, otp.input, home.title, cart.badge, product.addToCart. Bir xil mantiqiy elementga iOS va Android'da bir xil nom bersangiz, Maestro (id: selektori) va Appium testlari ikki platformada bir xil ishlaydi.
⚠️ Diqqat:
accessibilityIdentifiervacontentDescription/accessibilityLabel— boshqa-boshqa narsalar. Label'ni ekran o'quvchi (VoiceOver/TalkBack) foydalanuvchiga o'qib beradi. Ungalogin_btn_2kabi texnik nom yozish accessibility'ni buzadi (QA 25). Test uchun identifier yoki testTag ishlating.
2.2 Test rejimi (launch arguments)
Test ilovani boshqacha ishga tushirishi kerak: staging API, animatsiyalarsiz, onboarding'siz, qat'iy sana bilan. Buning uchun launch argument'lar ishlatiladi:
// iOS ilova kodi (AppDelegate yoki App init)
if ProcessInfo.processInfo.arguments.contains("-uiTesting") {
UIView.setAnimationsEnabled(false)
// staging API, onboarding'ni o'tkazib yuborish, test ma'lumotlari...
}
Android'da argument'lar intent extras sifatida keladi (Maestro hujjatidagi usul):
val isUiTesting = intent.extras?.getBoolean("uiTesting") ?: false
2.3 Deep link — uzun yo'lni qisqartirish
"Buyurtma tafsilotlari" ekranini testlash uchun har safar login → katalog → buyurtmalar → #1043 yo'lini bosib o'tish shart emas: demobozor://orders/1043 deep link bilan to'g'ridan-to'g'ri ochiladi. Login holatini esa API orqali tayyorlash mumkin (QA 20).
2.4 Test ma'lumotlari va SMS kod
SMS kodni avtomatik testda haqiqiy telefondan o'qish — beqarorlik manbai. Odatiy yechim: staging'da maxsus test raqamlari uchun oldindan ma'lum kod.
⚠️ Diqqat: Bunday "test kodi" faqat staging'da va faqat test raqamlari uchun ishlashi kerak. Prod'da bunday "orqa eshik" qolsa, bu Critical xavfsizlik zaifligi. QA buni release build va prod API'da alohida tekshiradi (QA 24).
2.5 Animatsiyalarni o'chirish (Android)
adb shell settings put global window_animation_scale 0
adb shell settings put global transition_animation_scale 0
adb shell settings put global animator_duration_scale 0
3. Maestro: YAML bilan tez start {#maestro}
Maestro — ochiq kodli UI avtomatlashtirish freymvorki. Testlar YAML'da yoziladi va Flow deb ataladi. Maestro qurilmani "tashqaridan" boshqaradi: accessibility daraxtini o'qiydi va teginish, surish, matn kiritish buyruqlarini yuboradi. Android va iOS'da u qurilmaga kichik "companion driver" ilovani o'rnatib, u bilan ishlaydi. Shuning uchun tizim dialoglari va bildirishnomalar bilan ham ishlay oladi.
Kuchli tomoni — o'rnatilgan sabr: assertVisible element darhol ko'rinmasa ham 7 soniyagacha avtomatik kutadi va qayta uriniladi. sleep yozish deyarli kerak bo'lmaydi.
O'rnatish (macOS)
Talab: Java 17+ (java -version), Xcode va Command Line Tools.
curl -fsSL "https://get.maestro.mobile.dev" | bash
maestro --help
Homebrew orqali ham bo'ladi (mobile-dev-inc/tap). Vizual muharrir — Maestro Studio desktop ilovasi: qurilma ekranini ko'rsatadi, element ustiga bossangiz, mos buyruqni taklif qiladi.
Loyiha tuzilmasi
flows/
├── config.yaml # qaysi flow'lar ishlashini belgilaydi
├── login_smoke.yaml
├── cart_add_product.yaml
├── offline_catalog.yaml
└── subflows/
└── login.yaml # qayta ishlatiladigan qadam (runFlow bilan chaqiriladi)
Subflow: kirish
# subflows/login.yaml — boshqa flow'lar qayta ishlatadigan kirish qadami
appId: uz.demobozor.app
env:
PHONE: ${PHONE || "901234567"}
---
- tapOn:
id: "login.phone"
- inputText: ${PHONE}
- tapOn:
id: "login.requestCode"
- assertVisible:
id: "otp.input"
- inputText: ${OTP}
- assertVisible:
id: "home.title"
${PHONE || "901234567"} — parametr berilmasa, standart test raqami ishlatiladi.
Smoke flow
# flows/login_smoke.yaml — DemoBozor: SMS kod orqali kirish (smoke)
appId: uz.demobozor.app
tags:
- smoke
- auth
---
- launchApp:
clearState: true
permissions:
notifications: deny
arguments:
uiTesting: true
- runFlow:
file: subflows/login.yaml
label: Test raqami bilan kirish
- takeScreenshot: login_success
clearState: true — toza o'rnatish holatidan boshlash. permissions — ruxsat dialogini oldindan hal qilish (bu yerda bildirishnoma rad etiladi). arguments esa ilovaga test rejimi bayrog'ini uzatadi (2.2).
Savatcha flow'i: scroll va tekshiruvlar
# flows/cart_add_product.yaml — katalogdan mahsulotni savatchaga qo'shish
appId: uz.demobozor.app
tags:
- regression
- cart
---
- launchApp:
arguments:
uiTesting: true
- runFlow: subflows/login.yaml
- tapOn:
id: "tab.catalog"
- scrollUntilVisible:
element:
id: "product.gilam-2x3"
direction: DOWN
timeout: 20000
- tapOn:
id: "product.gilam-2x3"
- tapOn:
id: "product.addToCart"
- assertVisible:
id: "cart.badge"
text: "1"
- tapOn:
id: "tab.cart"
- assertVisible: "Gilam \\(2x3 m\\)"
- assertVisible:
id: "cart.deliveryFee"
Ikki nozik joy:
- Maestro'da
textvaidselektorlari sukut bo'yicha regex. Shuning uchun qavslarni ekranlash kerak:Gilam \(2x3 m\)(YAML qo'sh tirnoq ichida\\(deb yoziladi). - Bir nechta atributni birga yozish (
id+text) — VA sharti: ikkalasi ham mos bo'lishi kerak.
Offline holat (faqat Android)
# flows/offline_catalog.yaml — internet yo'qligida katalog (faqat Android)
appId: uz.demobozor.app
tags:
- android-only
---
- launchApp
- setAirplaneMode: enabled
- tapOn:
id: "tab.catalog"
- assertVisible:
id: "error.offline"
- setAirplaneMode: disabled
- tapOn:
id: "error.retry"
- assertVisible:
id: "catalog.list"
Hujjatga ko'ra setAirplaneMode faqat Android'da ishlaydi. iOS simulatorda "parvoz rejimi" yo'q: buyruq xatosiz o'tadi, lekin hech narsa qilmaydi. iOS uchun offline'ni Proxyman yoki Network Link Conditioner bilan simulyatsiya qiling (QA 15).
Konfiguratsiya va ishga tushirish
# flows/config.yaml — qaysi flow'lar ishga tushishini belgilaydi
flows:
- "*"
- "!subflows/**"
Sukut bo'yicha Maestro faqat papkaning yuqori darajasidagi flow'larni ishga tushiradi, shuning uchun subflows/ o'zi ham ishlamaydi. ! bilan boshlanuvchi qator esa niyatni aniq ko'rsatadi.
# Faqat smoke testlar, JUnit hisobot bilan
maestro test flows/ --include-tags=smoke --format=JUNIT --output=maestro-report.xml \
-e PHONE=901234567 -e OTP=000000
# Ekrandagi elementlar daraxtini terminalda ko'rish
maestro hierarchy
# Flow'ni MP4 videoga yozish (--local: video kompyuteringizda render qilinadi)
maestro record --local flows/login_smoke.yaml login_smoke.mp4
Boshqa foydali buyruqlar: openLink (deep link), setLocation, setPermissions, swipe, back, hideKeyboard, extendedWaitUntil (7 soniyadan uzoq kutish), retry, repeat, runScript (JavaScript) va boshqalar.
⚠️ Diqqat: Maestro hujjatiga ko'ra
inputTextAndroid'da Unicode belgilarni qo'llab-quvvatlamaydi. Kirill (Ташкент),oʻ/gʻva emoji'larni Android'dainputTextbilan kiritib bo'lmaydi. Bunday testlar uchun ma'lumotni API orqali tayyorlang, clipboard'dan foydalaning (setClipboard+pasteText) yoki boshqa vosita tanlang. Bu cheklovni test rejasiga albatta yozing.
💡 Maslahat:
maestro mcp— Maestro'ning MCP serveri. Claude Code kabi AI yordamchilar u orqali flow yozib, ishga tushirib, xatosini tahlil qila oladi (QA 26).
4. Appium 3: arxitektura va o'rnatish {#appium}
Appium — mobil ilovalar uchun ochiq kodli avtomatlashtirish platformasi. U W3C WebDriver protokolida ishlaydi, ya'ni Selenium bilan bir xil g'oya: test kodi HTTP so'rov yuboradi, server esa uni qurilmadagi amalga aylantiradi.
Appium 3 arxitekturasi: test kodi, Appium server, driver'lar va qurilmalar
| Qism | Vazifasi |
|---|---|
| Klient | Test kodi: WebdriverIO (JS/TS), Java, Python, C# va boshqalar |
| Appium server | Node.js dasturi; sukut bo'yicha http://127.0.0.1:4723 da so'rov kutadi |
| Driver | Platformaga xos ko'prik: iOS uchun XCUITest driver, Android uchun UiAutomator2 driver |
| Qurilmadagi agent | iOS'da WebDriverAgent (XCTest asosidagi runner), Android'da UiAutomator2 server APK'si |
| Plugin'lar | Server imkoniyatlarini kengaytiradi, masalan, inspector |
Appium 3 da nima o'zgardi?
Appium 3 2025-yil avgustda chiqdi va "tozalash" relizi bo'ldi:
- Node.js ≥ 20.19 (
^20.19.0 || ^22.12.0 || >=24.0.0) va npm ≥ 10 talab qilinadi; - 100 ga yaqin eski endpoint olib tashlandi: masalan,
/touch/*o'rniga W3C Actions yokimobile:buyruqlari ishlatiladi; --allow-insecurefunksiyalariga driver prefiksi majburiy:appium --allow-insecure=uiautomator2:adb_shellyoki barcha driverlar uchun*:adb_shell;GET /sessionso'rnigaGET /appium/sessions(alohida flag bilan);- ilova arxivini endi server emas, driver ochadi — driverlarni yangilab turing;
- ichki Express 5 ga o'tkazildi (oddiy foydalanuvchiga ta'sir qilmaydi).
O'rnatish (o'z kompyuteringizda)
npm install -g appium # Appium 3 server
appium driver install xcuitest # iOS driveri (Xcode kerak)
appium driver install uiautomator2 # Android driveri (Android SDK + JDK kerak)
appium driver doctor uiautomator2 # muhitni tekshirish: ANDROID_HOME, JAVA_HOME...
appium driver list --installed
appium plugin install inspector # Appium Inspector plugin'i
appium --use-plugins=inspector # Inspector: http://127.0.0.1:4723/inspector
Appium Inspector — ekrandagi elementlar daraxtini, ularning identifikatorlari va xususiyatlarini ko'rsatadigan vosita. U ikki ko'rinishda bor: desktop ilova (GitHub Releases) va Appium server plugin'i. Eskirgan "Appium Desktop" endi yo'q. Avvalgi inspector.appiumpro.com veb-versiyasi esa Appium jamoasiga aloqador emas.
Capabilities
Sessiya ochishda klient capabilities yuboradi. Appium'ga xos kalitlar appium: prefiksi bilan yoziladi:
{
"platformName": "Android",
"appium:automationName": "UiAutomator2",
"appium:deviceName": "Pixel_7_API_34",
"appium:app": "/Users/qa/apps/demobozor-staging.apk",
"appium:noReset": false
}
{
"platformName": "iOS",
"appium:automationName": "XCUITest",
"appium:deviceName": "iPhone 17",
"appium:platformVersion": "26.5",
"appium:app": "/Users/qa/apps/DemoBozor.app",
"appium:noReset": false
}
appium:noReset: false — har sessiyada ilova holatini tozalash. true bo'lsa, oldingi test qoldirgan ma'lumot keyingisiga ta'sir qiladi (flaky test manbai).
5. WebdriverIO 10 + Appium: TypeScript'da birinchi test {#webdriverio}
WebdriverIO — Node.js uchun WebDriver klienti va test runner'i. WebdriverIO 10 Appium 3 ni talab qiladi: uning Appium service'i Appium 1.x/2.x ni ishga tushirmaydi.
npm init wdio@latest . # konfiguratsiya ustasi
npm install @wdio/appium-service appium@^3 --save-dev # Appium'ni loyiha ichida saqlash
Konfiguratsiya
// wdio.conf.ts — Android emulatorda DemoBozor testlari (Appium 3 + UiAutomator2)
export const config: WebdriverIO.Config = {
runner: "local",
specs: ["./test/specs/**/*.e2e.ts"],
maxInstances: 1,
port: 4723, // Appium'ning standart porti
services: ["appium"],
framework: "mocha",
reporters: ["spec"],
mochaOpts: { timeout: 120_000 },
capabilities: [
{
platformName: "Android",
"appium:automationName": "UiAutomator2",
"appium:deviceName": "Pixel_7_API_34",
"appium:app": "./apps/demobozor-staging.apk",
"appium:appWaitActivity": "*",
"appium:noReset": false,
},
],
};
services: ['appium'] — testlar boshlanganda Appium serverini avtomatik ishga tushiradi va oxirida to'xtatadi.
Screen Object (mobil Page Object)
Web'dagi Page Object Model (QA 19) mobil'da Screen Object deb ham ataladi:
// test/helpers/byTestId.ts — bitta identifier, ikki platforma
import { $, driver } from "@wdio/globals";
// iOS: accessibilityIdentifier -> ~login.phone
// Android: Compose testTag (testTagsAsResourceId bilan) -> resource-id "login.phone"
export function byTestId(id: string) {
return driver.isAndroid
? $(`android=new UiSelector().resourceId("${id}")`)
: $(`~${id}`);
}
// test/screens/LoginScreen.ts — mobil Page Object (Screen Object)
import { byTestId } from "../helpers/byTestId.js";
class LoginScreen {
get phoneInput() {
return byTestId("login.phone");
}
get requestCodeButton() {
return byTestId("login.requestCode");
}
get otpInput() {
return byTestId("otp.input");
}
get errorText() {
return byTestId("login.error");
}
async requestCode(phone: string): Promise<void> {
await this.phoneInput.setValue(phone);
await this.requestCodeButton.click();
}
async enterOtp(code: string): Promise<void> {
await this.otpInput.waitForDisplayed({ timeout: 10_000 });
await this.otpInput.setValue(code);
}
}
export default new LoginScreen();
// test/screens/HomeScreen.ts
import { byTestId } from "../helpers/byTestId.js";
class HomeScreen {
get title() {
return byTestId("home.title");
}
get catalogTab() {
return byTestId("tab.catalog");
}
}
export default new HomeScreen();
Test
// test/specs/login.e2e.ts — SMS kod bilan kirish (staging test raqami)
import { expect } from "@wdio/globals";
import LoginScreen from "../screens/LoginScreen.js";
import HomeScreen from "../screens/HomeScreen.js";
const TEST_PHONE = process.env.TEST_PHONE ?? "901234567";
const TEST_OTP = process.env.TEST_OTP ?? "";
describe("DemoBozor: kirish", () => {
it("to'g'ri SMS kod bilan bosh sahifa ochiladi", async () => {
await LoginScreen.requestCode(TEST_PHONE);
await LoginScreen.enterOtp(TEST_OTP);
await expect(HomeScreen.title).toBeDisplayed();
});
it("noto'g'ri kodda xato matni chiqadi", async () => {
await LoginScreen.requestCode(TEST_PHONE);
await LoginScreen.enterOtp("000001");
await expect(LoginScreen.errorText).toBeDisplayed();
});
});
Ishga tushirish:
TEST_OTP=000000 npx wdio run ./wdio.conf.ts
byTestId yordamchisi bitta nom bilan ikki platformada qidiradi. iOS'da ~login.phone — accessibility id selektori, u accessibilityIdentifier bo'yicha qidiradi. Android'da esa resource-id bo'yicha qidiriladi: Compose testTag (testTagsAsResourceId bilan). Nega Android'da ham ~ ishlatmaymiz? UiAutomator2'da ~ content-desc ni qidiradi, u esa ekran o'quvchi foydalanuvchiga o'qib beradigan matn. Unga texnik nom yozish accessibility'ni buzadi (2.1).
expect(...).toBeDisplayed() — WebdriverIO'ning avtomatik kutadigan tekshiruvi: element ko'ringuncha qayta uriniladi.
6. Locator strategiyasi va gesture'lar {#locators-gestures}
Locator ustuvorligi (mobil)
| O'rin | Strategiya | WebdriverIO misoli | Izoh |
|---|---|---|---|
| 1 | Accessibility ID (iOS) | $('~cart.badge') | iOS'da eng barqaror va tez |
| 1 | Resource ID (Android) | $(android=new UiSelector().resourceId("cart.badge")) yoki $('id=uz.demobozor.app:id/cartBadge') | Compose testTag / View android:id |
| 2 | iOS predicate string | $(-ios predicate string:type == 'XCUIElementTypeButton' AND name == 'tab.cart') | Murakkab shartlar, tez |
| 2 | iOS class chain | $('-ios class chain:**/XCUIElementTypeCell[1]') | Ierarxiya bo'yicha |
| 2 | Android UiSelector | $(android=new UiSelector().text("Savatcha")) | Matn, class, tartib |
| 3 | XPath | $('//android.widget.Button[@text="Savatcha"]') | Oxirgi chora: sekin va mo'rt |
⚠️ Diqqat: XPath mobil'da ayniqsa sekin. Appium har safar butun elementlar daraxtini XML'ga aylantiradi. Layout ozgina o'zgarsa ham XPath buziladi. Agar element'ga XPath'siz yetib bo'lmasa, bu — dasturchidan identifier so'rash uchun signal.
Gesture'lar
WebdriverIO 10'da native ilovalar uchun yuqori darajali buyruqlar bor (ular ichkarida W3C Actions va driver buyruqlaridan foydalanadi):
// gestures.e2e.ts — scroll, swipe va tap misollari
import { $, browser, driver, expect } from '@wdio/globals';
import { byTestId } from '../helpers/byTestId.js';
describe('DemoBozor: katalog gesture\'lari', () => {
it('mahsulotgacha scroll qilib, savatchaga qo\'shadi', async () => {
await byTestId('tab.catalog').tap();
// direction: 'up' — barmoq yuqoriga suriladi, ro'yxat pastga ko'chadi (standart qiymat)
const product = byTestId('product.gilam-2x3');
await product.scrollIntoView({ direction: 'up', maxScrolls: 10 });
await product.tap();
await byTestId('product.addToCart').tap();
await expect(byTestId('cart.badge')).toHaveText('1');
});
it('banner karuselini chapga suradi', async () => {
await browser.swipe({ direction: 'left', scrollableElement: byTestId('home.banners') });
});
it('platformaga xos selektorlar', async () => {
// Android: UiAutomator selektori
const uiSelector = 'new UiSelector().text("Savatcha").className("android.widget.TextView")';
const cartTabAndroid = $(`android=${uiSelector}`);
// iOS: predicate string va class chain
const cartTabIos = $(`-ios predicate string:type == 'XCUIElementTypeButton' AND name == 'tab.cart'`);
const firstCell = $('-ios class chain:**/XCUIElementTypeCell[1]');
const isAndroid = driver.isAndroid;
await (isAndroid ? cartTabAndroid : cartTabIos).tap();
if (!isAndroid) {
await expect(firstCell).toBeDisplayed();
}
});
it('W3C Actions bilan pastga tortib yangilash (pull-to-refresh)', async () => {
const { width, height } = await driver.getWindowSize();
await browser
.action('pointer', { parameters: { pointerType: 'touch' } })
.move({ x: Math.round(width / 2), y: Math.round(height * 0.3) })
.down()
.pause(100)
.move({ duration: 400, x: Math.round(width / 2), y: Math.round(height * 0.8) })
.up()
.perform();
});
});
| Buyruq | Nima qiladi |
|---|---|
$(sel).tap() | Elementni bosadi; kerak bo'lsa avval unga scroll qiladi (click dan ishonchliroq) |
$(sel).scrollIntoView({ maxScrolls }) | Element ko'ringuncha suradi (standart: 10 marta) |
browser.swipe({ direction, scrollableElement }) | Ekran yoki element ichida surish |
browser.action('pointer', ...) | Past darajadagi W3C Actions: istalgan harakat |
driver.execute('mobile: scrollGesture', {...}) / 'mobile: scroll' | Android/iOS driverining o'z buyruqlari |
💡 Maslahat: Koordinatalar bo'yicha bosishdan (
x: 120, y: 640) qoching — boshqa ekran o'lchamida test buziladi. Yuqoridagi pull-to-refresh misolida ham koordinatalar ekran o'lchamiga nisbatan (height * 0.3) hisoblangan.
7. XCUITest: iOS native UI testlar {#xcuitest}
XCUITest — Apple'ning UI testlash API'si. Apple hujjatlarida u endi XCUIAutomation freymvorki sifatida tasvirlanadi. Testlar alohida jarayonda ishlaydi va ilovani accessibility orqali boshqaradi. U Xcode bilan birga keladi va qo'shimcha o'rnatish talab qilmaydi.
Muhim fakt: unit testlar uchun yangi Swift Testing freymvorki tavsiya qilinadi, lekin Apple hujjati aniq aytadi — UI testlar va performance testlar uchun XCTest'dan foydalanishda davom eting (testing darsi).
import XCTest
final class DemoBozorLoginUITests: XCTestCase {
private var app: XCUIApplication!
override func setUpWithError() throws {
continueAfterFailure = false
app = XCUIApplication()
// Test rejimi: staging API, animatsiyalarsiz, onboarding'siz
app.launchArguments += ["-uiTesting", "-disableAnimations"]
app.launchEnvironment["API_BASE_URL"] = "https://staging-api.demobozor.example"
app.launch()
}
func testLoginWithValidOtpOpensHome() throws {
let phoneField = app.textFields["login.phone"]
XCTAssertTrue(phoneField.waitForExistence(timeout: 5))
phoneField.tap()
phoneField.typeText("901234567")
app.buttons["login.requestCode"].tap()
let otpField = app.textFields["otp.input"]
XCTAssertTrue(otpField.waitForExistence(timeout: 10))
otpField.typeText(ProcessInfo.processInfo.environment["TEST_OTP"] ?? "")
XCTAssertTrue(app.staticTexts["home.title"].waitForExistence(timeout: 10))
}
func testNotificationPermissionAlertCanBeDismissed() throws {
// Tizim oynalari ilovaga emas, SpringBoard'ga tegishli
let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
let dontAllow = springboard.buttons["Don’t Allow"]
if dontAllow.waitForExistence(timeout: 3) {
dontAllow.tap()
}
XCTAssertTrue(app.textFields["login.phone"].waitForExistence(timeout: 5))
}
}
Kodga izohlar:
app.textFields["login.phone"]— identifier bo'yicha qidirish (2.1).waitForExistence(timeout:)— kutish;sleep()emas.- Ruxsat dialoglari SpringBoard jarayoniga tegishli. Tugma matni ("Don’t Allow" — tipografik apostrof bilan) qurilma tiliga bog'liq, shuning uchun UI testlarni simulyator tili belgilangan holda ishga tushiring.
continueAfterFailure = false— birinchi xatodan keyin test to'xtaydi va keyingi qadamlarda chalg'ituvchi xatolar chiqmaydi.
Yozib olish (recording): Xcode'da UI test metodi ichiga kursorni qo'yib, muharrir chetidagi qizil yozish tugmasini bosasiz. Ilova bilan ishlaganingizda Xcode element so'rovlarini kodga o'zi qo'shadi. Natijani doim tozalang: yozilgan kodda ko'pincha mo'rt so'rovlar bo'ladi.
Terminaldan ishga tushirish
xcodebuild test \
-scheme DemoBozor \
-destination 'platform=iOS Simulator,name=iPhone 17' \
-only-testing:DemoBozorUITests \
-resultBundlePath build/DemoBozorUITests.xcresult
# Flaky testni ovlash: muvaffaqiyatsiz bo'lguncha 20 martagacha takrorlash
xcodebuild test -scheme DemoBozor \
-destination 'platform=iOS Simulator,name=iPhone 17' \
-only-testing:DemoBozorUITests/DemoBozorLoginUITests/testLoginWithValidOtpOpensHome \
-test-iterations 20 -run-tests-until-failure
-retry-tests-on-failure bayrog'i ham bor. Lekin uni faqat flaky testni vaqtincha yumshatish uchun ishlating — sababini topib tuzatish kerak (QA 21).
8. Espresso va Compose testing: Android native {#espresso-compose}
Espresso (View asosidagi ekranlar)
Espresso ilova ichida ishlaydi va UI thread bilan sinxronlashadi: ekran "tinchlanmaguncha" keyingi qadamga o'tmaydi.
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.closeSoftKeyboard
import androidx.test.espresso.action.ViewActions.typeText
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class LoginEspressoTest {
@get:Rule
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
private val testOtp: String
get() = InstrumentationRegistry.getArguments().getString("testOtp").orEmpty()
@Test
fun validOtpOpensHome() {
onView(withId(R.id.phoneInput)).perform(typeText("901234567"), closeSoftKeyboard())
onView(withId(R.id.requestCodeButton)).perform(click())
onView(withId(R.id.otpInput)).perform(typeText(testOtp), closeSoftKeyboard())
onView(withId(R.id.homeTitle)).check(matches(isDisplayed()))
}
}
⚠️ Diqqat: Espresso UI thread'ni kutadi, lekin tarmoq so'rovlarini (coroutine'lar, OkHttp) avtomatik kutmaydi. Bunday holatlar uchun IdlingResource yoziladi yoki testda mock server ishlatiladi.
Thread.sleep()— yechim emas, flaky testga yo'l.
Compose UI test
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onAllNodesWithTag
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTextInput
import org.junit.Rule
import org.junit.Test
class LoginComposeTest {
@get:Rule
val composeTestRule = createAndroidComposeRule<MainActivity>()
@Test
fun validOtpOpensHome() {
composeTestRule.onNodeWithTag("login.phone").performTextInput("901234567")
composeTestRule.onNodeWithText("Kodni olish").performClick()
// Tarmoq javobini kutish: otp maydoni paydo bo'lguncha (10 soniyagacha)
composeTestRule.waitUntil(timeoutMillis = 10_000) {
composeTestRule.onAllNodesWithTag("otp.input").fetchSemanticsNodes().isNotEmpty()
}
composeTestRule.onNodeWithTag("otp.input").performTextInput("000000")
composeTestRule.onNodeWithTag("home.title").assertIsDisplayed()
}
}
Ishga tushirish (ulangan qurilma yoki emulatorda):
./gradlew connectedDebugAndroidTest \
-Pandroid.testInstrumentationRunnerArguments.testOtp=000000
onNodeWithText("Kodni olish") — matn bo'yicha qidirish. Bu test faqat o'zbek tilida o'tadi. Ko'p tilli ilovada onNodeWithTag afzal.
9. Flaky mobil testlar: sabablar va davolash {#flaky}
Flaky test — kod o'zgarmasa ham ba'zan o'tib, ba'zan yiqiladigan test. Mobil'da bunday testlar web'dagidan ko'proq uchraydi.
| Sabab | Belgisi | Davosi |
|---|---|---|
| Animatsiyalar | Element "harakatda" — bosish o'tib ketadi | Test rejimida animatsiyalarni o'chirish (adb shell settings ..., iOS'da -uiTesting) |
Qat'iy sleep | Sekin qurilmada yiqiladi, tezida vaqt behuda ketadi | waitForExistence, waitForDisplayed, Maestro'ning avtomatik kutishi |
| Tarmoq | Staging sekin bo'lgan kuni testlar qizil | Mock server, API orqali tayyorlangan ma'lumot, timeout'lar |
| Holat oqib o'tishi | Oldingi test savatchada mahsulot qoldirgan | clearState, noReset: false, har test o'z ma'lumotini yaratadi |
| Umumiy test akkaunti | Parallel testlar bir-birining savatchasini o'zgartiradi | Har parallel oqimga alohida akkaunt |
| Tizim dialoglari | "Allow notifications?" kutilmaganda chiqadi | Ruxsatlarni oldindan berish (launchApp: permissions, simctl privacy, pm grant) |
| Klaviatura | Klaviatura tugmani yopib qo'yadi | hideKeyboard, closeSoftKeyboard() |
| Koordinatalar | Boshqa ekranda boshqa joyga bosiladi | Identifier bo'yicha selektorlar |
| Til va vaqt | Ingliz tilli CI'da "Kirish" topilmaydi | Identifier'lar; simulyator tili va vaqt zonasini qat'iy belgilash |
🎯 Intervyuda: "Mobil testlaringiz CI'da ba'zan yiqiladi. Nima qilasiz?" — Avval qayta urinish (retry) bilan "yashirmayman". Testni ko'p marta ishga tushirib (masalan,
-test-iterations 20 -run-tests-until-failure) takrorlanish ehtimolini o'lchayman. Video va loglardan sababni topaman: kutish, holat, tarmoq yoki animatsiya. Sabab topilguncha testni karantinga olaman va ticket ochaman.
10. CI va qurilma fermalari {#ci}
iOS
iOS UI testlar faqat macOS runner'larda ishlaydi (GitHub Actions'ning macOS image'lari, Xcode Cloud, Bitrise). Asosiy buyruq — yuqoridagi xcodebuild test. Batafsil iOS CI — DevOps 1: iOS CI/CD darsida.
Android: emulator + Maestro (GitHub Actions)
Linux runner'da Android emulatorini reactivecircus/android-emulator-runner action'i (v2) ko'taradi. Hardware acceleration uchun KVM ruxsatlari yoqiladi:
name: android-e2e
on:
pull_request:
workflow_dispatch:
jobs:
maestro-android:
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v7
- uses: actions/setup-java@v6
with:
distribution: temurin
java-version: "17"
- name: KVM ruxsatlarini yoqish
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Maestro CLI o'rnatish
run: |
curl -fsSL "https://get.maestro.mobile.dev" | bash
echo "$HOME/.maestro/bin" >> "$GITHUB_PATH"
- name: Debug APK yig'ish
run: ./gradlew assembleDebug
- name: Emulatorda smoke testlar
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 34
target: google_apis
arch: x86_64
profile: pixel_7_pro
script: |
adb install -r app/build/outputs/apk/debug/app-debug.apk
maestro test flows/ --include-tags=smoke --format=JUNIT --output=maestro-report.xml -e PHONE=901234567 -e OTP=${{ secrets.STAGING_TEST_OTP }}
- name: Hisobotni saqlash
if: always()
uses: actions/upload-artifact@v7
with:
name: maestro-report
path: maestro-report.xml
Bu YAML sintaksis jihatidan tekshirilgan. Action versiyalari 2026-yil oktabr holatiga mos: checkout@v7, setup-java@v6, upload-artifact@v7, android-emulator-runner@v2. CI'ning umumiy tamoyillari (sharding, hisobotlar, quality gate) — QA 21.
Qurilma fermalari
Ko'p real qurilmada parallel test uchun BrowserStack App Automate, Sauce Labs Real Device Cloud, TestMu AI (sobiq LambdaTest), AWS Device Farm yoki Firebase Test Lab ishlatiladi (QA 14). Appium testlari odatda faqat server manzili va capabilities'ni o'zgartirib, fermaga ko'chiriladi. Maestro'ning esa o'z bulut xizmati — Maestro Cloud (maestro cloud --app-file ... --flows ...) bor.
11. Qaysi vositani tanlash kerak? {#tanlov}
| Vaziyat | Tavsiya | Nega |
|---|---|---|
| iOS jamoasi o'z ilovasiga test yozadi | XCUITest | Xcode ichida, Swift, qo'shimcha infratuzilma yo'q |
| Android jamoasi, Compose ekranlar | Compose UI test (+ Espresso) | Tez, ilova ichida, sinxronizatsiya |
| Alohida QA jamoasi, iOS + Android, tez natija kerak | Maestro | YAML, o'rganish oson, tizim dialoglari bilan ishlaydi |
| Katta regression to'plami, ko'p qurilma, mavjud Selenium/JS tajribasi | Appium 3 + WebdriverIO 10 | Bitta kod ikki platformaga, fermalar qo'llab-quvvatlaydi |
| React Native | Detox yoki Maestro | Gray-box sinxronizatsiya / tez start |
| Flutter | Patrol, integration_test yoki Maestro | Flutter widget'lari va native dialoglar |
| Ilova kodiga kirish yo'q (black-box) | Maestro yoki Appium | Faqat tayyor build kerak |
💡 Maslahat: Bitta ilovada ikki qatlam bo'lishi normal: dasturchilar komponent darajasida XCUITest/Compose testlarini yozadi, QA esa 15–30 ta asosiy E2E ssenariyni Maestro yoki Appium bilan qamrab oladi.
12. Amaliy mashqlar {#mashqlar}
Mashq 1: Vosita tanlash
Har bir vaziyat uchun vosita tanlang va bir jumla bilan asoslang:
- Startapda 1 ta QA bor, Flutter ilova, 2 haftada smoke testlar kerak.
- Bank: Android (Kotlin, Compose) va iOS (SwiftUI) native jamoalari, kuchli dasturchilar.
- Outsourcing kompaniyasi: 5 ta mijoz ilovasi, BrowserStack'da 30 ta qurilmada tungi regression.
- React Native e-commerce, testlar ko'pincha animatsiya va tarmoq tufayli yiqiladi.
<details> <summary>💡 Javobni ko'rish</summary>
- Maestro — YAML tez o'rganiladi, Flutter accessibility orqali qo'llab-quvvatlanadi, kod yozish shart emas.
- XCUITest + Compose UI test — native jamoalar o'z tilida, ilova kodiga yaqin test yozadi.
- Appium 3 + WebdriverIO — bitta kod bazasi, capabilities bilan fermadagi istalgan qurilma, standart W3C protokoli.
- Detox (gray-box sinxronizatsiya animatsiya va tarmoqni kutadi) yoki Maestro (avtomatik kutish); qo'shimcha ravishda test rejimida animatsiyalarni o'chirish.
</details>
Mashq 2: Maestro flow yozing
Ssenariy: qidiruvga "choynak" yozish (qidiruv 2–100 belgi), birinchi natijani ochish, savatchaga qo'shish, savatcha belgisida 1 borligini va yetkazib berish narxi ko'rinishini tekshirish. Identifikatorlar: tab.search, search.input, search.result.0, product.addToCart, cart.badge, tab.cart, cart.deliveryFee.
<details> <summary>💡 Javobni ko'rish</summary>
appId: uz.demobozor.app
tags:
- regression
- search
---
- launchApp:
arguments:
uiTesting: true
- runFlow: subflows/login.yaml
- tapOn:
id: "tab.search"
- tapOn:
id: "search.input"
- inputText: "choynak"
- tapOn:
id: "search.result.0"
- tapOn:
id: "product.addToCart"
- assertVisible:
id: "cart.badge"
text: "1"
- tapOn:
id: "tab.cart"
- assertVisible:
id: "cart.deliveryFee"
Qo'shimcha: choynak narxi 300 000 so'mdan kam bo'lsa, yetkazib berish 20 000 so'm bo'lishi kerak. Matnni tekshirishda probel va valyuta formatiga e'tibor bering: assertVisible: ".*20 000.*" (regex).
</details>
Mashq 3: XPath'dan qutulish
Test kodida shunday locator bor: //android.widget.LinearLayout[2]/android.widget.FrameLayout[1]/android.widget.Button[@text="Savatchaga qo'shish"]. Muammolari nimada va qanday tuzatiladi?
<details> <summary>💡 Javobni ko'rish</summary>
Muammolar:
- layout'ga bog'langan (
LinearLayout[2]): dizayn o'zgarsa buziladi; - matnga bog'langan: rus yoki ingliz tilida ishlamaydi;
- XPath sekin: har safar butun daraxt XML'ga aylantiriladi.
Yechim: dasturchidan identifier so'rash (product.addToCart) va testda byTestId('product.addToCart') ishlatish (iOS'da accessibilityIdentifier, Android'da Compose testTag + testTagsAsResourceId). Identifier qo'shish kod-review'da QA talabiga aylanishi kerak.
</details>
Mashq 4: iOS capabilities
Appium 3 uchun iOS Simulator (iPhone 17, iOS 26.5) capabilities'ini yozing. Ilova: ~/apps/DemoBozor.app, har sessiyada toza holat.
<details> <summary>💡 Javobni ko'rish</summary>
{
"platformName": "iOS",
"appium:automationName": "XCUITest",
"appium:deviceName": "iPhone 17",
"appium:platformVersion": "26.5",
"appium:app": "/Users/qa/apps/DemoBozor.app",
"appium:noReset": false
}
E'tibor bering: platformName standart W3C kaliti, shuning uchun prefikssiz yoziladi. Qolgan Appium kalitlari appium: prefiksi bilan yoziladi.
</details>
Mashq 5: Flaky testni toping
Quyidagi testda kamida 4 ta flaky manbani toping:
it("savatchaga qo'shish", async () => {
await browser.pause(5000);
await $('//android.widget.Button[@text="Katalog"]').click();
await browser
.action("pointer")
.move({ x: 540, y: 1650 })
.down()
.up()
.perform();
await expect($("~cart.badge")).toHaveText("1"); // umumiy qa@demobozor akkaunti
});
<details> <summary>💡 Javobni ko'rish</summary>
browser.pause(5000)— qat'iy kutish: sekin qurilmada yetmaydi, tezida vaqt behuda ketadi.waitForDisplayedyoki avtomatik kutadiganexpectishlating.- Matnli XPath (
@text="Katalog") — til va layout'ga bog'liq;byTestId('tab.catalog')ishlating. - Absolyut koordinata (
540, 1650) — boshqa ekran o'lchamida boshqa joy bosiladi. - Umumiy akkaunt — boshqa test yoki parallel oqim savatchaga mahsulot qo'shgan bo'lsa,
1emas,2chiqadi. Har test o'z akkaunti va toza savatcha bilan ishlashi kerak. - Qo'shimcha: test savatchani oldindan tozalamaydi (
noReset: falseyoki API orqali tozalash).
</details>
Mashq 6: Bildirishnoma ruxsati (XCUITest)
Ilova birinchi ochilishda bildirishnoma ruxsatini so'raydi va bu dialog login testlarini to'sib qo'yyapti. Ikki xil yechim taklif qiling.
<details> <summary>💡 Javobni ko'rish</summary>
- Testda dialogni yopish:
XCUIApplication(bundleIdentifier: "com.apple.springboard")orqali tugmani topib bosish (yuqoridagi misol). Kamchiligi: tugma matni qurilma tiliga bog'liq. - Dialogni chiqarmaslik: test rejimida (
-uiTesting) ilova ruxsatni login'dan keyinroq so'raydi yoki umuman so'ramaydi. Ruxsat oqimining o'zi esa alohida, maxsus testda tekshiriladi.
Qo'shimcha: Maestro'da launchApp: permissions: notifications: deny bilan bu masala bitta qatorda hal bo'ladi.
</details>
13. O'zingizni tekshiring {#checklist}
- [ ] XCUITest, Espresso/Compose, Appium, Maestro, Detox va Patrol farqini tushuntira olaman
- [ ] Dasturchidan qanday identifier va test rejimi so'rashni bilaman
- [ ] Maestro'da subflow, tag, env va scroll bilan flow yoza olaman
- [ ] Appium 3 arxitekturasini (klient → server → driver → qurilma) chizib bera olaman
- [ ] WebdriverIO 10 bilan Screen Object va accessibility id'dan foydalanib test yoza olaman
- [ ] XPath nega oxirgi chora ekanini tushuntira olaman
- [ ] XCUITest'da
waitForExistenceva launch argument'lardan foydalana olaman - [ ] Flaky mobil testning 5 ta sababini va davosini bilaman
Xulosa {#xulosa}
- Mobil E2E testlar qimmat — ularni piramidaning tepasida, eng muhim ssenariylar uchun yozing.
- Avtomatlashtirish testability'dan boshlanadi: barqaror identifier'lar (
login.phone), test rejimi (-uiTesting), deep link'lar va staging test ma'lumotlari. - Maestro — YAML, avtomatik kutish, tez start. Android'da Unicode matn kiritish cheklovini yodda tuting.
- Appium 3 — W3C WebDriver, XCUITest/UiAutomator2 driverlari. Node 20.19+ talab qilinadi va
--allow-insecureuchun driver prefiksi majburiy. WebdriverIO 10 faqat Appium 3 bilan ishlaydi. - XCUITest va Espresso/Compose — native jamoalar uchun eng tez va barqaror yo'l. UI testlar uchun Apple XCTest'da qolishni tavsiya qiladi.
- Flaky testlarning asosiy sabablari: qat'iy
sleep, animatsiyalar, umumiy ma'lumot, tizim dialoglari, koordinatalar. Retry bilan yashirmang — sababini toping. - Keyingi dars: QA 23: Performance testing — Load, Stress, Spike, Soak va k6 bilan amaliyot
Manbalar {#manbalar}
- Maestro Docs — What is Maestro
- Maestro Docs — How Maestro works
- Maestro Docs — How to install Maestro CLI
- Maestro Docs — CLI commands and options
- Maestro Docs — inputText
- Maestro Docs — launchApp
- Maestro Docs — setAirplaneMode
- Maestro Docs — Test discovery and tags
- Appium — Install Appium
- Appium — Migrating from Appium 2 to Appium 3
- Appium — Capabilities
- Appium — UiAutomator2 driver quickstart
- Appium Inspector — Installation
- WebdriverIO — Appium Service
- WebdriverIO — Selectors
- WebdriverIO — swipe
- WebdriverIO — scrollIntoView
- Apple — XCTest
- Apple — XCUIAutomation
- Android Developers — Espresso basics
- Android Developers — Test your Compose layout
- Android Developers — Compose interoperability with UiAutomator
- GitHub — ReactiveCircus/android-emulator-runner