IlmHamroh
JavaScript Full-stack/23-qism. Next.js41/43-dars19 daqiqa
Mundarija (32)

Next.js ilovani test qilish: Vitest, Playwright va Server Component cheklovi

Qisqacha: Next.js ilovasi ikki qavatda sinaladi. Vitest + Testing Library — toza mantiq (Zod sxemasi, yordamchi funksiyalar) va client komponentlar; Next'ning router'i testda yo'q, shuning uchun next/navigation va next/link mock qilinadi, server-only esa bo'sh modulga almashtiriladi. Playwright — butun ilova: production build (next build + next start) ustida haqiqiy brauzerda forma, modal, i18n, 404 statuslar va axe. Async Server Component'lar va "use cache" li funksiyalar Vitest'da chizilmaydi — kanon tajribasi buni uch variantda ko'rsatdi — shuning uchun server qismlari e2e bilan tekshiriladi. portfolio da: Vitest 10 ta, Playwright 15 ta test.

Bu darsda

  • Next ilovasining qaysi qismini qaysi vosita bilan sinashni tanlay olasiz.
  • Vitest'ni Next uchun sozlaysiz: jsdom, @ alias, server-only almashtirish, next/navigation mock.
  • Client komponent testini yozasiz (natija oynasida next/navigation o'quv modeli bilan, haqiqiy loyihada vi.mock bilan).
  • Async Server Component nega Vitest'da chizilmasligini haqiqiy xato matnlari bilan tushuntirasiz.
  • Playwright'ni production build ustida ishga tushirasiz: webServer, JS'siz test, axe, flaky testni aniqlash.

Oldin bilishingiz kerak: Playwright II: auth, tarmoq, trace va CI, Vitest va RTL asoslari, Server Actions II, Server va Client kompozitsiyasi, Env o'zgaruvchilar.

1. Nega bu kerak?

22-qismda vazifalar ga 300 dan ortiq test yozdingiz — Vitest, RTL, MSW, Playwright, Storybook. U SPA edi: hamma kod brauzerda, test uni jsdom'da yoki Chrome'da ochardi.

portfolio da kod uch joyda yashaydi: brauzerda (client komponentlar), Node serverda (Server Component, Server Action, Route Handler, proxy) va build vaqtida (statik sahifalar, "use cache"). Vitest'ning jsdom'i birinchisini yaxshi taqlid qiladi. Ikkinchisi va uchinchisi uchun unda Next'ning o'zi yo'q.

Savol: nimani qayerda sinaymiz? Kanon bunga tajriba bilan javob berdi.

O'xshatish: avtomobil. Detallarni (tormoz kolodkasi, chiroq) stendda alohida sinaysiz — tez va arzon. Lekin "mashina yuradimi" degan savolga faqat yo'lda haydab javob olasiz. Vitest — stend, Playwright — sinov yo'li.

2. Kim nimani sinaydi

2.1 Taqsimot

Qism Misol (portfolio) Vosita
Toza mantiq tilYoli, oyniYoz, tezlikCheklovi Vitest
Zod sxemasi (Server Action'ning yuragi) aloqa formasi tekshir() Vitest
Client komponent TegFiltri, TemaTanlov, UlashishTugmasi Vitest + RTL
Server Component, Server Action butunligicha sahifalar, xabarYubor Playwright
proxy.ts, redirect, statuslar 404, 308 Playwright (request)
Kirish imkoniyati 8 sahifa Playwright + axe
flowchart TB
  M["toza mantiq, Zod"] --> V["Vitest<br/>10 test, ~2 s"]
  C["client komponentlar"] --> V
  S["Server Components,<br/>Server Actions, proxy"] --> P["Playwright<br/>15 test, prod build"]
  B["build: statik, use cache"] --> P

Chap tomondagi narsa qanchalik "serverga yaqin" bo'lsa, shuncha o'ngga — e2e'ga suriladi.

2.2 Server Action qanday sinaladi

Server Action — oddiy async funksiya, lekin ichida headers(), after() kabi Next API'lari bor (Request API'lar). Ularni Vitest'da chaqirsangiz — so'rov konteksti yo'q. Kanon yo'li: action'ni ikkiga bo'lish. Mantiq (tekshir() — Zod sxemasi, xatolarni tarjima kalitlariga aylantirish) — alohida, Next'siz funksiya — Vitest'da sinaladi. Ulash qismi (so'rov, IP, javob) — e2e'da. Server Actions II da sxemani alohida faylga chiqarishning bir sababi shu edi.

3. Vitest'ni Next uchun sozlash

3.1 O'rnatish

Kanon 22-qism kanonidagi aniq versiyalarni oldi (--save-exact): vitest 5.0.4, @vitejs/plugin-react 6.1.2, jsdom 30.1.2, @testing-library/react 16.3.3, dom 10.4.2, user-event 14.6.8, jest-dom 7.0.1. Ular faqat devDependencies da — brauzer JS'iga kirmaydi (bosh sahifa 149.6 KB — o'zgarmadi).

3.2 vitest.config.mts

ts
import { fileURLToPath } from "node:url";
import react from "@vitejs/plugin-react";
import { defineConfig } from "vitest/config";

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      "@": fileURLToPath(new URL("./src", import.meta.url)),
      "server-only": fileURLToPath(
        new URL("./tekshiruv/bosh-modul.ts", import.meta.url),
      ),
    },
  },
  test: {
    environment: "jsdom",
    include: ["tekshiruv/**/*.test.{ts,tsx}"],
    setupFiles: ["tekshiruv/sozlash.ts"],
  },
});

