Tüm yazılar
test-automation

TestFly Bölüm 17: Tarayıcılar Arası Güvenilirlik — BiDi Konsol Yakalama, Grid'de Edge/Safari ve Garantili Driver Temizliği

TestFly 1.1.0, Firefox'un kaçırdığı konsol hatalarını yakalamak için gerçek bir WebDriver BiDi bağlantısı açıyor, browser.matrix altında Selenium Grid'de Edge ve Safari çalıştırıyor, ve JVM anormal şekilde kapandığında izlenen her tarayıcının kapatılmasını garanti ediyor.

12 Ekim 20267 dk okuma
TestFly Bölüm 17: Tarayıcılar Arası Güvenilirlik — BiDi Konsol Yakalama, Grid'de Edge/Safari ve Garantili Driver Temizliği

Özet: TestFly 1.0.6 ve 1.1.0, tarayıcılar arası güvenilirlikte üç boşluğu kapatıyor: browser.bidi, yerel olarak başlatılan bir Chrome, Edge veya Firefox'a gerçek bir WebDriver BiDi bağlantısı açıyor; böylece ConsoleErrorCollector, console.error çağrılarını ve yakalanmamış istisnaları bir JS shim yerine BiDi log olaylarından okuyor — Firefox'un daha önce gizlediği hataları yakalıyor — ve ConsoleErrorCollector.isBiDiActive() hangi yakalama yolunun çalıştığını söylüyor. RemoteDriverProvider artık Selenium Grid üzerinde Edge ve Safari'yi çözümlüyor ve browser.matrix'e saygı gösteriyor; daha önce yalnızca chrome/firefox biliyordu ve her zaman browser.name'i okuyordu. Ve JVM shutdown hook'ları ile DriverManager.quitAllTrackedDrivers(), Ctrl+C veya System.exit ile bir çalıştırma durdurulduğunda izlenen her tarayıcının — yalnızca hook thread'inin kendisine değil, her thread'e ait olanların — tam olarak bir kez kapatılmasını garanti ediyor.


BiDi, Firefox'un Kaçırdığı Konsol Hatalarını Nasıl Yakalıyor?

TestFly'ın captureConsoleErrors özelliği her zaman tarayıcı konsol hatalarını okudu, ancak klasik WebDriver üzerinden hiçbir tarayıcı günlüğü sunmayan Firefox'ta bu, yalnızca mevcut sayfada çalıştıktan sonra hataları gören bir JavaScript shim'i enjekte etmek anlamına geliyordu. Sayfa hâlâ yüklenirken fırlatılan bir hata, ya da bir bağlantı takip edilerek ulaşılan bir sayfadaki hata, shim için görünmezdi.

browser.bidi (varsayılan false) bunu protokol seviyesinde çözüyor: yerel olarak bir Chrome, Edge veya Firefox oturumu başlatıldığında, TestFly gerçek bir WebDriver BiDi bağlantısı istiyor ve testin tarayıcısını aldığı andan itibaren BiDi log olaylarına abone oluyor:

yaml
# testfly.yml
browser:
  name: firefox
  bidi: true
  captureConsoleErrors: true

Capability isteği tek satır, yalnızca BiDi açık yapılandırıldığında ekleniyor:

java
// BiDiCapability.java
static void applyIfConfigured(MutableCapabilities options, TestFlyConfig config) {
    if (config != null && config.getBrowser() != null && config.getBrowser().isBidi()) {
        options.setCapability("webSocketUrl", true);
    }
}

ConsoleErrorCollector, bu bağlantı üzerinden oturum başına bir LogInspector açıyor; bunlar WeakHashMap<WebDriver, BiDiCapture> içinde tutuluyor, böylece driver kaybolduktan sonra oturumlar bellekte sabitlenmiyor:

java
private static final Map<WebDriver, BiDiCapture> BIDI =
        Collections.synchronizedMap(new WeakHashMap<>());

Abonelik beginTest(driver) ile başladığından, bir önceki testin hataları yeniden kullanılan, per-suite bir tarayıcıda bile unutuluyor — JS shim yok, sayfa yükleme yarışı yok.

Safari ve uzak driver'lar (Selenium Grid, BrowserStack, Sauce Labs) TestFly'da bugün BiDi bağlantısına sahip değil. Bu durumda oturum başına bir uyarı loglanıyor ve çerçeve önceki yakalama yoluna geri dönüyor — hiçbir şey hata fırlatmıyor.

Logları okumadan hangi yolun gerçekten çalıştığını öğrenmek için:

java
if (ConsoleErrorCollector.isBiDiActive()) {
    // Bu oturum için yakalamayı BiDi log olayları yürütüyor
}
java
// ConsoleErrorCollector.java
@TestFlyApi(since = "1.1.0")
public static boolean isBiDiActive() {
    try {
        return BIDI.containsKey(DriverManager.getDriver());
    } catch (RuntimeException e) {
        return false;
    }
}

