Puppetaria: सुलभता से जुड़ी पहली Puppeteer स्क्रिप्ट

जोहान बे
जोहान बे

Puppeteer और सिलेक्टर के लिए इसका तरीका

Puppeteer, Node के लिए एक ब्राउज़र ऑटोमेशन लाइब्रेरी है: इसकी मदद से, आसान और नए JavaScript API का इस्तेमाल करके, ब्राउज़र को कंट्रोल किया जा सकता है.

ब्राउज़र का सबसे अहम काम, वेब पेजों को ब्राउज़ करना है. इस टास्क को ऑटोमेट करने का मतलब है, वेबपेज के साथ अपने-आप होने वाले इंटरैक्शन.

Puppeteer में ऐसा करने के लिए, स्ट्रिंग पर आधारित सिलेक्टर का इस्तेमाल करके डीओएम एलिमेंट के लिए क्वेरी की जाती है और एलिमेंट पर क्लिक करने या टाइप करने जैसी कार्रवाइयां की जाती हैं. उदाहरण के लिए, कोई स्क्रिप्ट जो developer.google.com को खोलता है, खोज बॉक्स को ढूंढती है, और puppetaria के लिए खोजें ऐसी दिख सकती हैं:

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

क्वेरी सिलेक्टर का इस्तेमाल करके एलिमेंट की पहचान कैसे की जाती है, यह Puppeteer अनुभव का अहम हिस्सा है. अब तक, Puppeteer में मौजूद सिलेक्टर सिर्फ़ सीएसएस और XPath सिलेक्टर तक सीमित हैं. हालांकि, एक्सप्रेशन के हिसाब से ये बहुत शक्तिशाली हैं, लेकिन इसके लिए स्क्रिप्ट में ब्राउज़र इंटरैक्शन को बनाए रखने में नुकसान हो सकते हैं.

वाक्य-विन्यास बनाम सिमैंटिक सिलेक्टर

सीएसएस सिलेक्टर, वाक्यात्मक होते हैं. वे डीओएम ट्री के टेक्स्ट वाले तरीके के अंदरूनी कामकाज से इस तरह जुड़े होते हैं कि वे डीओएम के आईडी और क्लास के नाम का रेफ़रंस देते हैं. इसलिए, वे वेब डेवलपर को एक ऐसा ज़रूरी टूल उपलब्ध कराते हैं जिसका इस्तेमाल करके, वे पेज में किसी एलिमेंट में बदलाव कर सकते हैं या उसमें स्टाइल जोड़ सकते हैं. हालांकि, इस मामले में डेवलपर का पेज और उसके डीओएम ट्री पर पूरा कंट्रोल होता है.

दूसरी ओर, Puppeteer स्क्रिप्ट किसी पेज का बाहरी ऑब्ज़र्वर है. इसलिए, जब इस कॉन्टेक्स्ट में सीएसएस सिलेक्टर का इस्तेमाल किया जाता है, तो यह पेज को लागू करने के तरीके के बारे में छिपी हुई धारणाओं को दिखाता है. इस पर Puppeteer स्क्रिप्ट का कोई कंट्रोल नहीं होता.

इसका असर यह होता है कि ऐसी स्क्रिप्ट में सोर्स कोड में बदलाव करना मुश्किल हो सकता है और हो सकता है कि उन पर इसका ज़्यादा असर न हो. उदाहरण के लिए, मान लें कि कोई व्यक्ति किसी ऐसे वेब ऐप्लिकेशन की अपने-आप जांच करने के लिए Puppeteer स्क्रिप्ट का इस्तेमाल करता है जिसमें body एलिमेंट का तीसरा चाइल्ड नोड <button>Submit</button> है. टेस्ट केस का एक स्निपेट ऐसा दिख सकता है:

const button = await page.$('body:nth-child(3)'); // problematic selector
await button.click();

