İçeriğe atla

Playwright vs Cypress: Modern Web Uygulamaları için E2E Test Stratejileri

Playwright ve Cypress ile güvenilir, sürdürülebilir E2E suite'leri: framework seçimi, flaky test önleme, CI/CD entegrasyonu ve optimizasyon.

Ayhan Sipahi Ayhan Sipahi

E2E suite’ler her framework’te aynı birkaç sebeple çöküyor: testler arasında paylaşılan state, markup’a bağlanmış selector’ler ve gerçek bir koşulun yerine konan sabit bekleme süreleri. Framework debug deneyimini belirliyor. Mimari ise suite’in altı ay ve bir arayüz yenilemesi sonrasında hâlâ çalışıp çalışmayacağını belirliyor.

Yeni bir suite için Playwright ile başla. Ücretsiz native parallelism, WebKit ve Firefox kapsamı ve trace viewer, tekrar eden üç sıkıntıyı ücretli bir plana ihtiyaç duymadan ortadan kaldırıyor. Cypress ise zaten interaktif runner’ının içinde çalışan ekipler için makul bir seçim olmayı sürdürüyor. Bu karardan sonrası mimari: page object’ler, kararlı data-testid selector’ler, API üzerinden oluşturulan test verisi ve edge case’leri tarayıcının dışında tutan bir piramit.

Framework Seçimi: Playwright vs Cypress#

Mimari Farklar#

Yeni bir suite için güvenli varsayılan Playwright. Cypress ise ekip interaktif runner’ına ücretsiz shard’lardan daha çok değer veriyorsa yerini hak ediyor. Bu kararı belirleyen yetenekler şunlar:

Multi-browser + Native Parallelism

JavaScript-only + Hızlı Kurulum

E2E Testing Tool Seçimi

Proje Gereksinimleri

Playwright

Cypress

Güçlü Yönler

Native parallelism ücretsiz

Cross-browser WebKit/Firefox

Multi-language desteği

Auto-waiting mekanizması

Güçlü trace viewer

Güçlü Yönler

Interactive time-travel debugging

Real-time execution feedback

SPA'ler için mükemmel DX

Basit kurulum React/Vue/Angular

Sınırlamalar

Daha dik öğrenme eğrisi

Sık update maintenance

Sınırlamalar

Parallel execution için Cypress Cloud

Öncelikle Chrome odaklı

Sadece JavaScript/TypeScript

Örnek Testler#

Auto-waiting’i gösteren basit bir Playwright testi:

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

test('kullanıcı satın alma akışını tamamlayabilir', async ({ page }) => {
  await page.goto('/products');

  // Element actionable olana kadar auto-wait yapar
  await page.getByTestId('product-add-to-cart').click();
  await page.getByTestId('checkout-button').click();

  // Checkout formunu doldur
  await page.getByTestId('shipping-name').fill('Ahmet Yılmaz');
  await page.getByTestId('shipping-address').fill('Atatürk Cad. No:123');
  await page.getByTestId('payment-card').fill('4242424242424242');

  await page.getByTestId('place-order').click();

  // Web-first assertion auto-retry yapar
  await expect(page.getByTestId('order-confirmation')).toBeVisible();
});

Aynı test Cypress’te:

describe('Satın Alma Akışı', () => {
  it('kullanıcının satın almayı tamamlamasına izin verir', () => {
    cy.visit('/products');

    cy.get('[data-testid="product-add-to-cart"]').click();
    cy.get('[data-testid="checkout-button"]').click();

    cy.get('[data-testid="shipping-name"]').type('Ahmet Yılmaz');
    cy.get('[data-testid="shipping-address"]').type('Atatürk Cad. No:123');
    cy.get('[data-testid="payment-card"]').type('4242424242424242');

    cy.get('[data-testid="place-order"]').click();

    cy.get('[data-testid="order-confirmation"]').should('be.visible');
  });
});

Her ikisi de aynı amaca ulaşıyor. Playwright’ın avantajı parallel execution’da ortaya çıkıyor; 8 shard ek maliyet olmadan eşzamanlı çalışıyor. Cypress aynı yetenek için Cypress Cloud subscription gerektiriyor.

Page Object Model ile Test Mimarisi#

