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

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

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

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

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

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 में एआरआईए क्वेरी को लागू करने के कई तरीके हैं. इसकी वजह जानने के लिए, आइए पहले यह देखें कि Puppeteer, ब्राउज़र को कैसे कंट्रोल करता है.

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

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

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

CDP

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

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

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

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

JS DOM ट्रैवर्सल

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

Puppeteer AXTree ट्रैवर्सल

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

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

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

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

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

बेंचमार्क: चार एलिमेंट के लिए 1,000 बार क्वेरी करने में लगा कुल समय

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

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

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

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

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

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

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

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