İçeriğe geç

Test Otomasyon Araçları 2026: Playwright, Cypress ve Selenium’u Gerçek Kriterlerle Karşılaştırmak

Araç seçimi neden hâlâ yanlış kriterlerle yapılıyor

Yeni projede araç seçimi çoğu zaman teknik bir karar gibi görünür ama pratikte üç şeyle bitirilir: birinin syntax beğenisi, geçen ay okunan bir blog yazısı ve “ekip zaten biliyor” cümlesi. Bu üçü de kararı iki yıl sonra ödeyeceğin faturayı hesaba katmaz. Fatura CI dakikası, flaky test bakımı ve ekibe yeni giren kişinin ilk testini yazana kadar geçen süredir.

Seçimi gerçekten belirleyen üç ölçüt var: test edilecek uygulamanın mimarisi (kaç origin, kaç sekme, iframe var mı, OAuth yönlendirmesi var mı), CI bütçesi (paralel çalıştırmayı kim ödüyor, altyapıyı kim bakıyor) ve ekibin dil yetkinliği (testleri kim yazacak, kim gözden geçirecek). Aşağıdaki karşılaştırmanın tamamı bu üç eksende kurulu. Genel bir araç envanteri arıyorsan test otomasyonunda kullanılan popüler araçlar yazısı başlangıç noktası; burada üç aracı karar verecek kadar derin karşılaştırıyoruz.

2026’da üç aracın gerçek durumu

Playwright artık “deneyeceğimiz yeni araç” kategorisinde değil. Microsoft desteği, TypeScript/JavaScript, Python, Java ve .NET bağlamaları, kutudan çıkan trace viewer, HTML raporu, otomatik bekleme ve test runner’ı ile varsayılan tercih. Çözmek üzere tasarlandığı problem şu: modern tarayıcıyı dış taraftan, protokol seviyesinde, tek bağlantı üzerinden sürmek ve bunu yaparken ağ katmanını da eline almak.

Cypress frontend geliştiricisinin kendi uygulamasını kendi tarayıcısında hata ayıklaması için tasarlandı ve bu problemi hâlâ en iyi o çözüyor. Time-travel debugger ve component test entegrasyonu güçlü. Karşılığında tarayıcı içinde çalışmanın yapısal kısıtlarını taşıyor ve şirket ticari orkestrasyon tarafına ağırlık verdi — paralel çalıştırmayı ücretsiz istiyorsan üçüncü parti çözüme gidersin.

Selenium bir standart olarak varlığını sürdürüyor. WebDriver BiDi’nin olgunlaşmasıyla ağ dinleme ve konsol log erişimi gibi eksikleri kapanıyor; Grid ile geniş tarayıcı matrisi kurmak hâlâ en sağlam yol. Çözdüğü problem: tek bir API ile mümkün olan her tarayıcıyı, her dilden, her işletim sisteminde sürmek. Kurumsal regresyon ve legacy tarayıcı matrisi varsa rakibi yok.

Mimari fark: aynı tarayıcıya üç farklı bağlanma yolu

Üç aracın özellik listesindeki farklar tercihlerden değil, mimariden türüyor. Cypress test kodunu tarayıcı sekmesinin içinde, uygulamayla aynı JavaScript ortamında çalıştırır. Playwright testi Node sürecinde tutar, tarayıcıya tek bir WebSocket üzerinden CDP (Chromium) veya BiDi ile bağlanır. Selenium test kodundan sürücü ikilisine (chromedriver vb.) HTTP isteği gönderir, sürücü tarayıcıyı sürer.

Test kodundan tarayıcıya üç yol: Cypress aynı süreçte, Playwright CDP/BiDi ile, Selenium WebDriver sürücüsü üzerinden.

Sonuçları: Cypress uygulamayla aynı origin’e hapsolduğu için çok origin ve çok sekme senaryolarında kenar çözümlere muhtaç, buna karşılık ağ isteklerini doğal olarak görür ve hata ayıklaması eşsizdir. Playwright dışarıdan bağlandığı için sekme, pencere, indirme, iframe ve origin sınırı tanımaz; tek bağlantı komut gecikmesini düşük tutar. Selenium her komut için sürücü katmanından geçer — bu ek gidiş-dönüş maliyeti demektir ama aynı katman Safari’den eski Edge’e kadar her şeyi aynı API’nin arkasına koyar.

Karşılaştırma tablosu: on üç somut kriter