यहां, हम सबमिट बटन ढूंढने के लिए 'body:nth-child(3)' सिलेक्टर का इस्तेमाल कर रहे हैं, लेकिन यह वेबपेज के इसी वर्शन से जुड़ा हुआ है. अगर बाद में कोई एलिमेंट, बटन के ऊपर जोड़ा जाता है, तो यह सिलेक्टर काम नहीं करता!

यह लेखकों की जांच करने की खबर नहीं है: Puppeteer उपयोगकर्ताओं ने पहले से ही ऐसे सिलेक्टर चुनने की कोशिश की है जो इस तरह के बदलावों के लिए बेहतरीन हैं. Puppetaria की मदद से, हम लोगों को एक नया टूल उपलब्ध कराते हैं.

Puppeteer अब सीएसएस सिलेक्टर पर भरोसा करने के बजाय, सुलभता ट्री पर क्वेरी करने के आधार पर अन्य क्वेरी हैंडलर के साथ शिपिंग करता है. इसकी बुनियादी धारणा यह है कि जिस कंक्रीट एलिमेंट को हम चुनना चाहते हैं, अगर उसमें कोई बदलाव नहीं हुआ है, तो उससे जुड़े सुलभता नोड में भी कोई बदलाव नहीं हुआ होना चाहिए.

हम ऐसे सिलेक्टर को "ARIA सिलेक्टर" का नाम रखते हैं. साथ ही, सुलभता ट्री के कंप्यूट किए गए ऐक्सेस किए जा सकने वाले नाम और भूमिका के लिए, क्वेरी करने की सुविधा का इस्तेमाल करते हैं. सीएसएस सिलेक्टर की तुलना में, ये प्रॉपर्टी सिमैंटिक वाले होते हैं. ये टैग, डीओएम की सिंटैक्ट प्रॉपर्टी से नहीं जुड़े होते हैं. हालांकि, इनसे यह पता चलता है कि स्क्रीन रीडर जैसी सहायक टेक्नोलॉजी की मदद से, पेज की निगरानी कैसे की जाती है.

ऊपर दिए गए टेस्ट स्क्रिप्ट के उदाहरण में, हम इसके बजाय मनचाहे बटन को चुनने के लिए aria/Submit[role="button"] सिलेक्टर का इस्तेमाल कर सकते हैं, जहां Submit एलिमेंट के ऐक्सेस किए जा सकने वाले नाम को दिखाता है:

const button = await page.$('aria/Submit[role="button"]');
await button.click();

अब, अगर हम बाद में अपने बटन के टेक्स्ट कॉन्टेंट को Submit से Done में बदलने का फ़ैसला करते हैं, तो जांच फिर से लागू नहीं हो पाएगी. हालांकि, इस स्थिति में यह ज़रूरी है; बटन का नाम बदलकर, हम पेज के कॉन्टेंट को बदल देते हैं, न कि विज़ुअल प्रज़ेंटेशन या DOM में इसकी बनावट बदलने से. जांच के दौरान, हमें ऐसे बदलावों के बारे में चेतावनी मिलनी चाहिए, ताकि यह पक्का किया जा सके कि इस तरह के बदलाव जान-बूझकर किए गए हैं.

खोज बार में दिए गए बड़े उदाहरण पर वापस जाएं, तो हम नए aria हैंडलर का फ़ायदा ले सकते हैं

const search = await page.$('devsite-search > form > div.devsite-search-container');

के साथ

const search = await page.$('aria/Open search[role="button"]');

खोज बार का पता लगाने के लिए!

आम तौर पर, हमारा मानना है कि इस तरह के ARIA सिलेक्टर का इस्तेमाल करने से Puppeteer उपयोगकर्ताओं को ये फ़ायदे मिल सकते हैं:

  • सोर्स कोड में होने वाले बदलावों के हिसाब से, टेस्ट स्क्रिप्ट में सिलेक्टर को ज़्यादा सुविधाजनक बनाएं.
  • टेस्ट स्क्रिप्ट को ज़्यादा आसानी से पढ़ने लायक बनाएं (ऐक्सेस किए जा सकने वाले नाम सिमैंटिक डिस्क्रिप्टर होते हैं).
  • एलिमेंट के लिए सुलभता प्रॉपर्टी असाइन करने के सही तरीकों को बढ़ावा दें.