Page object’ler testleri UI yapısından ayırıyor. Bir button hareket ettiğinde veya class name değiştiğinde, onlarca test yerine bir dosyayı güncelliyorsun.

Modern Page Object Kurgusu#

// page-objects/LoginPage.ts
import { Page, Locator, expect } from '@playwright/test';

export class LoginPage {
  readonly page: Page;
  readonly emailInput: Locator;
  readonly passwordInput: Locator;
  readonly submitButton: Locator;
  readonly errorMessage: Locator;

  constructor(page: Page) {
    this.page = page;
    this.emailInput = page.getByTestId('login-email-input');
    this.passwordInput = page.getByTestId('login-password-input');
    this.submitButton = page.getByTestId('login-submit-button');
    this.errorMessage = page.getByTestId('login-error-message');
  }

  async goto() {
    await this.page.goto('/login');
  }

  async login(email: string, password: string) {
    await this.emailInput.fill(email);
    await this.passwordInput.fill(password);
    await this.submitButton.click();
  }

  async expectLoginSuccess() {
    await expect(this.page).toHaveURL(/\/dashboard/);
  }

  async expectLoginError(message: string) {
    await expect(this.errorMessage).toContainText(message);
  }
}

Testlerde kullanımı:

test('geçerli credential\'lar login\'e izin verir', async ({ page }) => {
  const loginPage = new LoginPage(page);
  await loginPage.goto();
  await loginPage.login('user@example.com', 'password123');
  await loginPage.expectLoginSuccess();
});

test('geçersiz credential\'lar hata gösterir', async ({ page }) => {
  const loginPage = new LoginPage(page);
  await loginPage.goto();
  await loginPage.login('user@example.com', 'wrongpassword');
  await loginPage.expectLoginError('Geçersiz kimlik bilgileri');
});

Selector Kararlılığı#

Test edeceğin elementler için data-testid attribute’ları kullan. Güvenilir bir naming convention: {scope}-{element}-{type}.

<!-- İyi: Stable, açıklayıcı test ID'ler -->
<button data-testid="product-list-add-to-cart-button">Sepete Ekle</button>
<input data-testid="checkout-shipping-name-input" />
<div data-testid="order-confirmation-message">Sipariş başarıyla verildi</div>

<!-- Kaçın: CSS class'lar refactor'larda değişir -->
<button class="btn btn-primary add-cart">Sepete Ekle</button>

Semantic HTML mevcut olduğunda, role-based locator’ları tercih et:

// Daha iyi: Accessible role kullanıyor
await page.getByRole('button', { name: 'Sepete Ekle' }).click();

// İyi: Explicit test ID
await page.getByTestId('add-to-cart-button').click();

// Kırılgan: Implementation-dependent
await page.locator('.product-card > .actions > button:nth-child(1)').click();

API Mocking Stratejileri#

External API’leri mocklamak test isolation ve reliability sağlıyor. Yaklaşım rendering stratejine bağlı.

Client-side only

Server-side SSR/SSG

Both CSR + SSR

API Mocking Strategy

Rendering Type

Playwright page.route

MSW with Next.js proxy

MSW + Playwright integration

Simple route mocking

Hızlı kurulum

Service worker overhead yok

MSW browser mode

Vitest/Storybook\'ta tekrar kullanılabilir

Full Request/Response API

Hybrid yaklaşım

@msw/playwright package

window.msw pattern

Playwright’ın Yerleşik Mock’lama Yolu#

Client-side app’ler için page.route() çoğu durumu hallediyor:

test('API fail olduğunda hata gösterir', async ({ page }) => {
  // API call'u intercept et ve error döndür
  await page.route('**/api/products', route => {
    route.fulfill({
      status: 500,
      contentType: 'application/json',
      body: JSON.stringify({ error: 'Internal Server Error' })
    });
  });

  await page.goto('/products');

  await expect(page.getByTestId('error-message'))
    .toContainText('Ürünler yüklenemedi');
});

MSW ile Kapsamlı Mock’lama#

Mock Service Worker kompleks senaryolar için daha robust bir API sağlıyor:

// mocks/handlers.ts
import { http, HttpResponse } from 'msw';