KriterPlaywrightCypressSelenium
Dil desteğiTS/JS, Python, Java, .NETYalnız JS/TSJava, C#, Python, JS, Ruby ve daha fazlası
Tarayıcı matrisiChromium, Firefox, WebKit (gerçek Safari değil)Chrome ailesi, Firefox, WebKit deneyselGerçek Safari, Edge, uzak/legacy dahil her şey
Paralel çalıştırma maliyetiWorker + shard, ek ücret yokÜcretsiz orkestrasyon yok; Cloud veya üçüncü partiGrid ücretsiz, altyapı bakımı sana ait
Otomatik beklemeLocator bazlı, her eylemde actionability kontrolüKomut zincirinde retry, should ile birlikteYok; WebDriverWait elle kurulur
Ağ mocklamaroute ile tam kontrol, HAR kaydı/oynatmaintercept olgun ve rahatBiDi/CDP ile mümkün, API daha kaba
Çok origin / çok sekmeDoğal; context, popup, newPageKısıtlı; cy.origin ile kısmenDoğal; pencere handle’ları
Mobil webCihaz emülasyonu var, gerçek cihaz yokViewport emülasyonu, cihaz yokAppium ile gerçek cihaz
iframe / shadow DOMframeLocator, shadow DOM şeffafiframe zahmetli, shadow için eklentiFrame geçişi elle, shadow için getShadowRoot
Trace ve görsel hata ayıklamaTrace viewer: DOM anlık görüntüsü, ağ, konsolTime-travel debugger, en iyi yerel deneyimEkran görüntüsü + log; gerisi sana kalmış
RaporlamaYerleşik HTML rapor, JUnit/JSON çıktıYerel rapor sade; zengini Cloud’daKütüphane dışı; Allure/TestNG eklenir
Component testDeneysel etikette, React/Vue/SvelteOlgun ve birinci sınıfYok
EkosistemHızlı büyüyor, çekirdek zaten doluEklenti kültürü güçlüEn geniş, en eski, bazısı bakımsız
Öğrenme eğrisiOrta; async/await ve locator mantığıDüşük (JS bilen için), zincir mantığı alışkanlık isterYüksek; bekleme ve sürücü yönetimi elle

Tablodaki en pahalı üç satır şunlar: paralel çalıştırma maliyeti, otomatik bekleme ve trace. İlki CI faturana, ikincisi flaky test sayına, üçüncüsü bir hatanın kök nedenini bulma sürene doğrudan yansır.

Aynı senaryo, üç araçta kod

Senaryo: giriş yap, kullanıcı tablosunda ara, aramayı tetikleyen API yanıtını yakala ve hem yanıtı hem ekranı doğrula.

Playwright (TypeScript)

import { test, expect } from '@playwright/test';

test('arama sonucu API ve ekranda tutarlı', async ({ page }) => {
  await page.goto('/login');
  await page.getByLabel('E-posta').fill('**@*****le.com');
  await page.getByLabel('Parola').fill('S3cret!');
  await page.getByRole('button', { name: 'Giriş yap' }).click();
  await expect(page.getByRole('heading', { name: 'Kullanıcılar' })).toBeVisible();

  const responsePromise = page.waitForResponse(
    (r) => r.url().includes('/api/users') && r.url().includes('q=ayse') && r.ok()
  );

  await page.getByPlaceholder('Ara').fill('ayse');

  const body = await (await responsePromise).json();
  expect(body.items).toHaveLength(1);
  expect(body.items[0].fullName).toBe('Ayşe Yılmaz');

  await expect(page.getByRole('row')).toHaveCount(2); // başlık + 1 sonuç
  await expect(page.getByRole('cell', { name: 'Ayşe Yılmaz' })).toBeVisible();
});

Cypress (JavaScript)

describe('kullanıcı arama', () => {
  it('API ve ekran tutarlı', () => {
    cy.intercept('GET', '**/api/users?*q=ayse*').as('arama');

    cy.visit('/login');
    cy.get('input[name="email"]').type('**@*****le.com');
    cy.get('input[name="password"]').type('S3cret!');
    cy.contains('button', 'Giriş yap').click();
    cy.contains('h1', 'Kullanıcılar').should('be.visible');

    cy.get('input[placeholder="Ara"]').type('ayse');

    cy.wait('@arama').then(({ response }) => {
      expect(response.statusCode).to.eq(200);
      expect(response.body.items).to.have.length(1);
      expect(response.body.items[0].fullName).to.eq('Ayşe Yılmaz');
    });

    cy.get('table tbody tr').should('have.length', 1);
    cy.contains('table tbody td', 'Ayşe Yılmaz').should('be.visible');
  });
});

Selenium (Java 17, Selenium 4, Jackson)

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.time.Duration;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.*;
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.devtools.NetworkInterceptor;
import org.openqa.selenium.remote.http.*;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import static org.junit.jupiter.api.Assertions.*;

class KullaniciAramaTest {

  private ChromeDriver driver;
  private WebDriverWait wait;
  private NetworkInterceptor interceptor;
  private final AtomicReference<String> yakalanan = new AtomicReference<>();

