İçeriğe geç

Playwright + TypeScript Kurulumuna AI’ı Katmak: Nerede İşe Yarar, Nerede Yaramaz

Playwright projelerine AI katma kararı çoğu ekipte yanlış soruyla başlıyor: “hangi aracı alalım?” Doğru soru şu: test yazma işinin hangi parçası mekanik, hangi parçası karar? Çünkü AI yalnızca birincisinde ölçülebilir kazanç veriyor; ikincisinde ürettiği şey, doğruluğu sana ait olan bir hipotez.

Aşağıda bu ayrımı Playwright + TypeScript bağlamında somutlaştırıyorum: nereye bağlanır, hangi işte kazanç verir, hangi işte verdiği cevaba güvenmek testin kendisini bozar.

AI’ın test otomasyonunda gerçekten çözdüğü problem

Bir E2E testi yazarken iki ayrı iş yaparsın. Birincisi mekanik üretim: fixture iskeleti, tip tanımları, veri fabrikası, aynı kalıbın on ikinci tekrarı, tekrar eden assertion blokları. Bu işte doğru cevap zaten bellidir, maliyet yalnızca yazma süresidir.

İkincisi karar verme: bu akış E2E’de olmayı hak ediyor mu, bu locator altı ay sonra da geçerli mi, bu fail gerçek bir regresyon mu yoksa ortam gürültüsü mü, bu test paralel koşuda komşusunu bozar mı. Burada doğru cevap ürünün riskine, veri modeline ve ortam kısıtlarına bağlı — yani modelin görmediği bilgilere.

AI mekanik üretimde dakikaları saniyeye indirir. Karar verme tarafında ise ikna edici cümleler üretir, ve ikna edicilik ile doğruluk aynı şey değildir. Yazının geri kalanı bu sınırın iki yakasını tek tek geziyor.

AI bir Playwright projesine nereden bağlanır

Üç farklı entegrasyon noktası var ve bunları tek bir “AI testi yazsın” başlığı altında toplamak, hangi katmanın ne kadar yetki aldığını görünmez kılıyor. Yetki burada tek ölçüt: model üretim koduna dokunabiliyor mu, çıktısı ne zaman doğrulanıyor, yanlışsa geri alma maliyeti ne?

Editör içi tamamlama ve Tarayıcı ajanının Taslak senaryosu Spec dosyasına; CI koşusu, Trace ve rapor, AI triyaj, İnsan kararı
Üç entegrasyon noktasının yetkisi: Editör içi tamamlama, Tarayıcı ajanı (MCP) ve CI sonrası analiz seçenekleri Üretim koduna yetki, Çıktının doğrulandığı an ve Risk kriterlerine göre karşılaştırılıyor. Editör içi tamamlama: Üretim koduna yetki Var, commit öncesi, Çıktının doğrulandığı an Derleyici ve review ve Risk Düşük. Tarayıcı ajanı (MCP): Üretim koduna yetki Var, dosya üretir, Çıktının doğrulandığı an Çoğu zaman hiç ve Risk Yüksek. CI sonrası analiz: Üretim koduna yetki Yok, Çıktının doğrulandığı an İnsan kararı ve Risk Düşük.

Editör içi tamamlama: en düşük riskli katman

Copilot benzeri tamamlama en kontrollü katman, çünkü çıktısı yazıldığı anda insan tarafından okunuyor ve commit edilmeden review’dan geçiyor. Yanlış öneri, kabul edilmeden ölüyor.

TypeScript burada ekstra bir filtre ekliyor. @playwright/test tipleri zengin: Page, Locator, expect zincirleri ve fixture imzaları tiplidir. Modelin uydurduğu API çağrıları — var olmayan bir page.clickIfVisible() gibi — derleyici seviyesinde elenir. Aynı öneri tipsiz bir JavaScript projesinde çalışma zamanına kadar hayatta kalır. Bu yüzden AI’ı iş akışına katmadan önce yapılacak en yüksek getirili şey araç seçmek değil, strict: true ile tam tipli bir Playwright kurulumuna geçmektir.

Tarayıcıyı süren ajanlar ve MCP

Playwright MCP gibi çözümlerde model canlı sayfanın accessibility ağacını görür, tıklar, gezinir ve gördüğünden senaryo üretir. Keşif için değerli: tanımadığın bir akışta hangi adımların var olduğunu çıkarmak, ilk taslağı almak, kabul kriterinden senaryo iskeleti türetmek.