Ikki Next'ga xos qator:

Qator Nega
"@": …/src Loyiha @/komponentlar/… import qiladi; Next buni tsconfig dan o'qiydi, Vitest'ga alohida aytiladi
"server-only": …/bosh-modul.ts Asl server-only paketi React server sharti bo'lmasa xato tashlaydi — test muhitida bo'sh fayl bilan almashtiriladi

bosh-modul.ts ichida bitta qator: export {};. Env darsida server-only build'da qanday himoya qilganini ko'rgansiz; testda esa u to'siq bo'lmasligi kerak — chunki biz serverga tegishli mantiqni sinaymiz.

sozlash.ts — 22-qismdan tanish uch narsa: jest-dom matcherlarini ulash (Vitest 5 tur aylanma yo'li bilan — RTL I), har testdan keyin cleanup(), IS_REACT_ACT_ENVIRONMENT = true.

3.3 Router mock'i

TegFiltri useSearchParams va usePathname ni ishlatadi. Testda Next router'i yo'q — bu hooklar xato beradi. Kanon ularni vi.mock bilan almashtirdi (vi.mock):

tsx
const url = vi.hoisted(() => ({ yol: "/en/loyihalar", qidiruv: "" }));
vi.mock("next/navigation", () => ({
  useParams: () => ({ locale: url.yol.startsWith("/en") ? "en" : "uz" }),
  usePathname: () => url.yol,
  useSearchParams: () => new URLSearchParams(url.qidiruv),
}));
// next/link App Router'siz ishlamaydi — oddiy <a> bilan
type LinkProps = { href: string; children: ReactNode };
vi.mock("next/link", () => ({
  default: ({ href, children }: LinkProps) => (
    <a href={href}>{children}</a>
  ),
}));

vi.hoisted — url obyektini vi.mock dan oldin yaratadi (vi.mock fayl boshiga ko'tariladi). Har test url.qidiruv = "teg=HTML" deb o'z holatini beradi. Kanondagi next/link mock'i aria-current ni ham uzatadi; bu yerda qisqartirildi (kanonda 4 qatorli tur).

3.4 Jonli: client komponent testi

Natija oynasida vi.mock yo'q — lekin unga kerak ham emas: next/navigation va next/link bu yerda allaqachon o'quv modeli (sahifa boshidagi ih.boshla — model sozlamasi, Next API'si emas). Kanon TegFiltri ning qisqa nusxasi:

tsx
import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import Link from "next/link";
import { ih, usePathname, useSearchParams } from "next/navigation";

type Item = { key: string; tags: string[]; title: string };

function TagFilter({ items }: { items: Item[] }) {
  const tag = useSearchParams().get("teg");
  const path = usePathname();
  const visible = tag
    ? items.filter((i) => i.tags.includes(tag))
    : items;
  const tags = [...new Set(items.flatMap((i) => i.tags))].sort();
  return (
    <>
      {tags.map((t) => (
        <Link key={t} href={`${path}?teg=${t}`}>
          {t}
        </Link>
      ))}
      <p role="status">{visible.length} ta loyiha ko'rsatilmoqda</p>
    </>
  );
}

const ITEMS: Item[] = [
  { key: "a", tags: ["React", "TS"], title: "Vazifalar" },
  { key: "b", tags: ["HTML"], title: "Bahor" },
];

describe("TagFilter", () => {
  it("?teg=HTML — faqat mos loyiha", () => {
    ih.boshla("/loyihalar?teg=HTML");
    render(<TagFilter items={ITEMS} />);
    const status = screen.getByRole("status");
    expect(status).toHaveTextContent("1 ta loyiha");
  });

  it("havolalar joriy yo'l bilan", () => {
    ih.boshla("/loyihalar");
    render(<TagFilter items={ITEMS} />);
    const react = screen.getByRole("link", { name: "React" });
    expect(react).toHaveAttribute("href", "/loyihalar?teg=React");
  });
});

Konsolda:

text
  ✓ TagFilter > ?teg=HTML — faqat mos loyiha
  ✓ TagFilter > havolalar joriy yo'l bilan
Testlar: 2 o'tdi (jami 2)

Natija oynasidagi vitest — 22-qismdagi o'quv modeli, next/navigation — 23-qismdagi model; haqiqiy chiqish terminalda (pastda). Haqiqiy loyihada ih.boshla o'rniga yuqoridagi vi.mock va url obyekti ishlaydi — g'oya bir xil: testda URL holatini o'zingiz berasiz.

3.5 Biz ishga tushirdik: npm test

portfolio (holat 43 nusxasi), AI_AGENT va CLAUDECODE o'chirilgan (aks holda Vitest 5 boshqa hisobot formatiga o'tadi — 22-qism):

text
 RUN  v5.0.4 C:/Users/…/p

 ✓ tekshiruv/birlik/mantiq.test.ts (6 tests) 30ms
 ✓ tekshiruv/birlik/komponentlar.test.tsx (4 tests) 1262ms
   ✓ TegFiltri (2)
     ✓ filtrsiz — hammasi, «All» joriy 709ms
   ✓ TemaTanlov (1)
     ✓ Qorong'i tanlansa — data-tema va localStorage 364ms

 Test Files  2 passed (2)
      Tests  10 passed (10)
   Start at  12:42:39
   Duration  17.49s (environment 87%, setup 6%, tests 4%, import 2%, transform 1%)

10 test. Vitest faqat sekin (300 ms dan uzoq) testlarni alohida qatorda ko'rsatadi — qolganlari fayl qatorida sanalgan. Duration 17.5 s, shundan 87% — jsdom muhitini ko'tarish; testlarning o'zi 1.3 s. Birinchi (sovuq) ishga tushishda 82.6 s ketdi. Kanon issiq holatda 1.96 s o'lchagan — sizning kompyuteringizda va antivirusingizga qarab raqam juda farq qiladi.

Tekshirib ko'ring: Nega TegFiltri testida next/link ni ham mock qilish kerak bo'ldi, faqat next/navigation yetmadimi?

Javob

next/link ichida App Router konteksti (router, prefetch) ishlatiladi. Testda Next router'i yo'q, <Link> uni topa olmaydi. Bizga <Link> ning faqat bitta xususiyati kerak — to'g'ri href — shuning uchun oddiy <a> yetarli. Natija oynasida next/link allaqachon shunday model.

4. Async Server Component — Vitest chizmaydi

4.1 Kanon tajribasi

KontentTiliIzohi — portfolio dagi kichik async Server Component: /en sahifalarida "bu matn o'zbekcha" degan izoh (next-intl). Kanon uni uch yo'l bilan sinab ko'rdi (xom chiqish, qisqartirilgan):

text
 ❯ tekshiruv/birlik/async-sinov.test.tsx (3 tests | 2 failed) 1052ms
   × A) async Server Component'ni RTL bilan to'g'ridan chizish 1037ms
   ✓ B) funksiya sifatida await qilib, natijani chizish 12ms
   × C) 'use cache' li funksiya (postlar) Vitest'da 2ms