Hangi Tarayıcı Özelliklerinden Ödün Verdiğinizi Nasıl Bilirsiniz?

Yerel olarak başlatılan bir Chrome veya Edge, TestFly'a Chrome DevTools Protocol'ünü (CDP) verir; bu da ağ mocking'ini, cihaz emülasyonunu, konum (geolocation) override'larını ve screencast tabanlı video kaydediciyi güçlendirir. Firefox, Safari, Selenium Grid ve bulut oturumları CDP'yi açığa çıkarmaz — hatta uzak bir Chrome bile — bu yüzden bir şey söylemedikçe bu özellikler sessizce bozulur.

1.1.0'da yeni olan BrowserFeatures, CDP'ye bağımlı dört özelliği ve bunun yerine ne olacağını adlandırıyor:

java
public enum Feature {
    NETWORK_MOCK("Network interception requires a local Chrome/Edge session... "
            + "routes are ignored and requests reach the real network."),
    CDP_RECORDING("Video recording uses periodic screenshots instead of the "
            + "DevTools screencast, so frames are less frequent."),
    DEVICE_EMULATION("Device emulation only resizes the window and overrides "
            + "navigator.userAgent in the current page..."),
    GEOLOCATION("The location is overridden through JavaScript in the current "
            + "page only and is lost on navigation.");
}

BrowserFeatures.supports(driver, feature) soruyu doğrudan cevaplıyor (dördün üçü için driver instanceof ChromiumDriver, kayıt için HasDevTools), ve warnOnce(driver, feature) fallback'i her tarayıcı oturumu ve özellik başına tam olarak bir kez loglayıp tarayıcıyı adlandırıyor:

java
BrowserFeatures.warnOnce(driver, BrowserFeatures.Feature.GEOLOCATION);

GeoLocation zaten Firefox'ta JS override'ına geri dönmeden önce bunu çağırıyor. Video kaydı fallback'i daha önce sessizdi; artık her bozulan özellik bunu söylüyor.

Selenium Grid Üzerinde Edge ve Safari Nasıl Çalıştırılır?

Bu sürümden önce RemoteDriverProvider yalnızca ChromeOptions veya FirefoxOptions oluşturuyordu ve her zaman doğrudan browser.name'i okuyordu — böylece bir Grid üzerindeki browser.matrix çalıştırması, matrix tarayıcısını tamamen göz ardı ederek browser.name'in söylediği her neyse onu kullanıyordu; Grid üzerinde Edge/Safari ise hiç desteklenmiyordu.

