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

Johan Bay
Johan Bay

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 सिलेक्टर तक सीमित थे. हालांकि, ये एक्सप्रेशन के लिहाज़ से काफ़ी असरदार हैं, लेकिन स्क्रिप्ट में ब्राउज़र इंटरैक्शन को बनाए रखने के लिए इनमें कुछ कमियां हो सकती हैं.

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

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

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

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

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 में बदलने का फ़ैसला लेते हैं, तो टेस्ट फिर से फ़ेल हो जाएगा. हालांकि, इस मामले में ऐसा होना सही है. बटन का नाम बदलकर, हम पेज के कॉन्टेंट में बदलाव करते हैं. यह बदलाव, पेज के विज़ुअल प्रज़ेंटेशन या डीओएम में उसके स्ट्रक्चर में नहीं होता. हमारे टेस्ट से हमें ऐसे बदलावों के बारे में चेतावनी मिलनी चाहिए, ताकि यह पक्का किया जा सके कि ऐसे बदलाव जान-बूझकर किए गए हैं.

खोज बार के बड़े उदाहरण पर वापस जाकर, हम नए 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 कार्रवाइयां, CDP कमांड का इस्तेमाल करके की जाती हैं. जैसे, Runtime.evaluate, जो सीधे पेज के कॉन्टेक्स्ट में JavaScript का आकलन करता है और नतीजा दिखाता है. Puppeteer की अन्य कार्रवाइयां, सीधे Blink की रेंडरिंग प्रोसेस के साथ कम्यूनिकेट करने के लिए CDP का इस्तेमाल करती हैं. जैसे, रंगों को ठीक से न देख पाने की समस्या को एमुलेट करना, स्क्रीनशॉट लेना या ट्रेस कैप्चर करना.

सीडीपी

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

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

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

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

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

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

Puppeteer AXTree ट्रैवर्सल

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

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

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

यूनिट टेस्ट बेंचमार्क

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

आखिर में खास जानकारी

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

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

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

झलक वाले चैनल डाउनलोड करना

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

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

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