4.2 A: render(<KontentTiliIzohi />)

React ogohlantirishi:

text
<KontentTiliIzohi> is an async Client Component. Only Server Components can be async at the moment. This error is often caused by accidentally adding `'use client'` to a module that was originally written for the server.

Tarjimasi: "<KontentTiliIzohi> — async Client Component. Hozircha faqat Server Component'lar async bo'la oladi". RTL komponentni brauzer React'i bilan chizadi — u yerda har komponent "client". Async komponent esa faqat server React'ida (RSC) ishlaydi. Ikkinchi ogohlantirish — act ni await qilmaslik haqida — va test matnni topa olmay yiqildi.

4.3 B: funksiya sifatida chaqirish — o'tdi

tsx
render(await KontentTiliIzohi());

Server Component — aslida JSX qaytaradigan async funksiya. Uni o'zingiz await qilib chaqirsangiz, natija oddiy JSX — RTL uni chizadi. Kanonda bu ishladi, lekin ikki shart bilan: ichidagi Next API'lari (next/root-params, next-intl/server) mock qilindi, ichida Suspense va hook yo'q. Murakkabroq komponentda bu yo'l tez buziladi.

4.4 C: "use cache" — Next'siz ishlamaydi

text
Error: `cacheLife()` is only available with the `cacheComponents` config.

