Playwright ile API Testi ve Playwright Test Agents: APIRequestContext, Hibrit Senaryolar ve Planner, Generator, Healer'ı Güvenle Kullanmak
storageState'i bir API context'ine vermek token tabanlı bir uygulamada oturumu taşımaz: Bookstore'da Ada'nın kendi siparişi 401 döndü. APIRequestContext ile tarayıcısız API testlerini, toBeOK'u, UI ile API'yi birleştiren hibrit senaryoyu, API testinin integration testlerine göre yerini ve Playwright Test Agents'ın init-agents kurulumunu, seed testi ve planner, generator ile healer'ın ürettiklerini review etmeyi anlatıyoruz.
Sürüm notu: Bu yazıdaki kodlar .NET 10, Angular 22.2 ve Playwright 1.65 (@playwright/test) ile derlenip denenmiştir.
Önce Tahmin Et
Sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) E2E (uçtan uca) suite'inde Ada'nın login'i bir kez yapılıyor ve playwright/.auth/customer.json dosyasına storageState olarak kaydediliyor (7. yazı). Tarayıcı testleri bu dosyayla giriş yapmış başlıyor. Bir testte Ada'nın siparişini doğrudan API'den okumak istedin ve aynı dosyayı bir API context'ine verdin:
const api = await playwrightRequest.newContext({ baseURL: apiURL, storageState: customerStorageState });
const response = await api.get(`/api/orders/${placedOrder.id}`);
console.log('status with storageState only:', response.status());
Sipariş Ada'nın, storageState de Ada'nın. Konsola hangi durum kodu yazılır?
Cevap: storageState Cookie Taşır, Token Değil
status with storageState only: 401
storageState dosyasında iki şey var: cookie'ler ve origin başına localStorage. Bir API context'i bunlardan yalnız cookie'leri isteklere ekliyor. Bookstore'un Angular istemcisi token'ı localStorage'da tutup bir interceptor'la Authorization header'ına koyuyor; API context'inde ne interceptor var ne de localStorage'ı okuyan biri. Uygulama cookie tabanlı bir oturum kullansaydı aynı satır çalışırdı. Bookstore için doğru yol, 12. yazıda support/api.ts'e taşıdığımız helper: token'ı E2E host'undan alıp extraHTTPHeaders ile her isteğe ekleyen bir context.
Bu ayrımın bir benzeri page.request'te de var. page.request ve context.request tarayıcı context'iyle aynı cookie deposunu paylaşıyor; yanıttaki Set-Cookie tarayıcıya da yazılıyor. playwright.request.newContext() ise bağımsız. Bu yazı, serinin son yazısı olarak, APIRequestContext'i veri hazırlamanın ötesinde bir test aracı olarak kullanıyor ve 2025'te gelen Playwright Test Agents'ı ele alıyor.
request Fixture'ı ve API Testleri
API testleri tarayıcı açmamalı. 12. yazıdaki pageErrors bir auto fixture olduğu ve page'e bağlı olduğu için, e2e/fixtures.ts'i kullanan her test bir tarayıcı başlatıyor. API testleri bu yüzden ayrı bir klasörde, ayrı bir project'te ve kendi fixture dosyasıyla duruyor:
// API testleri tarayıcısız; baseURL doğrudan API
{ name: 'api', testDir: './api-tests', use: { baseURL: apiURL } },
// API testleri tarayıcı açmaz: page'e bağlı fixture'lar (pageErrors gibi) burada yok
export const test = base.extend<ApiFixtures, { adminApi: APIRequestContext }>({
adminApi: [
async ({ playwright }, use) => {
const api = await signedInApi(playwright, { customerId: 0, roles: ['admin'] });
await use(api);
await api.dispose();
},
{ scope: 'worker' },
],
data: async ({ adminApi }, use, testInfo) => {
await use(new BookstoreData(adminApi, `api${testInfo.workerIndex}t${Date.now().toString(36)}-`));
},
buyerApi: async ({ playwright }, use) => {
const api = await signedInApi(playwright, { customerId: 2, roles: ['institution-buyer'] });
await use(api);
await api.dispose();
},
customerApi: async ({ playwright }, use) => {
const api = await signedInApi(playwright, { customerId: 1 });
await use(api);
await api.dispose();
},
});
Testler 10. yazıda eklenen kurumsal sipariş endpoint'lerini hedefliyor. Nevin Hanım'ın siparişi ve faturası:
test('a buyer places a bulk order and gets its invoice', async ({ data, buyerApi }) => {
const emma = await data.book('Emma', 39.5, 50);
const response = await buyerApi.post('/api/institutional/orders', {
data: { lines: [{ isbn: emma.isbn, quantity: 20 }] },
});
await expect(response).toBeOK();
expect(response.status()).toBe(201);
const order = await response.json();
expect(order).toMatchObject({ status: 'Placed', total: 711 });
expect(response.headers()['location']).toBe(`/api/orders/${order.id}`);
const invoice = await buyerApi.get(`/api/orders/${order.id}/invoice`);
await expect(invoice).toBeOK();
expect(invoice.headers()['content-type']).toBe('application/pdf');
expect(invoice.headers()['content-disposition']).toContain(`filename=invoice-${order.reference}.pdf`);
expect((await invoice.body()).subarray(0, 5).toString()).toBe('%PDF-');
});
test('only institution buyers may place bulk orders', async ({ data, customerApi, request }) => {
const emma = await data.book('Emma', 39.5, 50);
const body = { data: { lines: [{ isbn: emma.isbn, quantity: 20 }] } };
// request: config'deki baseURL'i kullanan, token'sız yerleşik fixture
expect((await request.post('/api/institutional/orders', body)).status()).toBe(401);
expect((await customerApi.post('/api/institutional/orders', body)).status()).toBe(403);
});
Dosyada iki test daha var: 19 adetlik siparişin 400 ve ProblemDetails gövdesi döndürmesi, Ada'nın Nevin Hanım'ın faturasını isteyince 404 alması. Yalnız API project'ini çalıştırınca:
$ npx playwright test --project=api
Running 4 tests using 4 workers
✓ 2 [api] › api-tests/institutional-orders.spec.ts:22:1 › fewer than 20 copies is a validation problem (98ms)
✓ 1 [api] › api-tests/institutional-orders.spec.ts:3:1 › a buyer places a bulk order and gets its invoice (300ms)
✓ 3 [api] › api-tests/institutional-orders.spec.ts:34:1 › only institution buyers may place bulk orders (399ms)
✓ 4 [api] › api-tests/institutional-orders.spec.ts:43:1 › an invoice is visible only to the buyer who ordered (565ms)
4 passed (4.7s)
Testler milisaniyeler sürdü ama koşu 4,7 saniye: webServer config'in tamamına ait olduğu için API project'i tek başına çalışırken de Angular dev server'ı başlatıldı. toBeOK(), durum kodunun 200–299 aralığında olmasını istiyor ve kaldığında isteği ve yanıtı birlikte basıyor. Ada'nın token'ıyla toplu sipariş denemesi:
Error: expect(response).toBeOK() failed
Call log:
→ POST http://127.0.0.1:52480/api/institutional/orders
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/158.0.8127.18 Safari/537.36
accept: */*
accept-encoding: gzip,deflate,br
Authorization: Bearer <token>
content-type: application/json
content-length: 51
← 403 Forbidden
content-type: application/problem+json
date: Sun, 15 Nov 2026 08:17:56 GMT
server: Kestrel
transfer-encoding: chunked
Response text:
{"type":"https://tools.ietf.org/html/rfc9110#section-15.5.4","title":"Forbidden","status":403,"traceId":"00-0ec87b4522934276a3e12db5fc24335b-4e252b240b1d4ee5-00"}
Token'ı çıktıda kısalttım: toBeOK header'ları olduğu gibi yazdırıyor, CI loglarına gerçek bir token düşebilir. Port 52480, 12. yazıdaki ortam değişkenlerinden geliyor.
Hibrit Senaryo: UI'da Sipariş, API'de Doğrulama
E2E testinin değerli olduğu yer, kullanıcının gördüğü akışla sistemin kaydettiği durumun aynı olduğunu göstermek. Ekranın göstermediği alanları API'den okumak bunun en ucuz yolu:
// Ekranın göstermediği alanlar API'den doğrulanır
const response = await customerApi.get(`/api/orders/${id}`);
await expect(response).toBeOK();
expect(await response.json()).toMatchObject({ status: 'Placed', total: 39.5, trackingNumber: 'TRK-E2E-1' });
// İptalin UI'ı yok; API'den yapılır, sonucu sayfada görülür
await expect(await customerApi.post(`/api/orders/${id}/cancel`)).toBeOK();
await page.reload();
await expect(page.getByRole('heading', { level: 1 })).toHaveText('Order cancelled');
Testin başı 7. yazıdaki akış: katalogda arama, sepet, "Place order" ve toHaveURL(/\/orders\/\d+$/); sipariş numarası adresten okunuyor. Test 1,8 saniyede geçti. Ters yön de işe yarıyor: henüz UI'ı olmayan bir işlemi (iptal) API'den yapıp sonucunu ekranda doğrulamak.
API Testi Nerede Durmalı?
Playwright'la API testi yazmak kolay olduğu için E2E suite'i bir süre sonra integration testlerinin yerine geçmeye başlayabiliyor. Bu seride iki katman ayrı işler görüyor:
| Soru | Integration testi (2–4. yazılar) | Playwright API testi |
|---|---|---|
| API nerede çalışıyor? | Test process'i içinde, WebApplicationFactory | Gerçek bir port'ta, ayrı process |
| Zaman, dış servisler | FakeTimeProvider, WireMock, service değiştirmek | Yalnız host'un sunduğu kadar |
| Veritabanını görmek | DbContext ile doğrudan | Yalnız API üzerinden |
| En iyi olduğu yer | İş kuralları, hata senaryoları, authorization matrisi | UI testinin yanında hibrit kontrol, sözleşmenin uçtan uca doğrulanması |
Kurumsal sipariş için 19 adet, stok yetersizliği ve rol kombinasyonlarının hepsi WebApplicationFactory testinde daha hızlı ve daha az bağımlılıkla yazılır. Playwright API testinde yalnız tarayıcı testinin ihtiyaç duyduğu sözleşmeyi (fatura header'ları, durum alanları) tutuyorum. Elle keşif ve paylaşılan istek koleksiyonları ise ayrı bir ihtiyaç; curl, .http dosyaları, Postman ve Bruno arasındaki seçimi API Araçları serisi anlattı.
Playwright Test Agents: Kurulum ve Seed Test
Playwright 1.56'da (Ekim 2025) üç agent tanımı geldi: planner, generator ve healer. Bunlar bir AI kodlama aracına verilen talimat dosyaları ve araç listeleri; asıl iş, Playwright'ın kendi MCP (Model Context Protocol; bir AI aracının dış araçları çağırma protokolü) sunucusu üzerinden yapılıyor. Bookstore'un web klasöründe kurulum:
$ npx playwright init-agents --loop=claude --project chromium
🎭 Using project "chromium" as a primary project
📝 specs/README.md - directory for test plans
🌱 e2e/seed.spec.ts - default environment seed file
🤖 .claude/agents/playwright-test-generator.md - agent definition
🤖 .claude/agents/playwright-test-healer.md - agent definition
🤖 .claude/agents/playwright-test-planner.md - agent definition
🔧 .mcp.json - mcp configuration
✅ Done.
1.65'in --help çıktısı --loop için claude, codex, copilot, opencode, vscode ve vscode-legacy seçeneklerini listeliyor; doküman sayfası bunlardan dördünü örnek veriyor. Oluşan .mcp.json tek bir sunucu tanımlıyor: npx playwright run-test-mcp-server. Agent tanımları Playwright sürümüne bağlı ve doküman her yükseltmede yeniden üretilmelerini istiyor; Bookstore'un snapshot'ına bu yüzden yalnız seed test ve specs/ klasörü girdi.
Seed test, agent'ların başlangıç noktası: planner onu çalıştırıp hazır bir sayfa alıyor, generator ürettiği testlerde onu örnek alıyor. init-agents'ın ürettiği boş seed yerine Bookstore'un fixture'larını kullanan bir seed yazdım; böylece agent'lar Ada'nın oturumuyla, data ve catalog fixture'larıyla başlıyor:
import { expect, test } from './fixtures';
// Playwright Test Agents'ın başlangıç noktası: Ada olarak giriş yapılmış, katalog açık
test.describe('Bookstore', () => {
test('seed', async ({ catalog, page }) => {
await catalog.open();
await expect(page.getByRole('heading', { name: 'Catalog' })).toBeVisible();
});
});
Planner, Generator ve Healer
Bu bölümde resmî belgelerden yararlanılmıştır.

Planner bir istek (örneğin "kupon uygulanmış bir siparişin checkout akışı") ve seed testle uygulamayı tarayıcıda geziyor ve specs/ altına adım adım senaryolar içeren bir Markdown planı kaydediyor. İsteğe bir ürün gereksinimleri dokümanı da eklenebiliyor. Bookstore için planner'a 7. yazıdaki kupon kuralını ve %10 indirimi vermek, planın gerçekten iş kuralını test etmesini sağlıyor.
Generator planın bir maddesini alıyor, adımları tarayıcıda canlı yürütüp locator ve assertion'ları doğruluyor ve tek testlik bir dosya yazıyor. Ürettiği dosyanın başında hangi plana ve hangi seed'e dayandığını yazan yorumlar oluyor. init-agents'ın yazdığı tanımda generator'ın kullanabildiği araçlar arasında browser_verify_element_visible, browser_verify_text_visible ve browser_verify_value var; ürettiği assertion'ların görünürlük ve metin kontrolüne yatkın olması bu yüzden şaşırtıcı değil.
Healer kalan bir testi yeniden çalıştırıyor, hata anında sayfanın snapshot'ına bakıyor, bir yama öneriyor ve test geçene ya da kendi sınırlarına gelene kadar tekrar deniyor. Dokümana göre işlevin gerçekten bozuk olduğuna karar verirse testi atlanmış olarak bırakabiliyor. Bookstore'da üretilen tanım bu davranışı açıkça yazıyor: healer'dan selector'ları ve beklenen değerleri güncellemesi, testin doğru olduğundan eminse onu test.fixme() ile işaretleyip açıklayan bir yorum bırakması ve kullanıcıya soru sormadan testi geçirecek en makul şeyi yapması isteniyor.
AI'ın Ürettiği Testi Review Etmek
Review'da aranacak sorunları göstermek için aşağıdaki testi, generator çıktılarında sık görülen kalıplarla elle yazdım ve Bookstore'a karşı çalıştırdım:
// Review örneği: generator'ın ürettiği testlerde aranacak sorunları göstermek için elle yazıldı
test('checkout with coupon', async ({ page }) => {
await page.goto('http://127.0.0.1:52481/');
await page.locator('li:nth-child(1) button').click();
await page.getByRole('link', { name: /Cart/ }).click();
await page.getByLabel('Coupon code').fill('SAVE10');
await page.getByRole('button', { name: 'Apply' }).click();
await page.waitForTimeout(1000);
await expect(page.getByText(/Total/)).toBeVisible();
});
Test 1,5 saniyede geçti. Oysa veritabanında SAVE10 diye bir kupon yok; ekranda Coupon SAVE10 is not valid. yazıyordu ve toplam ara toplamla aynı kaldı (₺64.90). Testin tek iddiası "Total kelimesi görünüyor". Review'da sorduğum sorular bu testin her satırına karşılık geliyor: adres baseURL'e göre mi yazılmış, locator rol ve adla mı (yoksa "ilk kartın düğmesi" mi), test verisi testin kendisi tarafından mı hazırlanmış, waitForTimeout var mı, assertion senaryonun iddiasını (indirimli toplam) mı kontrol ediyor? Bu testin düzeltilmiş hâli zaten 7. yazıdaki checkout.spec.ts: kupon data.coupon(10) ile oluşturuluyor ve toHaveText('Total: ₺58.41') bekleniyor.
Healer'ın değişikliklerini ayrıca oku. Bir uygulama hatası toplamı yanlış hesaplıyorsa, "beklenen değerleri güncelle" talimatı testin beklenen tutarını yeni, yanlış değere çekebilir; test.fixme() ise kırmızı bir testi sessizce atlanan bir teste çevirir. Healer'ın pull request'inde beklenen bir değer ya da bir fixme değiştiyse, önce uygulamanın doğru davrandığından emin ol. AI'ın yazdığı kodu review etmenin genel yöntemi AI Destekli Geliştirme serisinin "AI'ın Yazdığı Kodu Review Etmek ve Doğrulamak" yazısında.
Üretimde Dikkat
API testlerinin header'larını loglara karşı koru. toBeOK hatası Authorization header'ını tam yazdırıyor; Bookstore'da token her koşuda yeni üretilen bir anahtarla imzalanan kısa ömürlü bir test token'ı, ama gerçek bir ortama karşı koşan bir suite'te CI loglarına kalıcı bir sır düşebilir.
API project'ini ayrı çalıştırmanın maliyetine bak. webServer config geneline ait olduğu için yalnız API testleri koşarken de Angular dev server'ı açılıyor. Sık koşan bir API suite'i için ayrı bir config dosyası ya da webServer'ı ortam değişkeniyle kapatmak düşünülebilir.
Agent'ları kullanacaksan izinlerini sınırla. init-agents'ın yazdığı tanımlarda planner'ın araçları arasında sayfada kod çalıştıran browser_run_code_unsafe da var. Agent'ı test ortamına karşı, gerçek kullanıcı verisi ve gerçek sırlar olmadan çalıştır; ürettiği her dosya normal bir pull request gibi review'dan geçsin.
Sık Düşülen Tuzaklar
API testini E2E suite'ine yığıp integration testlerinin yerine kullanmak. Gerçek port, gerçek tarayıcı sunucusu ve paylaşılan veritabanıyla yazılan iş kuralı kombinasyonları yavaş ve kırılgan; WebApplicationFactory testleri aynı kuralları saniyenin altında ve zaman ile dış servisler kontrol altındayken test ediyor.
storageState'i API context'ine verip oturumun taşındığını sanmak. Tahmin bölümündeki 401, token tabanlı her uygulamada aynı sonucu verir.
Healer'ın bir uygulama hatasını "test düzeltmesi" ile örtmesi. Kırmızı testi yeşile çeviren her değişiklik bir soruyu cevaplamalı: değişen uygulama mı, test mi?
Generator'ın ürettiği testi review'suz commit'lemek. Yukarıdaki örnek geçiyordu ve hiçbir şey doğrulamıyordu.
Küçük Bir Deneme
★☆☆ API testlerine stok yetersizliği için bir test ekle: stoğu 10 olan bir kitaptan 25 adet sipariş et ve gövdedeki title alanının "Insufficient stock" olduğunu doğrula. Aynı kontrolü 2–4. yazılardaki gibi bir integration testi olarak da yaz ve iki sürenin farkına bak.
★★☆ Hibrit testi tersine çevir: siparişi customerApi ile oluştur, sipariş sayfasını aç ve tutarı toShowPrice ile doğrula. Hangi durumlarda bu yön UI'dan sipariş vermekten daha doğru?
★★★ Yukarıdaki review örneğini bir checklist'e dönüştür ve 11. yazıdaki eslint-plugin-playwright kurallarını (missing-playwright-await, prefer-web-first-assertions) bu checklist'in otomatik kısmı olarak projeye ekle. Örnek test hangi kurallara takılıyor, hangilerini yalnız insan görüyor?
Seri burada bitiyor. İlk dokuz yazıda Bookstore'un API'sini gerçek SQL Server'la ve sınırda taklit edilen dış servislerle, Angular ile arasındaki sözleşmeyi ve tarayıcıdan geçen uçtan uca akışları test altına aldık; flaky testlere ve sürüm değişikliklerine karşı bir yöntem kurduk. Son dört yazı Playwright'ın günlük kullanılan API'sini tamamladı: locator ve action'lar, assertion'lar, config ve fixture'lar, API testleri ve test üreten agent'lar. Aynı suite'i CI'da hızlı tutmak için GitHub Actions serisinin Playwright yazısına, axe ile accessibility kontrollerini otomatikleştirmek için Accessibility serisinin "Accessibility Test Otomasyonu" yazısına, yük altında davranışı görmek için Performance ve Load Testing serisine bakabilirsin.