इस लेख के बाकी हिस्से में, Puppetaria प्रोजेक्ट को लागू करने के तरीके की जानकारी दी गई है.

डिज़ाइन की प्रोसेस

बैकग्राउंड

जैसा कि ऊपर बताया गया है, हम क्वेरी करने वाले एलिमेंट को उनके ऐक्सेस किए जा सकने वाले नाम और भूमिका के हिसाब से चालू करना चाहते हैं. ये सुलभता ट्री की प्रॉपर्टी हैं. यह सामान्य डीओएम ट्री की तुलना में दोहराई जाती है. इसका इस्तेमाल, स्क्रीन रीडर जैसे डिवाइस, वेबपेज दिखाने के लिए करते हैं.

ऐक्सेस किए जा सकने वाले नाम की गिनती करने के स्पेसिफ़िकेशन को देखने से, यह साफ़ होता है कि किसी एलिमेंट के नाम की गिनती करना सामान्य काम नहीं है. इसलिए, शुरू से ही हमने तय किया कि हम इसके लिए Chromium के मौजूदा इन्फ़्रास्ट्रक्चर का फिर से इस्तेमाल करना चाहते हैं.

इसे लागू करने के लिए हमने क्या किया

यहां तक कि हमने खुद को Chromium की सुलभता ट्री तक सीमित कर लिया है, लेकिन ऐसे कुछ तरीके हैं जिनसे हम Puppeteer में ARIA क्वेरीिंग को लागू कर सकते हैं. इसकी वजह जानने के लिए, आइए पहले देखते हैं कि Puppeteer ने ब्राउज़र को कैसे कंट्रोल किया है.

ब्राउज़र, Chrome DevTools प्रोटोकॉल (सीडीपी) प्रोटोकॉल के ज़रिए, डीबग करने का इंटरफ़ेस दिखाता है. यह लैंग्वेज-ऐग्नोस्टिक इंटरफ़ेस की मदद से, "पेज को फिर से लोड करें" या "JavaScript के इस हिस्से को पेज में लागू करें और नतीजे को दें" जैसे फ़ंक्शन दिखाता है.

DevTools फ़्रंट-एंड और Puppeteer दोनों ही ब्राउज़र से बात करने के लिए CDP का इस्तेमाल कर रहे हैं. सीडीपी कमांड लागू करने के लिए, Chrome के सभी कॉम्पोनेंट में DevTools इंफ़्रास्ट्रक्चर होता है. जैसे, ब्राउज़र, रेंडरर वगैरह. सीडीपी, कमांड को सही जगह पर रूट करता है.

क्वेरी करने, क्लिक करने, और एक्सप्रेशन का आकलन करने जैसी Puppeteer की कार्रवाइयां, Runtime.evaluate जैसे सीडीपी निर्देशों की मदद से की जाती हैं. ये निर्देश सीधे पेज पर JavaScript का आकलन करते हैं और नतीजे देते हैं. Puppeteer की अन्य कार्रवाइयां, जैसे कि कलर विज़न की कमी को एम्युलेट करना, स्क्रीनशॉट लेना या ट्रेस कैप्चर करना जैसी कार्रवाइयां, Blink रेंडरिंग की प्रोसेस से सीधे तौर पर कम्यूनिकेट करने के लिए सीडीपी का इस्तेमाल करती हैं.

सीडीपी

