Tüm yazılar
test-automation

TestFly Part 16: Akıllı Triyaj ve Bulanık Kendi Kendine Onarım — Token Harcamadan Kırılgan Test Teşhisi

TestFly 1.0.6, yapay zeka çağrısından önce çalışan, ücretsiz ve yerel bir sezgisel katman ekliyor: SmartTriageEngine başarısızlıkları token harcamadan sınıflandırıyor, FuzzyHealingEngine kırık locator'ları Levenshtein mesafesiyle 5ms altında onarıyor, Auto-POM PageKnowledge öğrenilen elemanları önbelleğe alıyor.

12 Ekim 20268 dk okuma
TestFly Part 16: Akıllı Triyaj ve Bulanık Kendi Kendine Onarım — Token Harcamadan Kırılgan Test Teşhisi

TestFly Part 16: Akıllı Triyaj ve Bulanık Kendi Kendine Onarım — Token Harcamadan Kırılgan Test Teşhisi

Özet: TestFly 1.0.6, herhangi bir yapay zekaya danışılmadan önce çalışan, yerel ve LLM maliyeti olmayan bir sezgisel (heuristic) katman ekliyor: SmartTriageEngine, exception hiyerarşisini inceleyerek bir başarısızlığı mikrosaniyeler içinde SYSTEM_FLAKY, APPLICATION_BUG veya NEEDS_INVESTIGATION olarak sınıflandırıyor; FuzzyHealingEngine ise kırık bir locator'ı, DOM adayları üzerinde düz Levenshtein mesafesi kullanarak 5 milisaniyenin altında onarıyor — ağ çağrısı yok, token yok, API anahtarı yok. Bu, Part 4'te ele alınan LLM tabanlı self-healing'den (testfly-mcp) farklı bir şey: o yol gerçekten belirsiz locator'lar için son çare; burada anlatılan sezgisel katman ise model gerektirmeyen vakaların (triyaj ve onarımın büyük çoğunluğunun) sessizce halledildiği ilk çare. PageKnowledge / Auto-POM sistemi ise bir sayfanın elemanlarını bir kez öğrenip bu bilgiyi — tamamen yerel olarak — her sonraki çalıştırmada yeniden kullanmayı sağlayarak resmi tamamlıyor.

Kırılgan Test Triyajı Neden Bir Sezgisel Katmana İhtiyaç Duyuyor?

Her CI pipeline'ı eninde sonunda kimsenin güvenmediği bir kırmızı build yığını biriktirir. Pahalı olan kısım testi yeniden çalıştırmak değil — neden başarısız olduğunu anlamaktır: altyapı aksaklığı mı, gerçek bir regresyon mu, yoksa bir insanın daha yakından bakması gereken bir şey mi? TestFly 1.0.6'dan önce bu triyaj ya manueldi ya da her başarısızlığın stack trace'ini bir LLM'e gönderip "bu bir timeout gibi görünüyor" cümlesini duymak için token ödemek anlamına geliyordu.

TestExecutionListener'a bağlanan SmartTriageEngine, bu triyajı başarısızlık raporlama akışının bir parçası olarak, eşzamanlı ve ücretsiz şekilde yapıyor:

java
package io.testfly.flakiness;

public final class SmartTriageEngine {

    public enum TriageResult {
        APPLICATION_BUG,
        SYSTEM_FLAKY,
        NEEDS_INVESTIGATION
    }

    public static TriageResult triageFailure(Throwable exception, boolean wasRetriedAndPassed) {
        if (exception == null) return TriageResult.NEEDS_INVESTIGATION;

        // Kural 1: Flakiness'in anında göstergesi
        if (wasRetriedAndPassed) {
            return TriageResult.SYSTEM_FLAKY;
        }

        int flakyScore = 0;
        Throwable rootCause = getRootCause(exception);

        // Kural 2: Exception sezgiselleri
        flakyScore += switch (rootCause) {
            case TimeoutException t -> 80;
            case StaleElementReferenceException s -> 80;
            case ConnectException c -> 90;
            case AssertionError a -> -50; // neredeyse her zaman bir uygulama hatası
            case WebDriverException w -> 40;
            case null, default -> 0;
        };

        if (flakyScore >= 70) return TriageResult.SYSTEM_FLAKY;
        else if (flakyScore <= 0) return TriageResult.APPLICATION_BUG;
        else return TriageResult.NEEDS_INVESTIGATION;
    }
}

