محرك الدمى ومنهجه في استخدام أدوات الاختيار
Puppeteer هي مكتبة تشغيل تلقائي للمضيف Node: تتيح لك التحكم في المتصفح باستخدام واجهة برمجة تطبيقات JavaScript بسيطة وحديثة.
وبالطبع، تتمثّل أهم مهمّة في المتصفح في تصفّح صفحات الويب. وتؤدّي عملية أتمتة هذه المهمة في الأساس إلى أتمتة التفاعلات مع صفحة الويب.
في Puppeteer، يمكن تحقيق ذلك من خلال الاستعلام عن عناصر DOM باستخدام محددات مستندة إلى سلاسل وتنفيذ إجراءات مثل النقر على نص أو كتابته على العناصر. على سبيل المثال، يمكن أن يظهر نص برمجي يفتح 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 على محددات CSS وXPath، والتي قد تحمل بعض العيوب على الرغم من فعالية التفاعل بشكل مستمر في النصوص البرمجية.
المحدِّدات النحوية مقابل المحدِّدات الدلالية
تعد أدوات اختيار لغة CSS ذات طبيعة بنائية. ترتبط ارتباطًا وثيقًا بالأعمال الداخلية للتمثيل النصي لشجرة نموذج العناصر في المستند (DOM) بالمعنى بأنها تشير إلى المعرفات وأسماء الفئات من DOM. وبناءً على ذلك، فإنّها توفّر أداة متكاملة لمطوّري الويب لتعديل الأنماط أو إضافتها إلى عنصر في صفحة، ولكن في هذا السياق، يتحكّم مطوّر البرامج بشكل كامل في الصفحة وشجرة نموذج العناصر في المستند (DOM).
من ناحية أخرى، يُعدّ النص البرمجي Puppeteer مراقبًا خارجيًا للصفحة، لذا عند استخدام أدوات اختيار لغة CSS في هذا السياق، يقدّم افتراضات مخفية حول كيفية تنفيذ الصفحة ولا يمكن للنص البرمجي 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 معالِج طلبات بحث بديل يستند إلى الاستعلام عن شجرة تسهيل الاستخدام بدلاً من الاعتماد على أدوات اختيار لغة CSS. الفلسفة الأساسية هنا هي أنه إذا لم يتغير العنصر الملموس الذي نريد تحديده، فلن تكون عقدة إمكانية الوصول المقابلة قد تغيرت أيضًا.
نُطلق على هذه الأدوات اسم "أدوات اختيار ARIA" ودعم الاستعلام عن اسم الوصول المحسوب ودور شجرة إمكانية الوصول. بالمقارنة مع أدوات اختيار لغة CSS، تتميّز هذه السمات بطبيعة دلالية. وهي غير مرتبطة بالخصائص النحوية لنموذج DOM، وإنما تمثّل وصفًا لكيفية رصد الصفحة من خلال التكنولوجيات المساعِدة، مثل برامج قراءة الشاشة.
في مثال النص البرمجي للاختبار أعلاه، يمكننا بدلاً من ذلك استخدام أداة الاختيار 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.
عملية التصميم
الخلفية
كما هو موضح أعلاه، نريد تمكين عناصر الاستعلام حسب الاسم والدور المتاحين. في ما يلي خصائص شجرة إمكانية الوصول، وهي عبارة عن شجرة بيانات DOM المعتادة، التي تستخدمها أجهزة مثل برامج قراءة الشاشة لعرض صفحات الويب.
بدءًا من النظر إلى مواصفات احتساب الاسم الذي يمكن الوصول إليه، يتضح لنا أن احتساب اسم أي عنصر هي مهمة غير تافهة، لذا قررنا منذ البداية إعادة استخدام البنية الأساسية الحالية في Chromium لهذا الغرض.
كيف تعاملنا مع عملية التنفيذ
على الرغم من أنّنا حصرنا باستخدام شجرة تسهيل الاستخدام في Chromium، هناك بعض الطرق التي يمكننا من خلالها تنفيذ طلب ARIA في أداة Puppeteer. لمعرفة السبب، دعونا أولاً نتعرف على كيفية تحكم Puppeteer في المتصفح.
يعرض المتصفّح واجهة تصحيح الأخطاء من خلال بروتوكول يُسمّى بروتوكول أدوات مطوّري البرامج في Chrome (CDP). يؤدي ذلك إلى كشف وظائف مثل "إعادة تحميل الصفحة" أو "تنفيذ هذا الجزء من JavaScript في الصفحة وعرض النتيجة" عبر واجهة لا تعتمد على اللغة
تستخدم كل من الواجهة الأمامية في أدوات مطوّري البرامج وأداة Puppeteer أداة CDP للتحدث إلى المتصفح. لتنفيذ أوامر CDP، تتوفّر بنية أساسية لـ "أدوات مطوري البرامج" داخل جميع مكوّنات Chrome، مثل المتصفّح وفي العارض وما إلى ذلك. حيث تتولى منصة CDP توجيه الأوامر إلى المكان الصحيح.
يتم تنفيذ إجراءات الدُمى، مثل الاستعلام عن التعبيرات والنقر عليها وتقييمها من خلال الاستفادة من أوامر CDP، مثل Runtime.evaluate
التي تقيّم JavaScript مباشرةً في سياق الصفحة وتعيد عرض النتيجة. إنّ إجراءات الدُمى الأخرى، مثل محاكاة نقص رؤية الألوان أو أخذ لقطات شاشة أو التقاط صور آثار، تستخدم بروتوكول CDP للتواصل مباشرةً مع عملية عرض Blink.
هذا يترك لنا بالفعل مسارين لتنفيذ وظيفة الاستعلام لدينا؛ فيمكننا:
- كتابة منطق الاستعلام الخاص بنا بلغة JavaScript وإدخال ذلك في الصفحة باستخدام
Runtime.evaluate
، أو - استخدِم نقطة نهاية CDP يمكنها الوصول إلى شجرة تسهيل الاستخدام وإجراء طلبات بحث عنها مباشرةً من خلال عملية Blink.
قمنا بتنفيذ 3 نماذج أولية:
- اجتياز JS DOM - استنادًا إلى إدخال JavaScript في الصفحة
- اجتياز Puppeteer AXTree: استنادًا إلى استخدام وصول CDP الحالي إلى شجرة تسهيل الاستخدام
- اجتياز CDP DOM: استخدام نقطة نهاية CDP جديدة مصمّمة خصيصًا لطلب بحث شجرة تسهيل الاستخدام
اجتياز JS DOM
يُجري هذا النموذج الأولي عملية اجتياز كاملة لنموذج DOM ويستخدم element.computedName
وelement.computedRole
، وهما مرتبطان بعلامة التشغيل ComputedAccessibilityInfo
لاسترداد اسم كل عنصر ودوره أثناء الاجتياز.
اجتياز Puppeteer AXTree
وهنا، نسترد شجرة تسهيل الاستخدام بالكامل من خلال CDP ونستعرضها في Puppeteer. يتم بعد ذلك ربط عُقد تسهيل الاستخدام الناتجة بعُقد DOM.
اجتياز CDP DOM
بالنسبة لهذا النموذج الأولي، طبقنا نقطة نهاية CDP جديدة خصيصًا للاستعلام عن شجرة إمكانية الوصول. بهذه الطريقة، يمكن أن يحدث الاستعلام في الواجهة الخلفية من خلال تنفيذ C++ بدلاً من سياق الصفحة عبر JavaScript.
مقياس أداء اختبار الوحدة
يقارن الشكل التالي إجمالي وقت التشغيل للاستعلام عن أربعة عناصر 1000 مرة للنماذج الأولية الثلاثة. تم تنفيذ مقياس الأداء من خلال 3 إعدادات مختلفة تختلف في حجم الصفحة وما إذا كان قد تم تفعيل التخزين المؤقت لعناصر تسهيل الاستخدام أم لا.
من الواضح تمامًا أنّ هناك فجوة كبيرة في الأداء بين آلية طلب البحث المستندة إلى CDP والآلية الأخرى التي تم تنفيذهما في Puppeteer فقط، ويبدو أن الفرق النسبي يزداد بشكل كبير مع حجم الصفحة. من المثير للاهتمام إلى حد ما أن نرى أن النموذج الأولي لاجتياز JS DOM يستجيب بشكل جيد لتمكين التخزين المؤقت لإمكانية الوصول. عند إيقاف التخزين المؤقت، يتم احتساب شجرة تسهيل الاستخدام عند الطلب ويتم تجاهل الشجرة بعد كل تفاعل في حال إيقاف النطاق. يؤدي تفعيل النطاق إلى جعل Chromium يخزّن مؤقتًا الشجرة المحسوبة بدلاً من ذلك.
بالنسبة إلى اجتياز JS DOM، نطلب الاسم والدور المتاحَين لكل عنصر أثناء الاجتياز، لذا إذا تم إيقاف التخزين المؤقت، سيحتسب Chromium شجرة إمكانية الوصول ويتجاهلها لكل عنصر نزوره. أمّا بالنسبة إلى النُهج المستندة إلى CDP، فيتم تجاهل العرض التدرّجي فقط بين كل طلب إلى CDP، أي لكل طلب بحث. تستفيد هذه الأساليب أيضًا من تفعيل التخزين المؤقت، حيث تستمر شجرة تسهيل الاستخدام في جميع طلبات CDP، وبالتالي تكون تعزيز الأداء أصغر نسبيًا.
وعلى الرغم من أنّ تفعيل التخزين المؤقت يبدو مرغوبًا فيه، فإنّه يفرض تكلفة إضافية على استخدام الذاكرة. بالنسبة إلى النصوص البرمجية للدمى المتحركة، مثل سجلات تتبُّع، قد يمثل ذلك مشكلة. لهذا السبب، قرّرنا عدم تفعيل التخزين المؤقت لشجرة تسهيل الاستخدام بشكل تلقائي. يمكن للمستخدمين تفعيل التخزين المؤقت بأنفسهم من خلال تفعيل نطاق تسهيل الاستخدام CDP.
مقياس أداء مجموعة أدوات اختبار أدوات مطوّري البرامج
أظهر مقياس الأداء السابق أن تنفيذ آلية طلب البحث في طبقة CDP يمنحنا تحسينًا في الأداء في سيناريو اختبار الوحدة السريرية.
لمعرفة ما إذا كان الاختلاف واضحًا بما يكفي لملاحظته في سيناريو أكثر واقعية لتشغيل مجموعة اختبار كاملة، شرعنا في تصحيح مجموعة الاختبار المتكامل لـ "أدوات مطوّري البرامج" للاستفادة من النماذج الأولية المستندة إلى JavaScript وبرنامج CDP ومقارنة أوقات التشغيل. في مقياس الأداء هذا، غيَّرنا إجمالي 43 أداة اختيار من [aria-label=…]
إلى معالج طلب بحث مخصّص aria/…
، ثم نفّذناه باستخدام كل نموذج من النماذج الأولية.
يتم استخدام بعض أدوات الاختيار عدة مرات في النصوص البرمجية للاختبار، لذا كان العدد الفعلي لعمليات التنفيذ لمعالِج طلبات البحث aria
هو 113 لكل عملية تشغيل من المجموعة. كان العدد الإجمالي لتحديدات الاستعلام 2253، لذلك حدث جزء صغير فقط من تحديدات الاستعلام من خلال النماذج الأولية.
كما هو موضح في الشكل أعلاه، هناك فرق واضح في إجمالي وقت التشغيل. البيانات مزعجة جدًا بحيث لا يمكن استنتاج أي شيء محدد، ولكن من الواضح أن فجوة الأداء بين النموذجين الأوليين تظهر في هذا السيناريو أيضًا.
نقطة نهاية جديدة لـ CDP
في ضوء المعايير المذكورة أعلاه، وبما أنّ الأسلوب المستند إلى علامات الإطلاق لم يكن مرغوبًا فيه بشكل عام، قرّرنا المضي قدمًا في تنفيذ أمر CDP جديد لإرسال طلبات بحث في شجرة تسهيل الاستخدام. والآن، كان علينا معرفة واجهة نقطة النهاية الجديدة هذه.
بالنسبة إلى حالة الاستخدام في Puppeteer، نحتاج إلى نقطة النهاية لأخذ ما يُعرف باسم RemoteObjectIds
كوسيطة، ولتمكيننا من العثور على عناصر DOM المقابلة بعد ذلك، يجب أن تعرض نقطة النهاية قائمة بالكائنات التي تحتوي على backendNodeIds
لعناصر DOM.
كما هو موضح في المخطط أدناه، جربنا عددًا كبيرًا من الأساليب لتلبية هذه الواجهة. من هنا، وجدنا أن حجم العناصر التي تم عرضها، أي ما إذا كنا قد رجعنا عُقدًا كاملة لإمكانية الوصول أم لا أو backendNodeIds
فقط لم يحدث أي فارق ملحوظ. من ناحية أخرى، وجدنا أنّ استخدام NextInPreOrderIncludingIgnored
الحالي كان خيارًا سيئًا لتنفيذ منطق الاجتياز هنا، لأنّ ذلك أدّى إلى تباطؤ ملحوظ.
الاختتام
والآن، بعد استخدام نقطة نهاية CDP، نفّذنا معالِج طلب البحث على جانب Puppeteer. وكان الهدف من العمل هنا إعادة هيكلة رمز معالجة الطلبات من أجل السماح بحل طلبات البحث مباشرةً من خلال CDP بدلاً من إجراء طلبات بحث من خلال JavaScript تم تقييمه في سياق الصفحة.
ما هي الخطوات التالية؟
تم شحن معالج aria
الجديد مع Puppeteer v5.4.0 كمعالج طلبات بحث مدمج. نحن نتطلع إلى رؤية كيفية اعتماد المستخدمين له في نصوص الاختبار الخاصة بهم، ولا يسعنا الانتظار لسماع أفكارك حول كيفية جعل ذلك أكثر فائدة!
تنزيل قنوات المعاينة
يمكنك استخدام Chrome كناري أو إصدار مطوّري البرامج أو الإصدار التجريبي من المتصفِّح التلقائي للتطوير. وتتيح لك قنوات المعاينة هذه إمكانية الوصول إلى أحدث ميزات "أدوات مطوري البرامج" واختبار أحدث واجهات برمجة التطبيقات للأنظمة الأساسية للويب والعثور على المشاكل على موقعك الإلكتروني قبل أن يفعلها المستخدمون.
التواصل مع فريق "أدوات مطوري البرامج في Chrome"
يمكنك استخدام الخيارات التالية لمناقشة الميزات والتغييرات الجديدة في المشاركة أو مناقشة أي معلومات أخرى متعلّقة بأدوات مطوري البرامج.
- يمكنك إرسال اقتراح أو ملاحظات إلينا عبر crbug.com.
- الإبلاغ عن مشكلة في "أدوات مطوري البرامج" باستخدام خيارات إضافية > مساعدة > الإبلاغ عن مشاكل في "أدوات مطوري البرامج" في "أدوات مطوري البرامج"
- يمكنك نشر تغريدة على @ChromeDevTools.
- شارِك في التعليقات على الميزات الجديدة في فيديوهات YouTube أو نصائح حول أدوات مطوّري البرامج فيديوهات YouTube.