Tarayıcı çözümlemesi artık hem yerel hem uzak yolun çağırdığı tek bir paylaşılan metottan geçiyor; BrowserContext (matrix override'ı) YAML'a karşı kazanıyor:

java
// DriverProviderFactory.java
public static String resolveBrowserName(TestFlyConfig config) {
    String contextBrowser = BrowserContext.get();
    if (contextBrowser != null && !contextBrowser.isBlank()) {
        return contextBrowser;
    }
    String name = config.getBrowser().getName();
    if (name == null || name.isBlank()) {
        throw new IllegalStateException(
                "No browser selected: set browser.name, or run through browser.matrix.");
    }
    return name;
}

RemoteDriverProvider.buildOptions(config, browser) artık dört Grid'de desteklenen tarayıcının tümü üzerinde switch yapan, bağımsız olarak test edilebilir, public bir metot:

java
// RemoteDriverProvider.java
switch (browser.toLowerCase(Locale.ROOT)) {
    case "chrome" -> options = chromium(new ChromeOptions(), browserConfig.isHeadless(), arguments);
    case "edge" -> options = chromium(new EdgeOptions(), browserConfig.isHeadless(), arguments);
    case "firefox" -> { /* FirefoxOptions, -headless */ }
    case "safari" -> {
        if (browserConfig.isHeadless()) {
            System.err.println("[TestFly] Safari does not support headless mode via Selenium; ignoring headless=true.");
        }
        options = new SafariOptions();
    }
    default -> throw new IllegalStateException(
            "Unsupported browser for remote execution: " + browser + ". Supported: chrome, edge, firefox, safari.");
}
options.setCapability("unhandledPromptBehavior", "ignore");

Aynı suite'i bir Grid üzerinde Chrome, Edge ve Safari'de çalıştıran bir matrix config artık değişiklik yapılmadan çalışıyor:

yaml
# testfly.yml
execution:
  mode: remote
  gridUrl: http://grid.local:4444/wd/hub
browser:
  matrix: [chrome, edge, safari]

Yan bir düzeltme (do not reuse a per-suite driver for another matrix browser), DriverManager'ın bir Chrome per-suite driver'ını Edge test etmesi gereken bir thread'e vermesini durduruyor — DRIVER_BROWSER_CONTEXT artık thread başına izleniyor ve yeniden kullanımdan önce kontrol ediliyor. Bir diğeri (quit the browser when post-launch session setup fails), başlatma sonrası timeout yapılandırması hata fırlattığında her yerleşik provider'ın yeniden fırlatmadan önce oturumu kapatmasını sağlıyor, böylece bir kurulum hatası artık bir tarayıcı sürecini sızdırmıyor.

TestFly Tarayıcıların Asla Çalışır Durumda Kalmamasını Nasıl Garanti Ediyor?

DriverManager, başından beri bir JVM shutdown hook'u kayıtlıydı, ancak bu sürüme kadar yalnızca per-suite driver'ları ve shutdown hook'unun kendi thread'ine bağlı olan her neyse onu kapatıyordu — ki bu, per-test bir driver'ı gerçekten çalıştıran thread asla değildi. Paralel bir per-test çalıştırmasını Ctrl+C veya System.exit ile durdurduğunuzda, worker-thread tarayıcıları JVM çıktıktan sonra çalışmaya devam ediyordu.

Hook artık quitAllTrackedDrivers()'ı çağırıyor; bu, hâlâ bir oturum izni (permit) tutan her driver'ı, kayıtlı her per-suite driver ile, tüm thread'ler genelinde birleştiriyor ve her birini tam olarak bir kez kapatıyor:

java
static void quitAllTrackedDrivers() {
    java.util.Set<WebDriver> drivers =
            java.util.Collections.newSetFromMap(new java.util.IdentityHashMap<>());
    drivers.addAll(PERMIT_HOLDERS);
    drivers.addAll(SUITE_DRIVERS);
    for (WebDriver driver : drivers) {
        try {
            driver.quit();
        } catch (Throwable ignored) {
        } finally {
            SUITE_DRIVERS.remove(driver);
            releasePermitOf(driver);
        }
    }
}

PERMIT_HOLDERS, şu anda bir oturum-slotu izni tutan driver'ların kümesidir — driver başına tam olarak tek bir kod yolu tarafından doldurulur ve temizlenir, ki bu da execution.maxActiveSessions'ı sızdırmaz yapan şeydir. Normal suite teardown'ı hâlâ SuiteExecutionListener, TestFlyExtension (JUnit 5) ve CucumberHooks'tan DriverManager.quitAllSuiteDrivers() ardından quitDriver() çağırıyor; shutdown hook, hangi thread'in tarayıcıya sahip olduğundan bağımsız olarak aynı temizleme mantığını tetikleyen, anormal çıkışlar için güvenlik ağıdır.

Bu, 1.0.6'nın orijinal "Guaranteed Driver Cleanup" satırına (shutdown hook'ları artı @AfterAll listener yürütmesi) dayanıyor: hook 1.0.6'dan beri vardı, bu sürüm onu yalnızca suite seviyesindekilere değil, gerçekten her thread'in tarayıcısına ulaştırıyor.

SSS

browser.bidi, Selenium Grid veya BrowserStack/Sauce Labs ile çalışıyor mu?

Hayır. BiDi yalnızca yerel olarak başlatılan bir Chrome, Edge veya Firefox için geçerli. Safari'de veya herhangi bir uzak driver'da, TestFly bir uyarı loglar ve önceki yakalama yoluna (Chrome'da CDP logları, diğer yerlerde JS shim) geri döner; hiçbir şey başarısız olmaz.

Mevcut test için hangi konsol-hatası yakalama yolunun aktif olduğunu nasıl kontrol ederim?

ConsoleErrorCollector.isBiDiActive()'i çağırın. Yalnızca mevcut thread'in driver'ının açık bir BiDi log aboneliği olduğunda true döner; hiçbir zaman hata fırlatmaz, driver yoksa veya kontrol başarısız olursa false döner.

Aynı suite'i tek bir Selenium Grid'e karşı Chrome, Edge ve Safari'de çalıştırabilir miyim?

Evet, execution.mode: remote, execution.gridUrl ve browser.matrix: [chrome, edge, safari] ile. RemoteDriverProvider, matrix tarayıcısını browser.name'den önce çözümler ve Safari'nin headless kısıtlaması dahil, desteklenen dört tarayıcının tümü için Grid'e uygun seçenekler oluşturur.

Paralel bir çalıştırmayı Ctrl+C ile öldürürsem, tüm tarayıcı süreçlerinin kesinlikle gittiğinden emin olabilir miyim?

Evet. JVM shutdown hook'u artık bir oturum izni tutan her driver'ı ve her per-suite driver'ı, hangi thread'de olursa olsun toplar ve her birini, hook'un kendisinin hangi thread'de çalıştığından bağımsız olarak, tam olarak bir kez kapatır.

#TestFly#Selenium#Cross-Browser#Java#SDET
Paylaş X LinkedIn

Yorumlar

  • Henüz yorum yok. İlk yorumu sen yaz.