इसकी वजह से हमारे पास क्वेरी करने की सुविधा को लागू करने के लिए, पहले से ही दो तरीके हैं; हम ये काम कर सकते हैं:

  • हमारे क्वेरी लॉजिक को JavaScript में लिखें और उसे Runtime.evaluate का इस्तेमाल करके पेज में इंजेक्ट करें या
  • ऐसा सीडीपी एंडपॉइंट इस्तेमाल करें जो ब्लिंक करने की प्रोसेस में, सुलभता ट्री को सीधे ऐक्सेस और क्वेरी कर सके.

हमने तीन प्रोटोटाइप लागू किए:

  • JS DOM ट्रैवर्सल - पेज में JavaScript इंजेक्ट करने पर आधारित
  • Puppeteer AXTree traversal - सुलभता ट्री के लिए, मौजूदा सीडीपी ऐक्सेस के इस्तेमाल पर आधारित
  • सीडीपी डीओएम ट्रैवर्सल - सुलभता ट्री की क्वेरी करने के लिए, नए सीडीपी एंडपॉइंट का इस्तेमाल किया गया

JS DOM ट्रेवर्सल

यह प्रोटोटाइप, DOM का पूरा ट्रैवर्सल करता है. साथ ही, element.computedName और element.computedRole का इस्तेमाल करता है, जो ComputedAccessibilityInfo के लॉन्च फ़्लैग पर गेट होता है. इससे, ट्रैवर्सल के दौरान हर एलिमेंट के नाम और भूमिका को वापस पाया जा सकता है.

पपीटीयर AXTree traversal

यहां, हम सीडीपी से पूरे सुलभता ट्री को इकट्ठा करते हैं और Puppeteer में उसे पार करते हैं. इसके बाद, सुलभता नोड को डीओएम नोड पर मैप किया जाता है.

सीडीपी डीओएम ट्रैवर्सल

इस प्रोटोटाइप के लिए, हमने खास तौर पर सुलभता ट्री की क्वेरी के लिए एक नया सीडीपी एंडपॉइंट लागू किया है. इस तरह से, JavaScript के ज़रिए पेज के संदर्भ में क्वेरी करने के बजाय, C++ को लागू करके बैक-एंड पर क्वेरी की जा सकती है.

यूनिट टेस्ट के लिए मानदंड

नीचे दिया गया डायग्राम, तीन प्रोटोटाइप के लिए चार एलिमेंट की 1,000 बार क्वेरी करने के कुल रनटाइम की तुलना करता है. मानदंड को तीन अलग-अलग कॉन्फ़िगरेशन में चलाया गया था. इसमें पेज का साइज़ अलग-अलग है और सुलभता एलिमेंट को कैश मेमोरी में सेव करने की सुविधा चालू है या नहीं.

मानदंड: चार एलिमेंट की 1,000 बार क्वेरी करने का कुल रनटाइम

यह बिलकुल साफ़ है कि सीडीपी पर आधारित क्वेरी करने के तरीकों और सिर्फ़ Puppeteer में लागू की गई दो अन्य प्रोसेस के बीच परफ़ॉर्मेंस में काफ़ी अंतर है. इसलिए, पेज के साइज़ के साथ-साथ तुलना के अंतर में काफ़ी बढ़ोतरी हुई है. यह देखना थोड़ा दिलचस्प है कि JS DOM ट्रैवर्सल प्रोटोटाइप की मदद से, सुलभता को कैश मेमोरी में सेव किया जा सकता है. कैश मेमोरी की सुविधा बंद होने पर, सुलभता ट्री की गणना मांग पर की जाती है. साथ ही, अगर डोमेन बंद हो जाता है, तो हर इंटरैक्शन के बाद ट्री को हटा दिया जाता है. डोमेन को चालू करने से, Chromium कैश मेमोरी में सेव की गई फ़ाइल को कैश मेमोरी में सेव कर लेता है.

