# TestFly Part 16: Smart Triage & Fuzzy Self-Healing — Zero-Token Flaky-Test Diagnosis

URL: https://hakangul.lovable.app/blog/testfly-part-16-smart-triage-fuzzy-self-healing
Author: Hakan Gül — Team Lead Senior Test Automation Engineer
Published: 2026-10-12
Updated: 2026-10-12
Category: test-automation
Tags: TestFly, Self-Healing, Java, SDET, AI

> TestFly 1.0.6 adds a free, local, zero-LLM-cost heuristic layer that runs before any AI call: SmartTriageEngine classifies failures without tokens, FuzzyHealingEngine heals broken locators in under 5ms via Levenshtein distance, and Auto-POM PageKnowledge caches learned page elements across runs.

## English

# TestFly Part 16: Smart Triage & Fuzzy Self-Healing — Zero-Token Flaky-Test Diagnosis

**TL;DR:** TestFly 1.0.6 adds a *local, zero-LLM-cost heuristic layer* that runs before any AI is ever consulted: `SmartTriageEngine` inspects exception hierarchies to classify a failure as `SYSTEM_FLAKY`, `APPLICATION_BUG`, or `NEEDS_INVESTIGATION` in microseconds, and `FuzzyHealingEngine` repairs a broken locator in under 5ms using plain Levenshtein distance against DOM candidates — no network call, no tokens, no API key. This is distinct from Part 4's LLM-based self-healing (`testfly-mcp`): that path is the *last resort* for truly ambiguous locators; the heuristic layer covered here is the *first resort* that silently handles the 80% of healing and triage cases that don't need a model at all. The `PageKnowledge` / Auto-POM system ties it together by letting TestFly learn a page's elements once and reuse that knowledge — locally — on every subsequent run.

## Why Does Flaky-Test Triage Need a Heuristic Layer at All?

Every CI pipeline eventually accumulates a pile of red builds nobody trusts. The expensive part isn't re-running the test — it's figuring out *why* it failed: infrastructure hiccup, a real regression, or something that needs a human to look closer. Before TestFly 1.0.6, that triage was manual, or it meant shipping every failure's stack trace to an LLM and paying tokens for the privilege of hearing "this looks like a timeout."

`SmartTriageEngine`, wired into `TestExecutionListener`, does this triage synchronously, for free, as part of the failure-reporting path:

```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;

        // Rule 1: Immediate indicator of flakiness
        if (wasRetriedAndPassed) {
            return TriageResult.SYSTEM_FLAKY;
        }

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

        // Rule 2: Exception heuristics
        flakyScore += switch (rootCause) {
            case TimeoutException t -> 80;
            case StaleElementReferenceException s -> 80;
            case ConnectException c -> 90;
            case AssertionError a -> -50; // almost always an application bug
            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;
    }
}
```

No model call, no prompt, no round trip — just Java 21 pattern-matching `switch` over the root cause of a (possibly deeply wrapped) exception chain. A `ConnectException` scores 90 and is immediately flagged `SYSTEM_FLAKY`; an `AssertionError` scores -50 and is immediately flagged `APPLICATION_BUG`, because an assertion failing is almost always a real product bug, not infrastructure noise. Anything that doesn't clear either threshold lands in `NEEDS_INVESTIGATION` — an honest "I don't know, a human should look" rather than a confident-sounding guess.

`TestExecutionListener` calls this on every failed test and writes the verdict straight into the report:

```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);
}
```

A test that was retried and passed is flagged `SYSTEM_FLAKY` immediately — rule 1 short-circuits the whole exception-scoring path, because a pass-on-retry is the strongest possible signal of flakiness, stronger than any exception type.

## How Fast Is Fuzzy Self-Healing?

`FuzzyHealingEngine` is the fallback that runs *before* TestFly ever asks an LLM to re-find an element. It's pure, allocation-light Java: compute Levenshtein distance between locator clues and attribute/text content of interactive DOM candidates, score them, and pick the best match above a threshold.

```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;
    }
}
```

Clue extraction strips locator syntax noise (`cssselector`, `xpath`, `contains`, `text`, `data`, `testid`) and keeps alphanumeric tokens of 3+ characters via a single regex pass. Scoring then weighs exact attribute containment higher than fuzzy text matches: an `id` match is worth 5 points, a `placeholder` or `value` match 4, a CSS `class` match 3. For element text that doesn't contain the clue outright, the engine allows one typo for short words (≤5 chars) or two for longer ones, using the same `getLevenshteinDistance()` implementation (classic single-array DP, O(n·m)) that backs the attribute scoring:

```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()];
}
```

Only elements scoring at or above `THRESHOLD_SCORE = 5` are eligible to replace the broken locator — a single weak signal (one 3-point class match, say) isn't enough on its own. This is what makes the pass sub-5ms on a typical DOM: no screenshot, no DOM-to-markup serialization, no network hop — just string comparisons over the already-fetched `findElements()` candidate list. Only when this engine returns `null` does TestFly escalate to the LLM-based self-healing covered in Part 4.

## What Is Auto-POM / PageKnowledge?

The third piece closes the loop: `PageKnowledgeStore` persists a `LearnedPageModel` per URL path to `.testfly/page-knowledge.json`, so once TestFly (or a human, via recorded runs) discovers an element on a page, every later run — including on CI, as long as the file is restored — can reuse that knowledge instead of re-discovering or re-healing it.

```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();
}
```

URLs are normalized to their path component (`normalizeUrl`) so `/checkout?order=123` and `/checkout?order=456` share the same learned model. On first load, if the store is empty, `KnowledgeLearner.learnFromActionCache()` bootstraps it from whatever action history already exists — so the cache isn't cold even on a fresh checkout.

`LocalIntentResolver` is the consumer: given a natural-language goal and the current URL, it tries to resolve the goal entirely from `PageKnowledge`, *before* considering an LLM call at all:

```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);
}
```

It even handles a simple bilingual login pattern locally — keyword-matching `"user"/"kullanıcı"/"email"/"posta"` and `"pass"/"şifre"/"parola"` against the goal text plus quoted literal values, then emitting a `TYPE` → `TYPE` → `CLICK` action plan straight from the learned username/password/submit elements, no model inference required. Only a goal the local resolver can't match against the learned model falls through to the LLM agent path.

## How Does the Zero-Token Layer Fit With Part 4's LLM Self-Healing?

Think of it as three concentric fallback rings, cheapest first:

1. **`PageKnowledge` / `LocalIntentResolver`** — if the page has been learned and the goal matches a known element or form pattern, resolve locally. No DOM re-scan beyond what's cached, no LLM.
2. **`FuzzyHealingEngine`** — if a known locator breaks (renamed class, shifted DOM), score live interactive elements by Levenshtein distance and attribute overlap. Sub-5ms, no LLM.
3. **LLM-based self-healing (`testfly-mcp`, Part 4)** — only reached when steps 1 and 2 both fail to produce a confident match. This is where token cost and network latency actually show up, and it's reserved for genuinely ambiguous cases.

`SmartTriageEngine` sits alongside this as a post-failure classifier rather than a pre-failure healer: it doesn't prevent the failure, it tells you fast — for free — whether the failure you already have is worth a retry, an investigation, or a bug ticket.

## FAQ

### Does SmartTriageEngine call an LLM to classify failures?
No. It's pure Java: it walks the exception's cause chain to the root, pattern-matches the root's type against a fixed switch expression, and adds/subtracts fixed point values. The only external signal it uses is whether the test passed on retry.

### What score does FuzzyHealingEngine require before it heals a locator?
A cumulative score of at least `THRESHOLD_SCORE = 5` across all extracted clues. Exact `id` matches contribute 5, `placeholder`/`value` matches 4, `class` matches 3, and fuzzy text matches (within 1–2 Levenshtein edits depending on clue length) contribute 4. Below 5, the engine returns `null` and lets the caller escalate.

### Does Auto-POM require every page to be manually mapped first?
No — `PageKnowledgeStore` starts empty and grows via `recordElement()` as elements are discovered or healed during real runs; on an empty store, `KnowledgeLearner.learnFromActionCache()` also seeds it from prior recorded actions, so there's no separate "page object generation" step to run.

### If FuzzyHealingEngine fails, does TestFly immediately fail the test?
No — a `null` return from `tryFuzzyHeal` is the signal to escalate to the LLM-based self-healing path described in Part 4, not a hard failure. The heuristic layer is explicitly a cost-saving first attempt, not a replacement for the AI fallback.

## Türkçe

# 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.