export const handlers = [
  http.get('/api/products', () => {
    return HttpResponse.json([
      { id: 1, name: 'Ürün 1', price: 29.99 },
      { id: 2, name: 'Ürün 2', price: 39.99 }
    ]);
  }),

  http.post('/api/orders', async () => {
    return HttpResponse.json(
      { orderId: '12345', status: 'confirmed' },
      { status: 201 }
    );
  })
];

Playwright ile entegrasyon:

// tests/msw.setup.ts
test.beforeEach(async ({ page }) => {
  // addInitScript sayfanın içinde çalışıyor; orada module import'ları yok.
  // Önce setupWorker(...handlers).start() çağrısını bir dosyaya bundle et, sonra enjekte et.
  await page.addInitScript({ path: './tests/msw-init.bundle.js' });
});

Gotcha: MSW’nin service worker’ı network request’leri page.route()’a görünmez yapıyor. Bir yaklaşımı tutarlı kullan veya @msw/playwright ile açıkça entegre et.

Flaky Test Önleme#

Flaky test’ler güveni hiç test olmamaktan daha hızlı aşındırıyor. İşte onlara sebep olan şeyler ve nasıl düzeltilir:

Flaky Test Sebepleri

Timing Issues

External Dependencies

Test Isolation

Environment Inconsistency

Static wait\'ler await page.waitForTimeout

Missing element actionability check\'leri

Auto-waiting kullan

Web-first assertion\'lar kullan

Third-party API failure\'lari

Network latency

External call\'ları mockla

Real API\'ler için generous timeout\'lar

Shared test data

Test\'ler arası state leakage

Isolated database transaction\'lar

Düzgün beforeEach cleanup

Farklı OS rendering

Browser version mismatch\'leri

Containerized test execution

Browser version\'larını kilitle

Kaçınılacak Anti-pattern’ler#

// BAD: Static wait'ler flakiness'a sebep olur
await page.click('#submit');
await page.waitForTimeout(3000); // Çok kısa veya çok uzun olabilir
await page.click('#next-step');

// Auto-waiting timing'i hallediyor
await page.getByTestId('submit-button').click();
await expect(page.getByTestId('next-step-button')).toBeVisible();
// BAD: Unstable selector'ler UI değişiklikleriyle bozulur
await page.click('div.container > ul > li:nth-child(3) > button');

// Stable selector'ler refactoring'den kurtulur
await page.getByTestId('user-list-item-delete-button').click();

Retry Ayarları#

Retry’lar diagnostic tool’lar, çözüm değil. CI’da aralıklı infrastructure sorunlarını halletmek için kullan:

// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  retries: process.env.CI ? 2 : 0, // Sadece CI'da retry
  use: {
    actionTimeout: 10000,
    navigationTimeout: 30000,
    trace: 'retain-on-failure', // Debug için kritik
    screenshot: 'only-on-failure',
    video: 'retain-on-failure'
  }
});

CI/CD Entegrasyonu ve Sharding#

Parallel execution uzun bir suite’i kısa bir feedback loop’a çeviriyor; GitHub Actions Playwright’ın shard flag’ini doğrudan destekliyor:

# .github/workflows/e2e-tests.yml
name: E2E Tests
on: [push, pull_request]

jobs:
  playwright-tests:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        shardIndex: [1, 2, 3, 4, 5, 6, 7, 8]
        shardTotal: [8]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
        env:
          PLAYWRIGHT_BLOB_OUTPUT_DIR: blob-report
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: blob-report-${{ matrix.shardIndex }}
          path: blob-report
          retention-days: 1

  merge-reports:
    needs: playwright-tests
    if: always()
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - uses: actions/download-artifact@v4
        with:
          pattern: blob-report-*
          path: all-blob-reports
          merge-multiple: true
      - run: npx playwright merge-reports --reporter html ./all-blob-reports
      - uses: actions/upload-artifact@v4
        with:
          name: html-report
          path: playwright-report
          retention-days: 14

Performans etkisi: aritmetiği basit. 35 dakikalık bir suite 8 shard’a bölündüğünde duvar saatinde yaklaşık 5 dakikada bitiyor, toplam runner-dakikası ise yaklaşık %14 artıyor (her biri 5 dakikalık 8 shard, 35 dakikalık sıralı koşuya karşı). Ekipler runner-dakikasından çok duvar saatini hissediyor; bu yüzden takas genelde kendini ödüyor.