JS DOM ट्रैवर्सल के लिए, हम ट्रैवर्सल के दौरान हर एलिमेंट के ऐक्सेस किए जा सकने वाले नाम और भूमिका की जानकारी मांगते हैं. इसलिए, अगर कैश मेमोरी की सुविधा बंद होती है, तो Chromium, विज़िट किए गए हर एलिमेंट के लिए सुलभता ट्री की गिनती करता है और उसे खारिज कर देता है. वहीं दूसरी ओर, सीडीपी पर आधारित नज़रिए के लिए, सीडीपी को किए जाने वाले हर कॉल के बीच ट्री को हटाया जाता है, यानी हर क्वेरी के लिए. इन तरीकों को कैश मेमोरी में सेव करने की सुविधा चालू करने पर भी फ़ायदा होता है. ऐसा इसलिए, क्योंकि इसके बाद सुलभता ट्री सीडीपी कॉल में बना रहता है. हालांकि, परफ़ॉर्मेंस बूस्ट की तुलना में कम कमाई होती है.

भले ही यहां कैश मेमोरी को चालू करना ज़रूरी है, लेकिन इसके लिए अतिरिक्त मेमोरी के इस्तेमाल की लागत आती है. ट्रैक फ़ाइलों को रिकॉर्ड करने जैसी Puppeteer स्क्रिप्ट के लिए, इससे समस्या हो सकती है. इसलिए, हमने डिफ़ॉल्ट रूप से सुलभता ट्री को कैश मेमोरी में सेव करने की सुविधा चालू न करने का फ़ैसला किया है. उपयोगकर्ता, सीडीपी सुलभता डोमेन को चालू करके, खुद को कैश मेमोरी में सेव करने की सुविधा चालू कर सकते हैं.

DevTools टेस्ट सुइट का मानदंड

पिछले मानदंड से पता चला कि सीडीपी लेयर पर क्वेरी करने के हमारे तरीके को लागू करने से, क्लिनिकल यूनिट-टेस्ट के मामले में परफ़ॉर्मेंस को बेहतर बनाया जाता है.

यह देखने के लिए कि इस अंतर को पूरी तरह से टेस्ट सुइट चलाने की स्थिति में एकदम साफ़ किया गया है या नहीं, हमने JavaScript और सीडीपी पर आधारित प्रोटोटाइप का इस्तेमाल करने और रनटाइम की तुलना करने के लिए, DevTools के एंड-टू-एंड टेस्ट सुइट को पैच किया. इस मानदंड में, हमने कुल 43 सिलेक्टर को [aria-label=…] से बदलकर कस्टम क्वेरी हैंडलर aria/… किया. इसके बाद, हमने हर प्रोटोटाइप का इस्तेमाल करके, सिलेक्टर को लागू किया.

कुछ सिलेक्टर का इस्तेमाल, टेस्ट स्क्रिप्ट में कई बार किया जाता है. इसलिए, हर सुइट को चलाने पर aria क्वेरी हैंडलर की असल संख्या 113 थी. क्वेरी के चुने गए विकल्पों की कुल संख्या 2253 थी. इसलिए, क्वेरी के चुने गए विकल्पों का कुछ हिस्सा ही प्रोटोटाइप की मदद से चुना गया.

बेंचमार्क: e2e टेस्ट सुइट

जैसा कि ऊपर दिए गए डायग्राम में दिखाया गया है, कुल रनटाइम में साफ़ तौर पर अंतर देखा जा सकता है. किसी खास चीज़ को पूरा करने के लिए, डेटा बहुत शोर है. हालांकि, यह साफ़ तौर पर पता चलता है कि इस स्थिति में भी दो प्रोटोटाइप के बीच की परफ़ॉर्मेंस में फ़र्क़ दिख रहा है.

नया सीडीपी एंडपॉइंट

ऊपर दिए गए मानदंडों को ध्यान में रखते हुए, लॉन्च फ़्लैग पर आधारित अप्रोच को समझना अच्छा नहीं था. इसलिए, सुलभता ट्री की क्वेरी के लिए, हमने एक नया सीडीपी कमांड लागू करने का फ़ैसला किया. अब हमें इस नए एंडपॉइंट के इंटरफ़ेस को ढूंढना था.

