Mert Özen Her Satırda Daha İleri
Test ve Kalite Integration ve E2E Test — #08

Flaky E2E Testleri: İzolasyon, Paralellik, Retry, Network Mocking ve Görsel Karşılaştırma

Mert Özen 14 Kas 2026 14 dk 2 görüntülenme
Flaky E2E Testleri: İzolasyon, Paralellik, Retry, Network Mocking ve Görsel Karşılaştırma

Flaky bir E2E testi çoğu zaman uygulamadaki gerçek bir belirsizliği gösterir: Bookstore'daki arama testi, geç gelen bir HTTP cevabının sonucu ezdiği bir yarış durumunu yakalıyordu. Paylaşılan veri, saat dilimi, page.clock, worker ve sharding, retry'ın gizlediği hatalar, page.route'un meşru kullanımları, toHaveScreenshot'ta platform farkı ve axe ile accessibility kontrolünü gerçek koşularla anlatıyoruz.

Sürüm notu: Bu yazıdaki kodlar .NET 10, Angular 22.2, Playwright 1.65, @axe-core/playwright 4.14, Testcontainers for .NET 4.16 ve SQL Server 2025 ile derlenip denenmiştir.

Önce Tahmin Et

6. yazıdaki arama testi ilk koşularda iki kez kaldı ve tekrar çalıştırınca geçti. Birinin nedenini 7. yazıda bulduk: paralel çalışan başka bir testin oluşturduğu kitap. Diğeri ilk, soğuk başlangıçlı koşudaydı ve hata mesajını saklamamıştım. Şüphem, sayfa açılırken giden istekle arama isteğinin yarışmasıydı. Katalog sayfası açılırken bütün kitapları istiyor, arama düğmesine basınca da aramayı:

constructor() {
  this.search('');
}

protected search(term: string): void {
  this.books.set(null);
  this.api.search(term).subscribe((books) => this.books.set(books));
}

Yavaş başlayan API'yi taklit etmek için açılıştaki tam liste isteğini page.route ile 150 milisaniye geciktirdim; arama isteğine dokunmadım:

await page.route(
  (url) => url.pathname === '/api/books' && url.searchParams.get('search') === '',
  async (route) => {
    await new Promise((resolve) => setTimeout(resolve, delay));
    await route.continue();
  },
);

await catalog.open();
await catalog.search(book.title.toLowerCase());

await expect(catalog.results).toHaveCount(1);
await expect(catalog.results.getByRole('heading')).toHaveText(book.title);

Bu test altı kez çalışınca kaç kez kalır? Sıfır, altı, yoksa arada bir sayı mı?

Cevap: Flaky Olan Test Değil, Uygulamaydı

Altı koşunun üçü kaldı:

Error: expect(locator).toHaveCount(expected) failed

Locator:  getByRole('list', { name: 'Search results' }).getByRole('listitem')
Expected: 1
Received: 57
Timeout:  5000ms

Call log:
  - Expect "toHaveCount" getByRole('list', { name: 'Search results' }).getByRole('listitem') with timeout 5000ms
  - waiting for getByRole('list', { name: 'Search results' }).getByRole('listitem')
    2 × locator resolved to 0 elements
      - unexpected value "0"
    12 × locator resolved to 57 elements
       - unexpected value "57"

57, veritabanındaki bütün kitaplar. İki istek aynı anda yolda: açılıştaki tam liste ve arama. search her seferinde yeni bir subscription açıyor ve hangi cevap son gelirse ekranda o kalıyor. Arama cevabı önce gelirse liste bir an tek kitap gösteriyor, sonra geciken tam liste onu eziyor. Testin kalıp kalmaması, iki cevabın sırasına ve assertion'ın listeyi hangi anda örneklediğine bağlıydı. Aynı şey gerçek bir kullanıcının başına da gelir: yavaş bir bağlantıda arama yapan kullanıcı bir an sonucu görür, sonra liste kendiliğinden tam listeye döner.

Düzeltme uygulamada:

protected search(term: string): void {
  this.books.set(null);
  // Önceki istek hâlâ sürüyorsa iptal et; geç gelen cevap yeni sonucu ezmesin
  this.pending?.unsubscribe();
  this.pending = this.api.search(term).subscribe((books) => this.books.set(books));
}