Test Verisi Yönetimi#

Temiz test verisi alışkanlıkları testlerin birbirine karışmasını önlüyor ve güvenilirliği artırıyor.

Factory Pattern#

// test-data/factories.ts
import { Page } from '@playwright/test';

export class UserFactory {
  static async create(page: Page, overrides?: Partial<User>) {
    const userData = {
      email: `test-${Date.now()}@example.com`,
      name: 'Test Kullanıcı',
      role: 'member',
      ...overrides
    };

    // Kayıt formunu tıklamak yerine API üzerinden oluştur
    const response = await page.request.post('/api/users', {
      data: userData
    });

    return response.json();
  }

  static async cleanup(page: Page, userId: string) {
    await page.request.delete(`/api/users/${userId}`);
  }
}

// Testlerde kullanımı
test('kullanıcı profilini güncelleyebilir', async ({ page }) => {
  const user = await UserFactory.create(page);

  await page.goto(`/profile/${user.id}`);
  await page.getByTestId('profile-name').fill('Güncel İsim');
  await page.getByTestId('profile-save').click();

  await expect(page.getByTestId('profile-name')).toHaveValue('Güncel İsim');

  await UserFactory.cleanup(page, user.id);
});

Playwright Fixture’ları#

Fixture’lar setup ve teardown’ı otomatik hallediyor:

// fixtures/index.ts
import { test as base } from '@playwright/test';

export const test = base.extend({
  authenticatedUser: async ({ page }, use) => {
    const user = await UserFactory.create(page, { role: 'user' });
    await loginAs(page, user);
    await use(user);
    await UserFactory.cleanup(page, user.id);
  },

  adminUser: async ({ page }, use) => {
    const admin = await UserFactory.create(page, { role: 'admin' });
    await loginAs(page, admin);
    await use(admin);
    await UserFactory.cleanup(page, admin.id);
  }
});

// Temiz test kodu
test('kullanıcı sepete ürün ekleyebilir', async ({ authenticatedUser, page }) => {
  await page.goto('/products');
  await page.getByTestId('product-add-to-cart').first().click();
  await expect(page.getByTestId('cart-count')).toHaveText('1');
});

Görsel Regresyon Testleri#

Görsel regresyonlar functional test’lere yakalanmıyor. Otomatik screenshot karşılaştırması onları yakalıyor.

Playwright ile Yerleşik Görsel Test#

test('dashboard layout tutarlı kalıyor', async ({ page }) => {
  await page.goto('/dashboard');

  // Dinamik içeriğin yüklenmesini bekle
  await page.waitForLoadState('networkidle');

  // Dinamik elementleri maskele
  await expect(page).toHaveScreenshot('dashboard.png', {
    mask: [
      page.getByTestId('user-greeting'), // Timestamp içeriyor
      page.getByTestId('notification-badge') // Dinamik sayı
    ],
    maxDiffPixels: 100
  });
});

Gotcha: Screenshot’lar OS-dependent. macOS’ta çekilen screenshot Linux’la match etmez. Tutarlılık için visual test’leri Docker container’larında çalıştır:

# Dockerfile.test
FROM mcr.microsoft.com/playwright:v1.47.0-jammy

WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .

CMD ["npx", "playwright", "test"]

SaaS Alternatifleri#

Docker kompleksitesi olmadan cross-platform tutarlılığa ihtiyaç duyan ekipler için:

  • Percy: AI destekli diff tespiti ve cross-browser render, ekip büyüklüğüne göre fiyatlanıyor
  • Chromatic: Storybook entegrasyonu ve görsel onay akışı, snapshot hacmine göre fiyatlanıyor
  • Lost Pixel (open-source): Percy’ye self-hosted alternatif

Trade-off: SaaS tool’lar paraya mal oluyor ama infrastructure management’ı ortadan kaldırıyor. Built-in çözümler ücretsiz ama containerization disiplini gerektiriyor.

Mobil Testler#

Web trafiğinin yarısından fazlası mobil cihazlardan geliyor. Sadece masaüstünü test etmek kritik sorunları kaçırıyor.

Cihaz Emülasyonu#

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