Model çağrısı yok, prompt yok, gidiş-dönüş yok — sadece Java 21'in pattern-matching switch yapısı, (muhtemelen derinlemesine sarmalanmış) exception zincirinin kök nedeni üzerinde çalışıyor. Bir ConnectException 90 puan alır ve hemen SYSTEM_FLAKY olarak işaretlenir; bir AssertionError -50 puan alır ve hemen APPLICATION_BUG olarak işaretlenir, çünkü bir assertion'ın başarısız olması neredeyse her zaman altyapı gürültüsü değil, gerçek bir ürün hatasıdır. Hiçbir eşiği geçmeyen her şey NEEDS_INVESTIGATION'a düşer — kendinden emin görünen bir tahmin yerine dürüst bir "bilmiyorum, bir insan baksın".

TestExecutionListener, bunu her başarısız testte çağırıyor ve kararı doğrudan rapora yazıyor:

java
if (result.getThrowable() != null) {
    SmartTriageEngine.TriageResult triage =
        SmartTriageEngine.triageFailure(result.getThrowable(), false);

    String triageMsg = "[Triage] " + triage;
    if (triage == SmartTriageEngine.TriageResult.SYSTEM_FLAKY) {
        System.out.println("⚠️ " + triageMsg + " -> This is likely an infrastructure/flaky issue.");
    } else if (triage == SmartTriageEngine.TriageResult.APPLICATION_BUG) {
        System.out.println("🐞 " + triageMsg + " -> This is likely an application bug (Assertion).");
    }
    StepLogger.step(triageMsg, StepStatus.WARN);
}

Yeniden denendiğinde geçen bir test anında SYSTEM_FLAKY olarak işaretlenir — kural 1, exception puanlama akışının tamamını kısa devre yaptırır, çünkü yeniden denemede geçmek, herhangi bir exception türünden daha güçlü bir flakiness sinyalidir.

Bulanık Kendi Kendine Onarım Ne Kadar Hızlı?

FuzzyHealingEngine, TestFly bir elemanı yeniden bulmak için LLM'e sormadan önce çalışan fallback'tir. Saf, az tahsisli Java: locator ipuçları ile etkileşimli DOM adaylarının attribute/metin içeriği arasında Levenshtein mesafesi hesapla, puanla, eşiğin üzerindeki en iyi eşleşmeyi seç.

java
package io.testfly.healing;

public final class FuzzyHealingEngine {

    private static final String INTERACTIVE_ELEMENTS =
        "button, a, input, [role='button'], [role='link'], [tabindex='0']";
    private static final int THRESHOLD_SCORE = 5;

    public static WebElement tryFuzzyHeal(WebDriver driver, By originalLocator) {
        String desc = originalLocator.toString().toLowerCase();
        List<String> clues = extractClues(desc);
        if (clues.isEmpty()) return null;

        List<WebElement> candidates = driver.findElements(By.cssSelector(INTERACTIVE_ELEMENTS));

        WebElement bestMatch = null;
        int highestScore = 0;
        for (WebElement el : candidates) {
            int score = scoreElement(el, clues);
            if (score > highestScore) {
                highestScore = score;
                bestMatch = el;
            }
        }
        return highestScore >= THRESHOLD_SCORE ? bestMatch : null;
    }
}

