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

Johan Bay
Johan Bay

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

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

खोज बार के बड़े उदाहरण पर वापस जाकर, हम नए 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, दोनों ही ब्राउज़र से बात करने के लिए सीडीपी का इस्तेमाल कर रहे हैं. सीडीपी निर्देशों को लागू करने के लिए, Chrome के सभी कॉम्पोनेंट में DevTools इंफ़्रास्ट्रक्चर मौजूद होता है: ब्राउज़र, रेंडरर वगैरह में. सीडीपी, कमांड को सही जगह पर भेजने की सुविधा देता है.

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

CDP

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

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

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

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

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

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

Puppeteer AXTree ट्रैवर्सल

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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