Kritik nokta şu: ajan oturumu testin kendisi değildir. Model her koşuda sayfayı yeniden yorumluyorsa testin deterministik değildir; arayüz değişince “uyum sağlar” ve sana hiçbir şey söylemez. Ajan çıktısını kalıcı bir .spec.ts dosyasına dönüştürmeden bırakmak, testi her koşuda yeniden icat etmek demektir — maliyeti de token olarak her koşuda yeniden ödersin.

CI sonrası analiz katmanı

Trace, hata yığını, konsol logu ve ekran görüntüsünü model girdisi yapmak en güvenli yüksek değerli kullanım, çünkü bu katmanın üretim kodunu değiştirme yetkisi yok. Model girdiyi okur, bir özet ve bir hipotez üretir; karar insanda kalır.

Pratikte bu, playwright-report ve test-results klasöründeki çıktıları bir prompt’a özetleyip “bu failler kaç ayrı kök nedene ayrılıyor” sorusunu sormaktan ibarettir. Yanlış cevap verdiğinde kaybettiğin tek şey birkaç dakikadır.

İşe yarıyor: tekrarlı iskelet ve test verisi fabrikaları

On iki alanlı bir form için tip, fabrika ve fixture yazmak zaman alır ama karar gerektirmez. Girdi zaten bellidir: alan adları, zorunluluklar, API sözleşmesi. AI bu işi saniyeye indirir ve çıktı tipli olduğu için hatası derleme anında yakalanır.

// tests/factories/customer.ts
import { randomUUID } from 'node:crypto';

export type Customer = {
  externalId: string;
  firstName: string;
  lastName: string;
  email: string;
  phone: string;
  companyName: string;
  taxId: string;
  addressLine: string;
  city: string;
  postalCode: string;
  country: string;
  notes: string;
};

export function makeCustomer(overrides: Partial<Customer> = {}): Customer {
  const seed = randomUUID().slice(0, 8);
  return {
    externalId: `cust-${seed}`,
    firstName: `Ada-${seed}`,
    lastName: `Test-${seed}`,
    email: `ada.${seed}@example.test`,
    phone: '+905000000000',
    companyName: `Acme-${seed}`,
    taxId: `TR${seed.toUpperCase()}`,
    addressLine: 'Test Sokak No 1',
    city: 'Istanbul',
    postalCode: '34000',
    country: 'TR',
    notes: 'e2e fixture',
    ...overrides,
  };
}
// tests/fixtures/customer.ts
import { test as base, expect } from '@playwright/test';
import { makeCustomer, type Customer } from '../factories/customer';

type CustomerFixtures = {
  seededCustomer: Customer;
};

export const test = base.extend<CustomerFixtures>({
  seededCustomer: async ({ request }, use) => {
    const customer = makeCustomer();

    const created = await request.post('/api/customers', { data: customer });
    expect(created.ok(), await created.text()).toBeTruthy();

    await use(customer);

    await request.delete(`/api/customers/${customer.externalId}`);
  },
});

export { expect };
// tests/customer-detail.spec.ts
import { test, expect } from './fixtures/customer';

test('müşteri detay sayfası seed edilen kaydı gösterir', async ({
  page,
  seededCustomer,
}) => {
  await page.goto(`/customers/${seededCustomer.externalId}`);

  await expect(page.getByRole('heading', { name: seededCustomer.companyName }))
    .toBeVisible();
  await expect(page.getByTestId('customer-email'))
    .toHaveText(seededCustomer.email);
});

Dikkat: fabrikanın her çağrıda benzersiz kimlik üretmesi ve fixture’ın kendi temizliğini yapması, AI’ın kendiliğinden yazmadığı iki karardır. Bu kararların nedeni için test verisi yönetimi yazısına bakabilirsin.

İşe yarıyor: başarısız koşuyu triyaj etmek

200 testlik bir koşuda 7 fail gördüğünde ilk soru “neden patladı” değil, “bunlar kaç ayrı olay?” Çoğu zaman yedi failin beşi tek bir kök nedene — bir endpoint’in 500 dönmesine, bir deploy’un geç tamamlanmasına — bağlıdır. Bu gruplama işi örüntü eşlemedir ve model bunda iyidir.

Pratik kullanım: her fail için test adı, hata mesajının ilk satırı, başarısız olan adım ve ilgili ağ isteğinin durum kodunu tek bir metne toplayıp modele ver, “bu failleri ortak sebebe göre grupla ve her grup için bir hipotez yaz” de. Kazanç yedi trace dosyasını tek tek açmamaktır.

