JUnit 5 Desteği
TestFly, hem TestNG (yerleşik) hem de JUnit 5 (tercihe bağlı) test çatılarını birinci sınıf vatandaş olarak destekler. JUnit 5 entegrasyonu yalnızca basit bir çalıştırıcı (runner) sunmakla kalmaz; TestNG BaseTest ile temel yetenekleri paylaşır: framework tarafından yönetilen WebDriver yaşam döngüsü, ThreadLocal sürücü izolasyonu, akıcı locator'lar, web-öncelikli ve soft assertion'lar, yerleşik REST API testi, çoklu kullanıcı oturumları (multi-session), HTML zaman çizelgesi raporlaması, AI hata analizi ve flakiness takibi.
Kurulum
Maven
Projenizin pom.xml dosyasına TestFly'ın yanına JUnit 5 bağımlılıklarını ekleyin:
<dependencies>
<!-- TestFly Çekirdeği -->
<dependency>
<groupId>io.github.hakanngul</groupId>
<artifactId>testfly</artifactId>
<version>1.0.7</version>
</dependency>
<!-- JUnit 5 Jupiter ve Platform Launcher -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<version>1.10.2</version>
<scope>test</scope>
</dependency>
</dependencies>
Maven Surefire 3.x, ek bir eklenti yapılandırmasına ihtiyaç duymadan JUnit 5'i otomatik algılar.
Gradle
dependencies {
testImplementation 'io.github.hakanngul:testfly:1.0.7'
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.10.2'
}
tasks.named('test') {
useJUnitPlatform()
}
Entegrasyon Seçenekleri
TestFly, mimarinize uyum sağlayacak 3 farklı JUnit 5 kullanım modeli sunar.
Seçenek A — BaseJUnit5Test Sınıfını Genişletme (Önerilen)
En kolay ve en zengin yaklaşımdır. TestNG'deki BaseTest ile birebir aynı kolaylık metotlarını sunar:
import io.testfly.junit5.BaseJUnit5Test;
import io.testfly.locator.Role;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
class LoginTest extends BaseJUnit5Test {
@Test
@DisplayName("Kullanıcı geçerli bilgilerle giriş yapabilir")
void validLogin() {
open("/login");
step("Kullanıcı bilgileri girilir");
getByLabel("Kullanıcı Adı").type("admin");
getByLabel("Şifre").type("secret");
getByRole(Role.BUTTON, "Giriş Yap").click();
step("Dashboard ekran görüntüsüyle doğrulanır", true);
assertThat(By.id("dashboard")).isVisible();
}
}
BaseJUnit5Test'in Hazır Olarak Sağladığı Yetenekler:
| Kategori | Sunulan Metotlar ve Yetenekler |
|---|---|
| Gezinme (Navigation) | open(), open(path), getDriver(), getWait() |
| Anlamsal (Semantic) Locator'lar | getByRole(Role, name), getByText(), getByLabel(), getByPlaceholder(), getByTestId(), getByAltText(), getByTitle() |
| Akıcı (Fluent) Locator'lar | find(css), find(By); tüm eşleşmeler için find(css).elements() kullanın ($, kullanımdan kaldırılacak tek-locator takma adıdır) |
| Web-Öncelikli Doğrulamalar | assertThat(By).isVisible(), assertThat(Locator).hasText(...), assertThat(...).count(n) |
| Soft Doğrulamalar (SoftAssert) | softAssert(By).isVisible(), softAssert(By).hasText(...), softAssert().that(...) |
| Yerleşik REST API Testi | apiClient(), apiGet(path), apiPost(path), apiPut(path), apiPatch(path), apiDelete(path) (her biri bir ApiClient builder döndürür; .send() ile bitirin) |
| Çoklu Oturum (Multi-Session) | session(name), withSession(name, runnable) ile çoklu kullanıcı / chat / pazar yeri akışları |
| Veritabanı Doğrulama | db() (varsayılan veri kaynağı) ve db("datasourceName") ile SQL sorguları ve assertRowExists(table, conditions) gibi satır doğrulamaları |
| E-Posta Doğrulama | mailbox(), to("user@example.com") ile gelen kutusundan OTP, link ve i çerik kontrolleri |
| Erişilebilirlik (a11y) | accessibility().scan(), assertAccessibility() ile axe-core taramaları |
| Adım Kaydı (Step Logging) | step(name), step(name, takeScreenshot) ile HTML raporunda zaman çizelgesi |
Seçenek B — Kendi Taban Sınıfınızda @EnableTestFly Kullanımı
Projenizde halihazırda var olan bir sınıf hiyerarşisi varsa, taban sınıfınıza @EnableTestFly eklemeniz yeterlidir:
import io.testfly.driver.DriverManager;
import io.testfly.junit5.EnableTestFly;
import org.openqa.selenium.WebDriver;
@EnableTestFly
public abstract class CustomAppTest {
protected WebDriver getDriver() {
return DriverManager.getDriver();
}
}
@EnableTestFly, arka planda TestFlyExtension eklentisini otomatik olarak kaydeder.
Seçenek C — @ExtendWith(TestFlyExtension.class) ve Parametre Enjeksiyonu
Hiçbir sınıftan kalıtım almadan (POJO), doğrudan test metoduna WebDriver enjekte etmek istediğinizde:
import io.testfly.junit5.TestFlyExtension;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
@ExtendWith(TestFlyExtension.class)
class DirectInjectionTest {
@Test
void loginWithInjectedDriver(WebDriver driver) {
driver.get("https://example.com/login");
driver.findElement(By.id("username")).sendKeys("admin");
driver.findElement(By.cssSelector("button[type='submit']")).click();
}
}
TestFlyExtension, ilgili thread için sürücüyü otomatik başlatır, metoda geçirir ve test bittiğinde temizler.
Tarayıcısız Testler: @NoBrowser Desteği
Bir JUnit 5 test sınıfı veya metodu yalnızca REST API, veritabanı veya e-posta servislerini test ediyorsa, @NoBrowser ekleyebilirsiniz. TestFly gereksiz tarayıcı başlatma sürecini atlayarak testin saniyeler içinde tamamlanmasını sağlar:
import io.testfly.client.ApiResponse;
import io.testfly.junit5.BaseJUnit5Test;
import io.testfly.test.NoBrowser;
import org.junit.jupiter.api.Test;
import java.util.Map;
class UserApiIntegrationTest extends BaseJUnit5Test {
@Test
@NoBrowser // Tarayıcı açılmaz; doğrudan HTTP üzerinden koşar
void verifyUserCreationViaApi() {
ApiResponse response = apiPost("/api/users")
.body("{\"name\":\"John Doe\",\"email\":\"john@example.com\"}")
.send();
response.assertStatus(201)
.assertBodyContains("John Doe");
// Veritabanından kaydı doğrula
db().assertRowExists("users", Map.of("email", "john@example.com"));
}
}
@NoBrowser anotasyonunu sınıf düzeyinde tanımlayarak tüm metotları tarayıcısız hale de getirebilirsiniz.
Hibrit API ve UI Testi Örneği
BaseJUnit5Test, ApiSupport arayüzünü içerdiğinden, yavaş form doldurma adımları yerine arka planda API ile veri hazırlayıp doğrudan UI ekranına geçebilirsiniz:
import io.testfly.junit5.BaseJUnit5Test;
import io.testfly.locator.Role;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
class OrderHistoryTest extends BaseJUnit5Test {
@Test
void userCanViewCreatedOrder() {
// 1. Sipariş verisini REST API ile anında oluşturun
step("Sipariş verisi API ile oluşturulur");
String orderId = apiPost("/api/orders")
.body("{\"item\":\"Widget\",\"qty\":2}")
.send()
.json("$.id");
// 2. Sipariş geçmişi sayfasına doğrudan gidin
step("Sipariş detay sayfası tarayıcıda açılır");
open("/orders/" + orderId);
// 3. Anlamsal erişilebilirlik locator'ları ve soft assertion ile doğrulayın
softAssert(getByRole(Role.HEADING, "Sipariş Detayı")).isVisible();
softAssert(By.id("order-id")).hasText(orderId);
softAssert(By.className("order-status")).hasText("CONFIRMED");
}
}
Çoklu Kullanıcı / Multi-Session Senaryoları
Canlı sohbet (chat), ortak doküman düzenleme veya pazar yeri (alıcı ve satıcı) akışlarını test etmek için session() veya withSession() kullanabilirsiniz:
import io.testfly.junit5.BaseJUnit5Test;
import io.testfly.locator.Role;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
class MarketplaceChatTest extends BaseJUnit5Test {
@Test
void buyerAndSellerCanChat() {
// Oturum 1: Alıcı mesaj gönderir
session("buyer");
open("/chat/101");
getByPlaceholder("Mesajınızı yazın...").type("Ürün hala satılık mı?");
getByRole(Role.BUTTON, "Gönder").click();
// Oturum 2: Satıcı izole bir pencerede mesajı görüntüler
session("seller");
open("/chat/101");
assertThat(By.cssSelector(".message.received"))
.hasText("Ürün hala satılık mı?");
getByPlaceholder("Mesajınızı yazın...").type("Evet, hemen kargolayabilirim!");
getByRole(Role.BUTTON, "Gönder").click();
// Alıcı oturumuna dön ve gelen yanıtı doğrula
session("buyer");
assertThat(By.cssSelector(".message.incoming"))
.hasText("Evet, hemen kargolayabilirim!");
}
}
Her oturum aynı thread üzerinde tamamen izole çerezler, oturum depolama alanı (localStorage) ve tarayıcı profiliyle çalışır. Test bittiğinde tüm açık oturumlar güvenli şekilde kapatılır.
@PreCondition ile Oturum Önbellekleme
Ağır giriş (login) adımlarını her testte tekrarlamak yerine @PreCondition ile oturumu (çerezler + localStorage) önbelleğe alıp sonraki testlerde saniyeler içinde geri yükleyebilirsiniz:
import io.testfly.junit5.BaseJUnit5Test;
import io.testfly.precondition.PreCondition;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
class DashboardTest extends BaseJUnit5Test {
@Test
@PreCondition("loginAsAdmin")
@DisplayName("Dashboard görüntüleme — önbelleğe alınmış oturum geri yüklenir")
void viewDashboard() {
open("/dashboard");
assertThat(By.id("welcome-header")).isVisible();
}
@Test
@PreCondition("loginAsAdmin")
@DisplayName("Profil düzenleme — aynı oturum tekrar kullanılır")
void editProfile() {
open("/profile");
assertThat(By.id("profile-form")).isVisible();
}
}
Koşul Sağlayıcının (Condition Provider) Tanımlanması
BaseConditions sınıfını uygulayın ve Java SPI üzerinden kaydedin:
import io.testfly.precondition.BaseConditions;
import io.testfly.precondition.ConditionProvider;
import org.openqa.selenium.By;
public class AppConditions extends BaseConditions {
@ConditionProvider("loginAsAdmin")
public void loginAsAdmin() {
open("/login");
type(By.id("username"), "admin");
type(By.id("password"), "secret");
click(By.id("login-btn"));
}
}
SPI kayıt dosyası:
com.yourcompany.conditions.AppConditions
Yeniden Denemede Otomatik Temizlik: Bir
@PreConditiontesti hata alıp yeniden denendiğinde (retry), TestFly önbellekteki oturumu otomatik olarak temizler ve sağlayıcının sıfırdan taze çalışmasını sağlar.
@Retryable ile Akıllı Yeniden Deneme
Flaky (kararsız) testleri otomatik olarak yeniden denemek için sınıf veya metot düzeyinde @Retryable kullanabilirsiniz. Her deneme yepyeni ve temiz bir WebDriver örneğiyle başlatılır:
import io.testfly.junit5.BaseJUnit5Test;
import io.testfly.listeners.Retryable;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
class PaymentTest extends BaseJUnit5Test {
@Test
@Retryable(maxAttempts = 2) // Hata durumunda en fazla 2 kez yeniden dener (toplam 3 deneme)
void processPayment() {
open("/checkout");
find("#pay-btn").click();
assertThat(By.id("receipt")).isVisible();
}
}
@Retryable'ı sınıf düzeyine eklerseniz sınıftaki tüm test metotları için geçerli olur.maxAttemptsbelirtilmezsetestfly.ymliçindeki globalretry.maxAttemptsdeğeri kullanılır:
retry:
enabled: true
maxAttempts: 1
Yeniden denenmiş testler HTML test raporunda ↻ Nx rozeti ile işaretlenir.
JUnit 5 retry'ları testi yeniden planlayarak değil, TestFlyExtension.interceptTestMethod içinde uygulanır:
- Yalnızca test metodunun gövdesi yeniden çağrılır. Kendi
@BeforeEach/@AfterEachmetotlarınız ve TestFly'ınbeforeEachhazırlığı (test verisi yükleme,@UseAuth, kayıt başlatma, console-error shim) denemeler arasında tekrarlanmaz. Sürücü yeniden oluşturulur ve@PreConditionyeniden çalıştırılır. - JUnit tek bir test sonucu raporlar (son deneme). Önceki başarısız denemeler ayrı JUnit kayıtları olarak görünmez; yalnızca TestFly raporundaki retry sayısında görülür.
maxAttemptsek deneme sayısıdır (maxAttempts = 2→ en fazla 3 çalıştırma).- Retry sırasında yalnızca
WebDrivermetot parametreleri yeni sürücüyle değiştirilir; diğer enjekte edilen parametreler ilk değerlerini korur.
Tarayıcı Yaşam Döngüsü: Per-Test ve Per-Suite
testfly.yml dosyanızdan yaşam döngüsünü ayarlayabilirsiniz:
browser:
name: chrome
lifecycle: per-test # veya 'per-suite'
per-test(Varsayılan): Her test metodundan önce temiz bir tarayıcı açılır (beforeEach) ve test biter bitmez kapatılır (afterEach). Maksimum test izolasyonu sağlar.per-suite: Daha hızlı çalışma için tek bir tarayıcı testler arasında yeniden kullanılır. JUnit 5'teTestFlyExtension.afterAll()her test sınıfı bittiğinde tüm süit sürücülerini kapatır; bu nedenle yeniden kullanım TestNG'deki gibi tüm koşu boyunca değil, fiilen test sınıfı başına olur.
Paralel Çalıştırma
JUnit 5 paralel test çalıştırmayı yerleşik olarak destekler. src/test/resources/junit-platform.properties dosyasını oluşturun:
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.mode.default=concurrent
junit.jupiter.execution.parallel.mode.classes.default=concurrent
junit.jupiter.execution.parallel.config.strategy=fixed
junit.jupiter.execution.parallel.config.fixed.parallelism=4
TestFly'ın ThreadLocal sürücü mimarisi, paralel çalışan iş parçacıkları arasında tam oturum ve bellek izolasyonu sağlar.
Kurumsal Entegrasyonlar
JUnit 5 testleriniz TestFly'ın tüm kurumsal yeteneklerinden sıfır kod değişikliğiyle faydalanır:
1. Google Gemini & Claude AI Hata Analizi
Bir JUnit 5 testi fail ettiğinde, TestFly sayfa URL'ini, sayfa başlığını, DOM bağlamını ve stack trace'i toplayarak Google Gemini veya Anthropic Claude üzerinden kök neden analizi ve çözüm önerisi üretir:
ai:
failureAnalysis: true
provider: gemini
apiKey: ${GEMINI_API_KEY}
2. Test Yönetim Sistemleri (TestRail ve Xray)
Test sonuçları, çalışma süreleri ve hata mesajları TestRail veya Jira Xray sistemlerine otomatik olarak aktarılır.
3. Otomatik ReportPortal Köprüsü
reporting.reportportal.enabled=true ise TestFlyExtension otomatik olarak ReportPortalJUnit5Bridge üzerinden test başlatma, adım logları ve sonuçları ReportPortal'a aktarır.
4. Karantina Desteği (testfly-quarantine.yml)
Flaky testleri koda dokunmadan testfly-quarantine.yml ile karantinaya alabilirsiniz:
quarantine:
- test: com.yourcompany.tests.FlakyCheckoutTest#testPayment
reason: "Ödeme altyapısındaki timeout inceleniyor"
Karantinaya alınan testler henüz tarayıcı ayağa kaldırılmadan güvenle atlanır (SKIPPED).
Özellik Karşılaştırması: TestNG vs. JUnit 5
BaseTest yeteneklerinin çoğu JUnit 5'te mevcuttur. ⚠️ işareti, yukarıda açıklanan farklarla çalışan özellikleri gösterir.
| Özellik | TestNG | JUnit 5 |
|---|---|---|
| Otomatik WebDriver Yaşam Döngüsü | ✅ | ✅ |
per-suite Tarayıcı Yeniden Kullanımı | ✅ | ⚠️ test sınıfı başına |
| ThreadLocal Sürücü İzolasyonu | ✅ | ✅ |
Akıcı Locator'lar ($(), find()) | ✅ | ✅ |
Anlamsal Locator'lar (getByRole, getByText vb.) | ✅ | ✅ |
Web-Öncelikli Doğrulamalar (assertThat) | ✅ | ✅ |
Soft Doğrulamalar (softAssert) | ✅ | ✅ |
Yerleşik REST İstemcisi (apiClient, apiGet/Post) | ✅ | ✅ |
Çoklu Kullanıcı Oturumları (session(), withSession()) | ✅ | ✅ |
Veritabanı Doğrulamaları (db()) | ✅ | ✅ |
E-Posta Servis Testleri (mailbox()) | ✅ | ✅ |
Erişilebilirlik Taramaları (accessibility().scan()) | ✅ | ✅ |
@NoBrowser ile Tarayıcısız Çalışma | ✅ | ✅ |
| HTML Raporu ve Adım Zaman Çizelgesi | ✅ | ✅ |
| Hata Anında Otomatik Ekran Görüntüsü | ✅ | ✅ |
| Gemini / Claude AI Kök Neden Analizi | ✅ | ✅ |
| Yürütme İzi (Trace) ve Ekran Kaydı (Video) | ✅ | ✅ |
| JavaScript Konsol Hataları Denetimi | ✅ | ✅ |
@PreCondition Oturum Önbellekleme | ✅ | ✅ |
@Retryable Akıllı Yeniden Deneme Mekanizması | ✅ | ⚠️ yalnızca metot gövdesi |
testfly-quarantine.yml Karantina Desteği | ✅ | ✅ |
| ReportPortal, TestRail ve Xray Entegrasyonu | ✅ | ✅ |