# QA 29: Amaliyot — Capstone loyiha va 30 ta topshiriq (Zero to Hero Challenge)

> **Holat:** 2026-yil oktabr. Kursning yakuniy amaliy darsi: real amaliyot ilovasi (Toolshop) ustida 3 haftalik capstone loyiha va butun kursni qamraydigan 30 ta topshiriq. Kod yechimlari Playwright 1.64 (Chromium), Zod 4.6 va @axe-core/playwright 4.13 bilan, SQL yechimlari SQLite 3.51 da, Postman kolleksiyasi Newman 6.2 bilan ishga tushirib tekshirilgan; API misollari restful-booker va Toolshop'ning 2026-yil oktabrdagi haqiqiy javoblariga asoslangan.

> **Bu darsdan keyin siz:** ishga topshirishda ko'rsatadigan to'liq QA portfolio'ga va 1–28-darslardagi har bir ko'nikmani amalda sinab ko'rgan tajribaga ega bo'lasiz.

- real ilova uchun strategiyadan CI'gacha bo'lgan butun QA siklini o'zingiz bajarasiz;
- 30 ta topshiriq orqali test dizayn, hujjatlar, API, SQL, mobil, avtomatlashtirish va "hero" ko'nikmalarini mustahkamlaysiz;
- har bir yechimni tekshirilgan namuna bilan solishtirib, o'z xatolaringizni topasiz.

## Mundarija

