Puppeteer ونهجه في ما يتعلّق بالمحدِّدات
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.$(>'d>evsite-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 هي أدوات نحوية بطبيعتها، وهي مرتبطة ارتباطًا وثيقًا بالعمليات الداخلية للتمثيل النصي لشجرة نموذج العناصر في المستند، من حيث أنّها تشير إلى أرقام التعريف وأسماء الفئات من نموذج العناصر في المستند. وبالتالي، توفّر هذه الأنماط أداة أساسية لمطوّري تطبيقات الويب لتعديل الأنماط أو إضافتها إلى عنصر في صفحة معيّنة، ولكن في هذا السياق، يكون لدى المطوّر تحكّم كامل في الصفحة وشجرة نموذج العناصر في المستند.
من ناحية أخرى، فإنّ نص 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-sea>rch f>orm div.devsite-search-container');
مع
const search = await page.$('aria/Open search[role="button"]');
للعثور على شريط البحث
بشكل عام، نعتقد أنّ استخدام أدوات اختيار ARIA هذه يمكن أن يوفّر المزايا التالية لمستخدمي Puppeteer:
- جعل أدوات الاختيار في نصوص الاختبار البرمجية أكثر مرونة في مواجهة التغييرات في الرمز المصدر
- جعل نصوص الاختبار أكثر قابلية للقراءة (أسماء العناصر الظاهرة على واجهة المستخدم هي واصفات دلالية).
- تحفيز الممارسات الجيدة لتعيين خصائص تسهيل الاستخدام للعناصر.
يتناول الجزء المتبقي من هذه المقالة تفاصيل كيفية تنفيذ مشروع Puppetaria.
عملية التصميم
خلفية
كما ذكرنا أعلاه، نريد تفعيل طلب البحث عن العناصر حسب اسمها الظاهر على واجهة المستخدم ودورها. هذه هي سمات شجرة تسهيل الاستخدام، وهي نسخة مطابقة لشجرة نموذج العناصر في المستند (DOM) المعتادة، وتستخدمها أجهزة مثل برامج قراءة الشاشة لعرض صفحات الويب.
من خلال الاطّلاع على مواصفات احتساب اسم العنصر الظاهر على واجهة المستخدم، يتضح أنّ احتساب اسم العنصر ليس مهمة بسيطة، لذا قرّرنا منذ البداية إعادة استخدام البنية الأساسية الحالية في Chromium لهذا الغرض.
كيفية تنفيذها
حتى لو اقتصرنا على استخدام شجرة تسهيل الاستخدام في Chromium، هناك عدة طرق يمكننا من خلالها تنفيذ طلبات بحث ARIA في Puppeteer. لمعرفة السبب، لنتعرّف أولاً على كيفية تحكّم Puppeteer في المتصفّح.
يعرض المتصفّح واجهة لتصحيح الأخطاء من خلال بروتوكول يُعرف باسم بروتوكول أدوات مطوّري البرامج في Chrome. ويتيح ذلك وظائف مثل "إعادة تحميل الصفحة" أو "تنفيذ جزء من JavaScript في الصفحة وإرجاع النتيجة" من خلال واجهة مستقلة عن اللغة.
تستخدم كل من واجهة "أدوات مطوري البرامج" الأمامية وPuppeteer بروتوكول CDP للتواصل مع المتصفّح. لتنفيذ أوامر CDP، تتوفّر بنية أساسية لـ "أدوات مطوّري البرامج" داخل جميع مكوّنات Chrome: في المتصفّح، وفي العارض، وما إلى ذلك. يتولّى CDP توجيه الأوامر إلى المكان الصحيح.
يتم تنفيذ إجراءات Puppeteer، مثل الاستعلام والنقر وتقييم التعبيرات، من خلال الاستفادة من أوامر CDP، مثل Runtime.evaluate، التي تقيّم JavaScript مباشرةً في سياق الصفحة وتعرض النتيجة. تستخدم إجراءات Puppeteer الأخرى، مثل محاكاة عجز رؤية الألوان أو أخذ لقطات شاشة أو تسجيل عمليات التتبّع، بروتوكول CDP للتواصل مباشرةً مع عملية عرض Blink.

يترك لنا هذا بالفعل مسارين لتنفيذ وظيفة الاستعلام؛ يمكننا:
- كتابة منطق طلب البحث في JavaScript وإدخاله في الصفحة باستخدام
Runtime.evaluate، أو - استخدِم نقطة نهاية CDP يمكنها الوصول إلى شجرة تسهيل الاستخدام والاستعلام عنها مباشرةً في عملية Blink.
لقد نفّذنا 3 نماذج أولية:
- اجتياز JS DOM: يستند إلى إدخال JavaScript في الصفحة
- Puppeteer AXTree traversal: استنادًا إلى استخدام إمكانية الوصول الحالية إلى شجرة تسهيل الاستخدام من خلال بروتوكول Chrome DevTools
- اجتياز نموذج العناصر في المستند (DOM) لبروتوكول Chrome DevTools: استخدام نقطة نهاية جديدة لبروتوكول Chrome DevTools تم إنشاؤها خصيصًا لطلب البحث في شجرة تسهيل الاستخدام
التنقّل في نموذج العناصر في المستند (DOM) باستخدام JavaScript
يُجري هذا النموذج الأوّلي عملية انتقال كاملة في نموذج العناصر في المستند (DOM) ويستخدم element.computedName وelement.computedRole، استنادًا إلى علامة الإطلاق ComputedAccessibilityInfo، لاسترداد الاسم والدور لكل عنصر أثناء عملية الانتقال.
Puppeteer AXTree traversal
في هذه الحالة، نستردّ شجرة تسهيل الاستخدام الكاملة من خلال بروتوكول CDP ونتنقّل فيها في Puppeteer. بعد ذلك، يتم ربط عُقد تسهيل الاستخدام الناتجة بعُقد DOM.
اجتياز نموذج العناصر في المستند (DOM) من خلال بروتوكول Chrome DevTools
بالنسبة إلى هذا النموذج الأولي، نفّذنا نقطة نهاية جديدة لبروتوكول CDP مخصّصة لطلب البحث في شجرة تسهيل الاستخدام. بهذه الطريقة، يمكن إجراء طلب البحث في الخلفية من خلال تنفيذ C++ بدلاً من سياق الصفحة عبر JavaScript.
مقياس أداء اختبار الوحدة
يقارن الشكل التالي إجمالي وقت تشغيل الاستعلام عن أربعة عناصر 1000 مرة للنماذج الأولية الثلاثة. تم تنفيذ مقياس الأداء في 3 إعدادات مختلفة تختلف في حجم الصفحة وما إذا كان قد تم تفعيل التخزين المؤقت لعناصر تسهيل الاستخدام أم لا.
من الواضح أنّ هناك فجوة كبيرة في الأداء بين آلية طلب البحث المستندة إلى CDP والآليتين الأخريين اللتين تم تنفيذهما في Puppeteer فقط، ويبدو أنّ الفرق النسبي يزداد بشكل كبير مع حجم الصفحة. من المثير للاهتمام إلى حدّ ما رؤية أنّ النموذج الأوّلي لاجتياز JS DOM يستجيب بشكل جيد لتفعيل التخزين المؤقت لأدوات تسهيل الاستخدام. عند إيقاف التخزين المؤقت، يتم احتساب شجرة تسهيل الاستخدام عند الطلب ويتم تجاهل الشجرة بعد كل تفاعل إذا كان النطاق غير مفعَّل. يؤدي تفعيل النطاق إلى تخزين Chromium للشجرة المحسوبة مؤقتًا بدلاً من ذلك.
بالنسبة إلى اجتياز نموذج العناصر في JS، نطلب اسم العنصر الظاهر على واجهة المستخدم ودوره لكل عنصر أثناء الاجتياز، لذا إذا تم إيقاف التخزين المؤقت، يحسب Chromium شجرة تسهيل الاستخدام ويتجاهلها لكل عنصر نزوره. أما بالنسبة إلى النهج المستندة إلى CDP، فيتم تجاهل الشجرة فقط بين كل طلب إلى CDP، أي لكل طلب بحث. تستفيد هذه الأساليب أيضًا من تفعيل التخزين المؤقت، لأنّ شجرة تسهيل الاستخدام تظل محفوظة في جميع طلبات CDP، ولكنّ تحسين الأداء يكون أصغر نسبيًا.
على الرغم من أنّ تفعيل التخزين المؤقت يبدو مرغوبًا فيه هنا، إلا أنّه يتطلّب استخدام ذاكرة إضافية. بالنسبة إلى نصوص Puppeteer البرمجية التي تسجّل ملفات التتبُّع مثلاً، قد يؤدي ذلك إلى حدوث مشاكل. لذلك، قرّرنا عدم تفعيل ميزة التخزين المؤقت لشجرة تسهيل الاستخدام تلقائيًا. يمكن للمستخدمين تفعيل التخزين المؤقت بأنفسهم من خلال تفعيل نطاق أدوات تسهيل الاستخدام في 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 Canary أو Dev أو Beta كمتصفّح تلقائي للتطوير. تتيح لك قنوات المعاينة هذه الوصول إلى أحدث ميزات "أدوات مطوّري البرامج"، وتسمح لك باختبار واجهات برمجة التطبيقات المتطوّرة لمنصات الويب، وتساعدك في العثور على المشاكل في موقعك الإلكتروني قبل أن يكتشفها المستخدمون.
التواصل مع فريق "أدوات مطوّري البرامج في Chrome"
استخدِم الخيارات التالية لمناقشة الميزات الجديدة أو التحديثات أو أي شيء آخر يتعلّق بـ "أدوات مطوّري البرامج".
- يمكنك إرسال الملاحظات وطلبات الميزات إلينا على crbug.com.
- يمكنك الإبلاغ عن مشكلة في "أدوات مطوّري البرامج" من خلال النقر على خيارات إضافية > مساعدة > الإبلاغ عن مشكلة في "أدوات مطوّري البرامج" في "أدوات مطوّري البرامج".
- يمكنك نشر تغريدة على @ChromeDevTools.
- يمكنك إضافة تعليقات على فيديوهات على YouTube حول الميزات الجديدة في "أدوات مطوّري البرامج" أو فيديوهات على YouTube حول نصائح "أدوات مطوّري البرامج".