// Önceden yapılandırılmış cihaz kullan
test.use(devices['iPhone 14 Pro']);

test('mobil navigasyon çalışıyor', async ({ page }) => {
  await page.goto('/');

  // Touch event'ler otomatik etkin
  await page.getByTestId('mobile-menu-button').tap();
  await expect(page.getByTestId('mobile-nav')).toBeVisible();
});

// Birden fazla cihaz test et
const mobileDevices = ['iPhone 14 Pro', 'Pixel 5', 'Galaxy S24'];

for (const deviceName of mobileDevices) {
  test.describe(deviceName, () => {
    test.use(devices[deviceName]);

    test('checkout akışı tamamlanıyor', async ({ page }) => {
      await page.goto('/checkout');
      // Test viewport'a adapte oluyor
    });
  });
}

Konum Testleri#

test.use({
  geolocation: { longitude: 29.0104, latitude: 41.0082 },
  permissions: ['geolocation']
});

test('konuma göre yakın mağazaları gösteriyor', async ({ page }) => {
  await page.goto('/stores');

  await expect(page.getByTestId('store-location'))
    .toContainText('İstanbul');

  // Test ortasında konum değiştir
  await page.context().setGeolocation({
    longitude: 32.8597,
    latitude: 39.9334
  });

  await page.reload();

  await expect(page.getByTestId('store-location'))
    .toContainText('Ankara');
});

Erişilebilirlik Testleri#

Otomatik kontroller mekanik WCAG ihlallerini yakalıyor: eksik label, yetersiz kontrast, yanlış kullanılan ARIA. Yorum gerektiren kısımlar için hâlâ ekrana bakan bir insan gerekiyor. Otomatik kısmı her test run’ına bağla ki manuel inceleme daha kısa bir listeyle başlasın.

import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';

test('anasayfa WCAG 2.1 AA standartlarını karşılıyor', async ({ page }) => {
  await page.goto('/');

  const results = await new AxeBuilder({ page })
    .withTags(['wcag2a', 'wcag2aa', 'wcag21aa'])
    .exclude('#third-party-widget') // Kontrol etmediğin external widget'lar
    .analyze();

  expect(results.violations).toEqual([]);
});

test('klavye navigasyonu uygulama boyunca çalışıyor', async ({ page }) => {
  await page.goto('/');

  // İnteraktif elementler arasında tab ile gezin
  await page.keyboard.press('Tab');
  await expect(page.getByTestId('search-input')).toBeFocused();

  await page.keyboard.press('Tab');
  await expect(page.getByTestId('nav-link-about')).toBeFocused();

  await page.keyboard.press('Tab');
  await expect(page.getByTestId('nav-link-products')).toBeFocused();
});

Kademeli benimseme için, başlangıçta testleri fail etmeden violation’ları logla:

const results = await new AxeBuilder({ page }).analyze();

if (results.violations.length > 0) {
  console.warn(`[WARN] ${results.violations.length} accessibility violation bulundu:`);
  results.violations.forEach(violation => {
    console.warn(`  ${violation.id}: ${violation.description}`);
    console.warn(`  Etki: ${violation.impact}`);
    console.warn(`  Etkilenen elementler: ${violation.nodes.length}`);
  });
}

Component vs E2E Testing#

Her şey E2E testing gerektirmiyor. Test pyramid hala geçerli.

Bireysel component logic

Multi-component interaction

Full user journey

Test Senaryosu

Neyi test ediyorsun?

Component Test

Integration Test

E2E Test

Avantajlar

Hızlı execution 100ms\'den az

Kolay debug isolated scope

External dependency yok

Kullanım Alanları

Form validation logic

Calculator\'larda edge case\'ler

Component state management

Avantajlar

Gerçek user flow\'ları test eder

Integration bug\'larını yakalar

Dezavantajlar

Yavaş execution 5-30s per test

Debug etmesi daha zor

Daha fazla maintenance overhead

Kullanım Alanları

Kritik user path\'lar login/checkout

Cross-service workflow\'lar

Third-party integration\'lar

Pratik Dağılım#

  • %70 Unit/Component test’ler: Business logic, edge case’ler, hesaplamalar
  • %20 Integration test’ler: API + component interaction, multi-step workflow’lar
  • %10 E2E test’ler: Kritik user journey’ler (login, satın alma, kayıt)