  @BeforeEach
  void setUp() {
    driver = new ChromeDriver();
    wait = new WebDriverWait(driver, Duration.ofSeconds(10));
    // Not: yanıt gzip ile sıkıştırılmışsa Accept-Encoding'i identity'ye zorlamak gerekir.
    interceptor = new NetworkInterceptor(driver, (Filter) next -> req -> {
      HttpResponse res = next.execute(req);
      if (req.getUri().contains("/api/users") && req.getUri().contains("q=ayse")) {
        String govde = Contents.string(res);
        yakalanan.set(govde);
        HttpResponse kopya = new HttpResponse()
            .setStatus(res.getStatus())
            .setContent(Contents.utf8String(govde));
        res.getHeaderNames().forEach(n -> res.getHeaders(n).forEach(v -> kopya.addHeader(n, v)));
        return kopya;
      }
      return res;
    });
  }

  @Test
  void aramaSonucuApiVeEkrandaTutarli() throws Exception {
    driver.get("https://uygulama.example.com/login");
    driver.findElement(By.name("email")).sendKeys("**@*****le.com");
    driver.findElement(By.name("password")).sendKeys("S3cret!");
    driver.findElement(By.cssSelector("button[type='submit']")).click();
    wait.until(ExpectedConditions.textToBePresentInElementLocated(By.tagName("h1"), "Kullanıcılar"));

    driver.findElement(By.cssSelector("input[placeholder='Ara']")).sendKeys("ayse");

    wait.until(d -> yakalanan.get() != null);
    JsonNode body = new ObjectMapper().readTree(yakalanan.get());
    assertEquals(1, body.get("items").size());
    assertEquals("Ayşe Yılmaz", body.get("items").get(0).get("fullName").asText());

    wait.until(ExpectedConditions.numberOfElementsToBe(By.cssSelector("table tbody tr"), 1));
    assertTrue(driver.findElement(By.cssSelector("table tbody td")).getText().contains("Ayşe Yılmaz"));
  }

  @AfterEach
  void tearDown() {
    if (interceptor != null) interceptor.close();
    if (driver != null) driver.quit();
  }
}

Fark tabloda okunandan daha net görünüyor: Playwright ve Cypress’te ağ katmanı tek satır, Selenium’da yanıtı yakalamak için proxy filtresi kurup gövdeyi yeniden inşa etmen gerekiyor. Bekleme tarafında Playwright ve Cypress varsayılan olarak retry yaparken Selenium’da her doğrulamanın beklemesini sen yazıyorsun — ve yazmadığın her yer ileride flaky test adayı.

CI maliyeti ve çalıştırma süresi

Aracın en somut sonucu aylık CI faturasıdır. Playwright’ta paralellik iki katmanlı: süreç içinde worker, süreçler arası shard. İkisi de ücretsiz.

# .github/workflows/e2e.yml
name: e2e
on: [push]
jobs:
  test:
    runs-on: ubuntu-latest
    container: mcr.microsoft.com/playwright:v1.49.0-noble
    strategy:
      fail-fast: false
      matrix:
        shard: [1, 2, 3, 4]
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npx playwright test --shard=${{ matrix.shard }}/4 --workers=2
      - uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: trace-${{ matrix.shard }}
          path: test-results/
          retention-days: 7

Cypress’te aynı bölüşmeyi ücretsiz yapmak için ya spec listesini elle paylaştırırsın ya da üçüncü parti orkestratör kullanırsın:

  test:
    runs-on: ubuntu-latest
    container: cypress/included:13.15.0
    strategy:
      fail-fast: false
      matrix:
        grup: [a, b]
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npx cypress run --spec "cypress/e2e/${{ matrix.grup }}/**/*.cy.js"

Bu yöntemin bedeli dengesiz dağılımdır: bir grup on dakika, diğeri iki dakika sürer ve toplam süre en yavaş grubun süresidir.

Selenium Grid’de ölçek senin elinde ama altyapı da senin:

# docker-compose.yml
services:
  hub:
    image: selenium/hub:4.27.0
    ports: ["4442:4442", "4443:4443", "4444:4444"]
  chrome:
    image: selenium/node-chrome:4.27.0
    shm_size: 2gb
    depends_on: [hub]
    environment:
      - SE_EVENT_BUS_HOST=hub
      - SE_EVENT_BUS_PUBLISH_PORT=4442
      - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
      - SE_NODE_MAX_SESSIONS=4
      - SE_NODE_OVERRIDE_MAX_SESSIONS=true
    deploy:
      replicas: 3

Üç kalem daha hesaba kat: konteyner imajı boyutu (Playwright imajı tarayıcılarla birlikte gelir, çekme süresi cache’lenmezse her koşuya eklenir), soğuk başlatma (Grid’de node’ların hub’a kaydolması saniyeler alır) ve artifact saklama (trace ve video en hızlı büyüyen depolama kalemidir; retention süresini kısa tut). Süre optimizasyonunun araçtan bağımsız kısmı için test otomasyonunda performans optimizasyonu ve ölçeklenebilirlik uygulamaları yazılarına bak.

