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

Johan Bay
Johan Bay

Puppetier और सिलेक्टर तक पहुंचने की उनकी रणनीति

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

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

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

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

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

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

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

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

बैकग्राउंड

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

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

हमने इसे कैसे लागू किया

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

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

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

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

सीडीपी

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

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

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

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

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

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

पपीटीयर ऐक्सट्री ट्रैवर्सल

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

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

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

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

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

पूरी जानकारी दी जा रही है

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

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

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

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

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

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

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

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