postlar() (blog postlari ro'yxati) "use cache" va cacheLife("max") bilan yozilgan (Cache Components I). Bu direktiva va funksiyalarni Next build'i va serveri bajaradi. Vitest uchun ular — Next sozlamasi yo'q joydagi oddiy chaqiruv.

4.5 Xulosa

Next hujjati ham shuni aytadi: async Server Component'larni Vitest qo'llamaydi, ular uchun e2e tavsiya qilinadi. Kanon qarori — server qismlari Playwright'da. Unit testlar uchun esa ulardagi mantiqni toza funksiyaga chiqaring (oyniYoz, tekshir) — u Next'siz sinaladi.

5. Playwright: production build ustida

5.1 playwright.config.ts

ts
import { defineConfig } from "@playwright/test";

const PORT = 3200;
const CI = process.env["CI"] !== undefined;

export default defineConfig({
  testDir: "e2e",
  fullyParallel: true,
  forbidOnly: CI,
  retries: CI ? 2 : 0,
  reporter: [["list"]],
  use: {
    baseURL: `http://localhost:${PORT}`,
    channel: "chrome",
    trace: "on-first-retry",
  },
  webServer: {
    command: `npm run start -- -p ${PORT}`,
    url: `http://localhost:${PORT}/icon.svg`,
    reuseExistingServer: !CI,
    timeout: 60_000,
  },
});

Eng muhim qator — webServer.command: next start, next dev emas. Testlar production build ustida ishlaydi: o'quvchi ko'radigan aynan o'sha sahifalar, o'sha kesh, o'sha xato yashirish. Shuning uchun avval npm run build, keyin npm run test:e2e. url — server tayyorligini tekshirish manzili (kichik statik fayl). channel: "chrome" — tizimdagi Chrome (22-qism qarori), alohida brauzer yuklanmaydi.

5.2 15 ta test nimani tekshiradi

Guruh Nima Nega e2e
Bosh sahifa <h1>, joriy menyu, JSON-LD Server Component natijasi
Filtr ?teg=React → 1 ta, «Hammasi» → 3 ta URL + client komponent
Modal ushlovchi marshrut (intercepting route), Esc, to'g'ridan kirish Parallel va intercepting — faqat Next'da
Forma (JS bilan) server xatolari, aria-invalid, yuborish Server Action
Forma (JS'siz) yuborish ishlaydi Progressive enhancement
i18n almashtirgich, lang="en", inglizcha xatolar next-intl serverda
Statuslar va SEO 4 ta 404, RSS, sitemap hreflang, 308 proxy.ts, next.config
axe 8 sahifa (uz/en) Haqiqiy brauzer DOM'i

JS'siz test — Playwright'ning bitta sozlamasi:

ts
test.describe("JavaScript'siz", () => {
  test.use({ javaScriptEnabled: false });
  test("aloqa formasi ishlaydi", async ({ page }) => {
    await page.goto("/aloqa");
    await page.getByLabel("Ismingiz").fill("Malika");
    await page.getByLabel("Email").fill("malika@example.com");
    await page.getByLabel("Xabar").fill("JS o'chiq brauzerdan xabar.");
    await page.getByRole("button", { name: "Yuborish" }).click();
    await expect(page.getByText(/Rahmat, Malika!/)).toBeVisible();
  });
});

Server Action oddiy HTML forma sifatida ham ishlaydi — bu test buni har safar isbotlaydi. SPA'da bunday test umuman mumkin emas edi.

Yana bitta detal: har test o'z x-forwarded-for sarlavhasini yuboradi. Aloqa formasida tezlik cheklovi bor (IP boshiga 10 daqiqada 3 ta) — testlar bitta "IP" dan kelsa, to'rtinchi forma testi cheklovga uriladi va testlar bir-biriga bog'lanib qoladi.

5.3 Biz ishga tushirdik: e2e va flaky test

Birinchi ishga tushirish (build'dan keyin):

text
  1 failed
    e2e\sayt.spec.ts:42:5 › loyiha rasmi modal'da (intercepting route), Esc — orqaga
  14 passed (30.6s)

Xato:

text
    Expected pattern: /\/loyihalar\/vazifalar\/rasm$/
    Received string:  "http://localhost:3200/loyihalar/vazifalar"
    Timeout: 5000ms

Rasm havolasi bosildi, lekin 5 soniya ichida manzil o'zgarmadi. Hech narsani o'zgartirmasdan yana uch marta ishga tushirdik:

text
## e2e 1
  15 passed (30.3s)
## e2e 2
  1 failed
    e2esayt.spec.ts:42:5 › loyiha rasmi modal'da (intercepting route), Esc — orqaga
  14 passed (31.0s)
## e2e 3
  15 passed (27.2s)

To'rt urinishdan ikkitasida bitta test — modal — yiqildi. Kanon esa 15/15 ni ketma-ket 3 marta olgan (8.5–8.7 s, flaky 0). Bu — flaky (beqaror) test belgisi: kod bir xil, natija har xil. Farqqa qarang: bizning urinishlar 27–31 s davom etdi, kanonniki 9 s — kompyuter boshqa ishlar bilan band edi. Ehtimoliy sabab: yuk ostida sahifa hydration'ni kech tugatadi (Hydration), bosish client navigatsiya tayyor bo'lmasdan sodir bo'ladi va modal manzili 5 soniyaga sig'maydi. Sababni aniq isbotlamadik — bu taxmin. Kanon tinch mashinada buni ko'rmagan, biz yuk ostida topdik.

Nima qilish kerak? Uch variant: (1) CI'da retries: 2 — kanon konfigida bor, Playwright flaky'ni "flaky" deb belgilaydi; (2) testda bosishdan oldin interaktivlikni kutish (masalan sahifa tayyorligini bildiradigan element); (3) sababni topish. Retry — bandaj, sabab — davo. Playwright II darsidagi trace (trace: "on-first-retry") aynan shu holat uchun: qayta urinishda Playwright har qadamni yozib oladi. Flaky testlar bilan ishlash tartibini Test strategiyasi va flaky testlar darsida ko'rgansiz.

Halol eslatma: kanon testi va kanon kodi o'zgartirilmadi — kanon o'z o'lchovida (tinch mashinada) 3/3 marta toza o'tgan. Bu topilma keyingi ish ro'yxatiga yozildi: modal testida bosishdan oldin sahifaning tayyorligini kutish.

6. MSW server tomonda (ko'rsatkich)

22-qismda MSW brauzer so'rovlarini ushladi. Next'da tashqi API'ga so'rov server dan ketadi (portfolio da GitHub API — malumot/github.ts). MSW'ning Node rejimi (setupServer) Vitest'da server fetch ni ushlaydi — DAL funksiyasini soxta javob bilan sinash mumkin. Lekin githubProfil() "use cache" bilan yozilgan — «Async Server Component» bo'limidagi C variant: Vitest'da ishlamaydi. Kanon boshqa yo'lni tanladi: GITHUB_API_URL env'i (Env) — e2e'da ishlamaydigan manzil berilsa, zaxira UI ko'rinadi. MSW Next'da kanonda ishlatilmagan.

7. Portfolio qadami: testlar va commit (holat 41)

Fayl Nima
vitest.config.mts jsdom, @ alias, server-only → bo'sh modul
tekshiruv/sozlash.ts, bosh-modul.ts jest-dom, cleanup, act muhiti; export {}
tekshiruv/birlik/mantiq.test.ts 6 test: oyniYoz, tilYoli, tezlikCheklovi (soxta soat), Zod sxemasi
tekshiruv/birlik/komponentlar.test.tsx 4 test: TegFiltri, TemaTanlov + TemaProvider, UlashishTugmasi
playwright.config.ts, e2e/sayt.spec.ts 15 test, production build ustida
package.json test, test:kuzat, test:e2e; check = lint + tip + format + test

Biz topdik (kanon): @axe-core/playwright o'z peer'i playwright-core ni npm orqali 1.64.1 bilan o'rnatdi (7 kundan yangi versiya). Natija — ikki nusxa, Page turlari mos kelmadi: tsc TS2322 va next build yiqildi (u e2e fayllarni ham tur tekshiradi). Yechim: playwright-core ni aniq 1.64.0 bilan devDependencies ga qo'shish (bitta nusxa qoladi) va eski .next/cache/.tsbuildinfo ni o'chirish.

Kanon natijasi: Vitest 10/10 (1.96 s), Playwright 15/15 (9.2 s); JS o'zgarmadi, axe 0, hydration 0. .gitignore ga test-results va playwright-report.

bash
git add -A
git commit

Commit (kanon): Test: Vitest + RTL (mantiq, client komponentlar), Playwright e2e production build ustida + axe.

8. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
This module cannot be imported from a Client Component module testda Asl server-only Vitest'da alias — bo'sh modul
useRouter() testda: invariant expected app router to be mounted Router konteksti yo'q (matn Next manba kodidan) vi.mock("next/navigation")
… is an async Client Component Async Server Component RTL bilan chizildi E2E'da sinang yoki mantiqni ajrating
cacheLife() is only available with the cacheComponents config "use cache" li funksiya Vitest'da E2E
E2E dev serverda o'tadi, prod'da yiqiladi webServer next dev ni ishlatgan next start + oldin build
Forma testi to'rtinchi safar yiqiladi Tezlik cheklovi — testlar bir IP'dan Har testga o'z x-forwarded-for
Ba'zan o'tadi, ba'zan yo'q Flaky — vaqt yoki hydration Trace bilan sababni toping; CI'da retry
TS2322 Page turlari, build yiqiladi playwright-core ikki nusxa Aniq versiya bilan devDependencies

9. Mashqlar

Mashqlar kurs/mashqlar/23/41-test/ da. Jonli yechimlarni «Ishga tushir» bilan sinang.

1-mashq (oson): qaysi vosita?

  • tilYoli("en", "/blog") natijasi —
  • Aloqa formasi JS o'chiq brauzerda yuboriladimi —
  • Async Server Component KontentTiliIzohi ning matni —
  • TemaTanlov bosilganda data-tema o'zgaradimi —
  • /loyihalar/yoq statusi 404 mi —
Yechim
  • Vitest — toza funksiya.
  • Playwright — Server Action va haqiqiy HTML forma.
  • Playwright — Vitest async Server Component'ni chizmaydi (tajriba A).
  • Vitest — client komponent, jsdom yetadi.
  • Playwright — status proxy.ts va server javobidan keladi (request.get).

2-mashq (o'rta): tilga qarab havola

«Client komponent testi» blokidagi TagFilter ga til prefiksini qo'shing: useParams() dan locale olinadi, en bo'lsa havolalar /en bilan boshlanadi. Natija oynasida ih.boshla("/en/loyihalar", { locale: "en" }) bilan sinang: React havolasi /en/loyihalar?teg=React bo'lishi kerak. usePathname() /en/loyihalar ni qaytaradi — prefiksni ikki marta qo'shmang.

Yechim
tsx
import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import Link from "next/link";
import { ih, useParams, usePathname } from "next/navigation";

function withLocale(locale: string, path: string) {
  const bare = path.replace(/^\/en(?=\/|$)/, "") || "/";
  return locale === "en" ? `/en${bare === "/" ? "" : bare}` : bare;
}

function TagLinks({ tags }: { tags: string[] }) {
  const { locale = "uz" } = useParams<{ locale?: string }>();
  const path = withLocale(locale, usePathname());
  return (
    <>
      {tags.map((t) => (
        <Link key={t} href={`${path}?teg=${t}`}>
          {t}
        </Link>
      ))}
    </>
  );
}

describe("TagLinks", () => {
  it("en — prefiks bir marta", () => {
    ih.boshla("/en/loyihalar", { locale: "en" });
    render(<TagLinks tags={["React"]} />);
    const link = screen.getByRole("link", { name: "React" });
    expect(link).toHaveAttribute("href", "/en/loyihalar?teg=React");
  });

  it("uz — prefikssiz", () => {
    ih.boshla("/loyihalar", { locale: "uz" });
    render(<TagLinks tags={["React"]} />);
    const link = screen.getByRole("link", { name: "React" });
    expect(link).toHaveAttribute("href", "/loyihalar?teg=React");
  });
});

Konsolda:

text
  ✓ TagLinks > en — prefiks bir marta
  ✓ TagLinks > uz — prefikssiz
Testlar: 2 o'tdi (jami 2)

withLocale avval mavjud /en ni olib tashlaydi, keyin kerak bo'lsa qo'shadi — shuning uchun /en/en/… bo'lmaydi. Kanonda bu ish ikkiga bo'lingan: usePathname o'rami (/en ni olib tashlaydi) va tilYoli (qo'shadi) — va tilYoli alohida Vitest testida sinalgan. (?=\/|$) — oldinga qarash (Lookahead): /en dan keyin / yoki satr oxiri, /english emas.

3-mashq (qiyin): flaky'ni aniqlash

Playwright natijalari tarixidan beqaror testlarni topadigan funksiya yozing: findFlaky(runs) — runs — har ishga tushirish uchun { testNomi: "passed" | "failed" } obyektlari massivi. Test kamida bir marta o'tgan va kamida bir marta yiqilgan bo'lsa — flaky. Har biri uchun "<nom>: 2/3 o'tdi" ko'rinishida chiqaring; flaky yo'q bo'lsa — "flaky yo'q". Bizning to'rt ishga tushirishimizdagi modal testi bilan sinang.

Yechim
js
function findFlaky(runs) {
  const stats = new Map();
  for (const run of runs) {
    for (const [name, status] of Object.entries(run)) {
      const s = stats.get(name) ?? { passed: 0, total: 0 };
      s.total += 1;
      if (status === "passed") s.passed += 1;
      stats.set(name, s);
    }
  }
  const flaky = [...stats]
    .filter(([, s]) => s.passed > 0 && s.passed < s.total)
    .map(([name, s]) => `${name}: ${s.passed}/${s.total} o'tdi`);
  return flaky.length ? flaky : ["flaky yo'q"];
}

const modal = "loyiha rasmi modal'da";
const runs = [
  { [modal]: "failed", "bosh sahifa": "passed" },
  { [modal]: "passed", "bosh sahifa": "passed" },
  { [modal]: "failed", "bosh sahifa": "passed" },
  { [modal]: "passed", "bosh sahifa": "passed" },
];
for (const line of findFlaky(runs)) console.log(line);

Konsolda:

text
loyiha rasmi modal'da: 2/4 o'tdi

"bosh sahifa" har safar o'tdi — flaky emas. Hech qachon o'tmagan test ham flaky emas — u shunchaki buzuq. Katta jamoalarda aynan shunday hisobot CI tarixidan avtomatik tuziladi va flaky testlar alohida ro'yxatga olinadi.

4-mashq: Portfolio qadami

O'z portfolio ingizga Vitest (config, sozlash.ts, server-only alias) va kamida ikki test faylini qo'shing: mantiq (tilYoli yoki o'xshash) va bitta client komponent (vi.mock("next/navigation") bilan). Keyin Playwright: webServer — next start, kamida uchta e2e test (bosh sahifa, forma JS'siz, bitta 404 status). npm run build && npm run test:e2e ni uch marta ishga tushiring — har safar natija bir xilmi? npm run check toza bo'lsa — commit.

Yechim

Namuna — «Portfolio qadami» jadvali va yuqoridagi konfiglar. Uch marta ishga tushirishning maqsadi — flaky'ni erta ushlash: bitta yiqilish ham yozib qo'yilsin (qaysi test, qaysi qadamda). Agar next build e2e fayllardagi tur xatosi bilan yiqilsa — npm ls playwright-core bilan nusxalarni tekshiring (kanon topilmasi). AI agent ichida terminal ochsangiz, testlarni AI_AGENT/CLAUDECODE siz ishga tushiring — Vitest hisoboti o'zgaradi.

10. Real ishda

  • Test piramidasi Next'da — ko'p unit (mantiq, client), kamroq lekin muhim e2e (server oqimlari). Jamoalar ko'pincha e2e'ni preview deploy ustida ham yurgizadi (Deploy I: Vercel).
  • "Server Component'ni qanday test qilasiz?" — mashhur intervyu savoli. Javob: Vitest async RSC'ni chizmaydi; mantiqni toza funksiyaga ajratib unit test, butunligicha — Playwright.
  • Flaky testlar — katta loyihalarning eng qimmat muammolaridan: ishonch yo'qoladi, "qayta ishga tushir" odatiy bo'lib qoladi. Ularni ro'yxatga olish va sababini tuzatish — muhandislik madaniyati.
  • Progressive enhancement testi (javaScriptEnabled: false) — kam jamoa qiladi, lekin u Server Action'ning asosiy afzalligini himoya qiladi.

Xulosa

  • Mantiq va client komponentlar — Vitest + RTL; server qismlari va butun oqim — Playwright production build ustida.
  • Next uchun Vitest: @ alias, server-only → bo'sh modul, next/navigation va next/link mock (vi.hoisted bilan URL holati).
  • Async Server Component RTL'da chizilmaydi, "use cache" Vitest'da ishlamaydi; funksiya sifatida await qilish faqat oddiy holatda.
  • Playwright: webServer — next start, har testga o'z IP, JS'siz forma testi, axe 8 sahifada.
  • Biz topdik: yuk ostida modal testi 4 urinishdan 2 tasida yiqildi (flaky); kanon topdi: playwright-core ikki nusxasi build'ni buzdi.

Keyingi dars: Versiyalarni yangilash va Pages Router merosi — codemod bilan Next 15 → 16, va eski pages/ loyihalarini o'qish.

Manbalar

  • Next.js hujjati — Testing: Vitest, Playwright (async Server Component cheklovi): https://nextjs.org/docs/app/guides/testing (16.4.0 bilan kelgan nusxa, 2026-10-21 da o'qildi)
  • Vitest hujjati — vi.mock, vi.hoisted: https://vitest.dev/api/vi
  • Playwright hujjati — webServer, javaScriptEnabled, retries va trace: https://playwright.dev/docs/test-webserver
  • Kanon: portfolio v2, holat 41 (konfiglar, testlar, async RSC tajribasi, playwright-core topilmasi); npm test va e2e ishga tushirishlari (flaky modal) — biz ishga tushirdik (Vitest 5.0.4, Playwright 1.64.0, Chrome 155, Node 24.21.0).
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Next.js ilovani test qilish: Vitest, Playwright va Server Component cheklovi — IlmHamroh