# 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

1. [Mobil avtomatlashtirish xaritasi (2026)](#xarita)
2. [Testability: ilovani testga tayyorlash](#testability)
3. [Maestro: YAML bilan tez start](#maestro)
4. [Appium 3: arxitektura va o'rnatish](#appium)
5. [WebdriverIO 10 + Appium: TypeScript'da birinchi test](#webdriverio)
6. [Locator strategiyasi va gesture'lar](#locators-gestures)
7. [XCUITest: iOS native UI testlar](#xcuitest)
8. [Espresso va Compose testing: Android native](#espresso-compose)
9. [Flaky mobil testlar: sabablar va davolash](#flaky)
10. [CI va qurilma fermalari](#ci)
11. [Qaysi vositani tanlash kerak?](#tanlov)
12. [Amaliy mashqlar](#mashqlar)
13. [O'zingizni tekshiring](#checklist)
14. [Xulosa](#xulosa)
15. [Manbalar](#manbalar)

---

## 1. Mobil avtomatlashtirish xaritasi (2026) {#xarita}

Mobil UI testlar web testlarga qaraganda sekinroq va qimmatroq. Shuning uchun test piramidasi ([QA 4](/ios-lessons/qa-levels-types)) bu yerda ayniqsa muhim:

- **pastda** — ko'p unit testlar (Swift Testing/XCTest, JUnit);
- **o'rtada** — komponent va API testlari ([QA 20](/ios-lessons/qa-api-automation));
- **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**                  | Google                       | Kotlin/Java                          | Android (View)                   | Native, ilova ichida, UI thread bilan sinxron | Android View ilovalar                                         |
| **Compose UI test**           | Google                       | 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 qilish**dan 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:

```swift
// SwiftUI
TextField("Telefon raqam", text: $phone)
    .accessibilityIdentifier("login.phone")

// UIKit
requestCodeButton.accessibilityIdentifier = "login.requestCode"
```

```kotlin
// 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:** `accessibilityIdentifier` va `contentDescription`/`accessibilityLabel` — boshqa-boshqa narsalar. Label'ni ekran o'quvchi (VoiceOver/TalkBack) foydalanuvchiga **o'qib beradi**. Unga `login_btn_2` kabi texnik nom yozish accessibility'ni buzadi ([QA 25](/ios-lessons/qa-a11y-l10n-visual)). 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:

```swift
// 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):

```kotlin
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](/ios-lessons/qa-api-automation)).

### 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](/ios-lessons/qa-security)).

### 2.5 Animatsiyalarni o'chirish (Android)

```bash
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.

```bash
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

```text
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

```yaml
# 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

```yaml
# 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](#testability)).

### Savatcha flow'i: scroll va tekshiruvlar

```yaml
# 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 `text` va `id` selektorlari 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)

```yaml
# 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](/ios-lessons/qa-proxyman)).

### Konfiguratsiya va ishga tushirish

```yaml
# 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.

```bash
# 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 `inputText` Android'da **Unicode belgilarni qo'llab-quvvatlamaydi**. Kirill (`Ташкент`), `oʻ`/`gʻ` va emoji'larni Android'da `inputText` bilan 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](/ios-lessons/qa-ai-testing)).

---

## 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](/lessons/images/qa/qa_22_appium_arch.svg)

| 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** yoki `mobile:` buyruqlari ishlatiladi;
- `--allow-insecure` funksiyalariga driver prefiksi **majburiy**: `appium --allow-insecure=uiautomator2:adb_shell` yoki barcha driverlar uchun `*:adb_shell`;
- `GET /sessions` o'rniga `GET /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)

```bash
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:

```json
{
  "platformName": "Android",
  "appium:automationName": "UiAutomator2",
  "appium:deviceName": "Pixel_7_API_34",
  "appium:app": "/Users/qa/apps/demobozor-staging.apk",
  "appium:noReset": false
}
```

```json
{
  "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.

```bash
npm init wdio@latest .                                  # konfiguratsiya ustasi
npm install @wdio/appium-service appium@^3 --save-dev   # Appium'ni loyiha ichida saqlash
```

### Konfiguratsiya

```typescript
// 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](/ios-lessons/qa-playwright-advanced)) mobil'da **Screen Object** deb ham ataladi:

```typescript
// 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}`);
}
```

```typescript
// 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();
```

```typescript
// 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

```typescript
// 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:

```bash
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](#testability)).

`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):

```typescript
// 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](/ios-lessons/testing)).

```swift
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](#testability)).
- `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

```bash
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](/ios-lessons/qa-ci-cd)).

---

## 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.

```kotlin
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'rovlari**ni (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

```kotlin
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):

```bash
./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](/ios-lessons/devops-cicd) 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:

```yaml
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](/ios-lessons/qa-ci-cd).

### 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](/ios-lessons/qa-mobile-testing)). 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:

1. Startapda 1 ta QA bor, Flutter ilova, 2 haftada smoke testlar kerak.
2. Bank: Android (Kotlin, Compose) va iOS (SwiftUI) native jamoalari, kuchli dasturchilar.
3. Outsourcing kompaniyasi: 5 ta mijoz ilovasi, BrowserStack'da 30 ta qurilmada tungi regression.
4. React Native e-commerce, testlar ko'pincha animatsiya va tarmoq tufayli yiqiladi.

<details>
<summary>💡 Javobni ko'rish</summary>

1. **Maestro** — YAML tez o'rganiladi, Flutter accessibility orqali qo'llab-quvvatlanadi, kod yozish shart emas.
2. **XCUITest + Compose UI test** — native jamoalar o'z tilida, ilova kodiga yaqin test yozadi.
3. **Appium 3 + WebdriverIO** — bitta kod bazasi, capabilities bilan fermadagi istalgan qurilma, standart W3C protokoli.
4. **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>

```yaml
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>

```json
{
  "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:

```typescript
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>

1. `browser.pause(5000)` — qat'iy kutish: sekin qurilmada yetmaydi, tezida vaqt behuda ketadi. `waitForDisplayed` yoki avtomatik kutadigan `expect` ishlating.
2. Matnli XPath (`@text="Katalog"`) — til va layout'ga bog'liq; `byTestId('tab.catalog')` ishlating.
3. Absolyut koordinata (`540, 1650`) — boshqa ekran o'lchamida boshqa joy bosiladi.
4. Umumiy akkaunt — boshqa test yoki parallel oqim savatchaga mahsulot qo'shgan bo'lsa, `1` emas, `2` chiqadi. Har test o'z akkaunti va toza savatcha bilan ishlashi kerak.
5. Qo'shimcha: test savatchani oldindan tozalamaydi (`noReset: false` yoki 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>

1. **Testda dialogni yopish:** `XCUIApplication(bundleIdentifier: "com.apple.springboard")` orqali tugmani topib bosish (yuqoridagi misol). Kamchiligi: tugma matni qurilma tiliga bog'liq.
2. **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 `waitForExistence` va 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-insecure` uchun 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](/ios-lessons/qa-performance-k6)

## Manbalar {#manbalar}

1. [Maestro Docs — What is Maestro](https://docs.maestro.dev/get-started/what-is-maestro)
2. [Maestro Docs — How Maestro works](https://docs.maestro.dev/get-started/how-maestro-works)
3. [Maestro Docs — How to install Maestro CLI](https://docs.maestro.dev/maestro-cli/how-to-install-maestro-cli)
4. [Maestro Docs — CLI commands and options](https://docs.maestro.dev/maestro-cli/maestro-cli-commands-and-options)
5. [Maestro Docs — inputText](https://docs.maestro.dev/reference/commands-available/inputtext)
6. [Maestro Docs — launchApp](https://docs.maestro.dev/reference/commands-available/launchapp)
7. [Maestro Docs — setAirplaneMode](https://docs.maestro.dev/reference/commands-available/setairplanemode)
8. [Maestro Docs — Test discovery and tags](https://docs.maestro.dev/maestro-flows/workspace-management/test-discovery-and-tags)
9. [Appium — Install Appium](https://appium.io/docs/en/latest/quickstart/install/)
10. [Appium — Migrating from Appium 2 to Appium 3](https://appium.io/docs/en/latest/guides/migrating-2-to-3/)
11. [Appium — Capabilities](https://appium.io/docs/en/latest/guides/caps/)
12. [Appium — UiAutomator2 driver quickstart](https://appium.io/docs/en/latest/quickstart/uiauto2-driver/)
13. [Appium Inspector — Installation](https://appium.github.io/appium-inspector/latest/quickstart/installation/)
14. [WebdriverIO — Appium Service](https://webdriver.io/docs/appium-service)
15. [WebdriverIO — Selectors](https://webdriver.io/docs/selectors)
16. [WebdriverIO — swipe](https://webdriver.io/docs/api/mobile/swipe)
17. [WebdriverIO — scrollIntoView](https://webdriver.io/docs/api/element/scrollIntoView)
18. [Apple — XCTest](https://developer.apple.com/documentation/xctest)
19. [Apple — XCUIAutomation](https://developer.apple.com/documentation/xcuiautomation)
20. [Android Developers — Espresso basics](https://developer.android.com/training/testing/espresso/basics)
21. [Android Developers — Test your Compose layout](https://developer.android.com/develop/ui/compose/testing)
22. [Android Developers — Compose interoperability with UiAutomator](https://developer.android.com/develop/ui/compose/testing/interoperability)
23. [GitHub — ReactiveCircus/android-emulator-runner](https://github.com/ReactiveCircus/android-emulator-runner)