Sınırı net çiz: modelin verdiği kök neden iddiası hipotezdir, kanıt değildir. Kanıt, o hipotezi doğrulayan tekrarlanabilir koşudur. Modelin “muhtemelen yarış koşulu” demesi, yarış koşulu olduğunu göstermez.

Kısmen işe yarıyor: locator önerisi

Model DOM’u görünce getByRole tabanlı bir locator önerebilir ve bu iyi bir başlangıçtır. Sorun, erişilebilirlik açısından kötü yazılmış arayüzlerde ortaya çıkar: rol yoksa, etiket yoksa, metin dinamikse model kırılgan bir CSS veya XPath üretir ve bunu aynı güvenle sunar. Çıktının tonu, çıktının kalitesini yansıtmaz.

// Kötü: yapıya bağlı, ilk DOM değişiminde kırılır
await page.locator('div.table > div:nth-child(3) > button.btn-primary').click();
await page.locator('//*[@id="root"]/div[2]/form/div[4]/input').fill('42');
await page.locator('text=Kaydet').click(); // metin çevrilince ölür
// İyi: role ve test-id sözleşmesine bağlı
await page
  .getByRole('row', { name: 'Acme Ltd' })
  .getByRole('button', { name: 'Düzenle' })
  .click();

await page.getByLabel('Kredi limiti').fill('42');
await page.getByTestId('customer-save').click();

Kural şu: AI’ın önerdiği locator’ı olduğu gibi kabul etme, bir sözleşmeye dönüştür. Role tabanlı locator mümkünse onu kullan; değilse arayüze data-testid ekleyip testi o id’ye bağla. data-testid eklemek “hile” değil, test ile arayüz arasında bilinçli bir arayüz tanımlamaktır — ve bu kararı veren sensin, model değil.

İşe yaramıyor: kararsız testin kök nedeni

Flakiness tek bir koşunun içinde yaşamaz; koşular arasındaki farkta yaşar. Yarış koşulu, paylaşılan durum, saat/zaman dilimi, ağ gecikmesi, paralel worker’ların aynı kayda dokunması. Model elindeki tek trace’e bakar ve istatistiksel olarak en sık görülen cevabı verir: “bekleme ekle”.

Bu öneri semptomu bastırır, nedeni gizler. waitForTimeout(3000) ile yeşile dönen test, CI makinesi yavaşladığı gün yine kırmızıya döner — ama bu sefer koşu süresi de üç saniye uzamıştır.

Kök neden teşhisi tekrarlı koşu (--repeat-each), izolasyon testi (aynı testi tek başına ve paralelde koşturmak), kontrollü değişken ayıklama ve trace karşılaştırması gerektirir. Hiçbirini model senin yerine yapmaz; yöntem için flaky testleri teşhis etmek yazısındaki adımları izle.

İşe yaramıyor: test izolasyonunu tasarlamak

Her testin kendi verisini üretip temizlemesi bir mimari karardır, bir kod kalıbı değil. Hangi veri API ile seed edilir, hangisi UI’dan geçmek zorundadır, hangi kayıt benzersiz olmalıdır, hangi global ayar iki test paralel koştuğunda çakışır, temizlik test sonunda mı yoksa worker teardown’ında mı yapılır — bunların cevabı senin veri modelinde ve ortam kısıtlarında.

Model bu bilgilere sahip değildir. Tipik sonuç şu şekilde görünür: üretilen testler sabit bir kullanıcıya (“ad***@**st.com“) ve sabit bir kayda (“Test Şirketi”) yaslanır, tek worker’da yeşil geçer, --workers=4 ile koşunca birbirinin verisini siler. Bu hata lokalde görünmez; CI’da görünür ve flaky diye etiketlenir.

İşe yaramıyor: neyin test edileceğine karar vermek

Kapsam kararı risk değerlendirmesidir: hangi akış bozulursa para kaybedilir, hangi hata müşteriye anında görünür, hangi davranış zaten birim veya API testinde kapalı, hangi senaryo E2E’nin maliyetine değer.

Model kabul kriterlerinden senaryo türetebilir — bunu iyi yapar. Ama hangi senaryonun E2E katmanında olmayı hak ettiğini bilemez, çünkü bu bilgi test piramidinin alt katmanlarında ne olduğuna bağlıdır. Kapsam kararını modele bırakan ekiplerde tipik sonuç şudur: her kabul kriteri için bir E2E testi üretilir, set birkaç sprintte iki katına çıkar, regresyon koşusu uzar, flaky sayısı mutlak olarak artar ve ekip testlere güvenmeyi bırakır. AI’ın hacim üretme kolaylığı burada doğrudan bir bakım borcuna dönüşür.

