Playwright ile Test Otomasyonunu Kendi CMS Projemde Öğrenirken
Kişisel sitem ve içerik yönetim panelim üzerinde çalışırken başlangıç hedefim şuydu: Yazı yazabileceğim, Türkçe ve İngilizce içeriği ayrı yönetebileceğim, kendi kontrolümde bir alan oluşturmak.
Fakat proje büyüdükçe şunu fark ettim: Bir özelliğin ekranda çalışıyor gibi görünmesi, onun gerçekten doğru çalıştığı anlamına gelmiyor.
Admin panelinde yeni yazı açabiliyor olabilirim. Peki taslak yanlışlıkla ziyaretçiye görünürse? Türkçe yazıyı düzenlerken İngilizce çeviri de otomatik yayınlanırsa? Slug değiştiğinde eski URL kaybolursa? Giriş akışı bazen çalışıp bazen çalışmazsa? Ya da ana sayfada font, boşluk ve görsel düzen fark etmeden bozulursa gibi bir çok farklı soru geliyor insanın aklına.. Birde verdiğin promtu yapay zeka tam anlamıyla gerçekleştirmiyor, yada ben eksik yazıyorum yada kendine göre ucu acık alanlarda değişiklik yapıyor. Yani tam kontrollü ilerleyemiyorsun
Bu nedenle projede Playwright ile uçtan uca test altyapısı kurdum. Bu yazı, Playwright komutlarını ezberletmeye çalışan bir eğitim değil. İlerleyen dönemlerde kendime hatırlatma yapmak amacıyla oluşturulmuş bilgi veren bir doküman. Kendi CMS projemi geliştirirken testin nerede gerekli olduğunu, test kırıldığında nasıl düşünmem gerektiğini ve hangi karmaşıklıkları şimdilik eklemediğimi sonrasında nasıl kullanabileceğimi yazıyorum
Test etmek ne demek?
Bir yazılım projesinde “test ettim” tek başına yeterli bir açıklama değil. Çünkü farklı riskleri farklı araçlar yakalar.
KontrolSorduğu soruBizdeki araçLintKod temel kalite kurallarına uyuyor mu?ESLintTypecheckYanlış türde veri veya prop kullanılmış mı?TypeScriptUnit/component testKüçük bir kural veya bileşen doğru davranıyor mu?Vitest + RTLE2E testKullanıcı gerçekten işini tamamlayabiliyor mu?PlaywrightGörsel kontrolSayfa çalışsa bile görünümü bozulmuş mu?Playwright snapshot
Örneğin slug üretim kuralını unit test ile kontrol etmek mantıklı olabilir. Ancak admin olarak giriş yapmak, yazı oluşturmak, yayına almak ve public sayfada görünmesini doğrulamak uçtan uca bir akıştır. Bu yüzden bunu test etmek Playwright’ın alanıdır.
Her CSS değişikliğinde tüm E2E paketini çalıştırmak teoride güvenli görünür ama pratikte gereksiz zaman ve dikkat kaybı yaratır. Üstelik gereksiz token yer.
Benim için daha gerçekçi test akışı şu:
Küçük CSS/görsel düzenleme
→ lint + typecheck + tarayıcıda manuel kontrol
Küçük davranış değişikliği
→ ilgili unit/component testi
Giriş, yazı kaydetme, yayınlama, silme gibi kritik akış
→ ilgili Playwright testi
Bir özellik bölümü tamamlandığında
→ lint + typecheck + unit test + E2E + buildBuradaki hedef “her şeyi otomatik test etmek” değil. En yüksek riskli kullanıcı akışlarını güvenceye almak.
Playwright benim projemde neyi kontrol ediyor?
Playwright, tarayıcıyı açıp kullanıcı gibi hareket eder. Form doldurur, butona basar, sayfa açar ve sonucu doğrular.
CMS tarafında bunun anlamı şunlar:
Admin giriş yapabiliyor mu?
Admin oturumunu kapatabiliyor mu?
Yeni yazı oluşturabiliyor mu?
Yazıyı preview edebiliyor mu?
Boş veya sadece boşluklardan oluşan rich-text içerik kaydedilemiyor mu?
Türkçe slug doğru oluşuyor mu?
Bir çeviri yayınlandığında kardeş çeviri yanlışlıkla yayınlanmıyor mu?
Toplu silinen içerik, public sayfadan da kalkıyor mu?
Ana sayfa, About ve Resume görsel olarak bozulmuş mu?
Burada test, yalnızca teknik bir kontrol değil. Ürün kararının tarayıcıdaki kanıtı oluyor.
Bir Playwright testini nasıl okurum?
Bir testin en basit mantığı üç parçadan oluşuyor:
Arrange → Act → Assert
Hazırla → İşlemi yap → Sonucu doğrulaAdmin giriş örneği:
test('admin giriş yapabilir', async ({ page }) => {
// Arrange
await page.goto('/admin/login');
// Act
await page.getByLabel('E-posta').fill(email);
await page.getByLabel('Şifre').fill(password);
await page.getByRole('button', { name: 'Giriş Yap' }).click();
// Assert
await expect(page).toHaveURL(/\/admin\/?$/);
});Bu kodu ilk kez gören biri için çok satır varmış gibi görünebilir. Ama aslında şunu söylüyor:
Login ekranını aç.
Kullanıcının yapacağı işlemleri yap.
Başarılı olunca admin alanına geçtiğini doğrula.
Test okurken her komutu ezberlemeye çalışmak yerine önce bu üç parçayı ayırmak daha kolay.
Locator seçimi: Testin doğru öğeyi bulması
Playwright’ın ekrandaki öğeyi bulmak için kullandığı şey locator. Başta CSS seçicileriyle doğru alanı yakalamaya çalışacağımı düşünüyordum. Fakat testin stabil kalması için çoğu durumda butonun adı, form alanının etiketi veya kullanıcının gördüğü metin üzerinden seçim yapmak daha doğru oluyor.
Projede anlamlı locator’lar kullandık:
page.getByLabel('E-posta')
page.getByLabel('Şifre')
page.getByRole('button', { name: 'Giriş Yap' })
page.getByRole('link', { name: 'Yeni yazı' })
page.getByText('Yayınlandı', { exact: true })Bu yaklaşımın mantığı şu: Test, sayfadaki “üçüncü butonu” aramıyor. Kullanıcının gördüğü anlamlı öğeyi arıyor.
Kırılgan yaklaşım:
page.locator('div > div:nth-child(2) > button')Daha sağlam yaklaşım:
page.getByRole('button', { name: 'Yayınla' })Projede aynı “kategori yok” metni ekranda iki yerde bulunduğu için strict-mode hatası yaşadık. Playwright tek bir öğe bulması gerekirken iki olası öğe bulmuştu.
Bu durumda kolay ama kırılgan çözüm şudur:
page.getByText('Kategori yok').nth(1)Daha doğru çözüm, aramayı bağlama göre daraltmaktır:
page.getByRole('dialog').getByText('Kategori yok', { exact: true })Test hatası her zaman uygulamanın hatası değildir. Testin neyi kastettiği belirsiz olabilir; yani yanlış koşulu test ediyor olabilirsin.
Hazır admin oturumu: storageState
CMS testlerinin çoğu admin girişi gerektiriyor. Bunu en basit hâliyle şöyle düşünebilirim: Bir alışveriş sitesine her girdiğimde tekrar kullanıcı adı ve şifre yazmak yerine, tarayıcının “bu kişi daha önce giriş yaptı” bilgisini hatırlaması gibi.
Playwright’taki storageState de testler için buna benzer bir hafıza dosyasıdır. İlk aşamada test kullanıcısıyla gerçek bir giriş yapılır. Bu girişten sonra oluşan çerezler ve oturum bilgileri kaydedilir. Sonraki admin testleri, her seferinde formu yeniden doldurmak yerine bu kaydedilmiş oturumu kullanır.
test.use({ storageState });Bu satırın sade anlamı şudur: “Bu test grubunu çalıştırırken, admin daha önce giriş yapmış gibi davran.”
Burada testin yapması gereken işi atlamış olmuyorum. Admin girişinin çalışıp çalışmadığını ayrıca test ediyoruz. Ancak “yazı yayınlanıyor mu?” testinde tekrar login ekranını sınamak istemiyoruz. Çünkü o testin konusu giriş değil, yazının yayınlanması.
İlk bakışta bu yöntemin testin yaptığı işi kolaydan atlamak gibi göründüğünü düşündüm. Ama burada test edilen şeyi ayırıyoruz: giriş testinde giriş akışını, yazı testinde ise yazı akışını kontrol ediyoruz.
Hazır oturum, seni otomatik olarak istediğin sayfaya götürmez.
Test yine başlangıç noktasını açıkça söylemeli:
await page.goto('/admin/posts');Bu ayrım, testin neye bağımlı olduğunu görünür kılar: storageState “giriş yapıldı” bilgisidir; page.goto('/admin/posts') ise “şimdi yazılar sayfasını aç” komutudur.
Bir dosyada hem giriş gerektiren hem giriş gerektirmeyen test varsa, oturumu bütün dosyaya vermemek gerekir:
test.describe('Giriş gerektiren yazı işlemleri', () => {
test.use({ storageState });
test('admin yazıyı düzenleyebilir', async ({ page }) => {
await page.goto('/admin/posts');
// ...
});
});
test('ziyaretçi admin sayfasına erişemez', async ({ page }) => {
await page.goto('/admin/posts');
// girişsiz davranış burada test edilir
});Bu yapı, ziyaretçi testini yanlışlıkla giriş yapılmış hâlde çalıştırmamayı sağlar.
Testleri konuya göre bölmek
Testleri tek dev dosyada tutmadık. Alanlara göre ayırdık:
e2e/admin-dashboard.spec.ts
e2e/admin-pages.spec.ts
e2e/admin-posts.spec.ts
e2e/admin-post-workflow.spec.ts
e2e/public-posts.spec.ts
e2e/visual.spec.tsBu bölünme günlük çalışma için çok yararlı. Yazı editöründe değişiklik yaptıysam önce admin-post-workflow tarafına bakarım. Public blog görünümünü değiştirdiysem public testleri ve görsel testleri çalıştırırım.
Böylece testler yalnızca “çalışan kodlar” değil, projenin haritası hâline gelir.
playwright.config.ts neyi yönetiyor?
Bu dosya testlerin ortak ayar yeri. Projede test klasörü e2e/ olarak tanımlı. Masaüstü ve mobil görünüm için iki hedef de var:
Desktop Chrome
Mobile ChromeBununla her gün onlarca tarayıcıda test çalıştırmayı hedeflemedik. İlk aşamada desktop ve mobilde temel kullanıcı akışını, özellikle de düzenin bozulup bozulmadığını görmek yeterli.
Config’te uygulamayı test öncesinde başlatan webServer ayarı da var. Bir test hata verdiğinde sorunun her zaman testin içinde olmadığını burada gördüm.
Bir tam koşuda uygulama testten önce zamanında hazır olmadığı için webServer timeout’u yaşadık. Bu nedenle artık ekranda kırmızı sonuç gördüğümde önce şu ayrımı yapıyorum:
Uygulama hiç başlamadı mı?
Oturum mu düştü?
Locator mı belirsiz?
Beklenen sonuç mu yanlış?
Yoksa gerçekten ürün davranışı mı bozuldu?Görsel regresyon: Sayfa çalışıyor ama görünüm bozuk olabilir
Fonksiyonel test, “Blog bağlantısına tıklayınca blog açılıyor mu?” diye kontrol eder. Ama şunları kaçırabilir:
Hero görseli yüklenmiyor olabilir.
Font değişmiş olabilir.
Mobilde başlık taşıyor olabilir.
Boşluklar kaymış olabilir.
Buton rengi veya kontrastı değişmiş olabilir.
Bu nedenle e2e/visual.spec.ts içinde ana sayfa, About ve Resume için görsel snapshot kontrolleri kurduk. Kayıtlı koşuda dört görsel test geçti, iki test ise skip durumundaydı.
skip, testin başarısız olduğu anlamına gelmez; o koşuda bilerek çalıştırılmadığı anlamına gelir. Ancak bu iki testin neden skip edildiğini mevcut kayıttan doğrulamadığım için burada özel bir sebep yazmıyorum.
Snapshot mantığı basit:
Baseline = Daha önce doğru kabul edilen ekran görüntüsü
Actual = Şimdiki testte oluşan ekran görüntüsü
Diff = Aradaki farkFark varsa hemen “test bozuk” ya da “tasarım bozuk” demek doğru değil. Önce farkın nedenine bakmak gerekir.
Tasarımı bilinçli değiştirdiysem yeni görüntüyü kontrol edip baseline olarak kabul ederim.
Değişiklik beklenmiyorsa baseline’ı güncellemem; kodu düzeltirim.
Snapshot’ı sırf test yeşile dönsün diye otomatik güncellememek gerekiyor. Aksi hâlde görsel test, koruma işlevini kaybeder.
Applitools neden var, biz neden kullanmadık?
Applitools, Playwright’ın yerleşik ekran görüntüsü karşılaştırmasına alternatif olan harici bir görsel test platformu. Playwright ekran görüntülerini proje içinde karşılaştırırken, Applitools bu karşılaştırmaları kendi panelinde yönetir. Baseline ve yeni ekranı yan yana görmeyi, farkları vurgulamayı, bir farkı panelden kabul veya reddetmeyi; aynı ekranı farklı tarayıcı ve mobil cihazlarda bulut üzerinden karşılaştırmayı kolaylaştırır.
Bunun için ayrı bir paket, hesap ve API anahtarı gerekir. Biz bunu şimdilik kullanmadık. Tek admin benim; görsel farkı inceleyen ve kararı veren kişi zaten benim. Kontrol edilen sayfa sayısı sınırlı ve Playwright’ın yerleşik snapshot sistemi temel görsel bozulmaları yakalamak için yeterli. Applitools eklemek; yeni hesap, API anahtarı, harici servis bağımlılığı ve yönetilecek ek bir panel anlamına geliyor.
Applitools yanlış bir araç değil. Tasarım değişikliklerini birden fazla kişi onaylayacaksa, çok sayıda sayfa ve sık arayüz değişikliği varsa veya Chrome, Firefox, Safari ve farklı mobil cihazlarda düzenli görsel karşılaştırma gerekiyorsa anlamlı olur. Şu an Playwright snapshot’ları, projenin ihtiyacı ile kuracağımız sistemin karmaşıklığı arasında daha dengeli çözüm.
Kırmızı test geldiğinde ne yaparım?
Test otomasyonunda benim için en değerli öğrenme, kırmızı sonucu tek cümleyle yorumlamamak oldu.
Önce ilgili testi tek başına çalıştırmak daha doğru:
npx playwright test e2e/admin-post-workflow.spec.tsArdından hata türünü ayırırım:
Hata türüOlası anlamıLocator hatasıTest birden fazla veya hiç öğe bulamadı.Assertion hatasıÖğeyi buldu ama sonuç beklenen gibi değil.TimeoutSayfa/öğe hazır olmadı veya yanlış şeyi bekliyorum.Login/navigation sorunuOturum veya yönlendirme beklendiği gibi çalışmadı.webServer timeoutUygulama testten önce hazır hâle gelmedi.
Başarısız testlerde trace: 'retain-on-failure' ayarını kullanıyoruz. Trace; testin hangi adımlardan geçtiğini, bazı durumlarda ekranı, console ve network bilgilerini incelemeye yardım eder.
Özellikle login/navigation tarafında zaman zaman kararsız sonuçlar gördük. Bir test ilk çalıştırmada başarısız olup yeniden denendiğinde geçerse buna flaky test denir.
Burada öğrendiğim şey şu:
Retry, sorunu çözmez. Testin veya ortamın stabil olmadığını gösterir.
Örneğin test login ekranında kaldıysa, retry sayısını artırmak yerine neden login yönlendirmesinin kararsız olduğunu anlamak gerekir.
Şimdilik neyi öğrenmem yeterli?
Bu projede test otomasyon uzmanı olmaya çalışmıyorum. Uygulama geliştiren, doğru testi seçebilen ve yayın öncesi riskleri azaltabilen biri olmaya çalışıyorum. Yapay zekanın yönlendirmesini direkt kabul etmemek sorgulamak öğrenme süreci için yeterli.
Bu nedenle ilk etapta şunları bilmek yeterli:
Unit, component, E2E ve görsel testin farkını bilmek.
Kritik kullanıcı akışlarını Playwright ile test etmek.
Locator’ı kullanıcı açısından anlamlı seçmek.
Hazır oturumun testleri hızlandırdığını, ama sayfayı otomatik açmadığını bilmek.
Görsel snapshot farkını incelemeden kabul etmemek.
Kırmızı testte önce hata türünü, sonra gerçek ekranı ve trace’i okumak.
Küçük değişiklikte hedefli; büyük özellik tamamlandığında kapsamlı test çalıştırmak.
Henüz ihtiyacım olmayanlar da var: Her Playwright API’sini ezberlemek, her sayfa için Page Object Model oluşturmak, sürekli çoklu tarayıcı optimizasyonu yapmak veya harici görsel test platformu kurmak.
Sonuç
Bu projede Playwright benim için yalnızca bir test komutu olmadı. Bir özellik geliştirirken “Kullanıcı bunu gerçekten yapabiliyor mu?”, “Bu davranış bilinçli mi?” ve “Bu değişiklik başka bir alanı bozdu mu?” diye düşünme biçimi oldu.
Kendi CMS’ini geliştirmenin öğretici tarafı da burada: Testi dışarıdan anlatılan teorik bir konu olarak değil; taslak ile yayın arasındaki farkta, dil bağımsızlığında, slug üretiminde, login akışında ve görsel görünümde gerçek risklerle birlikte öğreniyorum.
Test yazmak artık ürün geliştirmeyi yavaşlatan bir iş gibi görünmüyor. Doğru yerde kullanıldığında, ertesi gün aynı özelliğe döndüğümde neyi bozmadığımı bilmemi sağlayan bir güvenlik ağı gibi duruyor.