Doğru seviyede test etme örneği:

// BAD: Edge case'leri E2E seviyesinde test etme
test('kupon kodu validasyonu: süresi geçmiş kuponlar', async ({ page }) => {
  await page.goto('/');
  await page.getByTestId('product-add').click();
  await page.getByTestId('checkout').click();
  await page.getByTestId('coupon-input').fill('EXPIRED2020');
  await page.getByTestId('coupon-apply').click();
  await expect(page.getByTestId('error')).toContainText('süresi geçmiş');
});

// Component seviyesinde test et
// tests/components/CouponValidator.test.ts
test('süresi geçmiş kupon kodlarını reddeder', () => {
  const validator = new CouponValidator();
  expect(validator.validate('EXPIRED2020')).toEqual({
    valid: false,
    error: 'Kuponun süresi geçmiş'
  });
});

// E2E testler happy path'lere odaklanıyor
test('kullanıcı geçerli kuponla satın almayı tamamlar', async ({ page }) => {
  await page.goto('/');
  await page.getByTestId('product-add').click();
  await page.getByTestId('checkout').click();
  await page.getByTestId('coupon-input').fill('SAVE20');
  await page.getByTestId('coupon-apply').click();
  await expect(page.getByTestId('discount')).toContainText('20 TL');
  await page.getByTestId('complete-order').click();
  await expect(page.getByTestId('confirmation')).toBeVisible();
});

Yaygın Tuzaklar ve Çözümler#

Tuzak 1: E2E Test’lere Aşırı Güven#

Belirti: Test suite 30+ dakika sürüyor, çoğunlukla unit-level bug’ları yakalıyor.

Çözüm: Edge case’leri component test’lere taşı. E2E’yi kritik user path’lar için ayır.

Tuzak 2: Flaky Test’leri Görmezden Gelme#

Belirti: “Tekrar çalıştır” kültürü güveni yok ediyor.

Çözüm: Flakiness metriklerini takip et. Flaky test’leri, ekip kırmızı koşuları görmezden gelmeyi öğrenmeden önce karantinaya al veya düzelt.

Tuzak 3: Test Isolation Eksikliği#

Belirti: Testler tek başına pass oluyor ama suite’te fail, sıraya bağımlı hatalar.

Çözüm: Her test izole çalıştırılabilir olmalı. Setup için factory’leri kullan, teardown’da temizle.

Tuzak 4: Trace Viewer Kullanmamak#

Belirti: CI hatalarını local’de debug için saatler harcama.

Çözüm: Config’de trace: 'retain-on-failure' etkinleştir. CI artifact’larından trace dosyalarını indir ve npx playwright show-trace trace.zip ile aç. Viewer DOM snapshot’ları, network call’ları, console log’ları ve exact timing gösteriyor; saatler kazandırıyor.

Tuzak 5: Her Şeyi Mocklamak#

Belirti: Tüm API call’lar mock’lanmış, testler pass ama production bozuk.

Çözüm: External third-party’leri ve error senaryolarını mockla. E2E testlerinde kendi API’ni mocklama; bu integration testing amacını bozuyor.

Varsayılanı Ne Zaman Değiştirmeli#

Playwright varsayılanı; suite CI’da koşuyorsa, Chromium’un ötesinde kapsama ihtiyaç duyuyorsa ve tek makinenin makul sürede bitiremeyeceği kadar büyüyorsa geçerli. Tek tarayıcılı bir SPA üzerinde gün boyu interaktif debug yapan ve Cypress Cloud bütçesi zaten ayrılmış bir ekip için Cypress daha doğru tercih. Onlar için interaktif runner ücretsiz shard’lardan daha değerli.

Hangisini seçersen seç page object’ler, data-testid selector’ler ve API üzerinden oluşturulan test verisi taşınıyor; sonradan framework değiştirmek göründüğü kadar pahalıya patlamıyor. 5-10 kritik path testiyle başla ve düzeltilene kadar flaky olanları suite dışında tut. Kapsamı, bu testler yayına gidecek bir regresyonu yakaladıktan sonra genişlet.

Kaynaklar#

İlgili yazılar