TestFly Bölüm 13: Actionability Motoru — Otomatik Bekleme ve Doğrulanmış Click/Fill/Clear Assertion'ları
TestFly 1.1.0, Playwright tarzı bir actionability motoru ekliyor: her click, hover, type ve clear, eylemden önce elementin görünür, etkin, sabit ve kapatılmamış olmasını bekliyor; yeni assertThat(x).click()/.fill()/.clear() ise eylemin gerçekten sayfaya ulaştığını ve onu değiştirdiğini doğruluyor.
Özet: TestFly 1.1.0, her click, hover, type, clear ve append çağrısının DOM'a dokunmadan önce geçtiği bir actionability (eyleme hazırlık) motoru ekliyor: görünür, gerektiğinde etkin (enabled), geometrik olarak sabit, başka bir eleman tarafından kapatılmamış ve gerekirse görünüme kaydırılmış. Bunun üzerine assertThat(x).click() / .fill(text) / .clear(), eylemin kendisini bir assertion'a dönüştürüyor — click olayının gerçekten elemente ulaştığını ve yazılan metnin alanda kaldığını doğruluyor. Bir hata NotActionableException fırlatır ve elementi ile tam engelleyici nedeni adlandırır.
TestFly Bir Elementin Eyleme Hazır Olduğuna Nasıl Karar Veriyor?
Her etkileşim, hangi kontrollerin uygulanacağına karar veren bir ActionKind ile etiketlenir:
public enum ActionKind {
CLICK(true, true), // etkin olmalı, ve başka bir eleman tarafından kapatılmamalı
HOVER(false, true), // kapatılmamalı; devre dışı bir eleman yine de hover edilebilir
EDIT(true, false); // etkin olmalı; kapatılmış olması klavye girişi için önemli değil
}Bayraklarından bağımsız olarak her tür, elementin görünür ve sabit olmasını gerektirir. WaitEngine.waitForActionable(element, kind), gereken tüm kontroller geçene kadar polling yapar ve element pollinglar arasında ekran dışına kayarsa yeniden görünüme kaydırır:
public static WebElement waitForActionable(WebElement element, ActionKind kind) {
TestFlyConfig.Actionability cfg = actionabilityConfig();
if (!cfg.isEnabled()) {
return kind == ActionKind.CLICK ? waitForClickable(element) : waitForVisible(element);
}
// görünür, etkin (gerekiyorsa), sabit bounding box, kapatılmamış (gerekiyorsa)...
}Stale (koptu) bir eleman StaleElementReferenceException olarak yukarı taşınır, böylece çağıran taraf (Locator zinciri) kopmuş bir düğümde hata vermek yerine elementi sıfırdan yeniden çözebilir.
Tam Olarak Ne Kontrol Ediliyor, Hangi Sırayla?
| Kontrol | Uygulandığı tür | Detay |
|---|---|---|
| Görünür | tüm türler | element boş olmayan bir bounding box'a sahip ve display:none/visibility:hidden değil |
| Etkin | CLICK, EDIT | HOVER için atlanır — devre dışı bir buton yine de hover edilebilir |
| Sabit | tüm türler | ardışık iki pollingde bounding box değişmemiş (varsayılan polling aralığı: 100ms) |
| Kapatılmamış | CLICK, HOVER | elementin merkezinde başka hiçbir şey hit-test'i almıyor; EDIT için atlanır |
| Otomatik kaydırma | tüm türler | kontrol sırasında ekran dışındaysa elementi viewport merkezine kaydırır |
stabilityIntervalMs ayarı (varsayılan 100ms) hem polling aralığıdır hem de bir elementin bounding box'ının yerinde kalması gereken minimum süredir — Playwright'ın kullandığı aynı anti-animasyon fikrinin, Selenium'un WebElement'ine uyarlanmış hali.
Bir Element Hiç Eyleme Hazır Olmazsa Ne Olur?
public class NotActionableException extends TimeoutException {
public NotActionableException(String message, Throwable cause) {
super(message, cause);
}
}TimeoutException'dan türetilir, böylece mevcut catch (TimeoutException e) yakalayıcıları çalışmaya devam eder. Mesaj, elementi ve tam engelleyici nedeni adlandırır — örneğin merkez noktayı hangi overlay'in kapattığını, ya da bounding box'ın sürekli hareket ettiğini. Hiçbir şey sessizce yanlış noktaya tıklamaz veya yarı animasyonlu bir alana yazmaz.
Actionability Kontrollerini Nasıl Yapılandırır veya Atlarım?
# testfly.yml
actionability:
enabled: true # false, 1.1.0 öncesi bekleme davranışını geri getirir
stabilityIntervalMs: 100
hitTargetCheck: true
autoScroll: truepublic static final class Actionability {
private boolean enabled = true;
private int stabilityIntervalMs = 100;
private boolean hitTargetCheck = true;
private boolean autoScroll = true;
}actionability.enabled: false ayarı, eski bekleme davranışına döner: click'ler için waitForClickable, diğer her şey için waitForVisible — bir geçiş sürecinde ya da bir sayfanın gerçekten eski davranışa ihtiyaç duyduğu durumlarda kullanışlıdır.
Tek bir locator için Locator.force(), yalnızca sabitlik ve kapatılmamışlık kontrollerini atlar (Playwright'ın force: true'su), elementin yine de görünür olmasını ve eylemin gerektirdiği yerde etkin olmasını zorunlu tutar:
find("#banner-close").force().click();BasePage.doubleClick, rightClick, selectBy* ve BaseConditions.click/type, hepsi aynı waitForActionable çağrısından geçer, böylece davranış tüm API yüzeyinde tutarlıdır.
assertThat(x).click() Bir Tıklamanın Gerçekten Ulaştığını Nasıl Doğruluyor?
Düz bir Locator.click(), actionability beklemesini yapar ve tıklar — öncekiyle aynı. assertThat(x).click() bir adım daha ileri gider ve eylemi bir assertion'a dönüştürür:
assertThat(loginButton).click();- Elementi
Locator.click()'in yaptığı gibi çözer (self-healing dahil) ve görünür ve etkin olana kadar bekler. - Onu viewport'un merkezine kaydırır ve
Actionsile gerçek bir kullanıcı gibi fareyi üzerine taşır. - Merkez noktasını başka hiçbir şeyin — çerez banner'ı, modal, yapışkan header — kapatmadığı ana kadar bekler.
- Tıklar, ardından
clickolayının gerçekten elemente ya da onun bir alt elementine ulaştığını assert eder.
Başarı durumunda geçen bir Clicked: ... adımı loglanır. Başarısızlıkta, hangi adımın başarısız olduğunu adlandıran normal bir AssertionError (ya da soft modda kaydedilmiş bir soft failure) döner. Eğer tıklama, olay dinleyicisi sonucu okuyabilmeden önce sayfadan uzaklaşır ya da bir alert açarsa, tıklama yine de ulaşmış sayılır — sayfa ilerledi diye başarısız olmaz. Locator.click()'in kendisi değişmedi; bu, LocatorAssert üzerinde eklenen, daha katı bir kardeş metottur.
assertThat(x).fill(text) ve .clear() Alanın Gerçekten Değiştiğini Nasıl Doğruluyor?
assertThat(userNameField).fill("admin");
assertThat(passwordField).fill(password);
assertThat(searchField).clear();İkisi de tek bir yolu (fillAndVerify) paylaşır:
- Elementi
Locator.fill(String)'in yapacağı gibi çözer (bekleme ve self-healing ile) ve görünür ve etkin olana kadar bekler. - Alanın
readonlyolmadığı ana kadar bekler, ardından viewport merkezine kaydırır. - Alanı temizler ve metni tam olarak
Locator.type(String)'in yaptığı gibi yazar. - Alanın değerinin metne tam olarak eşit olup öyle kaldığı ana kadar bekler —
maxlength, bir input mask ya da framework state'inin (React, Vue…) yazılan değeri sessizce değiştirdiği ya da sıfırladığı durumları yakalar.
clear(), boş bir string ile aynı yoldur. type="password" alanları için değerin kendisi hiçbir zaman loglanmaz ya da hata mesajına yazılmaz — yalnızca uzunluğu yazılır. Yazarken girişi yeniden biçimlendiren alanlar (örneğin telefon maskeleri) bu daha katı assertion'da başarısız olur; bunlar için Locator.fill(text)'i ardından assertThat(x).hasValue(formatlanmışDeğer) ile kullanın.
SSS
Actionability kontrollerini açmak her testi yavaşlatır mı?
Her eylem, 1.1.0 öncesi bekleyişlere kıyasla yaklaşık bir ekstra polling aralığı (varsayılan 100ms) bekler, çünkü motor bounding box'ın hareketi durdurduğunu doğrulamak için en az iki ardışık sabit polling'e ihtiyaç duyar. JavaScript kullanılamadığında kontroller fail-open olur, yani JS destekli bir oturuma zorunlu bir bağımlılık yoktur.
assertThat(x).click(), Locator.click()'in yerini mi alıyor?
Hayır. Locator.click() değişmedi ve hâlâ sadece actionability beklemesini yapıp tıklıyor. assertThat(x).click(), click olayının elemente ulaştığını da assert eden, geçen bir Clicked: ... adımı loglayan ve as(...), within(...) ve soft modu destekleyen, eklenmiş ve doğrulanmış bir kardeş metottur.
not()'u doğrulanmış click()/fill()/clear() ile birleştirebilir miyim?
Hayır — bu üç metot rejectNegation(...) çağırır ve not() kendilerinden önce zincirlenmişse hata fırlatır. Bunlar bir eylemin gerçekleştiğini ve sayfayı doğrulanabilir şekilde değiştirdiğini assert ediyor; iyi tanımlanmış bir "negatif tıklama" yok.
force() ile actionability.enabled: false arasındaki fark nedir?
force() locator başınadır ve yalnızca sabitlik ve kapatılmamışlık kontrollerini atlar — element yine de görünür olmalı, eylemin gerektirdiği yerde etkin olmalıdır. actionability.enabled: false globaldir ve suite'teki her eylemi 1.1.0 öncesi bekleyişlere (waitForClickable/waitForVisible) geri döndürür, yeni kontrollerin tümünü bir kerede kaldırır.