İpucu çıkarma, locator sözdizimi gürültüsünü (cssselector, xpath, contains, text, data, testid) ayıklar ve tek bir regex geçişiyle 3+ karakterlik alfanümerik token'ları tutar. Puanlama, tam attribute eşleşmelerini bulanık metin eşleşmelerinden daha ağır tartar: bir id eşleşmesi 5 puan, bir placeholder veya value eşleşmesi 4 puan, bir CSS class eşleşmesi 3 puan değerindedir. Eleman metni ipucunu doğrudan içermiyorsa, motor kısa kelimeler (≤5 karakter) için bir, uzun kelimeler için iki yazım hatasına izin verir — attribute puanlamasının da arkasındaki aynı getLevenshteinDistance() uygulamasını (klasik tek-dizi DP, O(n·m)) kullanarak:

java
public static int getLevenshteinDistance(String a, String b) {
    int[] costs = new int[b.length() + 1];
    for (int j = 0; j < costs.length; j++) costs[j] = j;
    for (int i = 1; i <= a.length(); i++) {
        costs[0] = i;
        int nw = i - 1;
        for (int j = 1; j <= b.length(); j++) {
            int cj = Math.min(1 + Math.min(costs[j], costs[j - 1]),
                    a.charAt(i - 1) == b.charAt(j - 1) ? nw : nw + 1);
            nw = costs[j];
            costs[j] = cj;
        }
    }
    return costs[b.length()];
}

Yalnızca THRESHOLD_SCORE = 5 değerine eşit veya üzerinde puan alan elemanlar kırık locator'ın yerini alabilir — tek başına zayıf bir sinyal (örneğin 3 puanlık bir class eşleşmesi) yeterli değildir. Tipik bir DOM üzerinde sürecin 5ms'nin altında olmasını sağlayan da budur: ekran görüntüsü yok, DOM'u markup'a serileştirme yok, ağ gidiş-dönüşü yok — zaten alınmış findElements() aday listesi üzerinde sadece string karşılaştırmaları. TestFly, Part 4'te ele alınan LLM tabanlı self-healing'e ancak bu motor null döndürdüğünde yükselir.

Auto-POM / PageKnowledge Nedir?

Üçüncü parça döngüyü kapatıyor: PageKnowledgeStore, her URL yolu için bir LearnedPageModel'i .testfly/page-knowledge.json dosyasına kalıcı hale getiriyor. Böylece TestFly (ya da kayıtlı çalıştırmalar üzerinden bir insan) bir sayfadaki bir elemanı bir kez keşfettiğinde, dosya CI'da geri yüklendiği sürece, sonraki her çalıştırma onu yeniden keşfetmek ya da yeniden onarmak yerine bu bilgiyi yeniden kullanabiliyor.

java
public static Optional<LearnedPageModel> getPageModel(String url) {
    load();
    String key = normalizeUrl(url);
    return Optional.ofNullable(MODELS.get(key));
}

public static void recordElement(String url, LearnedElement element) {
    if (element == null || element.name().isBlank()) return;
    load();
    LearnedPageModel model = getOrCreate(url, null);
    model.putElement(element);
    save();
}

URL'ler path bileşenlerine normalize edilir (normalizeUrl), böylece /checkout?order=123 ve /checkout?order=456 aynı öğrenilmiş modeli paylaşır. İlk yüklemede, eğer store boşsa, KnowledgeLearner.learnFromActionCache() zaten var olan aksiyon geçmişinden bootstrap yapar — yani cache, ilk çalıştırmada bile tamamen soğuk değildir.

LocalIntentResolver, bu bilginin tüketicisidir: mevcut URL ve doğal dilde bir hedef verildiğinde, bir LLM çağrısını düşünmeden önce hedefi tamamen PageKnowledge'dan çözmeye çalışır:

java
public static ActionPlan resolve(String url, String goal) {
    Optional<LearnedPageModel> modelOpt = PageKnowledgeStore.getPageModel(url);
    if (modelOpt.isEmpty()) return null;

    LearnedPageModel model = modelOpt.get();

    ActionPlan formPlan = tryResolveFormInput(model, url, goal);
    if (formPlan != null) return formPlan;

    return tryResolveSingleElement(model, url, goal);
}