HttpClient isteğinin subscription'ı kapatılınca istek iptal ediliyor ve cevabı hiç işlenmiyor. Gecikmeyi 0, 150, 300, 600 ve 1200 milisaniye yapan test varyasyonları düzeltmeden sonra 37 koşunun 37'sinde geçti. Flaky bir testi "test sorunu" diye etiketlemeden önce sorulacak ilk soru bu: test, kullanıcının da yaşayabileceği bir belirsizliği mi yakalıyor?

Martin Fowler "Eradicating Non-Determinism in Tests" (2011) yazısında belirsizliğin kaynaklarını izolasyon eksikliği, asenkron davranış, uzak servisler, zaman ve kaynak sızıntıları diye ayırıyor. E2E (uçtan uca) testte bunlara animasyon ve platform farkı da ekleniyor. Bookstore'da karşılaştıklarım:

KaynakBookstore'daki örnekDüzeltme
Paylaşılan veri"dune" araması başka testin kitabını da buldu (7. yazı)her test kendi verisini benzersiz adla oluşturur
Ağ ve asenkron sırageciken ilk yükleme arama sonucunu ezdiuygulamada eski isteği iptal et
Zaman ve saat dilimiiptal saati CI'da üç saat farklı çıktıtimezoneId ve aynı saat dilimiyle hesap
Animasyontoast kayarak geliyorassertion beklesin, screenshot animasyonu kapatsın
Platformscreenshot'lar işletim sistemine göre farklıbaseline'ı CI'daki image'da üret

Zaman: Saat Dilimi ve page.clock

Sipariş sayfası "You can cancel this order until 04:54." diyor; saat, siparişin oluşturulma zamanına 30 dakika eklenip tarayıcının saat diliminde biçimlendiriliyor. Bookstore'un kullanıcıları Türkiye'de olduğu için config'e timezoneId: 'Europe/Istanbul' eklendi; tarayıcı her makinede bu saat dilimiyle açılıyor. İlk yazdığım test beklenen değeri şöyle hesaplıyordu:

const deadline = new Date(Date.parse(order.createdAt) + 30 * 60 * 1000);
const expected = deadline.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' });
await expect(page.getByText(`You can cancel this order until ${expected}.`)).toBeVisible();

Benim makinemde geçti. CI runner'ları genelde UTC (eşgüdümlü evrensel saat, saat dilimi farkı sıfır) ile çalışır; aynı testi TZ=UTC ile çalıştırınca:

Error: expect(locator).toBeVisible() failed
Locator: getByText('You can cancel this order until 01:54.')
Expected: visible
Timeout: 5000ms
Error: element(s) not found

Sayfanın accessibility ağacında paragraph: You can cancel this order until 04:54. yazıyordu. Tarayıcı İstanbul saatini, test process'i ise kendi saat dilimini kullanmıştı. Bu, "bende çalışıyor" türünün en tipik örneği: test makineden makineye değişen bir değere, process'in saat dilimine bağlıydı. Düzeltme, beklenen değeri de tarayıcının saat diliminde hesaplamak:

// Beklenen saati tarayıcıyla aynı saat diliminde hesapla; test process'inin saat dilimine güvenme
const expected = deadline.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit', timeZone: timezoneId });

timezoneId config'le aynı sabitten geliyor. Test TZ=UTC ve TZ=America/New_York ile de geçti.

Zamanın kendisini kontrol etmek gerektiğinde Playwright'ın page.clock'u var. Sepete ekleyince çıkan toast üç saniye sonra kayboluyor; bunu gerçek üç saniye bekleyerek test etmek hem yavaş hem de sınır değerlerde kararsız. Saat sayfa açılmadan önce kuruluyor ve test zamanı kendisi ilerletiyor:

test('the cart toast disappears after three seconds', async ({ data, catalog, page }) => {
  const book = await data.book('Emma', 39.5);
  await page.clock.install();

  await catalog.open();
  await catalog.search(book.title);
  await catalog.addToCart(book.title);
  await expect(page.getByRole('status')).toBeVisible();

  await page.clock.runFor(2_900);
  await expect(page.getByRole('status')).toBeVisible();
  await page.clock.runFor(200);
  await expect(page.getByRole('status')).toBeHidden();
});

Test 543 milisaniyede bitti ve 2,9 saniyede toast'ın hâlâ göründüğünü, 3,1 saniyede kaybolduğunu kesin olarak söylüyor. install tarayıcıdaki Date, setTimeout ve benzerlerini sahteleriyle değiştiriyor; zaman yine akıyor ama runFor ile ileri sarılabiliyor. Dokümana göre install, saatle ilgili diğer çağrılardan önce yapılmalı. Bu saat yalnız tarayıcının saati; API'nin zamanı değişmiyor. Sunucu tarafındaki zamanı integration testlerinde 4. yazıdaki FakeTimeProvider ile kontrol ediyoruz.

