Puppeteer ve seçicilere yaklaşımı
Puppeteer, Node için bir tarayıcı otomasyon kitaplığıdır: Basit ve modern bir JavaScript API kullanarak tarayıcıları kontrol etmenize olanak tanır.
En önemli tarayıcı görevi elbette web sayfalarına göz atmaktır. Bu görevi otomatik hâle getirmek, temelde web sayfasıyla etkileşimlerin otomatikleştirilmesi anlamına gelir.
Puppeteer'da bu, dize tabanlı seçicileri kullanarak DOM öğelerini sorgulayarak ve öğeleri tıklama veya öğelerde metin yazma gibi işlemler gerçekleştirerek gerçekleştirilir. Örneğin, developer.google.com sayfasını açan, arama kutusunu bulan ve puppetaria
için arama yapan bir komut dosyası aşağıdaki gibi görünebilir:
(async () => {
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
await page.goto('https://developers.google.com/', { waitUntil: 'load' });
// Find the search box using a suitable CSS selector.
const search = await page.$('devsite-search > form > div.devsite-search-container');
// Click to expand search box and focus it.
await search.click();
// Enter search string and press Enter.
await search.type('puppetaria');
await search.press('Enter');
})();
Bu nedenle, sorgu seçiciler kullanılarak öğelerin nasıl tanımlandığı, Kuklacı deneyiminin tanımlayıcı bir parçasıdır. Şu ana kadar Puppeteer'daki seçiciler CSS ve XPath seçicilerle sınırlıydı. Bu seçiciler, ifade açısından çok güçlü olsalar da komut dosyalarındaki tarayıcı etkileşimlerinin devam etmesinde dezavantajlar yaratabiliyorlar.
Sözdizimsel ve anlamsal seçiciler
CSS seçiciler söz dizimseldir; DOM ağacındaki kimliklere ve sınıf adlarına referans verdikleri şekilde DOM ağacının metinsel temsilinin iç işleyişine sıkı sıkıya bağlıdırlar. Bu nedenle, web geliştiricilerinin sayfadaki bir öğeye stil eklemesi veya ekleme yapması için dahili bir araç sağlarlar. Ancak bu bağlamda, geliştirici, sayfa ve DOM ağacı üzerinde tam kontrole sahiptir.
Diğer yandan, Puppeteer komut dosyası bir sayfanın harici gözlemcisidir. Dolayısıyla bu bağlamda CSS seçiciler kullanıldığında, Puppeteer komut dosyasının üzerinde kontrol sahibi olmadığı, sayfanın nasıl uygulandığıyla ilgili gizli varsayımlar ortaya çıkar.
Bunun nedeni, bu tür komut dosyalarının kırılgan ve kaynak kodu değişikliklerine duyarlı olabilmesidir. Örneğin, body
öğesinin üçüncü alt öğesi olarak <button>Submit</button>
düğümünü içeren bir web uygulamasının otomatik test amacıyla Puppeteer komut dosyaları kullandığını varsayalım. Test durumundan alınan bir snippet şöyle görünebilir:
const button = await page.$('body:nth-child(3)'); // problematic selector
await button.click();
Burada, gönder düğmesini bulmak için 'body:nth-child(3)'
seçiciyi kullanıyoruz ancak bu seçici, web sayfasının tam olarak bu sürümüyle bağlantılıdır. Daha sonra düğmenin üzerine bir öğe eklenirse bu seçici artık çalışmaz.
Bu, yazarları test etmek için bir haber değil: Kukla kullanıcılar zaten bu tür değişikliklere uygun seçicileri seçmeye çalışıyorlar. Puppetaria ile bu görevde kullanıcılara yeni bir araç sunuyoruz.
Puppeteer artık CSS seçicilere güvenmek yerine erişilebilirlik ağacını sorgulamaya dayalı alternatif bir sorgu işleyici ile sunuluyor. Buradaki temel felsefe, seçmek istediğimiz somut öğe değişmediyse ona karşılık gelen erişilebilirlik düğümünün de değişmemiş olması gerektiğidir.
Bu seçicileri "ARIA seçiciler" olarak adlandırırız. ve erişilebilirlik ağacının hesaplanan erişilebilir adı ve rolü için sorgulama desteği vardır. CSS seçicilerle karşılaştırıldığında bu özellikler anlamsaldır. Bunlar, DOM'nin söz dizimsel özelliklerine bağlı değildir. Bunun yerine, ekran okuyucular gibi yardımcı teknolojiler aracılığıyla sayfanın nasıl gözlemlendiğini açıklar.
Yukarıdaki test komut dosyası örneğinde, istenen düğmeyi seçmek için aria/Submit[role="button"]
seçiciyi kullanabiliriz. Burada Submit
, öğenin erişilebilir adını ifade eder:
const button = await page.$('aria/Submit[role="button"]');
await button.click();
Şimdi, daha sonra Submit
olan düğmemizin metin içeriğini Done
olarak değiştirmeye karar verirsek test yine başarısız olur, ancak bu durumda arzu edilen bir şeydir; düğmenin adını değiştirerek sayfanın içeriğini, görsel sunumunu veya DOM'de nasıl yapılandırıldığını değil, içeriğini değiştiririz. Testlerimiz, bu tür değişikliklerin bilinçli olarak yapıldığından emin olmamız için bizi bu tür değişiklikler konusunda uyarmalıdır.
Arama çubuğunu içeren daha büyük örneğe geri dönecek olursak, yeni aria
işleyicisini kullanabilir ve
const search = await page.$('devsite-search > form > div.devsite-search-container');
ile
const search = await page.$('aria/Open search[role="button"]');
tıklayın!
Daha genel anlamda bu tür ARIA seçicileri kullanmanın Puppeteer kullanıcılarına aşağıdaki avantajları sağlayabileceğini düşünüyoruz:
- Test komut dosyalarındaki seçicileri kaynak kodu değişikliklerine karşı daha dirençli hale getirin.
- Test komut dosyalarını daha okunabilir hale getirin (erişilebilir adlar, anlamsal tanımlayıcılardır).
- Öğelere erişilebilirlik özellikleri atama konusunda iyi uygulamaları teşvik etme.
Bu makalenin devamında, Puppetaria projesini nasıl uyguladığımıza dair ayrıntılar ele alınmaktadır.
Tasarım süreci
Arka plan
Yukarıdaki amacımız doğrultusunda, sorgu öğelerini erişilebilir adları ve rollerine göre etkinleştirmek istiyoruz. Bunlar, web sayfalarını göstermek için ekran okuyucular gibi cihazlar tarafından kullanılan, olağan DOM ağacının bir parçası olan erişilebilirlik ağacının özellikleridir.
Erişilebilir adı hesaplama spesifikasyonuna bakıldığında, bir öğenin adını hesaplamanın önemsiz bir görev olduğu açıktır. Bu nedenle, başından itibaren bunun için Chromium'un mevcut altyapısını yeniden kullanmaya karar verdik.
Uygulama yaklaşımımız
Kendimizi Chromium'un erişilebilirlik ağacıyla sınırlı tutsak bile Puppeteer'da ARIA sorgulamasını uygulayabileceğimiz birkaç yol vardır. Nedenini görmek için, önce Puppeteer'ın tarayıcıyı nasıl kontrol ettiğine bakalım.
Tarayıcı, Chrome DevTools Protocol (CDP) adlı protokol aracılığıyla bir hata ayıklama arayüzü gösterir. Bu, "sayfayı yeniden yükle" gibi işlevler sunar. veya "sayfada bu JavaScript parçasını yürüt ve sonucu geri ver" bir deneyim sunar.
Hem Geliştirici Araçları kullanıcı arabirimi hem de Puppeteer, tarayıcıyla konuşmak için CDP kullanıyor. CDP komutlarını uygulamak için Chrome'un tüm bileşenlerinin içinde (tarayıcıda, oluşturucuda vb.) Geliştirici Araçları altyapısı bulunur. CDP, komutların doğru yere yönlendirilmesini sağlar.
İfadeleri sorgulama, tıklama ve değerlendirme gibi kuklacıların işlemleri, JavaScript'i doğrudan sayfa bağlamında değerlendiren ve sonucu geri veren Runtime.evaluate
gibi CDP komutlarından yararlanarak gerçekleştirilir. Renk körlüğü emülasyonu, ekran görüntüsü alma veya izleri yakalama gibi diğer Puppeteer işlemleri, Blink oluşturma süreciyle doğrudan iletişim kurmak için CDP'yi kullanır.
Bu durumda, sorgu işlevimizi uygulamak için kullanabileceğimiz iki yol var: şunları yapabiliriz:
- Sorgulama mantığımızı JavaScript'te yazıp
Runtime.evaluate
kullanarak sayfaya yerleştirin. - Doğrudan Blink işleminde erişilebilirlik ağacına erişebilen ve sorgulayabilen bir CDP uç noktası kullanın.
3 prototip uyguladık:
- JS DOM geçişi: Sayfaya JavaScript eklenmesine dayalı
- Puppeteer AXTree geçişi: Erişilebilirlik ağacına mevcut CDP erişiminin kullanılmasına dayanır
- CDP DOM geçişi: Erişilebilirlik ağacını sorgulamak için amaca yönelik yeni bir CDP uç noktası kullanır
JS DOM geçişi
Bu prototip, DOM'de tam bir geçiş yapar ve geçiş sırasında her bir öğenin adını ve rolünü almak için ComputedAccessibilityInfo
başlatma işaretinde korumalı element.computedName
ve element.computedRole
kullanır.
Puppeteer AXTree geçişi
Burada, onun yerine CDP aracılığıyla erişilebilirlik ağacının tamamını alıp Puppeteer'da tararız. Ortaya çıkan erişilebilirlik düğümleri daha sonra DOM düğümleriyle eşlenir.
CDP DOM geçişi
Bu prototip için özellikle erişilebilirlik ağacını sorgulamak amacıyla yeni bir CDP uç noktası uyguladık. Bu şekilde, sorgulama işlemi JavaScript aracılığıyla sayfa bağlamı yerine bir C++ uygulaması aracılığıyla arka uçta gerçekleştirilebilir.
Birim testi karşılaştırma değeri
Aşağıdaki şekilde, 3 prototip için dört öğenin 1.000 kez sorguladığı toplam çalışma süresi karşılaştırılmaktadır. Karşılaştırma, sayfa boyutuna ve erişilebilirlik öğelerinin önbelleğe alınıp alınmadığına bakılmaksızın 3 farklı yapılandırmada yürütüldü.
CDP destekli sorgu mekanizması ile yalnızca Puppeteer'da uygulanan diğer iki yöntem arasında kayda değer bir performans farkı olduğu oldukça açıktır ve göreli fark, sayfa boyutuyla birlikte büyük ölçüde artmaktadır. JS DOM geçiş prototipinin, erişilebilirlik önbelleğe almayı etkinleştirmeye çok iyi yanıt verdiğini görmek biraz ilginçtir. Önbelleğe alma devre dışı bırakıldığında erişilebilirlik ağacı isteğe bağlı olarak hesaplanır ve alan devre dışı bırakıldıysa her etkileşimden sonra ağacı silinir. Alan adı etkinleştirildiğinde Chromium, bunun yerine hesaplanan ağacı önbelleğe alır.
JS DOM geçişi için geçiş sırasında her öğenin erişilebilir adını ve rolünü isteriz. Bu nedenle, önbelleğe alma devre dışı bırakılırsa Chromium, ziyaret ettiğimiz her öğe için erişilebilirlik ağacını hesaplar ve siler. CDP tabanlı yaklaşımlarda ise ağaç yalnızca her bir CDP çağrısı arasında, yani her sorgu için silinir. Bu yaklaşımlar, önbelleğe almanın etkinleştirilmesinden de faydalanır. Erişilebilirlik ağacı daha sonra CDP çağrıları genelinde aynı kalır, ancak bu nedenle performans artışı nispeten daha az olur.
Önbelleğe almayı etkinleştirmek istenen bir yer gibi görünse de, ek bellek kullanımı maliyetini de beraberinde getirir. Bu durum, izleme dosyalarını kaydeden Puppeteer komut dosyaları için sorunlu olabilir. Bu nedenle, erişilebilirlik ağacı önbelleğine almayı varsayılan olarak etkinleştirmemeye karar verdik. Kullanıcılar, CDP Erişilebilirlik alanını etkinleştirerek önbelleğe almayı kendileri etkinleştirebilir.
Geliştirici Araçları test paketi karşılaştırması
Önceki karşılaştırma, sorgulama mekanizmamızı CDP katmanında uygulamanın, klinik birim testi senaryosunda performans artışı sağladığını gösterdi.
Tam test paketi çalıştırmaya yönelik daha gerçekçi bir senaryoda farkın belirgin ölçüde belirgin olup olmadığını görmek için Geliştirici Araçları uçtan uca test paketine yama uyguladık. Böylece JavaScript ve CDP tabanlı prototiplerden yararlanıp çalışma zamanlarını karşılaştırdık. Bu karşılaştırmada, toplam 43 seçiciyi [aria-label=…]
yerine aria/…
özel sorgu işleyicisi olarak değiştirdik. Daha sonra bu prototiplerin her birini kullanarak bunları uyguladık.
Seçicilerden bazıları test komut dosyalarında birden çok kez kullanıldığından, aria
sorgu işleyicisinin gerçek yürütme sayısı paket başına 113 idi. Toplam sorgu seçim sayısı 2253 olduğundan, sorgu seçimlerinin yalnızca bir kısmı prototipler aracılığıyla gerçekleşti.
Yukarıdaki şekilde görüldüğü gibi toplam çalışma süresinde göze çarpan bir fark vardır. Veriler, belirli bir sonuca varmak için çok karmaşıktır, ancak bu senaryoda iki prototip arasındaki performans farkının da gösterildiği açıktır.
Yeni bir CDP uç noktası
Yukarıdaki karşılaştırmalar ışığında, lansman işaretini temel alan yaklaşım genel olarak istenmeyen bir işlem olduğu için erişilebilirlik ağacını sorgulamak amacıyla yeni bir CDP komutu uygulamaya karar verdik. Şimdi, bu yeni uç noktanın arayüzünü bulmamız gerekiyordu.
Puppeteer'daki kullanım örneğimiz için uç noktanın, bağımsız değişken olarak RemoteObjectIds
adını alması gerekir. Daha sonra karşılık gelen DOM öğelerini bulabilmemiz için uç noktanın, DOM öğeleri için backendNodeIds
içeren nesnelerin listesini döndürmesi gerekir.
Aşağıdaki grafikte görüldüğü gibi, bu arayüzü karşılayan birçok yaklaşım denedik. Bunun sonucunda, döndürülen nesnelerin boyutunun, yani tam erişilebilirlik düğümleri döndürüp döndürmediğimizi veya yalnızca backendNodeIds
öğesinin belirgin bir fark yaratmadığını tespit ettik. Diğer yandan, burada geçiş mantığını uygulamak için mevcut NextInPreOrderIncludingIgnored
kullanımını kullanmanın kötü bir seçim olduğunu tespit ettik. Çünkü bu belirgin bir yavaşlamaya neden olmuştur.
Özet
CDP uç noktası uygulandıktan sonra sorgu işleyiciyi kuklacı tarafında uyguladık. Buradaki çalışmanın en büyük sonucu, sorgu işleme kodunun, sayfa bağlamında değerlendirilen JavaScript aracılığıyla sorgulama yapmak yerine, sorguların doğrudan CDP üzerinden çözülmesini sağlayacak şekilde yeniden yapılandırıldı.
Sırada ne var?
Yeni aria
işleyici, yerleşik sorgu işleyici olarak Puppeteer v5.4.0 ile gönderilir. Kullanıcıların bunu test komut dosyalarına nasıl uyarladıklarını görmek için sabırsızlanıyoruz. Bunu nasıl daha da yararlı hale getirebileceğimizle ilgili fikirlerinizi duymak için sabırsızlanıyoruz.
Önizleme kanallarını indirme
Varsayılan geliştirme tarayıcınız olarak Chrome Canary, Dev veya Beta'yı kullanabilirsiniz. Bu önizleme kanalları en yeni Geliştirici Araçları özelliklerine erişmenizi, son teknoloji ürünü web platformu API'lerini test etmenizi ve kullanıcılarınızdan önce sitenizdeki sorunları bulmanızı sağlar.
Chrome Geliştirici Araçları ekibiyle iletişim kurma
Yayındaki yeni özellikleri ve değişiklikleri ya da Geliştirici Araçları ile ilgili diğer her şeyi tartışmak için aşağıdaki seçenekleri kullanın.
- Öneri veya geri bildirimlerinizi crbug.com adresinden bize iletebilirsiniz.
- Geliştirici Araçları sorunlarını bildirmek için Diğer seçenekler'i
> Yardım > Geliştirici Araçları'nda Geliştirici Araçları ile ilgili sorunları bildirin.
- @ChromeDevTools adresinden tweet atabilirsiniz.
- Geliştirici Araçları YouTube videoları veya Geliştirici Araçları ipuçları YouTube videolarına yorum yazın.