Kararı verdiren dört senaryo

1. Yeni SPA, küçük ekip, TypeScript. Playwright. Gerekçe: kutudan çıkan trace, ücretsiz paralellik, tek bağımlılıkla test runner + assertion + rapor. Bu profilde başka bir aracı seçmek için özel bir nedene ihtiyacın var.

2. Uzun tarayıcı matrisi zorunlu kurumsal regresyon. Selenium. Gerçek Safari, kurum içi dağıtılmış eski Edge ya da imzalı sertifika gerektiren masaüstü tarayıcılarını kapsaman gerekiyorsa WebDriver hâlâ tek standart yol. Grid bakım maliyetini kabul ediyorsun çünkü alternatif kapsamı daraltmak.

3. Frontend ekibinin sahiplendiği, component testiyle iç içe suite. Cypress savunulabilir. Aynı araçta component ve e2e yazmak, geliştiricinin testi kendi akışında çalıştırması, time-travel ile hata ayıklaması gerçek kazanç. Tek origin’de yaşayan, OAuth yönlendirmesi olmayan uygulamalarda mimari kısıt seni yormaz.

4. Java/C# monolit ve büyük mevcut Selenium yatırımı. Göç etmemek de bir karardır. Çalışan bin testi yeniden yazmanın getirisi çoğu zaman maliyetini karşılamaz. Doğru hamle: yeni akışları yeni araçta yazıp mevcut seti yerinde bakımda tutmak.

Selenium veya Cypress’ten Playwright’a geçiş planı

Toptan yeniden yazma denemeleri tipik olarak aynı yerde tıkanır: göç ekibi eski setin kapsamını bitiremeden yeni özellik akışı üstüne biner, iki suite de yarım kalır. Kademeli sıra şu:

  1. Yeni testleri yeni araçta yaz. Kural bugünden yürürlüğe girer, eski sete dokunmadan.
  2. İki suite’i CI’da paralel koştur. Eski set hâlâ merge’ü bloklar, yeni set önce bilgilendirme modunda çalışır; kararlılığı kanıtlanınca bloklayıcı olur.
  3. En kırılgan 20 testi taşı. Bakım maliyeti en yüksek testler en çok kazandıran testlerdir; sıralamayı son üç ayın flaky ve düzeltme kayıtlarından çıkar.
  4. Kapsam eşitlendikçe eski setten sil. Taşınan testin eskisi aynı PR’da silinir; yoksa iki set yıllarca yan yana yaşar.

Göçte en çok vakit alan kısım genellikle testlerin kendisi değil, altındaki iki katmandır: locator stratejisi ve test verisi kurulumu. XPath ve CSS zincirlerini birebir çevirmek, eski kırılganlığı yeni araca taşımak demek — rol tabanlı locator ve data-testid düzenine geçmek için doğru an tam olarak bu göçtür. Aynı şekilde paylaşılan sabit fixture veritabanı taşınmamalı; her testin kendi verisini üretmesi kuralı göç sırasında kurulmazsa sonradan hiç kurulmaz. Giriş gibi tekrar eden hazırlıkları UI yerine API üzerinden yapmak da bu aşamada devreye alınmalı; API test otomasyonu yazısındaki yaklaşım burada doğrudan işe yarar.

Seçimi kağıda dökmek

Bu yazıdaki tablo seni iki aday arasına indirir; kararı POC verir. Yapacağın şu: üç ölçütü (mimari uyum, CI maliyeti, ekip dil yetkinliği) kendi projen için 1–5 arası ağırlıklandır, sonra kendi uygulamanın en kritik üç akışını iki adayda da yaz ve iki hafta boyunca gerçek CI’ında günde en az beş kez koştur.

POC bitiminde ölçeceklerin:

  • Üç akışın toplam satır sayısı ve dosya sayısı
  • Lokal tek koşu süresi ve CI’da paralel koşu süresi
  • Koşu başına CI dakikası ve gereken runner sayısı
  • Elli koşuda kaç kez kırmızı, bunların kaçı gerçek hata
  • Bilerek kırdığın bir testte kök nedeni bulma süresi (trace/video ile)
  • Ekipteki en az deneyimli kişinin dördüncü akışı tek başına yazma süresi
  • Konteyner imajı çekme süresi ve artifact boyutu

Son maddeyi atlama: aracı seçen sen olsan da onunla beş yıl yaşayacak olan ekip. Ölçümü tabloya yaz, ağırlıklarla çarp, kararı bir sayfada gerekçesiyle sakla. Bir yıl sonra “neden bunu seçmiştik” sorusunun cevabı o sayfada olsun.

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.