Basit, iki dilli bir login kalıbını bile tamamen yerel olarak çözebiliyor — hedef metninde "user"/"kullanıcı"/"email"/"posta" ve "pass"/"şifre"/"parola" anahtar kelimelerini tırnaklı literal değerlerle eşleştirip, öğrenilmiş kullanıcı adı/şifre/gönder elemanlarından doğrudan bir TYPE → TYPE → CLICK aksiyon planı üretiyor, model çıkarımı gerekmeden. Yerel çözücünün öğrenilmiş modelle eşleştiremediği bir hedef ancak o zaman LLM ajan yoluna düşer.

Token Harcamayan Katman Part 4'ün LLM Self-Healing'iyle Nasıl Bir Araya Geliyor?

Bunu en ucuzdan başlayan üç iç içe fallback halkası olarak düşünün:

  1. PageKnowledge / LocalIntentResolver — sayfa öğrenilmişse ve hedef bilinen bir eleman veya form kalıbıyla eşleşiyorsa, yerel olarak çözülür. Önbelleklenenin ötesinde DOM'u yeniden taramak yok, LLM yok.
  2. FuzzyHealingEngine — bilinen bir locator kırılırsa (yeniden adlandırılmış class, kaymış DOM), canlı etkileşimli elemanları Levenshtein mesafesi ve attribute örtüşmesiyle puanlar. 5ms'nin altında, LLM yok.
  3. LLM tabanlı self-healing (testfly-mcp, Part 4) — yalnızca 1 ve 2. adımların ikisi de güvenilir bir eşleşme üretemediğinde devreye girer. Token maliyeti ve ağ gecikmesi gerçekten burada ortaya çıkar ve bu, gerçekten belirsiz vakalara ayrılmıştır.

SmartTriageEngine ise bunun yanında bir başarısızlık-öncesi onarıcı değil, başarısızlık-sonrası sınıflandırıcı olarak yer alır: başarısızlığı önlemez, zaten elinizdeki başarısızlığın yeniden denemeye, araştırmaya ya da bir hata kaydına değip değmediğini hızlı ve ücretsiz şekilde söyler.

SSS

SmartTriageEngine, başarısızlıkları sınıflandırmak için bir LLM çağırıyor mu?

Hayır. Tamamen saf Java: exception'ın cause zincirini köküne kadar yürür, kökün tipini sabit bir switch ifadesiyle pattern-match eder ve sabit puanlar ekler/çıkarır. Kullandığı tek dış sinyal, testin yeniden denemede geçip geçmediğidir.

FuzzyHealingEngine bir locator'ı onarmadan önce hangi puanı istiyor?

Çıkarılan tüm ipuçları üzerinden en az THRESHOLD_SCORE = 5 kümülatif puan. Tam id eşleşmeleri 5, placeholder/value eşleşmeleri 4, class eşleşmeleri 3 puan katkı sağlar; bulanık metin eşleşmeleri de (ipucu uzunluğuna bağlı olarak 1-2 Levenshtein düzenlemesi içinde) 4 puan katkı sağlar. 5'in altında motor null döner ve çağıranın yükselmesine izin verir.

Auto-POM, her sayfanın önce manuel olarak haritalanmasını mı gerektiriyor?

Hayır — PageKnowledgeStore boş başlar ve gerçek çalıştırmalar sırasında elemanlar keşfedildikçe veya onarıldıkça recordElement() ile büyür; boş bir store'da KnowledgeLearner.learnFromActionCache() de onu önceden kaydedilmiş aksiyonlardan tohumlar, yani çalıştırılması gereken ayrı bir "sayfa nesnesi oluşturma" adımı yoktur.

FuzzyHealingEngine başarısız olursa TestFly testi hemen başarısız mı sayar?

Hayır — tryFuzzyHeal'den gelen bir null, Part 4'te anlatılan LLM tabanlı self-healing yoluna yükselme sinyalidir, sert bir başarısızlık değil. Sezgisel katman açıkça maliyet tasarrufu sağlayan bir ilk deneme olarak tasarlanmıştır, AI fallback'in yerine geçen bir şey değil.

#TestFly#Self-Healing#Java#SDET#AI
Paylaş X LinkedIn

Yorumlar

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