Puppeteer में हमारे इस्तेमाल के उदाहरण के लिए, हमें एंडपॉइंट की ज़रूरत होगी, ताकि वह तथाकथित RemoteObjectIds को तर्क के रूप में ले जाए और बाद में हमें उससे संबंधित DOM एलिमेंट ढूंढने में मदद मिले. इसके लिए, हमें उन ऑब्जेक्ट की सूची दिखानी चाहिए जिनमें डीओएम एलिमेंट के लिए backendNodeIds शामिल हों.

जैसा कि नीचे दिए गए चार्ट में दिखाया गया है, हमने इस इंटरफ़ेस से जुड़ी समस्या को हल करने के लिए, कुछ तरीके आज़माए. इससे, हमें पता चला कि लौटाए गए ऑब्जेक्ट के साइज़, जैसे कि हमने पूरे सुलभता नोड लौटाए या नहीं या सिर्फ़ backendNodeIds के साइज़ में कोई अंतर नहीं आया. वहीं दूसरी ओर, हमने पाया कि मौजूदा NextInPreOrderIncludingIgnored का इस्तेमाल करना, यहां ट्रैवर्सल लॉजिक को लागू करने के लिहाज़ से अच्छा विकल्प नहीं है. इसकी वजह से, धीमी गति से प्रोसेस में गिरावट आई.

बेंचमार्क: सीडीपी पर आधारित AXTree ट्रैवर्सल प्रोटोटाइप की तुलना

पूरा रैप हो रहा है

अब सीडीपी एंडपॉइंट की मदद से हमने क्वेरी हैंडलर को Puppeteer साइड पर लागू किया. इसलिए, हमें क्वेरी हैंडलिंग कोड को फिर से तैयार करने की ज़रूरत थी, ताकि पेज के कॉन्टेक्स्ट में आकलन की गई JavaScript के बजाय क्वेरी को सीधे सीडीपी के ज़रिए हल किया जा सके.

आगे क्या करना है?

नया aria हैंडलर, पहले से मौजूद क्वेरी हैंडलर के तौर पर Puppeteer v5.4.0 के साथ भेजा गया. हमें यह देखने का बेसब्री से इंतज़ार है कि उपयोगकर्ता, टेस्ट स्क्रिप्ट में इस सुविधा को कैसे अपनाते हैं. हमें यह जानने का बेसब्री से इंतज़ार रहेगा कि इसे और काम का कैसे बनाया जा सकता है!

झलक दिखाने वाले चैनलों को डाउनलोड करना

अपने डिफ़ॉल्ट डेवलपमेंट ब्राउज़र के तौर पर, Chrome Canary, Dev या बीटा का इस्तेमाल करें. झलक दिखाने वाले इन चैनलों की मदद से, आपको DevTools की नई सुविधाओं का ऐक्सेस मिलता है. साथ ही, सबसे नए वेब प्लैटफ़ॉर्म एपीआई को टेस्ट किया जा सकता है और उपयोगकर्ताओं के आने से पहले ही अपनी साइट पर समस्याओं का पता लगाया जा सकता है!

Chrome DevTools टीम से संपर्क करना

पोस्ट में हुई नई सुविधाओं और बदलावों या DevTools से जुड़ी किसी भी चीज़ के बारे में, नीचे दिए गए विकल्पों का इस्तेमाल करें.

  • crbug.com के ज़रिए हमें कोई सुझाव या सुझाव सबमिट करें.
  • DevTools में ज़्यादा विकल्प   ज़्यादा दिखाएं   > सहायता > DevTools से जुड़ी समस्याओं की शिकायत करें का इस्तेमाल करके, DevTools से जुड़ी समस्या की शिकायत करें.
  • @ChromeDevTool पर ट्वीट करें.
  • हमारे DevTools YouTube वीडियो या DevTools सलाह YouTube वीडियो में नया क्या है पर टिप्पणी करें.