Paralellik, Worker'lar ve Sharding

Playwright testleri ayrı worker process'lerinde paralel çalıştırıyor; varsayılan worker sayısı mantıksal CPU çekirdeklerinin yarısı. fullyParallel: true aynı dosyadaki testleri de dağıtıyor. Sunucular açıkken suite'i farklı worker sayılarıyla ölçtüm (11 test, setup dahil):

workers=1   11 passed (6.1s)
workers=2   11 passed (4.1s)
workers=4   11 passed (3.4s)
default     11 passed (3.3s)

Dört worker'dan sonrası kazandırmıyor; tek API ve tek veritabanı paylaşılıyor ve en uzun test (siparişli akış) tek başına iki saniyeye yakın. Paralellik, testlerin birbirinden bağımsız olduğu varsayımını sınıyor: 7. yazıdaki "Dune" hatası yalnız paralel koşuda çıktı. Bir test grubunun gerçekten sırayla çalışması gerekiyorsa test.describe.configure({ mode: 'serial' }) var, ama bu genelde testlerin bir durumu paylaştığının işareti.

Sharding, suite'i birden çok makineye bölüyor:

$ npx playwright test --shard=1/2
Running 6 tests using 5 workers, shard 1 of 2
  6 passed (1.9s)
$ npx playwright test --shard=2/2
Running 6 tests using 5 workers, shard 2 of 2
  6 passed (2.0s)

11 test iki shard'da 12'ye çıktı: --list ile bakınca setup project'inin iki shard'da da yer aldığı görülüyor, çünkü her shard bağımlı olduğu projeyi kendisi çalıştırıyor. Her shard'ın kendi HTML raporu olur; CI'da shard'lara blob reporter verilip sonuçlar npx playwright merge-reports ile tek rapora birleştiriliyor. 1.65'ten itibaren, hiçbir shard'da test bulunamayan bir koşu "No tests found" ile başarısız sayılıyor. Bookstore'un E2E suite'i bu boyutta sharding'e ihtiyaç duymuyor; container ve ng serve'ün başlangıcı her shard'da tekrar ödendiği için iki shard toplamda daha fazla makine süresi harcıyor.

Retry Neyi Gizler?

Config'de CI için retries: 2 var. Düzeltmeden önceki arama hatasını, ilk yüklemeyi 0 ile 300 milisaniye arasında rastgele geciktirerek on kez çalıştırdım:

$ npx playwright test race2 --repeat-each 10 --retries 2
  ✘   2 [chromium] › e2e/race2.spec.ts:3:1 › search survives a slow initial load (5.5s)
  ✓  12 [chromium] › e2e/race2.spec.ts:3:1 › search survives a slow initial load (retry #1) (398ms)
  ...
  ✘  14 [chromium] › e2e/race2.spec.ts:3:1 › search survives a slow initial load (retry #1) (5.4s)
  ✘  16 [chromium] › e2e/race2.spec.ts:3:1 › search survives a slow initial load (retry #2) (5.3s)

  1 failed
  3 flaky
  7 passed (18.7s)

Playwright, ilk denemede kalıp retry'da geçen testi "flaky" diye ayrı sayıyor. Rapor dürüst, ama pipeline'ın kararı değil: on koşunun üçünde retry hatayı tamamen örttü ve kalan tek "failed" olmasa, varsayılan ayarlarla koşu başarılı biterdi. Uygulamadaki gerçek bir hata, raporda sarı bir etiket olarak kalırdı. Bunu engellemenin yolu --fail-on-flaky-tests bayrağı ya da config'deki failOnFlakyTests: true; flaky çıkan her test koşuyu kırmızıya çeviriyor.

Retry'ın değeri teşhiste: trace: 'on-first-retry' ile kalan ilk denemenin hemen ardından gelen deneme kayıt altına alınıyor ve flaky testin trace'i elde oluyor. Benim kuralım şu: retry CI'da açık, flaky sayısı her koşuda raporlanıyor, flaky çıkan test bir issue'ya bağlanıp karantinaya alınıyor (Playwright'ta test.fixme ya da bir tag ile ayrı bir projede) ve düzeltilene kadar orada kalıyor. Pipeline'daki flaky politikası CI/CD Temelleri serisinin "Pipeline'da Test: Hızlı Geri Bildirim, Quality Gate'ler ve Flaky Test'ler" yazısında.

page.route: Ne Zaman Meşru?

Bu yazıda page.route'u üç amaçla kullandım ve üçü de meşru. Birincisi zamanlamayı yeniden üretmek: yukarıdaki geciktirme, soğuk başlangıçta ara sıra olan bir sırayı her seferinde üretilebilir yaptı. İkincisi uçtan uca kurguda üretilmesi zor hata durumları. E2E host'undaki sahte ödeme sağlayıcısı hep onay veriyor; müşterinin ödeme kesintisinde ne gördüğünü test etmek için cevabı tarayıcı tarafında değiştiriyorum:

test('a payment outage is shown to the customer', async ({ data, catalog, cart, page }) => {
  const book = await data.book('Emma', 39.5);
  // Sahte ödeme sağlayıcısı E2E'de hep onay veriyor; hata durumunu tarayıcı tarafında üretiyoruz
  await page.route('**/api/orders', (route) =>
    route.fulfill({
      status: 503,
      contentType: 'application/problem+json',
      json: { status: 503, title: 'Payment provider unavailable' },
    }),
  );

  await catalog.open();
  await catalog.search(book.title);
  await catalog.addToCart(book.title);
  await cart.open();
  await cart.placeOrder('Izmir');

  await expect(cart.error).toHaveText('Payment provider unavailable');
  await expect(page.getByRole('button', { name: 'Place order' })).toBeEnabled();
});

Bu test API'nin 503 ürettiğini değil, Angular'ın 503'ü nasıl gösterdiğini doğruluyor. API'nin ödeme kesintisinde gerçekten 503 ve bu başlığı döndürdüğü 4. yazıdaki integration testinde; iki test birlikte zinciri kapatıyor. Üçüncü kullanım, görsel testlerin verisini sabitlemek; bir sonraki bölümde.

Sınır şu: mock'lanan istek testin konusu olmamalı. Sipariş akışını test ederken /api/orders'ı mock'larsan artık API'yi, veritabanını ve aradaki sözleşmeyi test etmiyorsun; elinde tarayıcıda koşan, yavaş bir component testi kalıyor. Bütün API'yi mock'layan bir E2E suite'i, Frontend Testleri serisindeki component testlerinin daha pahalı bir kopyası olur.

Görsel Karşılaştırma ve Accessibility

toHaveScreenshot sayfanın ekran görüntüsünü bir baseline'la piksel piksel karşılaştırıyor. İlk denemem canlı veriyle oldu: test kendi kitabını oluşturup aradı ve sayfayı karşılaştırdı. İlk koşu baseline'ı yazdı, ikinci koşu kaldı:

Error: expect(page).toHaveScreenshot(expected) failed

  281 pixels (ratio 0.01 of all image pixels) are different.

Fark, kitap başlığındaki benzersiz ekti; 7. yazıda testleri birbirinden ayıran şey, görsel testi her koşuda farklı yapıyor. Görsel testin verisi sabit olmalı. Burada page.route meşru, çünkü testin konusu verinin kendisi değil, nasıl göründüğü:

// Görsel testin verisi sabit olmalı: kitap listesi API yerine bu diziden gelir
const books = [
  { id: 1, isbn: '978-0441172719', title: 'Dune', price: 64.9, stock: 7 },
  { id: 2, isbn: '978-0141439587', title: 'Emma', price: 39.5, stock: 3 },
  { id: 4, isbn: '978-0141439549', title: 'Middlemarch', price: 48.75, stock: 5 },
  { id: 3, isbn: '978-0547928227', title: 'The Hobbit', price: 52, stock: 0 },
];

test.beforeEach(async ({ page }) => {
  await page.route((url) => url.pathname === '/api/books', (route) => route.fulfill({ json: books }));
  await page.goto('/');
  await expect(page.getByRole('list', { name: 'Search results' }).getByRole('listitem')).toHaveCount(4);
});

test('catalog page', async ({ page }) => {
  await expect(page).toHaveScreenshot('catalog.png');
});

test('toast after adding to cart', async ({ page }) => {
  await page.getByRole('listitem').filter({ hasText: 'Emma' }).getByRole('button', { name: 'Add to cart' }).click();
  await expect(page).toHaveScreenshot('catalog-toast.png');
});

Toast'ın kayarak gelmesi sorun çıkarmadı: toHaveScreenshot varsayılan olarak animasyonları kapatıyor (sonlu animasyonları sonuna sarıyor, sonsuz olanları başlangıç durumuna alıyor) ve art arda iki aynı görüntü alana kadar bekliyor. Toast testinin log'unda ara görüntüler bu bekleyişi gösteriyordu. threshold varsayılanı 0,2; iki pikselin "farklı" sayılması için renk farkının ne kadar büyük olması gerektiğini belirliyor. maxDiffPixels ve maxDiffPixelRatio ise kaç farklı piksele izin verildiğini; ikisi de varsayılan olarak tanımsız, yani tek piksel bile testi kırıyor.

Snapshot dosyasının adı tarayıcıyı ve platformu taşıyor: Windows'ta catalog-chromium-win32.png, Linux'ta catalog-chromium-linux.png. Bir geliştirici makinesinde üretilen baseline'ı Playwright'ın Linux Docker image'ına (mcr.microsoft.com/playwright:v1.65.0-noble) kopyalayıp karşılaştırdım:

  ✘  1 [chromium] › e2e/visual.spec.ts:17:1 › catalog page (464ms)
  ✘  2 [chromium] › e2e/visual.spec.ts:21:1 › toast after adding to cart (714ms)
    Error: expect(page).toHaveScreenshot(expected) failed
      26966 pixels (ratio 0.03 of all image pixels) are different.

Aynı sayfa, aynı veri, aynı Chromium sürümü: farkın tamamı font render'ından geliyor. Bunu maxDiffPixelRatio: 0.05 ile susturmak cazip, ama aşağıdaki accessibility düzeltmesinde düğme rengini değiştirdiğimde görsel test 8410 piksel (%1) fark raporladı. Platform farkını tolere edecek bir eşik, gerçek bir tasarım değişikliğini de görmez. Çözüm baseline'ları CI'ın kullandığı ortamda üretmek ve görsel testleri yalnız orada çalıştırmak. Bookstore'da görsel testler ayrı bir config'le, build edilmiş uygulamayı küçük bir statik sunucuyla sunarak koşuyor; ana config onları testIgnore ile dışarıda bırakıyor:

// Görsel testler yalnız Playwright'ın Linux image'ında koşar; baseline'lar o ortamda üretilir
export default defineConfig({
  testDir: './e2e',
  testMatch: /visual\.spec\.ts/,
  reporter: [['list'], ['html', { open: 'never' }]],
  use: { baseURL: 'http://localhost:4300', timezoneId },
  projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
  webServer: {
    command: 'node e2e/support/static-server.mjs dist/bookstore-web/browser 4300',
    url: 'http://localhost:4300',
  },
});
$ npm run build -- --configuration development
$ docker run --rm -v ${PWD}:/work -w /work mcr.microsoft.com/playwright:v1.65.0-noble npx playwright test -c playwright.visual.config.ts --update-snapshots
A snapshot doesn't exist at /work/e2e/visual.spec.ts-snapshots/catalog-chromium-linux.png, writing actual.
  ✓  1 [chromium] › e2e/visual.spec.ts:17:1 › catalog page (255ms)
A snapshot doesn't exist at /work/e2e/visual.spec.ts-snapshots/catalog-toast-chromium-linux.png, writing actual.
  ✓  2 [chromium] › e2e/visual.spec.ts:21:1 › toast after adding to cart (584ms)
  2 passed (1.2s)

${PWD} PowerShell'de de çalışıyor. Bayraksız tekrar çalıştırınca altı koşunun altısı geçti. Baseline'lar commit'leniyor; değişen bir baseline pull request'te bir resim diff'i olarak görünüyor ve review eden kişi değişikliğin bilerek yapıldığını onaylıyor.

axe ile hızlı bir accessibility kontrolü

@axe-core/playwright, açık kaynak axe motorunu sayfanın o anki DOM'u (tarayıcının sayfayı tuttuğu element ağacı) üzerinde çalıştırıyor:

test('the catalog page has no detectable WCAG A/AA violations', async ({ page }) => {
  await page.goto('/');
  await expect(page.getByRole('list', { name: 'Search results' })).toBeVisible();

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

  expect(results.violations.map((v) => `${v.id}: ${v.nodes.length} node(s)`)).toEqual([]);
});
- Array []
+ Array [
+   "color-contrast: 173 node(s)",
+ ]

WCAG (Web Content Accessibility Guidelines; web içeriğinin erişilebilirlik standardı) normal boyutlu metin için 4.5:1 kontrast istiyor; mavi zemin üzerindeki beyaz düğme yazısı 2.77:1'di. 173 element, test verileriyle dolan katalogdaki "Add to cart" düğmeleri ve "Search" düğmesi. Düğme rengini koyulaştırınca axe testi geçti ve iki görsel test de bu değişikliği yakaladı; baseline'ları container'da bilerek güncelledim. Otomatik kontrol WCAG ihlallerinin bir kısmını bulabiliyor: klavye akışı, odak sırası ve ekran okuyucu deneyimi elle test gerektiriyor. Ayrıntısı Accessibility (a11y) serisinde.

Üretimde Dikkat

Flaky oranını ölç ve görünür yap. Playwright'ın raporu flaky testleri ayrı sayıyor; bu sayıyı her koşuda bir yere yazmak (CI özeti, bir dashboard) eğilimi gösteriyor. Yeni bir testin flaky olup olmadığını merge'den önce görmek için --repeat-each 10 ile değişen testleri birkaç kez çalıştırmak ucuz bir kontrol; --only-changed bayrağı yalnız değişen test dosyalarını seçiyor.

Zaman aşımlarını flaky'liğe çözüm diye artırma. Yukarıdaki yarış durumu 5 saniyelik expect zaman aşımıyla da, 30 saniyeyle de aynı oranda kalırdı; liste yanlış duruma oturduktan sonra beklemek bir şey değiştirmiyor. Zaman aşımı yalnız gerçekten yavaş ama doğru biten bir işlem için artırılmalı.

Görsel testlerin sayısını az tut. Her screenshot bir bakım yükü: font, tarayıcı sürümü ya da küçük bir CSS değişikliği baseline güncellemesi demek. Bookstore'da yalnız iki ekran görsel testte; tasarımı sık değişen ekranlar için role ve metin assertion'ları yetiyor.

Sık Düşülen Tuzaklar

Retry'ı kalıcı çözüm yapmak. Retry raporu yeşile çeviriyor ama hatayı düzeltmiyor; yukarıdaki örnekte retry'ın örttüğü şey testteki bir zayıflık değil, uygulamadaki gerçek bir yarış durumuydu. failOnFlakyTests kapalıysa flaky testler zamanla birikiyor ve ekip raporu okumayı bırakıyor.

Her şeyi mock'layıp E2E'yi component testine çevirmek. Hızlı ve stabil bir suite elde ediliyor, ama API sözleşmesindeki bir kırılma, eksik bir migration ya da yanlış bir authorization ayarı artık hiçbir testte görünmüyor. Mock yalnız testin konusu olmayan ve gerçek kurguda üretilemeyen durumlar için.

Flaky testi silmek ya da skip ile unutmak. Karantina bir son tarih ve sahip ister; yoksa kapsam sessizce küçülür. Silmeden önce trace'e bak: bu yazıdaki iki flaky testin ikisi de uygulama ya da test verisi hakkında bir şey söylüyordu.

Görsel test eşiğini platform farkına göre ayarlamak. %3'lük bir platform farkını tolere eden eşik, %1'lik bir renk değişikliğini kaçırır. Eşiği değil, ortamı sabitle.

Küçük Bir Deneme

★☆☆ catalog-page.ts'teki unsubscribe satırını geri al ve geciktirme testini 0 ile 1200 milisaniye arasındaki değerlerle --repeat-each 10 ile çalıştır. Hangi gecikmede en çok kalıyor? Neden çok kısa ve çok uzun gecikmeler daha az kalıyor?

★★☆ Arama yarışı için deterministik bir regresyon testi yaz: geciken ilk yükleme isteğinin iptal edildiğini page.on('requestfailed') ile doğrula. waitForTimeout kullanmadan.

★★★ CI için bir GitHub Actions job'ı tasarla: iki shard, blob reporter, merge-reports ile tek HTML raporu ve flaky test sayısını job özetine yazan bir adım. Görsel testleri aynı image'da ayrı bir job olarak çalıştır.

Seriyle birlikte WebApplicationFactory'yi, Testcontainers'ı ve Playwright'ı bugünkü sürümleriyle kullandık. Ama ekipteki testlerin çoğu bu sürümlerden önce yazıldı. Son yazıda eski integration ve E2E testlerini okumayı anlatıyoruz: .NET 6'dan 11'e WebApplicationFactory, Testcontainers 3'ten 4'e, Protractor'dan Playwright'a ve Playwright'ın son sürümlerindeki kırıcı değişiklikler.