Puppetaria: erişilebilirliğe öncelik veren Puppeteer metinleri

Johan Bay
Johan Bay

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 doğası gereği söz dizimlidir; DOM'deki 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 ve erişilebilirlik ağacının hesaplanmış erişilebilir adı ve rolü için sorgulamayı destekleriz. 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 tekrar başarısız olur, ancak bu durumda istenirse düğmenin adını değiştirerek görsel sunumunun veya DOM'de nasıl yapılandırılacağının aksine sayfanın 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');

ve

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, dilden bağımsız bir arayüz aracılığıyla "sayfayı yeniden yükle" veya "sayfada bu JavaScript parçasını yürüt ve sonucu geri ver" gibi işlevleri açığa çıkarır.

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.

CDP

Bu durumda, sorgu işlevimizi uygulamak için kullanabileceğimiz iki yol kalır:

  • 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ü.

Karşılaştırma: Dört öğeyi 1.000 kez sorgulamanın toplam süresi

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.

Karşılaştırma: e2e test paketi

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 için 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.

Karşılaştırma: CDP tabanlı AXTree geçiş prototiplerinin karşılaştırması

Ö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ı.

Sonraki adım

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ı'ndaki Diğer seçenekler   Diğer > Yardım > Geliştirici Araçları ile ilgili sorun bildir bölümüne giderek Geliştirici Araçları sorunlarını bildirin.
  • @ChromeDevTools adresinden tweet atabilirsiniz.
  • Geliştirici Araçları YouTube videoları veya Geliştirici Araçları ipuçları YouTube videolarına yorum yazın.