1. [Bu dars qanday ishlaydi](#qanday)
2. [Capstone loyiha: Toolshop'ni to'liq QA qilish](#capstone)
3. [Topshiriqlar xaritasi](#mashqlar)
4. [Test dizayn: 1–8](#test-dizayn)
5. [Hujjatlar va bug'lar: 9–13](#hujjatlar)
6. [Web, API va SQL: 14–19](#web-api-sql)
7. [Mobil va Proxyman: 20–22](#mobil)
8. [Avtomatlashtirish: 23–27](#avtomatlashtirish)
9. [Hero darajasi: 28–30](#hero)
10. [Zero → Hero: bosib o'tgan yo'lingiz](#yol)
11. [Xulosa](#xulosa)
12. [Manbalar](#manbalar)

---

## 1. Bu dars qanday ishlaydi {#qanday}

Dars ikki qismdan iborat:

1. **Capstone loyiha** — 2–3 haftalik katta ish, natijasi GitHub'dagi portfolio ([QA 28](/ios-lessons/qa-career-interview)'da nega muhimligini ko'rdik);
2. **30 ta topshiriq** — har biri 15 daqiqadan 2 soatgacha; capstone'dan oldin "isinish" yoki keyin takrorlash uchun.

| Belgi | Daraja | Taxminiy vaqt |
| ----- | ------ | ------------- |
| 🟢    | Oson   | 15–30 daqiqa  |
| 🟡    | O'rta  | 30–60 daqiqa  |
| 🔴    | Qiyin  | 1–2 soat      |

**Qoidalar:**

- Avval o'zingiz yeching, keyin yechimni oching. Yechim — **to'g'ri variantlardan biri**, yagona to'g'ri javob emas.
- Kod yechimlari biz tekshirgan muhitda ishlaydi: Node.js 24, `@playwright/test` 1.64.0 (Chromium), `zod` 4.6.5, `@axe-core/playwright` 4.13.0, `newman` 6.2.3, SQLite 3.51. Boshqa versiyada kichik farqlar bo'lishi mumkin.
- Amaliyot saytlari — **umumiy ommaviy resurs**. Ularga yuklama (load) testi, xavfsizlik skanerlari yoki yuzlab akkaunt yaratish bilan zarar yetkazmang.
- DemoBozor — kursdagi xayoliy mahsulot; uning qoidalari ([QA 5](/ios-lessons/qa-test-design-1) va boshqa darslarda) topshiriqlarda ham amal qiladi.

---

## 2. Capstone loyiha: Toolshop'ni to'liq QA qilish {#capstone}

![Capstone: Toolshop'ni 3 haftada to'liq QA qilish — haftalar va natijalar](/lessons/images/qa/qa_29_capstone.svg)

### 2.1. Nega aynan Toolshop

**Practice Software Testing — "Toolshop"** — testlashni o'rganish uchun maxsus yaratilgan ochiq loyiha: Angular'da yozilgan do'kon interfeysi, Laravel'dagi REST API va Swagger hujjati. Eng qimmatli tomoni — **ataylab bug'lar qo'shilgan versiyasi** bor: haqiqiy bug hunting mashqi uchun ideal.

| Nima                 | Manzil                                                    |
| -------------------- | --------------------------------------------------------- |
| UI (asosiy, v5)      | https://practicesoftwaretesting.com                       |
| UI (ataylab bug'li)  | https://with-bugs.practicesoftwaretesting.com             |
| API                  | `https://api.practicesoftwaretesting.com`                  |
| Swagger hujjati      | https://api.practicesoftwaretesting.com/api/documentation |
| API (bug'li versiya) | `https://api-with-bugs.practicesoftwaretesting.com`         |
| Manba kod            | https://github.com/testsmith-io/practice-software-testing |

**Demo akkauntlar** (loyiha README'sidan; customer va admin login'lari API orqali 2026-yil oktabrda tekshirildi):

| Rol      | Email                                   | Parol       |
| -------- | --------------------------------------- | ----------- |
| admin    | `admin@practicesoftwaretesting.com`     | `welcome01` |
| customer | `customer@practicesoftwaretesting.com`  | `welcome01` |
| customer | `customer2@practicesoftwaretesting.com` | `welcome01` |

**Nimalarni testlash mumkin:** katalog (kategoriyalar, brendlar, eco-friendly filtri, saralash, qidiruv), mahsulot sahifasi, savatcha, checkout, kirish/ro'yxatdan o'tish/parolni tiklash, shaxsiy kabinet, aloqa formasi, ijara (rentals) bo'limi; v5'da 7 tilga o'tish (DE, EL, EN, ES, FR, NL, TR).

**Texnik faktlar** (2026-yil oktabrda tekshirilgan):

- UI elementlarida `data-test` atributlari bor (`email`, `password`, `login-submit`, `search-query`, `sort`, `product-name`, `product-price` ...) → Playwright'da `testIdAttribute: 'data-test'`;
- v5 oddiy yo'llardan foydalanadi (`/auth/login`, `/category/hand-tools`), bug'li versiya esa hash yo'llardan (`/#/auth/login`);
- `POST /users/login` → `{ "access_token": "..." }`; `GET /products?page=1` → sahifalangan javob (`current_page`, `data`, `per_page`, `total`, `last_page`).

> **💡 Maslahat:** Soddaroq yo'l kerak bo'lsa — capstone'ni **saucedemo + restful-booker** juftligida bajaring. [QA 18](/ios-lessons/qa-playwright-basics)–[QA 21](/ios-lessons/qa-ci-cd)dagi kod deyarli o'zgarishsiz ishlaydi. Lekin Toolshop real mahsulotga yaqinroq va bug'li versiyasi bug report'lar uchun haqiqiy material beradi.

### 2.2. Deliverable'lar va ball taqsimoti

| #   | Deliverable                                       | Format / joyi                      | Darslar                                    | Ball    |
| --- | ------------------------------------------------- | ---------------------------------- | ------------------------------------------ | ------- |
| 1   | 1 sahifali test strategiya                        | `docs/test-strategy.md`            | [QA 27](/ios-lessons/qa-strategy-metrics)  | 5       |
| 2   | Risk matritsasi (kamida 8 funksiya)               | `docs/risk-matrix.md`              | QA 27                                      | 5       |
| 3   | Smoke va regression checklist                     | `docs/checklist.md`                | [QA 7](/ios-lessons/qa-test-documentation) | 5       |
| 4   | 30 ta test case (EP, BVA, decision table, state)  | `docs/test-cases.csv`              | QA 5–7                                     | 15      |
| 5   | Bug'li versiyadan 10 ta bug report                | `docs/bugs/BUG-001.md` ...         | [QA 8](/ios-lessons/qa-bug-reports)        | 15      |
| 6   | API kolleksiya (Postman yoki Bruno) + newman run  | `postman/` yoki `bruno/`           | QA 10–11                                   | 10      |
| 7   | Playwright framework: POM + fixtures + UI testlar | `pages/`, `fixtures/`, `tests/ui/` | QA 18–19                                   | 15      |
| 8   | API testlar (Zod sxemalari bilan)                 | `tests/api/`, `schemas/`           | [QA 20](/ios-lessons/qa-api-automation)    | 10      |
| 9   | a11y (axe) va visual test                         | `tests/a11y/`, `tests/visual/`     | [QA 25](/ios-lessons/qa-a11y-l10n-visual)  | 5       |
| 10  | GitHub Actions CI + HTML report artifact          | `.github/workflows/`               | QA 21                                      | 10      |
| 11  | README + 2–3 daqiqalik demo video                 | `README.md`                        | QA 28                                      | 5       |
|     | **Jami**                                          |                                    |                                            | **100** |

### 2.3. Repo tuzilmasi

```text
toolshop-qa-portfolio/
├── README.md
├── .env.example               # BASE_URL, API_BASE_URL, TOOLSHOP_EMAIL, TOOLSHOP_PASSWORD
├── docs/
│   ├── test-strategy.md
│   ├── risk-matrix.md
│   ├── checklist.md
│   ├── test-cases.csv
│   ├── exploratory-sessions.md
│   └── bugs/                  # BUG-001.md ... BUG-010.md + skrinshotlar
├── postman/                   # yoki bruno/
├── pages/                     # LoginPage.ts, CatalogPage.ts, ProductPage.ts, CartPage.ts
├── fixtures/test.ts
├── schemas/product.ts         # Zod sxemalari
├── tests/
│   ├── ui/
│   ├── api/
│   ├── a11y/
│   └── visual/
├── playwright.config.ts
├── package.json
└── .github/workflows/playwright.yml
```

### 2.4. Boshlang'ich kod (tekshirilgan)

`playwright.config.ts`:

```typescript
import { defineConfig, devices } from "@playwright/test";

export default defineConfig({
  testDir: "./tests",
  fullyParallel: true,
  retries: process.env.CI ? 1 : 0,
  reporter: [["list"], ["html", { open: "never" }]],
  use: {
    baseURL: process.env.BASE_URL ?? "https://practicesoftwaretesting.com",
    testIdAttribute: "data-test",
    trace: "on-first-retry",
  },
  projects: [{ name: "chromium", use: { ...devices["Desktop Chrome"] } }],
});
```

Birinchi UI testi — `tests/ui/login.spec.ts`:

```typescript
import { test, expect } from "@playwright/test";

test('customer tizimga kiradi va "My account" sahifasini ko\'radi @smoke', async ({ page }) => {
  await page.goto("/auth/login");
  await page
    .getByTestId("email")
    .fill(process.env.TOOLSHOP_EMAIL ?? "customer@practicesoftwaretesting.com");
  await page.getByTestId("password").fill(process.env.TOOLSHOP_PASSWORD ?? "welcome01");
  await page.getByTestId("login-submit").click();

  await expect(page).toHaveURL(/\/account$/);
  await expect(page.getByTestId("page-title")).toHaveText("My account");
});
```

Birinchi API testlari — `tests/api/products.spec.ts`:

```typescript
import { test, expect } from "@playwright/test";
import { z } from "zod";

test.use({
  baseURL: process.env.API_BASE_URL ?? "https://api.practicesoftwaretesting.com",
});

const ProductPage = z.object({
  current_page: z.number(),
  per_page: z.number(),
  total: z.number(),
  last_page: z.number(),
  data: z.array(
    z.object({
      id: z.string(),
      name: z.string(),
      price: z.number(),
      in_stock: z.boolean(),
    }),
  ),
});

test("API: login token qaytaradi", async ({ request }) => {
  const res = await request.post("/users/login", {
    data: {
      email: process.env.TOOLSHOP_EMAIL ?? "customer@practicesoftwaretesting.com",
      password: process.env.TOOLSHOP_PASSWORD ?? "welcome01",
    },
  });
  expect(res.status()).toBe(200);
  const body = await res.json();
  expect(typeof body.access_token).toBe("string");
});

test("API: mahsulotlar sahifalanadi va sxemaga mos", async ({ request }) => {
  const res = await request.get("/products?page=1");
  expect(res.ok()).toBeTruthy();
  const page1 = ProductPage.parse(await res.json());
  expect(page1.data.length).toBeLessThanOrEqual(page1.per_page);
  expect(page1.last_page).toBe(Math.ceil(page1.total / page1.per_page));
});
```

Bu uchta test 2026-yil oktabrda Chromium'da o'tdi. Login ma'lumotlari koddan emas, muhit o'zgaruvchilaridan olinadi (`TOOLSHOP_EMAIL`, `TOOLSHOP_PASSWORD`, `BASE_URL`, `API_BASE_URL`); fallback qiymatlar faqat ommaviy demo akkaunt uchun. `.env` faylini o'qish uchun [QA 19](/ios-lessons/qa-playwright-advanced)dagi kabi `dotenv` ulang va o'zgaruvchilarni prefiks bilan nomlang (`USERNAME` kabi tizim nomlaridan qoching). Shu yerdan boshlab QA 19'dagi kabi POM va fixture'larga o'ting.

> **💡 Maslahat:** Network mocking uchun tayyor g'oya: [QA 19](/ios-lessons/qa-playwright-advanced)da mahsulotlar API'si 500 qaytarganda Toolshop interfeysi foydalanuvchiga hech qanday xato xabarini ko'rsatmasligi aniqlangan. Shu holat uchun `page.route` testi va bug report — capstone'ga kuchli qo'shimcha.

API qismi uchun tayyor g'oyalar (2026-yil 9-oktabrda curl bilan tekshirilgan):

| So'rov                                                    | Natija | Nimani ko'rsatadi                                    |
| --------------------------------------------------------- | ------ | ---------------------------------------------------- |
| `GET /users` token'siz                                    | `401`  | Autentifikatsiya ishlaydi                            |
| `GET /users` customer token bilan                         | `403`  | Avtorizatsiya: oddiy foydalanuvchi admin ro'yxatini ko'rmaydi |
| `GET /users/me` customer token bilan                      | `200`  | O'z profiliga ruxsat bor                             |
| `GET /products?sort=hacker,up`                            | `500`  | Noto'g'ri parametr validatsiya qilinmagan — 400 kutilardi; bug report uchun material |

### 2.5. 3 haftalik reja

| Hafta | Kunlar | Nima qilasiz                                                                         |
| ----- | ------ | ------------------------------------------------------------------------------------ |
| 1     | 1–2    | Ilovani o'rganish (har bir sahifa, rollar, API Swagger), savollar ro'yxati           |
| 1     | 3–4    | Test strategiya va risk matritsasi                                                   |
| 1     | 5–7    | Checklist, 30 ta test case, RTM                                                      |
| 2     | 8–9    | v5 build'da test case'larni bajarish, natijalarni yozish                             |
| 2     | 10–12  | Bug'li build'da bug hunting: 2 ta 60 daqiqalik exploratory sessiya, 10 ta bug report |
| 2     | 13–14  | API kolleksiya: login → token → mahsulotlar → savatcha; negative testlar; newman     |
| 3     | 15–17  | Playwright: config, POM, fixtures, 10–15 ta UI test                                  |
| 3     | 18–19  | API testlar (Zod), a11y va visual test                                               |
| 3     | 20–21  | GitHub Actions, README, demo video, yakuniy tekshiruv                                |

### 2.6. Baholash rubrikasi

| Soha               | A'lo                                                                     | Qayta ishlash kerak                                      |
| ------------------ | ------------------------------------------------------------------------ | -------------------------------------------------------- |
| Strategiya va risk | Risklar asosli, test chuqurligi risk bilan bog'langan                    | Umumiy gaplar, risk va test o'rtasida bog'liqlik yo'q    |
| Test case'lar      | Texnikalar ko'rinadi (chegaralar, kombinatsiyalar), kutilgan natija aniq | "Ishlashi kerak" kabi kutilgan natijalar, faqat positive |
| Bug report'lar     | Har biri qayta hosil qilinadi, bitta bug — bitta report, dalil bor       | Bir report'da bir nechta bug, qadamlar yo'q              |
| API                | Chaining, assertion'lar, negative holatlar                               | Faqat "status 200" tekshiruvi                            |
| Avtomatlashtirish  | POM, mustaqil testlar, barqaror locator'lar, sleep yo'q                  | `waitForTimeout`, testlar bir-biriga bog'liq             |
| CI va hisobot      | PR'da ishlaydi, report artifact, yashil                                  | CI yo'q yoki doimiy qizil                                |
| README             | 1 daqiqada tushuniladi, natijalar va cheklovlar halol yozilgan           | Faqat "npm test"                                         |

> **⚠️ Diqqat:** Toolshop — ko'pchilik o'quvchilar foydalanadigan ommaviy sayt. Ro'yxatdan o'tishni testlasangiz, o'rtacha miqdorda va unikal emaillar bilan qiling; yuklama testi va xavfsizlik skanerlarini **ishga tushirmang**. Bug'li versiyada topilgan bug'larni portfolio'ingizda hujjatlashtiring.

---

## 3. Topshiriqlar xaritasi {#mashqlar}

| #          | Topshiriq                                    | Daraja | Darslar   |
| ---------- | -------------------------------------------- | ------ | --------- |
| [01](#t01) | SMS kod uchun Equivalence Partitioning       | 🟢     | QA 5      |
| [02](#t02) | Savatcha soni uchun 2 va 3 qiymatli BVA      | 🟢     | QA 5      |
| [03](#t03) | Bepul yetkazib berish chegarasi              | 🟢     | QA 2, 5   |
| [04](#t04) | Chegirmalar decision table                   | 🟡     | QA 5      |
| [05](#t05) | Buyurtma holatlari: state transition         | 🟡     | QA 6      |
| [06](#t06) | Pairwise: 54 kombinatsiyadan 9 ta test       | 🟡     | QA 6      |
| [07](#t07) | Muddatli to'lov: EP + BVA kombinatsiyasi     | 🔴     | QA 5      |
| [08](#t08) | Exploratory charter va sessiya hisoboti      | 🟡     | QA 6      |
| [09](#t09) | OTP login uchun 5 ta test case               | 🟢     | QA 7      |
| [10](#t10) | Yomon test case'ni tuzatish                  | 🟢     | QA 7      |
| [11](#t11) | Toolshop'dagi real bug uchun bug report      | 🟡     | QA 8      |
| [12](#t12) | 8 ta bug: severity va priority               | 🟡     | QA 8      |
| [13](#t13) | Mini RTM va qamrov                           | 🟡     | QA 7      |
| [14](#t14) | DevTools: bug kimniki?                       | 🟢     | QA 9      |
| [15](#t15) | curl bilan booking yaratish va o'qish        | 🟢     | QA 9–10   |
| [16](#t16) | API negative va authZ testlari               | 🟡     | QA 11, 24 |
| [17](#t17) | Postman: token chaining                      | 🟡     | QA 10     |
| [18](#t18) | SQL: buyurtmalar yaxlitligi                  | 🟡     | QA 12     |
| [19](#t19) | SQL: to'lovlar va eng ko'p daromad           | 🔴     | QA 12     |
| [20](#t20) | Checkout uchun mobil interruption checklisti | 🟢     | QA 14     |
| [21](#t21) | Proxyman: har stsenariyga to'g'ri asbob      | 🟡     | QA 15     |
| [22](#t22) | Proxyman Map Local: chegaraviy javoblar      | 🟡     | QA 15     |
| [23](#t23) | Playwright: login testlari                   | 🟢     | QA 18     |
| [24](#t24) | Playwright: savatcha badge                   | 🟡     | QA 18     |
| [25](#t25) | Playwright POM: checkout summasi             | 🔴     | QA 19     |
| [26](#t26) | Playwright API + Zod                         | 🔴     | QA 20     |
| [27](#t27) | GitHub Actions: PR'da smoke                  | 🟡     | QA 21     |
| [28](#t28) | k6: thresholds va natijani o'qish            | 🟡     | QA 23     |
| [29](#t29) | Accessibility: axe bilan skanerlash          | 🔴     | QA 25     |
| [30](#t30) | AI yozgan test case'larni audit qilish       | 🔴     | QA 26     |

---

## 4. Test dizayn: 1–8 {#test-dizayn}

### Topshiriq 01: SMS kod uchun Equivalence Partitioning (🟢) {#t01}

DemoBozor qoidasi: SMS kod — **6 raqam**, **120 soniya** amal qiladi, **3 marta** noto'g'ri kiritilsa raqam 15 daqiqaga bloklanadi. Kod kiritish maydoni uchun ekvivalent bo'laklarni (valid va invalid) aniqlang va har biriga vakil qiymat hamda kutilgan natija yozing.

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

| #   | Bo'lak                                | Vakil qiymat                      | Kutilgan natija                                       |
| --- | ------------------------------------- | --------------------------------- | ----------------------------------------------------- |
| V1  | 6 raqam, to'g'ri, 120 s ichida        | SMS'dagi kod, masalan `482915`    | Kirish muvaffaqiyatli                                 |
| V2  | 6 raqam, `0` bilan boshlanadi         | `012345` (agar SMS'da shu bo'lsa) | Qabul qilinadi — kod **matn** sifatida solishtiriladi |
| I1  | 6 raqam, noto'g'ri                    | `000000`                          | "Kod noto'g'ri", qolgan urinishlar soni kamayadi      |
| I2  | 5 raqam                               | `48291`                           | Yuborib bo'lmaydi yoki format xatosi                  |
| I3  | 7 raqam                               | `4829150`                         | 7-raqam kiritilmaydi yoki format xatosi               |
| I4  | Raqam bo'lmagan belgi                 | `48a915`                          | Harf qabul qilinmaydi                                 |
| I5  | Bo'sh                                 | ``                                | Tugma faol emas yoki "kodni kiriting"                 |
| I6  | To'g'ri kod, lekin muddati o'tgan     | to'g'ri kod 121-soniyada          | "Kod muddati tugagan", yangi kod so'rash taklifi      |
| I7  | 3 marta noto'g'ri kiritilgandan keyin | to'g'ri kod 4-urinishda           | Raqam bloklangan, to'g'ri kod ham qabul qilinmaydi    |

E'tibor bering:

- **V2** — klassik tuzoq: kod son sifatida saqlansa, `012345` → `12345` bo'lib qoladi va to'g'ri kod rad etiladi;
- **I6** va **I7** — vaqt va holatga bog'liq bo'laklar; ularning chegaralarini (119/120/121 soniya; 2- va 3-xato) keyingi texnikalar — BVA va state transition bilan to'liq tekshiramiz.

</details>

### Topshiriq 02: Savatcha soni uchun 2 va 3 qiymatli BVA (🟢) {#t02}

DemoBozor: savatchada bitta mahsulot soni **1–99** (butun son). Ekvivalent bo'laklarni yozing, keyin 2 qiymatli va 3 qiymatli BVA bo'yicha test qiymatlarini va kutilgan natijalarni chiqaring.

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

Bo'laklar: **≤ 0** (invalid) · **1–99** (valid) · **≥ 100** (invalid). Har bir chegarada **ikkita** chegaraviy qiymat bor — chap bo'lakning oxiri va o'ng bo'lakning boshi: `0 | 1` va `99 | 100`.

- **2 qiymatli BVA** (chegaraviy qiymat + qo'shni bo'lakdagi eng yaqin qiymat): **0, 1, 99, 100**;
- **3 qiymatli BVA** (har bir chegaraviy qiymat + ikkala qo'shnisi): 0 → {−1, 0, 1}, 1 → {0, 1, 2}, 99 → {98, 99, 100}, 100 → {99, 100, 101}. Birlashtirilganda: **−1, 0, 1, 2, 98, 99, 100, 101**.

| Qiymat | Kutilgan natija                                                                        |
| ------ | -------------------------------------------------------------------------------------- |
| −1     | Rad etiladi: UI'da manfiy son kiritib bo'lmaydi, API'da 400/422                        |
| 0      | Rad etiladi (yoki talab bo'yicha mahsulot savatchadan o'chiriladi — PO bilan aniqlang) |
| 1      | Qabul qilinadi; "−" tugmasi faol emas yoki o'chirishni taklif qiladi                   |
| 2      | Qabul qilinadi                                                                         |
| 98     | Qabul qilinadi                                                                         |
| 99     | Qabul qilinadi; "+" tugmasi faol emas                                                  |
| 100    | Rad etiladi: UI'da kiritib bo'lmaydi, API'da 400/422                                   |
| 101    | Rad etiladi                                                                            |

> **⚠️ Diqqat:** Ko'p manbalarda 3 qiymatli BVA faqat valid bo'lakning chegaralari atrofida olinadi ({0, 1, 2, 98, 99, 100}). Bu ISTQB ta'rifining keng tarqalgan noto'g'ri talqini: invalid bo'laklarning chegaraviy qiymatlari (0 va 100) ham chegaraviy qiymat hisoblanadi. Batafsil: [QA 5](/ios-lessons/qa-test-design-1).

Qo'shimcha: −1 va 100 ni **API orqali** ham yuboring (`POST /api/v1/cart/items` — xayoliy endpoint) — UI cheklovi server cheklovi emas.

</details>

### Topshiriq 03: Bepul yetkazib berish chegarasi (🟢) {#t03}

Qoida: mahsulotlar summasi **≥ 300 000 so'm** bo'lsa yetkazib berish bepul, aks holda 20 000 so'm. Premium a'zolar uchun har doim bepul. Chegaraviy test qiymatlarini yozing. Qaysi qiymat [QA 2](/ios-lessons/qa-principles-istqb)dagi `>` vs `>=` defektini ushlaydi? Qanday savollarni PO'ga berasiz?

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

Bo'laklar: **< 300 000** (yetkazish pullik) va **≥ 300 000** (bepul); chegara `299 999 | 300 000`.

| Mahsulotlar summasi | Premium | Kutilgan yetkazish narxi | Qaysi BVA                    |
| ------------------- | ------- | ------------------------ | ---------------------------- |
| 299 998             | Yo'q    | 20 000                   | 3 qiymatli                   |
| 299 999             | Yo'q    | 20 000                   | 2 va 3 qiymatli              |
| **300 000**         | Yo'q    | **0**                    | 2 va 3 qiymatli              |
| 300 001             | Yo'q    | 0                        | 3 qiymatli                   |
| 299 999             | Ha      | 0                        | Premium istisnosi            |

- `>` vs `>=` defektini aynan **300 000** ushlaydi: noto'g'ri kodda 300 000 so'mlik buyurtmaga 20 000 qo'shiladi. Bu — [QA 8](/ios-lessons/qa-bug-reports)dagi `deliveryFee()` bug'i (DB-210).
- Amaliy nuans: agar narxlar faqat mingga karrali bo'lsa, real qo'shnilar 299 000 va 301 000 bo'ladi. BVA'da qo'shni qiymat — ma'lumotning **eng kichik qadami**.

PO'ga savollar:

1. Chegirma qo'llanib, summa 300 000 dan pastga tushsa — yetkazish pullikmi? (Qoida "mahsulotlar summasi" deydi — chegirmadan oldingi summa, lekin tasdiqlatish kerak.)
2. Savatchadan mahsulot o'chirilib, summa chegaradan tushsa, narx darhol qayta hisoblanadimi?
3. Viloyatlar uchun ham shu qoidami?

</details>

### Topshiriq 04: Chegirmalar decision table (🟡) {#t04}

DemoBozor qoidalari: yangi mijoz (birinchi buyurtma) — **10%**; yaroqli `BAHOR15` promo-kodi — **15%** (minimal summa 200 000 so'm); chegirmalar qo'shilmaydi — **kattasi** qo'llanadi; Premium a'zo — har doim bepul yetkazib berish. Mahsulotlar summasi **250 000 so'm** deb oling (promo minimalidan yuqori, bepul yetkazish chegarasidan past). To'liq decision table tuzing (chegirma, yetkazish, jami), keyin uni qisqartiring.

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

To'liq jadval (2³ = 8 qoida):

| Shart / natija      | R1           | R2           | R3           | R4           | R5           | R6           | R7      | R8      |
| ------------------- | ------------ | ------------ | ------------ | ------------ | ------------ | ------------ | ------- | ------- |
| Yangi mijoz         | Ha           | Ha           | Ha           | Ha           | Yo'q         | Yo'q         | Yo'q    | Yo'q    |
| `BAHOR15` yaroqli   | Ha           | Ha           | Yo'q         | Yo'q         | Ha           | Ha           | Yo'q    | Yo'q    |
| Premium             | Ha           | Yo'q         | Ha           | Yo'q         | Ha           | Yo'q         | Ha      | Yo'q    |
| **Chegirma**        | 15% (37 500) | 15% (37 500) | 10% (25 000) | 10% (25 000) | 15% (37 500) | 15% (37 500) | 0       | 0       |
| **Yetkazish**       | 0            | 20 000       | 0            | 20 000       | 0            | 20 000       | 0       | 20 000  |
| **Jami**            | 212 500      | 232 500      | 225 000      | 245 000      | 212 500      | 232 500      | 250 000 | 270 000 |

Qisqartirilgan jadval: promo-kod yaroqli bo'lsa, "yangi mijoz" sharti natijaga ta'sir qilmaydi (15% > 10%), shuning uchun R1+R5 va R2+R6 birlashadi:

| Shart / natija    | A   | B      | C    | D      | E    | F      |
| ----------------- | --- | ------ | ---- | ------ | ---- | ------ |
| Yangi mijoz       | –   | –      | Ha   | Ha     | Yo'q | Yo'q   |
| `BAHOR15` yaroqli | Ha  | Ha     | Yo'q | Yo'q   | Yo'q | Yo'q   |
| Premium           | Ha  | Yo'q   | Ha   | Yo'q   | Ha   | Yo'q   |
| Chegirma          | 15% | 15%    | 10%  | 10%    | 0    | 0      |
| Yetkazish         | 0   | 20 000 | 0    | 20 000 | 0    | 20 000 |

8 o'rniga 6 ta test. Lekin ehtiyot bo'ling: qisqartirish **haqiqiy foizlarga** bog'liq. `TEJA5` (5%) promo-kodi bilan yangi mijozning 10% i yutadi va R1/R5 natijalari farq qiladi — konfiguratsiyani tekshirib, kerak bo'lsa to'liq jadvalga qayting. Muddati o'tgan `QISH10` yoki `KUZ10` esa "promo-kod yaroqli = Yo'q" ustunlariga tushadi.

</details>

### Topshiriq 05: Buyurtma holatlari — state transition (🟡) {#t05}

DemoBozor buyurtma holatlari: `CREATED` → `PENDING_PAYMENT` → `PAID` → `PACKING` → `SHIPPED` → `DELIVERED`; `CANCELLED` — `CREATED`, `PENDING_PAYMENT`, `PAID` (pul qaytariladi) va `PACKING` dan; `RETURNED` — `DELIVERED` dan 10 kun ichida; naqd to'lovda `CREATED` → `PACKING`.

1. Barcha ruxsat etilgan o'tishlarni sanang.
2. Barcha o'tishlarni qamraydigan **minimal** test yo'llarini (path) tuzing.
3. Kamida 5 ta ruxsat etilmagan o'tishni yozing.

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

**1. Ruxsat etilgan o'tishlar — 11 ta** (hodisa nomlari [QA 6](/ios-lessons/qa-test-design-2)dagi state table bilan bir xil):

| #   | Dan             | Ga              | Hodisa                             |
| --- | --------------- | --------------- | ---------------------------------- |
| 1   | CREATED         | PENDING_PAYMENT | E1 to'lovni boshlash [karta]       |
| 2   | PENDING_PAYMENT | PAID            | E3 to'lov o'tdi                    |
| 3   | PAID            | PACKING         | E4 yig'ishni boshlash              |
| 4   | PACKING         | SHIPPED         | E5 kuryerga topshirish             |
| 5   | SHIPPED         | DELIVERED       | E6 yetkazildi                      |
| 6   | DELIVERED       | RETURNED        | E8 qaytarish [≤ 10 kun]            |
| 7   | CREATED         | CANCELLED       | E7 bekor qilish                    |
| 8   | PENDING_PAYMENT | CANCELLED       | E7 bekor qilish                    |
| 9   | PAID            | CANCELLED       | E7 bekor qilish (pul qaytariladi)  |
| 10  | PACKING         | CANCELLED       | E7 bekor qilish                    |
| 11  | CREATED         | PACKING         | E2 tasdiqlash [naqd]               |

8 holat × 8 hodisa = 64 katak; shundan faqat 11 tasi ruxsat etilgan o'tish, qolgan 53 tasi — "ruxsat etilmagan" kataklar, ularning eng xavflilarini alohida testlaymiz.

**2. Barcha 11 o'tishni qamraydigan 5 ta yo'l:**

| Yo'l | Ketma-ketlik                                                                | Qamralgan o'tishlar |
| ---- | --------------------------------------------------------------------------- | ------------------- |
| P1   | CREATED → PENDING_PAYMENT → PAID → PACKING → SHIPPED → DELIVERED → RETURNED | 1, 2, 3, 4, 5, 6    |
| P2   | CREATED → CANCELLED                                                         | 7                   |
| P3   | CREATED → PENDING_PAYMENT → CANCELLED                                       | 8                   |
| P4   | CREATED → PENDING_PAYMENT → PAID → CANCELLED (pul qaytgani tekshiriladi)    | 9                   |
| P5   | CREATED → PACKING (naqd) → CANCELLED                                        | 11, 10              |

**3. Ruxsat etilmagan o'tishlar (kutilgan natija: rad etiladi, holat o'zgarmaydi, tushunarli xabar):**

- `SHIPPED` → `CANCELLED`
- `DELIVERED` → `CANCELLED`
- `CANCELLED` → `PAID`
- `PENDING_PAYMENT` → `SHIPPED` (bosqichdan sakrash)
- `RETURNED` → `DELIVERED`
- `DELIVERED` → `RETURNED` **11-kuni** (guard sharti: chegarani BVA bilan — 10-kun ruxsat, 11-kun rad)

Ruxsat etilmagan o'tishlarni odatda **API orqali** testlash qulay — UI'da bunday tugma ko'rinmasligi mumkin, lekin server buni baribir tekshirishi shart.

</details>

### Topshiriq 06: Pairwise — 54 kombinatsiyadan 9 ta test (🟡) {#t06}

Checkout'ni quyidagi parametrlar bilan testlash kerak:

- Brauzer: Chrome, Safari, Firefox
- Til: uz, ru, en
- To'lov: Uzcard, Humo, Naqd
- Qurilma: Desktop, Mobil

To'liq kombinatsiyalar soni nechta? Har bir **juftlik** kamida bir marta uchraydigan to'plam tuzing.

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

To'liq: 3 × 3 × 3 × 2 = **54** kombinatsiya. Pairwise to'plam — **9** ta test (uchta 3-qiymatli parametr juftligi uchun kamida 3 × 3 = 9 qator kerak):

| #   | Brauzer | Til | To'lov | Qurilma |
| --- | ------- | --- | ------ | ------- |
| 1   | Chrome  | uz  | Uzcard | Desktop |
| 2   | Chrome  | ru  | Humo   | Mobil   |
| 3   | Chrome  | en  | Naqd   | Mobil   |
| 4   | Safari  | uz  | Humo   | Desktop |
| 5   | Safari  | ru  | Naqd   | Desktop |
| 6   | Safari  | en  | Uzcard | Mobil   |
| 7   | Firefox | uz  | Naqd   | Mobil   |
| 8   | Firefox | ru  | Uzcard | Mobil   |
| 9   | Firefox | en  | Humo   | Desktop |

Bu to'plam L9 ortogonal massiviga asoslangan; barcha juftliklar qamralgani skript bilan tekshirildi (yetishmayotgan juftlik — 0). Amalda bunday jadvalni qo'lda emas, Microsoft PICT kabi vosita bilan yarating va cheklovlarni (masalan, "Safari faqat Apple qurilmalarida") model faylida yozing — [QA 6](/ios-lessons/qa-test-design-2).

</details>

### Topshiriq 07: Muddatli to'lov — EP + BVA kombinatsiyasi (🔴) {#t07}

Qoida: muddatli (foizsiz) to'lov — yosh **18–65** (shu jumladan), buyurtma summasi **500 000 – 30 000 000 so'm**, muddat **3, 6 yoki 12 oy**. Minimal, lekin kuchli test to'plamini tuzing. Nega bitta testda bir nechta noto'g'ri qiymatni birlashtirmaslik kerak?

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

| #   | Yosh | Summa      | Muddat | Kutilgan    | Nimani tekshiradi                |
| --- | ---- | ---------- | ------ | ----------- | -------------------------------- |
| 1   | 18   | 500 000    | 3      | Ruxsat      | Pastki chegaralar birga          |
| 2   | 65   | 30 000 000 | 12     | Ruxsat      | Yuqori chegaralar birga          |
| 3   | 40   | 15 000 000 | 6      | Ruxsat      | O'rta (tipik) qiymatlar          |
| 4   | 17   | 1 000 000  | 6      | Rad: yosh   | Yosh pastki chegaradan tashqari  |
| 5   | 66   | 1 000 000  | 6      | Rad: yosh   | Yosh yuqori chegaradan tashqari  |
| 6   | 30   | 499 999    | 6      | Rad: summa  | Summa pastki chegaradan tashqari |
| 7   | 30   | 30 000 001 | 6      | Rad: summa  | Summa yuqori chegaradan tashqari |
| 8   | 30   | 1 000 000  | 9      | Rad: muddat | Ruxsat etilmagan muddat          |

Bu yerda **2 qiymatli BVA** ishlatildi (17/18, 65/66, 499 999/500 000, 30 000 000/30 000 001). 3 qiymatli BVA har chegaraga yana qo'shni qiymatlarni qo'shadi — masalan, yosh uchun 16, 19, 64 va 67 ([QA 5](/ios-lessons/qa-test-design-1)).

**"Bir vaqtda bitta noto'g'ri qiymat"** qoidasi: agar testda yosh ham, summa ham noto'g'ri bo'lsa va tizim rad etsa — qaysi tekshiruv ishlaganini bilmaysiz; bittasi buzilgan bo'lsa ham test "o'tadi" (defect masking).

Qo'shimcha nozik joylar:

- Yosh tug'ilgan sanadan hisoblanadi: **bugun 18 yoshga to'lgan** va **ertaga 66 ga to'ladigan** (bugun hali 65) mijozlar — ikkalasi ham ruxsat;
- "Bugun" qaysi vaqt zonasida? Asia/Tashkent (UTC+5) — server UTC'da ishlasa, tunda chegara siljishi mumkin;
- Summa chegarasi chegirmadan oldingi yoki keyingi summaga qo'llanadimi — talabni aniqlashtiring.

</details>

### Topshiriq 08: Exploratory charter va sessiya hisoboti (🟡) {#t08}

DemoBozor checkout'idagi promo-kod maydoni uchun 60 daqiqalik exploratory sessiya rejalang: charter yozing va kamida 10 ta g'oya ro'yxatini tuzing. Sessiya hisoboti qanday ko'rinishda bo'lishi kerak?

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

**Charter:** _Promo-kod maydonini checkout'da (Web, Chrome) turli formatdagi va holatdagi kodlar, ikki brauzer tab'i va sekin tarmoq bilan tekshirib, chegirma hisobidagi va qayta ishlatishdagi xatolarni topish._

**G'oyalar:**

1. Kichik harflar: `bahor15` (qoida: faqat katta harf — avtomatik o'zgartiriladimi yoki rad etiladimi?);
2. Boshida/oxirida probel: `" BAHOR15 "`;
3. Uzunlik chegaralari: 5, 6, 12, 13 belgi;
4. Kirill harflari lotinga o'xshash: `ВАНОR15` (В, А, Н, О — kirill) — ko'zga bir xil, lekin boshqa kod; eng "makkor" holatlardan biri;
5. Muddati o'tgan kodlar (`QISH10`, `KUZ10`) va kelajakda boshlanadigan kod;
6. `BAHOR15`ning minimal summasi chegarasida (200 000) va undan 1 so'm kam (199 999);
7. Promo-kod + yangi mijoz chegirmasi birga: `TEJA5` (5%) bilan yangi mijozning 10% i qo'llanishi kerak;
8. Kod qo'llangandan keyin mahsulot o'chirilib, summa minimaldan tushsa — chegirma olib tashlanadimi?;
9. Ikki tab'da bir vaqtda bitta kodni ishlatish (race condition);
10. Sekin tarmoqda "Qo'llash" tugmasini ikki marta bosish;
11. Kod qo'llanib, sahifa yangilansa yoki "orqaga" qaytilsa;
12. Bir marta ishlatilgan kodni boshqa buyurtmada qayta ishlatish.

**Sessiya hisoboti tuzilmasi** (session-based test management uslubida):

| Bo'lim               | Mazmuni                                                                   |
| -------------------- | ------------------------------------------------------------------------- |
| Charter              | Yuqoridagi matn                                                           |
| Tester, sana, vaqt   | Ism, sana, 60 daqiqa (sozlashga ~10, testlashga ~45, hisobotga ~5)        |
| Muhit                | Staging, build raqami, brauzer                                            |
| Test eslatmalari     | Nima qilindi, nima kuzatildi                                              |
| Bug'lar              | Topilgan bug'lar ticket raqamlari bilan                                   |
| Savollar / muammolar | Talabdagi noaniqliklar, bloklovchi muammolar                              |
| Qamrov               | Yuqoridagi g'oyalardan qaysilari tekshirildi, qaysilari keyingi sessiyaga |

</details>

---

## 5. Hujjatlar va bug'lar: 9–13 {#hujjatlar}

### Topshiriq 09: OTP login uchun 5 ta test case (🟢) {#t09}

DemoBozor mobil ilovasida telefon raqami va SMS kod bilan kirish uchun 5 ta test case yozing (ID, sarlavha, oldingi shartlar, qadamlar, test ma'lumotlari, kutilgan natija, ustuvorlik).

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

| ID          | Sarlavha                                                     | Oldingi shartlar                                  | Qadamlar                                                                               | Kutilgan natija                                                               | Ustuvorlik |
| ----------- | ------------------------------------------------------------ | ------------------------------------------------- | -------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ---------- |
| TC-AUTH-001 | To'g'ri SMS kod bilan kirish                                 | Test akkaunt `+998 90 000 00 01`, SMS keladi      | 1) Raqamni kiriting 2) "Kod olish" 3) SMS'dagi 6 raqamni 120 s ichida kiriting         | Bosh sahifa ochiladi, foydalanuvchi nomi ko'rinadi                            | P0         |
| TC-AUTH-002 | Noto'g'ri kod xato xabarini chiqaradi                        | TC-001 dagi kabi                                  | 1–2) Kod oling 3) `000000` kiriting                                                    | "Kod noto'g'ri" xabari; qolgan urinishlar: 2; kirish yo'q                     | P0         |
| TC-AUTH-003 | Muddati o'tgan kod qabul qilinmaydi                          | Kod olingan                                       | 1) 121 soniya kuting 2) To'g'ri kodni kiriting                                         | "Kod muddati tugagan", "Qayta yuborish" taklifi; kirish yo'q                  | P1         |
| TC-AUTH-004 | 3 ta noto'g'ri urinishdan keyin raqam 15 daqiqaga bloklanadi | Kod olingan                                       | 1) 3 marta noto'g'ri kod 2) To'g'ri kodni kiriting 3) 15 daqiqadan keyin qayta urining | 2-qadamda "15 daqiqaga bloklangan"; 3-qadamda yangi kod olish mumkin          | P0         |
| TC-AUTH-005 | "Qayta yuborish" 60 soniyadan keyin faollashadi              | Kod olingan                                       | 1) Taymerni kuzating 2) 59-soniyada va 60-soniyadan keyin tugmani tekshiring           | 59 s da tugma faol emas; 60 s dan keyin faol; yangi kod eskisini bekor qiladi | P1         |

Test ma'lumotlari va muhit (staging, test raqamlari) test case'ning o'zida yoki test suite darajasida ko'rsatiladi. TC-005'dagi "yangi kod eskisini bekor qiladi" — talabda yo'q bo'lsa, PO'dan tasdiqlatiladigan taxmin.

</details>

### Topshiriq 10: Yomon test case'ni tuzatish (🟢) {#t10}

Quyidagi test case'ning kamchiliklarini toping va uni qayta yozing:

```text
TC1: Login'ni tekshirish
Qadamlar: Ilovaga kiring va hamma narsani tekshiring.
Kutilgan natija: Hammasi ishlaydi.
```

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

**Kamchiliklar:** sarlavha nimani tekshirishini aytmaydi; bitta case'da "hamma narsa" (atomar emas); oldingi shartlar va test ma'lumotlari yo'q; qadamlar takrorlanmaydi; kutilgan natija o'lchab bo'lmaydigan ("ishlaydi"); talabga bog'lanmagan; ustuvorlik yo'q.

**Qayta yozilgani** (bir nechta case'ga bo'linadi; bittasi namuna):

| Maydon           | Qiymat                                                                                          |
| ---------------- | ----------------------------------------------------------------------------------------------- |
| ID               | TC-AUTH-006                                                                                     |
| Sarlavha         | 9 raqamdan kam telefon raqami bilan SMS kod so'rab bo'lmaydi                                    |
| Talab            | REQ-AUTH-01 (telefon: `+998` + 9 raqam)                                                         |
| Oldingi shartlar | Ilova o'rnatilgan, foydalanuvchi tizimdan chiqqan                                               |
| Test ma'lumoti   | `+998 90 123 45 6` (8 raqam)                                                                    |
| Qadamlar         | 1) Kirish ekranini oching 2) Raqamni kiriting 3) "Kod olish" tugmasini bosing                   |
| Kutilgan natija  | Tugma faol emas yoki "Raqamni to'liq kiriting" xabari; SMS yuborilmaydi; API'ga so'rov ketmaydi |
| Ustuvorlik / tur | P1 / Negative, funksional                                                                       |

</details>

### Topshiriq 11: Toolshop'dagi real bug uchun bug report (🟡) {#t11}

https://with-bugs.practicesoftwaretesting.com ni oching. Saralash ro'yxatidan **"Price (High - Low)"** ni tanlang va natijani kuzating. Bu xatti-harakat uchun to'liq bug report yozing.

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

Biz 2026-yil oktabrda kuzatgan natija: saralashdan keyin narxlar **o'sish** tartibida chiqdi ($4.92, $9.17, $11.14, $11.48, ...). Normal v5 build'da xuddi shu variant narxlarni kamayish tartibida ko'rsatadi ($89.55, $86.71, $80.19, ...).

```text
ID: BUG-003
Sarlavha: [Katalog] "Price (High - Low)" saralash mahsulotlarni narx o'sishi bo'yicha ko'rsatadi

Muhit: https://with-bugs.practicesoftwaretesting.com (Toolshop v5.0 with bugs)
       Chrome for Testing 156 (Playwright 1.64), macOS; mehmon foydalanuvchi
Oldingi shartlar: yo'q

Qayta hosil qilish qadamlari:
1. Bosh sahifani oching.
2. "Sort" ro'yxatidan "Price (High - Low)" ni tanlang.
3. Birinchi qatordagi mahsulot narxlarini kuzating.

Kutilgan natija: mahsulotlar narx kamayishi bo'yicha (eng qimmati birinchi).
Haqiqiy natija:  mahsulotlar narx o'sishi bo'yicha ($4.92, $9.17, $11.14, $11.48, ...).

Takrorlanishi: 3/3
Severity: Major (funksiya teskari ishlaydi; workaround: "Low - High" ni tanlash)
Priority: P2
Komponent: Catalog / Sorting
Ilovalar: skrinshot (sort-high-low.png)

Qo'shimcha ma'lumot: DevTools'da "Price (High - Low)" option'ining qiymati "price,asc".
Label va qiymat almashib qolgan bo'lishi mumkin. Boshqa variantlar ("Name (A - Z)" — qiymati
"name,desc") alohida tekshirilib, alohida report qilinadi.
```

Nega bunday yozildi: sarlavha **qayerda + nima** formulasida ([QA 8](/ios-lessons/qa-bug-reports)); bitta report — bitta bug; haqiqiy natijada aniq qiymatlar; ildiz sababi haqida taxmin "Qo'shimcha" bo'limida, faktlardan ajratilgan. Bu build'da boshqa bug'lar ham bor — ularni capstone uchun o'zingiz topasiz.

</details>

### Topshiriq 12: 8 ta bug — severity va priority (🟡) {#t12}

Har bir DemoBozor bug'iga severity (Blocker/Critical/Major/Minor/Trivial) va priority (P0–P3) bering, qisqa asoslang:

1. Android 14'da "To'lash" tugmasi bosilganda ilova yopilib qoladi (barcha foydalanuvchilar).
2. Bosh sahifadagi logotip ostida "DemoBozr" deb yozilgan.
3. Admin panelidagi oylik PDF hisobot eksporti crash bo'ladi (oyiga 2 marta ishlatiladi, CSV eksport ishlaydi).
4. Aynan 300 000 so'mlik buyurtmaga yetkazish haqi olinadi.
5. Sharh matni 1000 belgidan uzun bo'lsa server 500 qaytaradi (UI 1000 dan ortiq yozdirmaydi, faqat API orqali mumkin).
6. Dark mode'da "Savatcha" tugmasi kontrasti juda past.
7. Bitta promo-kodni ikki tab'da bir vaqtda ikki marta ishlatish mumkin.
8. Rus tilida savatcha sahifasi sarlavhasi "Cart" bo'lib qolgan.

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

| #   | Severity | Priority | Asos                                                                                                                                                         |
| --- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 1   | Blocker  | P0       | Asosiy biznes oqimi barcha Android 14 foydalanuvchilari uchun to'xtagan                                                                                      |
| 2   | Trivial  | P1       | Texnik ta'sir yo'q, lekin brend obro'si — har kim ko'radi                                                                                                    |
| 3   | Major    | P3       | Funksiya buziladi, lekin kam ishlatiladi va workaround (CSV) bor                                                                                             |
| 4   | Major    | P1       | Pul bilan bog'liq, ko'p buyurtmaga ta'sir qiladi ([QA 8](/ios-lessons/qa-bug-reports)dagi DB-210)                                                                                                             |
| 5   | Minor    | P2       | Server validatsiyasi yo'q (500 o'rniga 400/422 kerak); oddiy foydalanuvchiga ta'siri yo'q, lekin API sifati va xavfsizlik nuqtai nazaridan tuzatilishi kerak |
| 6   | Minor    | P2       | A11y muammosi; ba'zi foydalanuvchilar uchun ishlatish qiyin ([QA 25](/ios-lessons/qa-a11y-l10n-visual))                                                      |
| 7   | Critical | P0–P1    | To'g'ridan-to'g'ri pul yo'qotish va suiiste'mol imkoniyati                                                                                                   |
| 8   | Minor    | P2–P3    | Lokalizatsiya xatosi, funksiya ishlaydi                                                                                                                      |

±1 daraja farq normal — muhimi asoslash. Esda tuting: severity — texnik ta'sir, priority — biznes shoshilinchligi; ularni triage'da PO bilan birga yakunlaysiz.

</details>

### Topshiriq 13: Mini RTM va qamrov (🟡) {#t13}

Talablar va test case'lar:

- REQ-01: SMS kod 6 raqam, 120 soniya amal qiladi → TC-AUTH-001, TC-AUTH-003
- REQ-02: 300 000 so'mdan yetkazish bepul → TC-DLV-001, TC-DLV-002
- REQ-03: Promo-kod har foydalanuvchiga bir marta → TC-PRM-004
- REQ-04: Savatchada soni 1–99 → TC-CRT-001 ... TC-CRT-006
- REQ-05: Sharhni faqat DELIVERED buyurtma egasi yozadi → (yo'q)

Oxirgi run: TC-DLV-002 — Failed (BUG-012), TC-PRM-004 — Blocked (staging'da promo-kodlar yo'q), qolganlari — Passed. RTM tuzing, talablar qamrovini hisoblang va xulosa yozing.

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

| Talab  | Test case'lar            | Natija             | Bug'lar | Holat                      |
| ------ | ------------------------ | ------------------ | ------- | -------------------------- |
| REQ-01 | TC-AUTH-001, TC-AUTH-003 | Passed, Passed     | —       | ✅ Qamralgan, o'tdi        |
| REQ-02 | TC-DLV-001, TC-DLV-002   | Passed, **Failed** | BUG-012 | ❌ Qamralgan, bug bor      |
| REQ-03 | TC-PRM-004               | **Blocked**        | —       | ⚠️ Qamralgan, bajarilmagan |
| REQ-04 | TC-CRT-001 ... 006       | Passed             | —       | ✅ Qamralgan, o'tdi        |
| REQ-05 | —                        | —                  | —       | ❌ **Qamrov yo'q**         |

- Talablar qamrovi (kamida 1 testi bor): 4 / 5 = **80%**;
- To'liq tasdiqlangan talablar: 2 / 5 (REQ-01, REQ-04).

Xulosa va harakatlar: REQ-05 uchun test case'lar yozish (DELIVERED egasi — ruxsat; boshqa holatdagi buyurtma egasi va begona foydalanuvchi — rad, API orqali ham); REQ-03 uchun staging'ga test promo-kodlari qo'shilishi kerak (bloklovchi muammoni DevOps/PO'ga ko'tarish); BUG-012 tuzatilgach TC-DLV-002 ni qayta testlash va regressiya.

</details>

---

## 6. Web, API va SQL: 14–19 {#web-api-sql}

### Topshiriq 14: DevTools — bug kimniki? (🟢) {#t14}

Har bir holat uchun bug front-end'dami yoki back-end'dami aniqlang va bug report'ga nimani biriktirishni ayting:

1. Katalog bo'sh ko'rinadi. Network: `GET /api/v1/products` → `200`, javobda 24 ta mahsulot. Console'da `TypeError: Cannot read properties of undefined (reading 'map')`.
2. "Buyurtma berish" bosilganda xato oynasi. Network: `POST /api/v1/orders` → `500`, javobda stack trace va SQL so'rovi ko'rinadi.
3. Narx API'da `250000`, UI'da "25 000 so'm".
4. "Saqlash" tugmasi bosilganda hech narsa bo'lmaydi; Network'da so'rov yo'q; Console toza.

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

| #   | Kimniki                 | Asos                                                                                                                   | Biriktiriladi                              |
| --- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ |
| 1   | Front-end               | API to'g'ri ma'lumot qaytargan, JS xatosi render'ni buzgan                                                             | Console xatosi (stack bilan), javob JSON'i |
| 2   | Back-end (+ xavfsizlik) | Server xatosi; stack trace va SQL'ning javobga chiqishi — alohida xavfsizlik bug'i ([QA 24](/ios-lessons/qa-security)) | So'rov body'si, javob, vaqt, request id    |
| 3   | Front-end               | Ma'lumot to'g'ri, formatlashda xato (bir nol yo'qolgan)                                                                | Javob qiymati va UI skrinshoti yonma-yon   |
| 4   | Front-end (ehtimol)     | So'rov umuman ketmagan: event handler ulanmagan yoki validatsiya jim to'xtatgan                                        | Video, qadamlar, Network bo'sh ekani       |

4-holatda jim ishlamaslik — UX bug'i ham: foydalanuvchi hech qanday xabar ko'rmaydi.

</details>

### Topshiriq 15: curl bilan booking yaratish va o'qish (🟢) {#t15}

restful-booker'da curl bilan yangi booking yarating, uni ID bo'yicha o'qing va status kodlarini ko'rsating.

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

```bash
# 1) Booking yaratish
curl -s -w "\nHTTP %{http_code}\n" -X POST https://restful-booker.herokuapp.com/booking \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"firstname":"Aziz","lastname":"Karimov","totalprice":250,"depositpaid":true,"bookingdates":{"checkin":"2026-11-01","checkout":"2026-11-05"},"additionalneeds":"Breakfast"}'

# 2) Javobdagi bookingid ni qo'yib o'qish (masalan, 2142)
curl -s -w "\nHTTP %{http_code}\n" https://restful-booker.herokuapp.com/booking/2142 \
  -H "Accept: application/json"
```

2026-yil oktabrdagi haqiqiy javob (ID har safar boshqacha):

```json
{
  "bookingid": 2142,
  "booking": {
    "firstname": "Aziz",
    "lastname": "Karimov",
    "totalprice": 250,
    "depositpaid": true,
    "bookingdates": { "checkin": "2026-11-01", "checkout": "2026-11-05" },
    "additionalneeds": "Breakfast"
  }
}
```

E'tibor bering: yaratish **`200 OK`** qaytaradi. REST konvensiyasida yangi resurs uchun odatda `201 Created` kutiladi — bu farqni ham kuzatuv sifatida qayd qiling. `GET` esa `200` va booking obyektini qaytaradi.

</details>

### Topshiriq 16: API negative va authZ testlari (🟡) {#t16}

restful-booker uchun kamida 6 ta negative/avtorizatsiya testini rejalashtiring, har biri uchun REST nuqtai nazaridan kutilgan natijani yozing. Keyin ularni curl bilan bajarib, haqiqiy natijani solishtiring. DemoBozor'da IDOR (BOLA) testini qanday qilgan bo'lardingiz?

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

2026-yil 9-oktabrda bajarilgan natijalar:

| #   | So'rov                                                   | Kutilgan (REST)    | Haqiqiy                                 | Xulosa                               |
| --- | -------------------------------------------------------- | ------------------ | --------------------------------------- | ------------------------------------ |
| 1   | `PUT /booking/{id}` token'siz                            | 401 yoki 403       | `403`                                   | ✅ Himoyalangan                      |
| 2   | `DELETE /booking/{id}` noto'g'ri token (`token=abc123`)  | 401 yoki 403       | `403`                                   | ✅ Himoyalangan                      |
| 3   | `POST /booking` faqat `firstname` bilan                  | 400 + xato tavsifi | `500 Internal Server Error`             | 🐞 Validatsiya yo'q                  |
| 4   | `POST /booking`: `totalprice=-5`, `checkout` < `checkin` | 400 yoki 422       | `200`, booking saqlandi                 | 🐞 Biznes validatsiya yo'q           |
| 5   | `POST /auth` noto'g'ri parol bilan                       | 401                | `200` va `{"reason":"Bad credentials"}` | 🐞 Noto'g'ri status kod              |
| 6   | `DELETE /booking/{id}` to'g'ri token bilan               | 200 yoki 204       | `201 Created`                           | 🐞 Noto'g'ri status (ma'lum "quirk") |
| 7   | O'chirilgan booking'ni `GET`                             | 404                | `404`                                   | ✅                                   |
| 8   | O'chirilgan booking'ni yana `DELETE` (token bilan)       | 404                | `405 Method Not Allowed`                | 🐞 Noto'g'ri status kod              |

Negative so'rov namunasi (3-qator):

```bash
curl -s -w "\nHTTP %{http_code}\n" -X POST https://restful-booker.herokuapp.com/booking \
  -H "Content-Type: application/json" -H "Accept: application/json" \
  -d '{"firstname":"Aziz"}'
```

**IDOR / BOLA** — restful-booker'da bitta admin bor, shuning uchun "begona resurs" testini qilib bo'lmaydi. DemoBozor'da (xayoliy API) ikkita xaridor akkaunti kerak:

1. A foydalanuvchi o'z buyurtmasini oladi: `GET /api/v1/orders/1001` (A token) → `200`;
2. A foydalanuvchi B'ning buyurtmasini so'raydi: `GET /api/v1/orders/1002` (A token) → **`403` yoki `404`** kutiladi; `200` va B'ning ma'lumoti — kritik xavfsizlik bug'i;
3. Xuddi shuni `POST /api/v1/orders/1002/cancel` uchun ham — o'qish himoyalangan, lekin o'zgartirish himoyalanmagan holatlar uchraydi.

Batafsil: [QA 11](/ios-lessons/qa-api-advanced), [QA 24](/ios-lessons/qa-security). Faqat o'z test muhitingizda!

</details>

### Topshiriq 17: Postman — token chaining (🟡) {#t17}

Postman kolleksiyasi yarating: (1) `POST /auth` → token'ni o'zgaruvchiga saqlash; (2) `POST /booking` → `bookingId`ni saqlash; (3) token bilan `DELETE`; (4) o'chirilgan booking `GET` → 404. Har so'rovga Post-response testlari yozing va kolleksiyani Newman bilan ishga tushiring.

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

Kolleksiya o'zgaruvchilari: `baseUrl = https://restful-booker.herokuapp.com`, `token`, `bookingId`.

**1. Auth** — Post-response (Tests) skripti:

```javascript
pm.test("Status 200", () => pm.response.to.have.status(200));
const body = pm.response.json();
pm.test("Token bor va bo'sh emas", () => {
  pm.expect(body.token).to.be.a("string").and.not.empty;
});
pm.collectionVariables.set("token", body.token);
```

**2. Booking yaratish** (`POST {{baseUrl}}/booking`, `Accept: application/json`):

```javascript
pm.test("Status 200", () => pm.response.to.have.status(200));
const body = pm.response.json();
pm.test("bookingid son", () => pm.expect(body.bookingid).to.be.a("number"));
pm.test("Ism saqlangan", () =>
  pm.expect(body.booking.firstname).to.eql("Aziz"),
);
pm.test("Javob 2 soniyadan tez", () =>
  pm.expect(pm.response.responseTime).to.be.below(2000),
);
pm.collectionVariables.set("bookingId", body.bookingid);
```

**3. O'chirish** (`DELETE {{baseUrl}}/booking/{{bookingId}}`, header `Cookie: token={{token}}`):

```javascript
pm.test("Status 201 (restful-booker quirk)", () =>
  pm.response.to.have.status(201),
);
```

**4. Tekshirish** (`GET {{baseUrl}}/booking/{{bookingId}}`):

```javascript
pm.test("Status 404", () => pm.response.to.have.status(404));
```

Ishga tushirish (kolleksiyani eksport qilib):

```bash
npx newman run booker.postman_collection.json
```

Biz Newman 6.2.3 bilan ishga tushirdik: **4 ta so'rov, 8 ta assertion, 0 ta xato**. Eslatma: Postman'ning Free tarifi 2026-yil martidan bitta foydalanuvchiga cheklangan — jamoaviy ish uchun Bruno yoki Hoppscotch'da xuddi shu mantiqni qurish mumkin ([QA 10](/ios-lessons/qa-api-postman)).

</details>

### Topshiriq 18: SQL — buyurtmalar yaxlitligi (🟡) {#t18}

Quyidagi DemoBozor ma'lumotlarini SQLite'ga yuklang (`sqlite3 capstone.db < capstone_seed.sql`) va to'rtta so'rov yozing:

- (a) `subtotal` buyurtma qatorlari yig'indisiga (`quantity × unit_price`) teng bo'lmagan buyurtmalar;
- (b) `total ≠ subtotal − discount + delivery_fee` bo'lgan buyurtmalar;
- (c) yetkazish qoidasi buzilgan buyurtmalar (≥ 300 000 bo'lsa ham haq olingan yoki < 300 000 bo'lsa ham premium bo'lmagan mijozdan olinmagan);
- (d) mavjud bo'lmagan buyurtmaga bog'langan "yetim" `order_items`.

```sql
-- capstone_seed.sql
CREATE TABLE users (
  id INTEGER PRIMARY KEY,
  phone TEXT UNIQUE NOT NULL,
  full_name TEXT NOT NULL,
  role TEXT CHECK (role IN ('buyer','seller','admin')) DEFAULT 'buyer',
  is_premium INTEGER DEFAULT 0,
  created_at TEXT
);
CREATE TABLE products (
  id INTEGER PRIMARY KEY,
  name TEXT NOT NULL,
  category TEXT,
  price INTEGER NOT NULL,
  stock INTEGER NOT NULL,
  seller_id INTEGER REFERENCES users(id)
);
CREATE TABLE orders (
  id INTEGER PRIMARY KEY,
  user_id INTEGER REFERENCES users(id),
  status TEXT CHECK (status IN ('CREATED','PENDING_PAYMENT','PAID','PACKING','SHIPPED','DELIVERED','CANCELLED','RETURNED')),
  payment_method TEXT CHECK (payment_method IN ('UZCARD','HUMO','VISA','MASTERCARD','CASH')),
  promo_code TEXT,
  subtotal INTEGER,
  discount INTEGER,
  delivery_fee INTEGER,
  total INTEGER,
  created_at TEXT
);
CREATE TABLE order_items (
  id INTEGER PRIMARY KEY,
  order_id INTEGER REFERENCES orders(id),
  product_id INTEGER REFERENCES products(id),
  quantity INTEGER CHECK (quantity BETWEEN 1 AND 99),
  unit_price INTEGER
);
CREATE TABLE payments (
  id INTEGER PRIMARY KEY,
  order_id INTEGER REFERENCES orders(id),
  amount INTEGER,
  status TEXT CHECK (status IN ('SUCCESS','FAILED','REFUNDED')),
  provider TEXT,
  created_at TEXT
);

INSERT INTO users VALUES
  (1, '+998900000001', 'Aziz Karimov',     'buyer',  0, '2026-08-01 10:00:00'),
  (2, '+998900000002', 'Malika Rasulova',  'buyer',  1, '2026-08-03 12:30:00'),
  (3, '+998900000003', 'Jasur Toshmatov',  'seller', 0, '2026-07-15 09:00:00'),
  (4, '+998900000004', 'Dilnoza Yusupova', 'buyer',  0, '2026-08-20 18:45:00'),
  (5, '+998900000005', 'Admin',            'admin',  0, '2026-07-01 08:00:00');

INSERT INTO products VALUES
  (1, 'Elektr choynak',   'Maishiy texnika', 250000,  40, 3),
  (2, 'Paxta ko''ylak',   'Kiyim',           120000, 100, 3),
  (3, 'Simsiz quloqchin', 'Elektronika',     450000,  25, 3),
  (4, 'Gilam (2x3 m)',    'Uy',             1800000,   5, 3),
  (5, 'Telefon g''ilofi', 'Aksessuar',        45000, 200, 3);

INSERT INTO orders VALUES
  (101, 1, 'DELIVERED', 'UZCARD',     NULL,       295000,  29500, 20000,  285500, '2026-09-01 11:00:00'),
  (102, 2, 'DELIVERED', 'VISA',       NULL,       450000,      0,     0,  450000, '2026-09-02 15:20:00'),
  (103, 4, 'PAID',      'HUMO',       NULL,       240000,      0, 20000,  260000, '2026-09-05 09:10:00'),
  (104, 1, 'SHIPPED',   'UZCARD',     'BAHOR15', 1890000, 283500,     0, 1606500, '2026-09-07 19:00:00'),
  (105, 2, 'DELIVERED', 'MASTERCARD', NULL,       340000,      0,     0,  340000, '2026-09-08 13:40:00'),
  (106, 4, 'CANCELLED', 'VISA',       NULL,       250000,      0, 20000,  270000, '2026-09-10 10:05:00'),
  (107, 1, 'CREATED',   'CASH',       NULL,       450000,      0,     0,  405000, '2026-09-12 21:15:00'),
  (108, 4, 'DELIVERED', 'UZCARD',     NULL,       700000,      0, 20000,  720000, '2026-09-14 08:30:00'),
  (109, 1, 'DELIVERED', 'HUMO',       NULL,        90000,      0, 20000,  110000, '2026-09-15 17:50:00');

INSERT INTO order_items VALUES
  (1, 101, 1, 1, 250000),
  (2, 101, 5, 1,  45000),
  (3, 102, 3, 1, 450000),
  (4, 103, 2, 2, 120000),
  (5, 104, 4, 1, 1800000),
  (6, 104, 5, 2,  45000),
  (7, 105, 2, 3, 120000),
  (8, 106, 1, 1, 250000),
  (9, 107, 3, 1, 450000),
  (10, 108, 3, 1, 450000),
  (11, 108, 1, 1, 250000),
  (12, 109, 5, 2,  45000),
  (99, 999, 5, 1,  45000);

INSERT INTO payments VALUES
  (1, 101,  285500, 'SUCCESS',  'UZCARD',     '2026-09-01 11:01:00'),
  (2, 102,  450000, 'SUCCESS',  'VISA',       '2026-09-02 15:21:00'),
  (3, 104, 1606000, 'SUCCESS',  'UZCARD',     '2026-09-07 19:02:00'),
  (4, 105,  340000, 'SUCCESS',  'MASTERCARD', '2026-09-08 13:41:00'),
  (5, 106,  270000, 'REFUNDED', 'VISA',       '2026-09-10 12:00:00'),
  (6, 108,  720000, 'SUCCESS',  'UZCARD',     '2026-09-14 08:31:00'),
  (7, 109,  110000, 'FAILED',   'HUMO',       '2026-09-15 17:51:00');
```

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

```sql
-- (a) subtotal != buyurtma qatorlari yig'indisi
SELECT o.id, o.subtotal, SUM(oi.quantity * oi.unit_price) AS items_sum
FROM orders o
JOIN order_items oi ON oi.order_id = o.id
GROUP BY o.id, o.subtotal
HAVING o.subtotal <> SUM(oi.quantity * oi.unit_price);

-- (b) total formulasi buzilgan
SELECT id, subtotal, discount, delivery_fee, total,
       subtotal - discount + delivery_fee AS expected_total
FROM orders
WHERE total <> subtotal - discount + delivery_fee;

-- (c) yetkazish qoidasi buzilgan
SELECT o.id, o.subtotal, o.delivery_fee, u.is_premium
FROM orders o
JOIN users u ON u.id = o.user_id
WHERE (o.subtotal >= 300000 AND o.delivery_fee > 0)
   OR (o.subtotal < 300000 AND o.delivery_fee = 0 AND u.is_premium = 0);

-- (d) yetim order_items
SELECT oi.id, oi.order_id
FROM order_items oi
LEFT JOIN orders o ON o.id = oi.order_id
WHERE o.id IS NULL;
```

SQLite 3.51 dagi haqiqiy natijalar:

| So'rov | Natija                                       | Ma'nosi                                          |
| ------ | -------------------------------------------- | ------------------------------------------------ |
| (a)    | `105`: subtotal 340 000, items_sum 360 000   | Buyurtma qatorlari 20 000 ga ko'p — hisob xatosi |
| (b)    | `107`: total 405 000, expected 450 000       | Chegirma yozilmagan, lekin summa kamaygan        |
| (c)    | `108`: subtotal 700 000, delivery_fee 20 000 | Bepul bo'lishi kerak edi                         |
| (d)    | `99`: order_id 999                           | Mavjud bo'lmagan buyurtmaga bog'langan qator     |

(d) qanday paydo bo'ldi? SQLite'da tashqi kalit tekshiruvi standart holatda **o'chiq** (`PRAGMA foreign_keys = ON;` bilan yoqiladi). Real loyihada bu — "DB cheklovlari mavjudmi va ishlayaptimi?" degan alohida tekshiruv.

</details>

### Topshiriq 19: SQL — to'lovlar va eng ko'p daromad (🔴) {#t19}

18-topshiriqdagi ma'lumotlar bilan:

- (a) `PAID`, `PACKING`, `SHIPPED` yoki `DELIVERED` holatida, lekin **muvaffaqiyatli to'lovi yo'q** karta buyurtmalarini toping (naqd to'lovlar bundan mustasno);
- (b) muvaffaqiyatli to'lov summasi buyurtma `total`iga teng bo'lmagan holatlarni toping;
- (c) `DELIVERED` buyurtmalar bo'yicha daromad keltirgan TOP-3 mahsulotni chiqaring.

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

```sql
-- (a) to'lovsiz "to'langan" buyurtmalar
SELECT o.id, o.status, o.payment_method
FROM orders o
WHERE o.status IN ('PAID','PACKING','SHIPPED','DELIVERED')
  AND o.payment_method <> 'CASH'
  AND NOT EXISTS (
    SELECT 1 FROM payments p
    WHERE p.order_id = o.id AND p.status = 'SUCCESS'
  );

-- (b) to'lov summasi != buyurtma summasi
SELECT o.id, o.total, p.amount, p.amount - o.total AS diff
FROM orders o
JOIN payments p ON p.order_id = o.id AND p.status = 'SUCCESS'
WHERE p.amount <> o.total;

-- (c) TOP-3 mahsulot (DELIVERED buyurtmalar bo'yicha)
SELECT p.name, SUM(oi.quantity) AS sold_qty, SUM(oi.quantity * oi.unit_price) AS revenue
FROM order_items oi
JOIN orders o   ON o.id = oi.order_id
JOIN products p ON p.id = oi.product_id
WHERE o.status = 'DELIVERED'
GROUP BY p.id, p.name
ORDER BY revenue DESC
LIMIT 3;
```

Haqiqiy natijalar:

```text
(a)  103 | PAID      | HUMO     -- to'lov yozuvi umuman yo'q
     109 | DELIVERED | HUMO     -- faqat FAILED urinish bor

(b)  104 | total 1 606 500 | amount 1 606 000 | diff -500

(c)  Simsiz quloqchin | 2 | 900 000
     Elektr choynak   | 2 | 500 000
     Paxta ko'ylak    | 3 | 360 000
```

Nega `NOT EXISTS` va `LEFT JOIN ... IS NULL` emas? 109-buyurtmada to'lov yozuvi **bor**, lekin `FAILED`. Oddiy LEFT JOIN uni o'tkazib yuboradi; `NOT EXISTS` ichidagi `status = 'SUCCESS'` sharti ikkala holatni ham ushlaydi. (a) va (b) natijalari — to'lov integratsiyasidagi jiddiy muammolar: ular "buyurtma to'landi" degan UI xabariga ishonmaslik kerakligini ko'rsatadi.

</details>

---

## 7. Mobil va Proxyman: 20–22 {#mobil}

### Topshiriq 20: Checkout uchun mobil interruption checklisti (🟢) {#t20}

DemoBozor mobil ilovasida karta bilan to'lov oqimi uchun kamida 8 ta interruption/holat testini va kutilgan natijani yozing.

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

| #   | Holat                                                                 | Kutilgan natija                                                                     |
| --- | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| 1   | SMS kodni ko'rish uchun Messages ilovasiga o'tib, qaytish             | Kiritilgan ma'lumot saqlanadi; SMS autofill ishlaydi                                |
| 2   | Ilovani fonda 3 daqiqa qoldirish (SMS kod 120 s amal qiladi)          | "Kod muddati tugadi" xabari, yangi kod so'rash; to'lov ikki marta ketmaydi          |
| 3   | To'lov so'rovi paytida kiruvchi qo'ng'iroq                            | Qo'ng'iroqdan keyin to'lov holati to'g'ri ko'rsatiladi (muvaffaqiyatli yoki xato)   |
| 4   | "To'lash" bosilgan zahoti Wi-Fi → mobil internetga o'tish             | Takroriy yechish yo'q; holat serverdan qayta so'raladi                              |
| 5   | "To'lash" paytida samolyot rejimi                                     | Tushunarli xato; savatcha va kiritilgan ma'lumot saqlanadi                          |
| 6   | To'lov muvaffaqiyatli, tasdiq ekranidan oldin ilova majburan yopiladi | Qayta ochilganda buyurtma `PAID` holatida ko'rinadi; ikkinchi buyurtma yaratilmaydi |
| 7   | Ekran qulflanishi va ochilishi                                        | Sessiya va forma holati saqlanadi                                                   |
| 8   | Quvvat tejash rejimi / past batareya ogohlantirishi                   | Oqim to'xtamaydi, crash yo'q                                                        |
| 9   | Android: "Don't keep activities" yoqilgan holda fon ↔ oldingi plan    | Activity qayta yaratilganda holat tiklanadi                                         |
| 10  | iOS ilovalar almashtirgichi (app switcher)                            | Karta ma'lumotlari ekran tasvirida yashirilishi tavsiya etiladi                     |

Har bir holatda **ikki marta pul yechilmasligi** — eng muhim tekshiruv. Batafsil: [QA 14](/ios-lessons/qa-mobile-testing).

</details>

### Topshiriq 21: Proxyman — har stsenariyga to'g'ri asbob (🟡) {#t21}

Har bir QA vazifasi uchun Proxyman'ning qaysi vositasini ishlatasiz?

1. Bo'sh savatcha ekranini backend'da ma'lumot yaratmasdan ko'rish.
2. Production build'ni yangi build qilmasdan staging API'ga yo'naltirish.
3. Server manfiy `quantity`ni rad etishini tekshirish.
4. Rasmlar sekin yuklanganda UI qanday ko'rinishini tekshirish.
5. Analitika SDK serveri ishlamay qolganda ilova crash bo'lmasligini tekshirish.
6. Har bir so'rovga `X-Debug-User: qa` header qo'shish.
7. `POST /orders` ni ikki marta yuborib, dublikat buyurtma yaratilmasligini tekshirish.
8. v1 va v2 build'lar orasida bir xil endpoint javoblarini solishtirish.

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

| #   | Vosita                                | Qanday                                                            |
| --- | ------------------------------------- | ----------------------------------------------------------------- |
| 1   | **Map Local**                         | `GET /cart` javobini lokal JSON bilan almashtirish (22-topshiriq) |
| 2   | **Map Remote**                        | prod host'ni staging host'ga yo'naltirish                         |
| 3   | **Breakpoint** (yoki Edit and Repeat) | So'rov body'sida `quantity`ni `-1` ga o'zgartirib yuborish        |
| 4   | **Network Conditions**                | Rasm CDN domeni uchun sekin tarmoq profili                        |
| 5   | **Block List**                        | Analitika domenini bloklash                                       |
| 6   | **Scripting**                         | `onRequest` funksiyasida header qo'shish                          |
| 7   | **Repeat** (yoki Compose)             | So'rovni qayta yuborib, server javobi va DB'ni tekshirish         |
| 8   | **Diff**                              | Ikki javobni yonma-yon solishtirish                               |

Eslatma: 3 va 7 — server validatsiyasi va idempotentlik testlari; faqat o'z test muhitingizda. Batafsil: [QA 15](/ios-lessons/qa-proxyman).

</details>

### Topshiriq 22: Proxyman Map Local — chegaraviy javoblar (🟡) {#t22}

DemoBozor'ning (xayoliy) `GET /api/v1/cart` endpoint'i uchun Map Local'da ishlatiladigan 4 ta JSON javob tayyorlang: bo'sh savatcha; bitta mahsulot 99 dona; juda uzun nom + rasm yo'q (`null`) + emoji; 500 xato. Har biri bilan UI'da nimani tekshirasiz?

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

**1. Bo'sh savatcha:**

```json
{
  "items": [],
  "itemsCount": 0,
  "subtotal": 0,
  "discount": 0,
  "deliveryFee": 0,
  "total": 0,
  "currency": "UZS"
}
```

**2. Maksimal son (450 000 × 99 = 44 550 000):**

```json
{
  "items": [
    {
      "id": "ci_1",
      "productId": 3,
      "name": "Simsiz quloqchin",
      "imageUrl": "https://cdn.demobozor.example/img/3.jpg",
      "unitPrice": 450000,
      "quantity": 99,
      "lineTotal": 44550000
    }
  ],
  "itemsCount": 99,
  "subtotal": 44550000,
  "discount": 0,
  "deliveryFee": 0,
  "total": 44550000,
  "currency": "UZS"
}
```

**3. Uzun nom, rasm yo'q, emoji:**

```json
{
  "items": [
    {
      "id": "ci_2",
      "productId": 4,
      "name": "Gilam (2x3 m) — qo'lda to'qilgan, an'anaviy naqshli, ipak aralash, juda uzun nomli mahsulot 🧶 Samarqand uslubida",
      "imageUrl": null,
      "unitPrice": 1800000,
      "quantity": 1,
      "lineTotal": 1800000
    }
  ],
  "itemsCount": 1,
  "subtotal": 1800000,
  "discount": 0,
  "deliveryFee": 0,
  "total": 1800000,
  "currency": "UZS"
}
```

**4. Server xatosi** (Map Local'da status kodni **500** qilib qo'ying):

```json
{
  "error": {
    "code": "INTERNAL_ERROR",
    "message": "Kutilmagan xatolik. Keyinroq urinib ko'ring.",
    "requestId": "req_7f3a9c"
  }
}
```

| Javob    | UI'da nimani tekshiramiz                                                                |
| -------- | --------------------------------------------------------------------------------------- |
| Bo'sh    | Empty state (rasm, matn, "Xarid qilish" tugmasi), "Buyurtma berish" faol emas           |
| 99 dona  | "44 550 000 so'm" formatlanishi, "+" faol emas, katta raqamlar qatorga sig'ishi         |
| Uzun nom | Matn qisqartirilishi (truncation), placeholder rasm, emoji to'g'ri chiqishi, crash yo'q |
| 500      | Tushunarli xato xabari, "Qayta urinish" tugmasi, ilova qotib qolmasligi                 |

Barcha JSON'lar validatsiyadan o'tkazilgan. `cdn.demobozor.example` — `.example` domeni ataylab xayoliy manzillar uchun ajratilgan.

</details>

---

## 8. Avtomatlashtirish: 23–27 {#avtomatlashtirish}

Barcha topshiriqlar uchun umumiy konfiguratsiya (`playwright.config.ts`, saucedemo):

```typescript
import { defineConfig, devices } from "@playwright/test";

export default defineConfig({
  testDir: "./tests",
  fullyParallel: true,
  retries: 0,
  reporter: "list",
  use: {
    baseURL: "https://www.saucedemo.com",
    testIdAttribute: "data-test",
    trace: "on-first-retry",
  },
  projects: [{ name: "chromium", use: { ...devices["Desktop Chrome"] } }],
});
```

### Topshiriq 23: Playwright — login testlari (🟢) {#t23}

Saucedemo uchun ikkita test yozing: `standard_user` tizimga kiradi (URL va "Products" sarlavhasi tekshiriladi) va `locked_out_user` aniq xato matnini ko'radi. Birinchi testni `@smoke` bilan belgilang.

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

```typescript
import { test, expect } from "@playwright/test";

test.describe("Login", () => {
  test("standard_user tizimga kiradi @smoke", async ({ page }) => {
    await page.goto("/");
    await page.getByTestId("username").fill("standard_user");
    await page.getByTestId("password").fill("secret_sauce");
    await page.getByTestId("login-button").click();

    await expect(page).toHaveURL(/\/inventory\.html$/);
    await expect(page.getByTestId("title")).toHaveText("Products");
  });

  test("locked_out_user bloklangan xabarini ko'radi", async ({ page }) => {
    await page.goto("/");
    await page.getByTestId("username").fill("locked_out_user");
    await page.getByTestId("password").fill("secret_sauce");
    await page.getByTestId("login-button").click();

    await expect(page.getByTestId("error")).toHaveText(
      "Epic sadface: Sorry, this user has been locked out.",
    );
    await expect(page).not.toHaveURL(/inventory/);
  });
});
```

Ishga tushirish: `npx playwright test tests/ui/login.spec.ts --project=chromium` — ikkala test o'tdi. `testIdAttribute: 'data-test'` tufayli `getByTestId('username')` saucedemo'ning `data-test="username"` atributini topadi.

</details>

### Topshiriq 24: Playwright — savatcha badge (🟡) {#t24}

Test yozing: login → ikkita mahsulot qo'shish → badge "2" → bittasini olib tashlash → badge "1" → savatchada aynan "Sauce Labs Bike Light" qolgani.

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

```typescript
import { test, expect } from "@playwright/test";

test("savatcha badge: 2 ta qo'shish, 1 tasini olib tashlash", async ({
  page,
}) => {
  await page.goto("/");
  await page.getByTestId("username").fill("standard_user");
  await page.getByTestId("password").fill("secret_sauce");
  await page.getByTestId("login-button").click();

  const badge = page.getByTestId("shopping-cart-badge");
  await expect(badge).toHaveCount(0); // boshida badge yo'q

  await page.getByTestId("add-to-cart-sauce-labs-backpack").click();
  await page.getByTestId("add-to-cart-sauce-labs-bike-light").click();
  await expect(badge).toHaveText("2");

  await page.getByTestId("remove-sauce-labs-backpack").click();
  await expect(badge).toHaveText("1");

  await page.getByTestId("shopping-cart-link").click();
  await expect(page.getByTestId("inventory-item")).toHaveCount(1);
  await expect(page.getByTestId("inventory-item-name")).toHaveText(
    "Sauce Labs Bike Light",
  );
});
```

E'tibor bering: `toHaveCount(0)` — elementning **yo'qligini** auto-wait bilan tekshirishning to'g'ri usuli. Test 2026-yil oktabrda o'tdi.

</details>

### Topshiriq 25: Playwright POM — checkout summasi (🔴) {#t25}

[QA 19](/ios-lessons/qa-playwright-advanced)dagi page object'lardan (`LoginPage`, `Header`, `InventoryPage`, `CartPage`, `CheckoutPage`) foydalaning. `CheckoutPage`ga Overview sahifasidagi mahsulot narxlarini yig'adigan metod qo'shing va test yozing: Backpack + Bike Light → checkout → narxlar yig'indisi "Item total" bilan mos, soliq va jami to'g'ri → buyurtma yakunlanadi. Login ma'lumotlari `SAUCE_USERNAME` / `SAUCE_PASSWORD` muhit o'zgaruvchilaridan olinsin.

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

`pages/CheckoutPage.ts` — QA 19'dagi sinf va ikkita yangi a'zo (`// QA 29: yangi` deb belgilangan):

```typescript
// pages/CheckoutPage.ts
import type { Locator, Page } from "@playwright/test";

export interface CustomerInfo {
  firstName: string;
  lastName: string;
  postalCode: string;
}

export class CheckoutPage {
  readonly page: Page;
  readonly firstNameInput: Locator;
  readonly lastNameInput: Locator;
  readonly postalCodeInput: Locator;
  readonly continueButton: Locator;
  readonly finishButton: Locator;
  readonly errorMessage: Locator;
  readonly subtotalLabel: Locator;
  readonly taxLabel: Locator;
  readonly totalLabel: Locator;
  readonly completeHeader: Locator;
  readonly overviewItemPrices: Locator; // QA 29: yangi

  constructor(page: Page) {
    this.page = page;
    this.firstNameInput = page.getByTestId("firstName");
    this.lastNameInput = page.getByTestId("lastName");
    this.postalCodeInput = page.getByTestId("postalCode");
    this.continueButton = page.getByTestId("continue");
    this.finishButton = page.getByTestId("finish");
    this.errorMessage = page.getByTestId("error");
    this.subtotalLabel = page.getByTestId("subtotal-label");
    this.taxLabel = page.getByTestId("tax-label");
    this.totalLabel = page.getByTestId("total-label");
    this.completeHeader = page.getByTestId("complete-header");
    this.overviewItemPrices = page.getByTestId("inventory-item-price"); // QA 29: yangi
  }

  async fillInformation(info: CustomerInfo) {
    await this.firstNameInput.fill(info.firstName);
    await this.lastNameInput.fill(info.lastName);
    await this.postalCodeInput.fill(info.postalCode);
  }

  async continue() {
    await this.continueButton.click();
  }

  async finish() {
    await this.finishButton.click();
  }

  // QA 29: Overview sahifasidagi narxlar yig'indisi ("$29.99" -> 29.99)
  async overviewItemsTotal(): Promise<number> {
    // allTextContents() kutmaydi — avval Overview chizilganini kutamiz
    await this.subtotalLabel.waitFor();
    const texts = await this.overviewItemPrices.allTextContents();
    return texts.reduce((sum, text) => sum + Number(text.replace("$", "")), 0);
  }
}
```

Test:

```typescript
// tests/ui/checkout-total.spec.ts
import { test, expect } from "@playwright/test";
import { LoginPage } from "../../pages/LoginPage";
import { InventoryPage } from "../../pages/InventoryPage";
import { CartPage } from "../../pages/CartPage";
import { CheckoutPage } from "../../pages/CheckoutPage";

test("checkout: summalar to'g'ri va buyurtma yakunlanadi", async ({ page }) => {
  const loginPage = new LoginPage(page);
  const inventoryPage = new InventoryPage(page);
  const cartPage = new CartPage(page);
  const checkoutPage = new CheckoutPage(page);

  await loginPage.goto();
  await loginPage.login(
    process.env.SAUCE_USERNAME ?? "standard_user",
    process.env.SAUCE_PASSWORD ?? "secret_sauce",
  );
  await expect(inventoryPage.title).toHaveText("Products");

  await inventoryPage.addToCart("Sauce Labs Backpack");
  await inventoryPage.addToCart("Sauce Labs Bike Light");
  await inventoryPage.header.openCart();
  await expect(cartPage.itemNames).toHaveText([
    "Sauce Labs Backpack",
    "Sauce Labs Bike Light",
  ]);

  await cartPage.checkout();
  await checkoutPage.fillInformation({
    firstName: "Aziz",
    lastName: "Karimov",
    postalCode: "100000",
  });
  await checkoutPage.continue();

  // Overview'dagi narxlar yig'indisi "Item total" bilan mos bo'lishi kerak
  const itemsTotal = await checkoutPage.overviewItemsTotal();
  expect(itemsTotal).toBeCloseTo(39.98, 2);
  await expect(checkoutPage.subtotalLabel).toHaveText("Item total: $39.98");
  await expect(checkoutPage.taxLabel).toHaveText("Tax: $3.20");
  await expect(checkoutPage.totalLabel).toHaveText("Total: $43.18");

  await checkoutPage.finish();
  await expect(checkoutPage.completeHeader).toHaveText(
    "Thank you for your order!",
  );
});
```

QA 19'dagi `fixtures/test.ts` va `storageState` bilan `new LoginPage(page)` qatorlari va login qadamlari yo'qoladi — testda faqat biznes oqim qoladi. Bu yerda ular test mustaqil ishga tushishi uchun ataylab ochiq yozilgan.

**Biz yechimni tekshirganda real flaky testni ko'rdik.** `overviewItemsTotal()` dastlab `waitFor()`siz yozildi. Testni 3 marta (`--repeat-each=3`) ishga tushirganimizda **2 tasi yiqildi, 1 tasi o'tdi**: `Expected: 39.98, Received: 0`. Sabab — `allTextContents()` auto-wait qilmaydi: "Continue" bosilgan zahoti, Overview sahifasi chizilmasdan oldin ishlab, bo'sh ro'yxat qaytaradi. Natija vaqtga bog'liq — klassik flaky test. `await this.subtotalLabel.waitFor()` qo'shilgach, test 3/3 barqaror o'tdi. Xulosa: action'lar va `expect(locator)` kutadi, lekin `allTextContents()`, `count()`, `isVisible()` kabi "bir martalik" metodlar **kutmaydi** ([QA 18](/ios-lessons/qa-playwright-basics), [QA 21](/ios-lessons/qa-ci-cd)).

Narxlar uchun `toBeCloseTo` ishlatildi — `29.99 + 9.99` floating-point'da aynan `39.98` bo'lmasligi mumkin.

</details>

### Topshiriq 26: Playwright API + Zod (🔴) {#t26}

restful-booker uchun API test yozing: booking yaratish → javobni Zod sxemasi bilan tekshirish → `GET` bilan solishtirish → token olib o'chirish → `404`. Test o'z ma'lumotini yaratib, o'zi tozalashi kerak.

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

```typescript
import { test, expect } from "@playwright/test";
import { z } from "zod";

test.use({ baseURL: "https://restful-booker.herokuapp.com" });

const BookingSchema = z.object({
  firstname: z.string(),
  lastname: z.string(),
  totalprice: z.number().int(),
  depositpaid: z.boolean(),
  bookingdates: z.object({
    checkin: z.iso.date(), // "YYYY-MM-DD"
    checkout: z.iso.date(),
  }),
  additionalneeds: z.string().optional(),
});

const CreateBookingResponse = z.object({
  bookingid: z.number().int().positive(),
  booking: BookingSchema,
});

test("booking yaratish: javob sxemaga mos va GET bilan bir xil", async ({
  request,
}) => {
  const payload = {
    firstname: "Aziz",
    lastname: `Karimov-${Date.now()}`, // unikal ma'lumot
    totalprice: 250,
    depositpaid: true,
    bookingdates: { checkin: "2026-11-01", checkout: "2026-11-05" },
    additionalneeds: "Breakfast",
  };

  const createRes = await request.post("/booking", {
    data: payload,
    headers: { Accept: "application/json" },
  });
  expect(createRes.status()).toBe(200); // restful-booker 201 emas, 200 qaytaradi
  const created = CreateBookingResponse.parse(await createRes.json());
  expect(created.booking).toEqual(payload);

  const getRes = await request.get(`/booking/${created.bookingid}`, {
    headers: { Accept: "application/json" },
  });
  expect(getRes.ok()).toBeTruthy();
  expect(BookingSchema.parse(await getRes.json())).toEqual(payload);

  // tozalash: token olib, o'chiramiz
  const authRes = await request.post("/auth", {
    data: { username: "admin", password: "password123" },
  });
  const { token } = z
    .object({ token: z.string().min(1) })
    .parse(await authRes.json());
  const delRes = await request.delete(`/booking/${created.bookingid}`, {
    headers: { Cookie: `token=${token}` },
  });
  expect(delRes.status()).toBe(201); // ma'lum "quirk": DELETE -> 201 Created

  const afterDelete = await request.get(`/booking/${created.bookingid}`);
  expect(afterDelete.status()).toBe(404);
});
```

Nimalarga e'tibor bering:

- `z.iso.date()` — Zod 4'dagi sana formati tekshiruvi (`YYYY-MM-DD`);
- `parse()` sxema buzilsa aniq xabar bilan yiqiladi — API kontrakti o'zgarganini darhol ko'rasiz;
- `test.use({ baseURL })` built-in `request` fixture'iga ham ta'sir qiladi;
- Status kodlar restful-booker'ning **haqiqiy** xatti-harakatiga yozilgan; "to'g'ri REST" bo'yicha kutilgan qiymatlar bilan farqlarini (16-topshiriq) bug sifatida alohida qayd qiling.

Kengaytirish: bu so'rovlarni `BookingClient` sinfiga ko'chiring va auth'ni fixture qiling ([QA 20](/ios-lessons/qa-api-automation)).

</details>

### Topshiriq 27: GitHub Actions — PR'da smoke (🟡) {#t27}

Har bir `main`ga ochilgan pull request'da faqat `@smoke` testlarni Chromium'da ishga tushiradigan va HTML report'ni artifact sifatida saqlaydigan workflow yozing.

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

`.github/workflows/smoke.yml`:

```yaml
name: PR smoke tests

on:
  pull_request:
    branches: [main]

jobs:
  smoke:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: 24
          cache: npm
      - run: npm ci
      - run: npx playwright install --with-deps chromium
      - name: Smoke testlar
        run: npx playwright test --grep @smoke --project=chromium
      - uses: actions/upload-artifact@v7
        if: ${{ !cancelled() }}
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 14
```

- Action versiyalari 2026-yil 9-oktabrdagi so'nggi relizlarga mos (checkout v7.0.1, setup-node v7.1.0, upload-artifact v7.0.2). O'qiyotganingizda yangi major versiya chiqqan bo'lishi mumkin — GitHub'dagi relizlarni tekshiring;
- `if: ${{ !cancelled() }}` — testlar yiqilganda ham report yuklanadi (aynan shunda kerak!);
- HTML report hosil bo'lishi uchun config'dagi reporter'ga `html` qo'shilgan bo'lishi kerak;
- Bu workflow'ni branch protection'da **required status check** qilsangiz — u quality gate'ga aylanadi.

Fayl YAML parser bilan tekshirilgan; `--grep @smoke` 23-topshiriqdagi `@smoke` testni topadi. Batafsil: [QA 21](/ios-lessons/qa-ci-cd).

</details>

---

## 9. Hero darajasi: 28–30 {#hero}

### Topshiriq 28: k6 — thresholds va natijani o'qish (🟡) {#t28}

1. QuickPizza demo sayti (k6 hujjatlaridagi rasmiy maqsad) uchun 5 VU'gacha ko'tariladigan k6 skript yozing. Thresholds: xatolar < 1%, p95 < 500 ms, p99 < 1000 ms, check'lar > 99%.
2. Test natijasida: p95 = 640 ms, p99 = 980 ms, xatolar = 0.4%, check'lar = 99.6%. Qaysi threshold'lar o'tdi, qaysilari yo'q? CI'da nima bo'ladi va keyingi qadam nima?

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

```javascript
import http from "k6/http";
import { check, sleep } from "k6";

export const options = {
  stages: [
    { duration: "30s", target: 5 }, // 0 -> 5 VU (ommaviy demo: kichik yuk!)
    { duration: "1m", target: 5 }, // 5 VU da barqaror
    { duration: "30s", target: 0 }, // pasaytirish
  ],
  thresholds: {
    http_req_failed: ["rate<0.01"], // xatolar < 1%
    http_req_duration: ["p(95)<500", "p(99)<1000"], // ms
    checks: ["rate>0.99"], // check'larning 99%+ o'tsin
  },
};

export default function () {
  const res = http.get("https://quickpizza.grafana.com");
  check(res, {
    "status 200": (r) => r.status === 200,
  });
  sleep(1); // haqiqiy foydalanuvchi "o'ylash vaqti"
}
```

Ishga tushirish: `k6 run smoke-load.js` (k6 o'rnatish — [QA 23](/ios-lessons/qa-performance-k6)). Skript sintaksisi tekshirilgan, threshold yozilishi k6 hujjatidagi bilan bir xil; biz uni bu darsda ishga tushirmadik.

| Threshold              | Natija | Holat                     |
| ---------------------- | ------ | ------------------------- |
| `http_req_failed` < 1% | 0.4%   | ✅                        |
| `p(95)` < 500 ms       | 640 ms | ❌                        |
| `p(99)` < 1000 ms      | 980 ms | ✅ (chegaraga juda yaqin) |
| `checks` > 99%         | 99.6%  | ✅                        |

k6 hujjatiga ko'ra, birorta threshold bajarilmasa test muvaffaqiyatsiz tugaydi va k6 **noldan farqli exit code** bilan chiqadi — CI job qizil bo'ladi. Bu — kerakli xatti-harakat.

Keyingi qadam: threshold'ni "500 → 700" qilib ko'tarib qo'yish emas! Avval sekin so'rovlarni toping (tag/group bo'yicha), server metrikalarini (CPU, DB) ko'ring, oldingi natija (baseline) bilan solishtiring va sababni aniqlab, jamoa bilan qaror qiling. Esda tuting: QuickPizza — ommaviy demo; jiddiy yuk testlarini faqat o'z muhitingizda bajaring.

</details>

### Topshiriq 29: Accessibility — axe bilan skanerlash (🔴) {#t29}

`@axe-core/playwright` yordamida Toolshop'ning ikki versiyasi (v5 va bug'li) bosh sahifalarini WCAG A/AA qoidalari bo'yicha skanerlaydigan test yozing. Faqat `critical` darajadagi buzilishlar testni yiqitsin, qolganlari hisobotga yozilsin. Natijani qanday talqin qilasiz?

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

```typescript
import { test, expect } from "@playwright/test";
import AxeBuilder from "@axe-core/playwright";

const pages = [
  { name: "Toolshop v5", url: "https://practicesoftwaretesting.com/" },
  {
    name: "Toolshop with-bugs",
    url: "https://with-bugs.practicesoftwaretesting.com/",
  },
];

for (const { name, url } of pages) {
  test(`${name}: critical a11y xatolar yo'q`, async ({ page }, testInfo) => {
    await page.goto(url);
    await page.waitForLoadState("networkidle");

    const results = await new AxeBuilder({ page })
      .withTags(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa", "wcag22aa"])
      .analyze();

    await testInfo.attach("axe-violations.json", {
      body: JSON.stringify(results.violations, null, 2),
      contentType: "application/json",
    });

    // har bir qoidani qisqa ko'rinishda chiqaramiz
    for (const v of results.violations) {
      console.log(
        `${name}: ${v.id} [${v.impact}] x${v.nodes.length} — ${v.help}`,
      );
    }

    const critical = results.violations.filter((v) => v.impact === "critical");
    expect.soft(critical.map((v) => v.id)).toEqual([]);
  });
}
```

2026-yil 9-oktabrdagi haqiqiy natija:

```text
Toolshop v5: list [serious] x3 — <ul> and <ol> must only directly contain <li>, <script> or <template> elements
  ✓  Toolshop v5: critical a11y xatolar yo'q
Toolshop with-bugs: color-contrast [serious] x3 — Elements must meet minimum color contrast ratio thresholds
Toolshop with-bugs: image-alt [critical] x11 — Images must have alternative text
Toolshop with-bugs: label [critical] x1 — Form elements must have labels
Toolshop with-bugs: list [serious] x2 — <ul> and <ol> must only directly contain <li>, <script> or <template> elements
Toolshop with-bugs: select-name [critical] x1 — Select element must have an accessible name
  ✘  Toolshop with-bugs: critical a11y xatolar yo'q
```

Talqin:

- **v5** — kritik buzilish yo'q (test o'tdi), lekin 1 ta `serious` (ro'yxat tuzilmasi) hisobotda qoldi — backlog uchun;
- **Bug'li versiya** — 3 ta `critical` qoida: 11 ta rasmda `alt` yo'q, label'siz forma maydoni, nomsiz `select` (saralash ro'yxati screen reader uchun "nomsiz"). Har biri — alohida bug report;
- Saytlar yangilanib turadi — sizning natijangiz farq qilishi mumkin;
- Avtomatik vositalar a11y muammolarining **faqat bir qismini** topadi: klaviatura bilan yurish, fokus tartibi va screen reader'da tekshirish baribir qo'lda ([QA 25](/ios-lessons/qa-a11y-l10n-visual)).

`expect.soft` nega? U testni darhol to'xtatmaydi — hisobot to'liq yig'iladi, test oxirida baribir yiqiladi.

</details>

### Topshiriq 30: AI yozgan test case'larni audit qilish (🔴) {#t30}

Jamoadoshingiz AI chatbot'dan "DemoBozor SMS orqali login uchun test case'lar yoz" deb so'rab, quyidagini oldi. Javobdagi xatolar va bo'shliqlarni toping, keyin yaxshiroq prompt yozing.

```text
1. Foydalanuvchi to'g'ri 4 raqamli kodni kiritadi — tizimga kiradi.
2. Kod 5 daqiqa davomida amal qiladi; 5 daqiqadan keyin rad etiladi.
3. Foydalanuvchi Face ID bilan tizimga kiradi.
4. Noto'g'ri kod kiritilganda "Xato" xabari chiqadi.
5. Foydalanuvchi to'g'ri kodni kiritadi — tizimga kiradi.
6. Telefon raqami +7 bilan boshlansa ham qabul qilinadi.
7. Kod kiritish maydoni ishlaydi.
```

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

| #   | Muammo                                                                                | Turi                           |
| --- | ------------------------------------------------------------------------------------- | ------------------------------ |
| 1   | Kod **6** raqam, 4 emas                                                               | Gallyutsinatsiya (talabga zid) |
| 2   | Kod **120 soniya** amal qiladi, 5 daqiqa emas                                         | Gallyutsinatsiya               |
| 3   | Face ID talablarda yo'q — o'ylab topilgan funksiya                                    | Mavjud bo'lmagan funksiya      |
| 4   | Qolgan urinishlar soni va 3 xatodan keyin **15 daqiqa blok** tekshirilmagan           | Muhim bo'shliq                 |
| 5   | 1-band bilan dublikat                                                                 | Dublikat                       |
| 6   | Qoida `+998` + 9 raqam; `+7` qabul qilinishi kerak emas — kutilgan natija **teskari** | Noto'g'ri kutilgan natija      |
| 7   | "Ishlaydi" — o'lchab bo'lmaydi                                                        | Noaniq kutilgan natija         |

Umuman yo'q narsalar: qayta yuborish (60 s), chegaraviy qiymatlar (119/120/121 s; 5/6/7 raqam), `0` bilan boshlanuvchi kod, SMS autofill, xavfsizlik (brute-force) va API darajasidagi tekshiruvlar.

**Yaxshiroq prompt** (kontekst + texnika + format + cheklov):

```text
Sen tajribali QA muhandisisan. Quyidagi talablar asosida test case'lar yoz.
Talablar yo'q narsani qo'shma; noaniqlik bo'lsa, alohida "Savollar" ro'yxatiga yoz.

Talablar (DemoBozor, mobil ilova, SMS orqali kirish):
- Telefon: +998 va 9 raqam.
- SMS kod: 6 raqam, 120 soniya amal qiladi.
- "Qayta yuborish" 60 soniyadan keyin faollashadi.
- 3 marta noto'g'ri kod -> raqam 15 daqiqaga bloklanadi.

Vazifa:
1) Equivalence Partitioning va Boundary Value Analysis'ni qo'lla
   (kod uzunligi, vaqt chegaralari, urinishlar soni).
2) Positive, negative va xavfsizlik holatlarini qamra.
3) Natijani jadval qilib ber: ID | Sarlavha | Oldingi shartlar | Qadamlar | Test ma'lumoti | Kutilgan natija | Texnika.
4) Har bir kutilgan natija o'lchanadigan bo'lsin.
```

Va eng muhimi: AI javobini **har doim** talablar bilan solishtirib tekshiring — mas'uliyat sizda qoladi. Haqiqiy foydalanuvchi ma'lumotlari, token'lar va maxfiy hujjatlarni ommaviy AI xizmatlariga joylamang. Batafsil: [QA 26](/ios-lessons/qa-ai-testing), [Prompt Engineering asoslari](/ios-lessons/prompt-basics).

</details>

---

## 10. Zero → Hero: bosib o'tgan yo'lingiz {#yol}

| Modul                 | Darslar                                                                                                                                                                                                                                                                                       | Endi siz nima qila olasiz                                                    |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| 1. Poydevor           | [QA 1](/ios-lessons/qa-intro-career), [QA 2](/ios-lessons/qa-principles-istqb), [QA 3](/ios-lessons/qa-sdlc-stlc-agile), [QA 4](/ios-lessons/qa-levels-types)                                                                                                                                 | QA tilida gapirasiz, test jarayoni va darajalarini tushunasiz                |
| 2. Manual QA yadrosi  | [QA 5](/ios-lessons/qa-test-design-1), [QA 6](/ios-lessons/qa-test-design-2), [QA 7](/ios-lessons/qa-test-documentation), [QA 8](/ios-lessons/qa-bug-reports)                                                                                                                                 | Test dizayn texnikalari bilan test yozasiz, sifatli bug report yozasiz       |
| 3. Texnik ko'nikmalar | [QA 9](/ios-lessons/qa-web-http-devtools), [QA 10](/ios-lessons/qa-api-postman), [QA 11](/ios-lessons/qa-api-advanced), [QA 12](/ios-lessons/qa-sql), [QA 13](/ios-lessons/qa-terminal-git-logs), [QA 14](/ios-lessons/qa-mobile-testing), [QA 15](/ios-lessons/qa-proxyman)                  | Web, API, SQL, terminal, mobil va Proxyman bilan muammoning ildiziga yetasiz |
| 4. Avtomatlashtirish  | [QA 16](/ios-lessons/qa-automation-intro), [QA 17](/ios-lessons/qa-typescript), [QA 18](/ios-lessons/qa-playwright-basics), [QA 19](/ios-lessons/qa-playwright-advanced), [QA 20](/ios-lessons/qa-api-automation), [QA 21](/ios-lessons/qa-ci-cd), [QA 22](/ios-lessons/qa-mobile-automation) | Playwright + TypeScript framework qurasiz, CI'ga ulaysiz                     |
| 5. Hero darajasi      | [QA 23](/ios-lessons/qa-performance-k6), [QA 24](/ios-lessons/qa-security), [QA 25](/ios-lessons/qa-a11y-l10n-visual), [QA 26](/ios-lessons/qa-ai-testing), [QA 27](/ios-lessons/qa-strategy-metrics), [QA 28](/ios-lessons/qa-career-interview), QA 29                                       | Performance, security, a11y, AI, strategiya va karyera                       |

**Keyingi qadamlar:**

1. Capstone'ni yakunlang va GitHub'ga joylang — bu sizning asosiy "diplom"ingiz;
2. CV va LinkedIn'ni [QA 28](/ios-lessons/qa-career-interview) bo'yicha yangilang, haftasiga moslashtirilgan arizalar yuboring;
3. Ixtiyoriy: ISTQB CTFL'ga tayyorlaning — terminlar sizga allaqachon tanish;
4. Bitta yo'nalishni tanlab chuqurlashing: SDET (avtomatlashtirish), performance, security, mobil yoki QA Lead;
5. Bilimingizni ulashing: maqola, ichki taqdimot yoki yangi boshlovchiga mentorlik — eng yaxshi o'rganish usuli.

> **🧠 Esda tuting:** "Hero" — hamma narsani bilgan odam emas. Bu — sifat haqidagi har qanday savolga qanday yondashishni biladigan, bilmaganini tez o'rganadigan va jamoaga ishonchli ma'lumot beradigan muhandis. Siz bu yo'lni bosib o'tdingiz.

---

## Xulosa {#xulosa}

- Capstone — strategiyadan CI'gacha bo'lgan butun QA siklini bitta real ilovada (Toolshop) bajarish; natijasi — ishga topshirishdagi asosiy dalilingiz.
- 30 ta topshiriq butun kursni qamraydi: test dizayn, hujjatlar va bug'lar, web/API/SQL, mobil va Proxyman, avtomatlashtirish va "hero" ko'nikmalari.
- Kod yechimlari real muhitda ishga tushirilib tekshirildi — hatto `allTextContents()` kutmasligi kabi real tuzoqni ham ko'rdik.
- Real saytlar "ideal" emas: restful-booker'ning 500, 200 va 201 javoblari — bug report'lar uchun haqiqiy material.
- Amaliyot saytlari ommaviy: yuklama va xavfsizlik skanerlarini faqat o'z muhitingizda ishlating.
- Endi navbat — portfolio, arizalar va tanlagan yo'nalishda chuqurlashish.
- **Boshiga qaytish:** kursni yangi ko'z bilan qayta o'qish uchun — [QA 1: QA Engineer kim — Testing, QC va QA farqi, rollar va 2026-yil kasb xaritasi](/ios-lessons/qa-intro-career)

## Manbalar {#manbalar}

1. [Practice Software Testing (Toolshop) — GitHub repo va demo akkauntlar](https://github.com/testsmith-io/practice-software-testing)
2. [Toolshop — Swagger API hujjati](https://api.practicesoftwaretesting.com/api/documentation)
3. [Sauce Demo (Swag Labs)](https://www.saucedemo.com)
4. [Restful-Booker API hujjati](https://restful-booker.herokuapp.com/apidoc/index.html)
5. [Playwright — Page Object Models](https://playwright.dev/docs/pom)
6. [Playwright — API testing](https://playwright.dev/docs/api-testing)
7. [Playwright — Accessibility testing](https://playwright.dev/docs/accessibility-testing)
8. [Playwright — Setting up CI](https://playwright.dev/docs/ci-intro)
9. [Zod — hujjat](https://zod.dev/)
10. [Postman — Write test scripts](https://learning.postman.com/docs/tests-and-scripts/write-scripts/test-scripts/)
11. [Grafana k6 — Thresholds](https://grafana.com/docs/k6/latest/using-k6/thresholds/)
12. [GitHub — actions/checkout relizlari](https://github.com/actions/checkout/releases)
13. [ISTQB — CTFL Syllabus v4.0.1 (PDF)](https://www.istqb.org/wp-content/uploads/2024/11/ISTQB_CTFL_Syllabus_v4.0.1.pdf)
14. [Microsoft PICT — pairwise vosita](https://github.com/microsoft/pict)