Self-healing locator vaadi neden tehlikeli

Kendi kendini onaran locator, bir buton taşındığında ya da etiketi değiştiğinde testi yeşil tutar. Satış argümanı da budur: bakım maliyeti düşer.

Sorun şu ki o butonun kaybolması bazen bug’ın ta kendisidir. “Siparişi onayla” butonu bir regresyonla sayfanın altına düşmüş ya da başka bir metinle render edilmişse, testin işi uyum sağlamak değil, bu değişimi bildirmektir. Sessizce onaran bir mekanizma, bulması için para verdiğin hatayı senden gizler.

Makul kullanım sınırı: self-healing yalnızca “locator eşleşmedi, şu alternatifi buldum” diye uyarı üretsin ve bir düzeltme önerisi açsın. Onarımın koda girmesi insan onayından geçsin. Sessizce geçen otomatik onarıma izin verirsen, test setin zamanla ne test ettiğini kimsenin bilmediği bir yeşil ışığa dönüşür.

AI çıktısını merge etmeden önceki kontroller

AI üretimi hacmi artırır; hacim arttığında manuel review ilk kopan halkadır. O yüzden kuralları insan disiplinine değil, lint ve CI adımına bağla.

// eslint.config.js
import tseslint from 'typescript-eslint';

export default tseslint.config({
  files: ['tests/**/*.ts'],
  languageOptions: {
    parser: tseslint.parser,
  },
  rules: {
    'no-restricted-syntax': [
      'error',
      {
        selector: "CallExpression[callee.property.name='waitForTimeout']",
        message:
          'Sabit bekleme yasak. expect(...).toBeVisible() gibi web-first assertion kullan.',
      },
      {
        selector: "CallExpression[callee.property.name='waitForSelector']",
        message: 'waitForSelector yerine locator + expect kullan.',
      },
      {
        selector: "CallExpression[callee.property.name='locator']",
        message:
          'CSS/XPath locator yasak. getByRole, getByLabel veya getByTestId kullan.',
      },
      {
        selector: "CallExpression[callee.property.name=/^\\$\\$?$/]",
        message: 'page.$ ve page.$$ yasak. Locator API kullan.',
      },
    ],
  },
});
# .github/workflows/e2e.yml
name: e2e
on: pull_request

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npx playwright install --with-deps chromium

      - name: Lint kuralları
        run: npx eslint tests

      - name: Tam regresyon
        run: npx playwright test --workers=4

      - name: Değişen testleri 3 kez koştur
        run: |
          CHANGED=$(git diff --name-only origin/${{ github.base_ref }}...HEAD \
            -- 'tests/**/*.spec.ts' | tr '\n' ' ')
          if [ -n "$CHANGED" ]; then
            npx playwright test $CHANGED --repeat-each=3 --workers=4
          else
            echo "Değişen spec dosyası yok."
          fi

Dördüncü kuralı — her testin kendi verisini üretmesi — lint ile tam yakalayamazsın; onu fixture katmanını zorunlu kılarak uygula: testler @playwright/test‘ten değil, yalnızca kendi fixtures/ modülünden test import edebilsin. Bunu da no-restricted-imports ile bağlarsın.

Bundan sonra ne yapmalı

Önce ölç. Üç sayı yeterli: bir testi yazıp merge etmeye kadar geçen süre, son 50 koşuda flaky oranı, tam regresyonun duvar saati süresi. Bu üçünü yazmadan AI adaptasyonu yaparsan, sonrasında ne değiştiğini tartışamazsın — yalnızca izlenim biriktirirsin.

Sonra AI’ı iki katmanla sınırla: iskelet/fabrika üretimi ve CI sonrası triyaj. Locator, izolasyon ve kapsam kararlarını elde tut. Lint kurallarını ve üç kez koşturma adımını AI’dan önce devreye al, çünkü bunlar olmadan hacim artışı sessizce teknik borç olur. İki hafta sonra aynı üç sayıyı tekrar ölç; iyileşme yoksa aracı değil, hangi işe soktuğunu sorgula. Genel tabloyu yapay zeka destekli test otomasyonu yazısında, araç seçimini ise 2026 araç karşılaştırmasında bulabilirsin.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Bu site istenmeyenleri azaltmak için Akismet kullanır. Yorum verilerinizin nasıl işlendiğini öğrenin.