كيفية كتابة اختبارات متصفح موثوق بها باستخدام السيلينيوم و Node.js

هناك العديد من المقالات الجيدة حول كيفية البدء في اختبار المستعرض الآلي باستخدام إصدار NodeJS من السيلينيوم.

يختتم البعض الاختبارات في Mocha أو Jasmine ، ويقوم البعض بأتمتة كل شيء باستخدام npm أو Grunt أو Gulp. يصف كل منهم كيفية تثبيت ما تحتاج إليه ، إلى جانب إعطاء مثال لرمز العمل الأساسي. هذا مفيد للغاية لأن الحصول على جميع القطع المختلفة للعمل لأول مرة يمكن أن يمثل تحديًا.

لكنهم أقل من الخوض في تفاصيل العديد من getchs وأفضل الممارسات لأتمتة اختبار المتصفح الخاص بك عند استخدام Selenium.

تستمر هذه المقالة في المكان الذي تترك فيه هذه المقالات الأخرى ، وستساعدك على كتابة اختبارات المتصفح الآلية الأكثر موثوقية وقابلية للصيانة باستخدام واجهة برمجة تطبيقات NodeJS Selenium API.

تجنب النوم

طريقة السيلينيوم driver.sleep هي أسوأ عدو لك. والجميع يستخدمه. قد يكون هذا لأن الوثائق الخاصة بإصدار Node.js من Selenium هي مقتضبة وتغطي فقط بناء جملة API. إنه يفتقر إلى أمثلة الحياة الحقيقية.

أو قد يكون السبب في ذلك هو أن الكثير من أمثلة التعليمات البرمجية في مقالات المدونة وفي مواقع الأسئلة والأجوبة مثل StackOverflow تستخدمها.

دعنا نقول أن لوحة متحركة من حجم صفر ، إلى الحجم الكامل. دعونا ننظر.

يحدث ذلك بسرعة قد لا تلاحظ أن الأزرار وعناصر التحكم داخل اللوحة تتغير باستمرار في الحجم والموضع.

إليك نسخة تباطأت. انتبه إلى زر الإغلاق الأخضر ويمكنك رؤية تغيير حجم وموضع اللوحة.

هذا لا يمثل مشكلة بالنسبة للمستخدمين الحقيقيين لأن الرسوم المتحركة تحدث بسرعة كبيرة. إذا كان بطيئًا بدرجة كافية ، كما هو الحال في الفيديو الثاني ، وحاولت النقر فوق زر الإغلاق يدويًا أثناء حدوث ذلك ، فقد تنقر فوق الزر الخطأ أو تفوت الزر تمامًا.

لكن هذه الرسوم المتحركة تحدث عادة بسرعة كبيرة ، فلن تكون لديك فرصة للقيام بذلك. البشر فقط ننتظر لإكمال الرسوم المتحركة. ليس صحيحا مع السيلينيوم. إنها سريعة جدًا بحيث يمكنها محاولة النقر فوق العناصر التي لا تزال متحركة ، وقد تتلقى رسالة خطأ مثل:

System.InvalidOperationException: العنصر غير قابل للنقر عند النقطة (326 ، 792.5)

هذا عندما يقول العديد من المبرمجين "آها! يجب أن أنتظر انتهاء الرسوم المتحركة ، لذا سأستخدم driver.sleep (1000) لانتظار أن تكون اللوحة قابلة للاستخدام. "

إذا ما هي المشكلة؟

بيان driver.sleep (1000) يفعل ما يبدو. توقف عن تنفيذ البرنامج لمدة 1000 مللي ثانية ، ويسمح للمتصفح بمواصلة العمل. القيام بتخطيط العناصر أو تلاشيها أو تنشيطها أو تحميل الصفحة أو أي شيء آخر.

باستخدام المثال الوارد أعلاه ، إذا تلاشت اللوحة خلال فترة تزيد عن 800 مللي ثانية ، فعادةً ما ينجح driver.sleep (1000) في تحقيق ما تريد. لماذا لا نستعمله اذا؟

السبب الأكثر أهمية هو أنها ليست حتمية. وهذا يعني أنها ستعمل فقط بعض الوقت. نظرًا لأنه لا يعمل إلا في بعض الوقت ، ينتهي بنا الأمر إلى اختبارات هشة تنكسر في ظل ظروف معينة. هذا يعطي متصفح الآلي اختبار اسم سيء.

لماذا لا تعمل إلا في بعض الوقت؟ بعبارة أخرى ، لماذا لا تكون حتمية؟

غالبًا ما لا تلاحظه بعينيك هو الشيء الوحيد الذي يحدث على موقع الويب. يعد عنصر التلاشي أو الرسوم المتحركة مثالًا مثاليًا. ليس من المفترض أن نلاحظ هذه الأشياء إذا تمت بشكل جيد.

إذا طلبت من السيلينيوم البحث أولاً عن عنصر ثم النقر فوقه ، فقد يكون هناك بضع ميلي ثانية فقط بين هاتين العمليتين. يمكن أن يكون السيلينيوم أسرع بكثير من الإنسان.

عندما يستخدم الإنسان الموقع ، ننتظر أن يتلاشى العنصر قبل النقر فوقه. وعندما يستغرق هذا التلاشي أقل من ثانية ، فإننا ربما لا نلاحظ أننا نقوم بهذا "الانتظار". السيلينيوم ليس فقط أسرع وأقل تسامحًا ، اختباراتك الآلية يجب أن تتعامل مع كل أنواع العوامل الأخرى التي لا يمكن التنبؤ بها:

  1. قد يغير مصمم صفحة الويب وقت الحركة من 800 مللي ثانية إلى 1200 مللي ثانية. اختبار الخاص بك فقط كسر.
  2. لا تفعل المتصفحات دائمًا ما تطلبه بالضبط. بسبب تحميل النظام ، قد تتوقف الرسوم المتحركة بالفعل وتستغرق أكثر من 800 ميلي ثانية ، وربما أطول من نومك البالغ 1000 ميلي ثانية. اختبار الخاص بك فقط كسر.
  3. المتصفحات المختلفة لها محركات تخطيط مختلفة وترتيب أولويات عمليات التخطيط بشكل مختلف. أضف مستعرضًا جديدًا إلى مجموعة الاختبار الخاصة بك واختبرت للتو الاختبارات
  4. المتصفحات وجافا سكريبت التي تتحكم في الصفحة غير متزامنة بطبيعتها. إذا غيرت الرسوم المتحركة في مثالنا وظائف تحتاج إلى معلومات من النهاية الخلفية ، فقد يضيف مبرمج مكالمة AJAX وينتظر النتيجة قبل إطلاق الرسوم المتحركة.
    نحن نتعامل الآن مع زمن انتقال الشبكة والضمان الصفري للمدة التي سيستغرقها عرض اللوحة. اختبار الخاص بك فقط كسر.
  5. هناك بالتأكيد أسباب أخرى لا أعرفها.
    حتى متصفح واحد من تلقاء نفسه هو وحش معقدة وجميعهم لديهم أخطاء. لذلك نحن نتحدث عن محاولة جعل نفس الشيء يعمل على العديد من المتصفحات المختلفة ، والعديد من إصدارات المتصفح المختلفة ، والعديد من أنظمة التشغيل المختلفة ، والعديد من إصدارات نظام التشغيل المختلفة.
    في مرحلة ما ، تنهار اختباراتك إذا لم تكن حتمية. لا عجب أن يتخلى المبرمجون عن اختبار المتصفح الآلي ويشكون من مدى هشاشة الاختبارات.

ماذا يفعل المبرمجون عادة لإصلاح الأشياء عندما يحدث أي مما سبق؟ إنهم يتتبعون الأشياء إلى مشاكل التوقيت لذا فإن الإجابة الواضحة هي زيادة الوقت في بيان driver.sleep. ثم عبر أصابعهم بحيث يغطي جميع السيناريوهات المستقبلية المحتملة من تحميل النظام ، والاختلافات محرك التخطيط ، وهلم جرا. إنها ليست حتمية وتكسر ، لذا لا تفعل هذا!

إذا لم تكن مقتنعا بعد ، فهناك سبب آخر: اختباراتك ستعمل بشكل أسرع. الرسوم المتحركة من مثالنا لا تستغرق سوى 800 مللي ثانية ، كما نأمل. للتعامل مع "نأمل" وجعل الاختبارات تعمل في جميع الظروف ، من المحتمل أن ترى شيئًا مثل driver.sleep (2000) في العالم الواقعي.

لقد ضاعت أكثر من ثانية كاملة لخطوة واحدة فقط من اختباراتك الآلية. على العديد من الخطوات ، يضيف بسرعة. يستغرق الاختبار الذي تم إعادة تشكيله مؤخرًا لأحد صفحات الويب الخاصة بنا والذي استغرق عدة دقائق بسبب الإفراط في استخدام driver.sleep الآن أقل من خمسة عشر ثانية.

يقدم معظم ما تبقى من هذه المقالة أمثلة محددة حول كيفية إجراء اختباراتك بشكل حتمي تمامًا ، وتجنب استخدام driver.sleep.

مذكرة حول الوعود

تستخدم واجهة برمجة تطبيقات جافا سكريبت لـ Selenium الاستخدامات الكثيفة للوعود ، كما أنها تقوم بعمل جيد في إخفاء ذلك باستخدام مدير الوعد المدمج. هذا يتغير وسيتم إهماله.

في المستقبل ، ستحتاج إما إلى معرفة كيفية استخدام سلسلة الوعد بنفسك ، أو استخدام وظائف JavaScript الجديدة مثل الانتظار.

في هذه المقالة ، لا تزال الأمثلة تستفيد من مدير وعد السيلينيوم التقليدي المدمج وتستفيد من سلسلة الوعد. أمثلة التعليمات البرمجية هنا ستكون أكثر منطقية إذا فهمت كيف تعمل الوعود. ولكن لا يزال بإمكانك الحصول على الكثير من هذه المقالة إذا كنت تريد تخطي وعود التعلم في الوقت الحالي.

هيا بنا نبدأ

بالاستمرار في مثالنا على زر نريد أن نقره على لوحة تنعش ، دعنا نلقي نظرة على العديد من المسابقات المحددة التي يمكن أن تنهي اختباراتنا.

ماذا عن عنصر تمت إضافته ديناميكيًا إلى الصفحة ولا يوجد حتى الآن بعد الانتهاء من تحميل الصفحة؟

في انتظار وجود عنصر في DOM

لن تعمل التعليمة البرمجية التالية إذا تمت إضافة عنصر ذي معرف CSS لـ "my-button" إلى DOM بعد تحميل الصفحة:

/ / رمز التهيئة السيلينيوم تركت من أجل الوضوح
// تحميل الصفحة.
driver.get ( 'الشبكي: /foobar.baz')؛
/ / ابحث عن العنصر.
const const = driver.findElement (By.id ('my-button')) ؛
button.click ()؛

تتوقع طريقة driver.findElement أن يكون العنصر موجودًا بالفعل في DOM. سيخطئ إذا كان العنصر لا يمكن العثور عليه على الفور. في هذه الحالة ، يعني على الفور "بعد اكتمال تحميل الصفحة" بسبب بيان driver.get السابق.

تذكر أن الإصدار الحالي من JavaScript Selenium يدير الوعود لك. لذلك سوف تكتمل كل عبارة بالكامل قبل الانتقال إلى البيان التالي.

ملاحظة: السلوك أعلاه غير مرغوب فيه دائمًا. driver.findElement من تلقاء نفسه قد يكون في الواقع مفيد إذا كنت متأكدا من أن العنصر يجب أن يكون بالفعل هناك.

أولاً ، دعنا ننظر إلى الطريقة الخاطئة لإصلاح ذلك. بعد إخبارك ، قد يستغرق الأمر بضع ثوانٍ لإضافة العنصر إلى DOM:

driver.get ( 'الشبكي: /foobar.baz')؛
// الصفحة تم تحميلها ، اذهب الآن إلى النوم لبضع ثوان.
driver.sleep (3000)؛
// نصلي أن ثلاث ثوان كافية والعثور على العنصر.
const const = driver.findElement (By.id ('my-button')) ؛
button.click ()؛

لكل الأسباب المذكورة سابقًا ، يمكن أن يحدث هذا ، وربما سيفعل ذلك. نحن بحاجة إلى معرفة كيفية انتظار وجود عنصر. هذا أمر سهل إلى حد ما ، وسترى ذلك كثيرًا في أمثلة من جميع أنحاء الويب. في المثال أدناه ، نستخدم طريقة driver.wait الموثقة جيدًا لانتظار ما يصل إلى عشرين ثانية حتى يمكن العثور على العنصر في DOM:

زر const = driver.wait (
  until.elementLocated (By.id ( 'بلدي على زر'))،
  20000
)؛
button.click ()؛

هناك مزايا فورية لهذا. على سبيل المثال ، إذا تمت إضافة العنصر إلى DOM في ثانية واحدة ، فستكمل طريقة driver.wait في ثانية واحدة. لن تنتظر الثواني العشرين المحددة بالكامل.

بسبب هذا السلوك ، يمكننا وضع الكثير من الحشوة في مهلنا دون القلق بشأن إبطاء مهلة اختباراتنا. على عكس driver.sleep الذي سوف ننتظر دائما طوال الوقت المحدد.

هذا يعمل في كثير من الحالات. لكن إحدى الحالات التي لا تعمل فيها هي محاولة النقر فوق عنصر موجود في DOM ، ولكنه غير مرئي بعد.

السيلينيوم ذكي بما يكفي لعدم النقر فوق عنصر غير مرئي. هذا أمر جيد ، لأنه لا يمكن للمستخدمين النقر فوق العناصر غير المرئية ، ولكنه يجعلنا نعمل بجدية أكبر في إنشاء اختبارات آلية موثوقة.

الانتظار حتى يصبح العنصر مرئيًا

سنبني على المثال أعلاه لأنه من المنطقي انتظار وجود عنصر قبل ظهوره.

ستجد أيضًا استخدامنا الأول لسلسلة الوعد أدناه:

زر const = driver.wait (
  until.elementLocated (By.id ( 'بلدي على زر'))،
  20000
)
.ثم (عنصر => {
   العودة driver.wait (
     until.elementIsVisible (عنصر)،
     20000
   )؛
})؛
button.click ()؛

يمكن أن نتوقف هنا تقريبًا وستكون حالًا أفضل حالًا. باستخدام الرمز أعلاه ، ستقوم بإزالة الكثير من حالات الاختبار التي من شأنها أن تنكسر بسبب عدم وجود عنصر على الفور في DOM. أو لأنه غير مرئي على الفور بسبب أشياء مثل الرسوم المتحركة. أو حتى لكلا السببين.

الآن بعد أن تفهمت هذه التقنية ، يجب ألا يكون هناك أي سبب لكتابة كود السيلينيوم غير القطعي. هذا لا يعني أن هذا سهل دائمًا.

عندما تصبح الأمور أكثر صعوبة ، غالبًا ما يستسلم المطورون مرة أخرى ويلجئون إلى driver.sleep. آمل بإعطاء المزيد من الأمثلة ، يمكنني أن أشجعك على جعل اختباراتك حتمية.

كتابة الشروط الخاصة بك

بفضل الأسلوب حتى ، فإن JavaScript Selenium API لديه بالفعل عدد قليل من وسائل الراحة التي يمكنك استخدامها مع driver.wait. يمكنك أيضًا الانتظار حتى لم يعد هناك عنصر ، لعنصر يحتوي على نص معين ، أو حتى يتوفر تنبيه ، أو العديد من الشروط الأخرى.

إذا لم تتمكن من العثور على ما تحتاج إليه في أساليب الراحة المتوفرة ، فستحتاج إلى كتابة الشروط الخاصة بك. هذا في الواقع سهل للغاية ، لكن من الصعب العثور على أمثلة. وهناك مسكتك واحدة - والتي سنصل إليها.

وفقًا للوثائق ، يمكنك تزويد driver.wait بوظيفة ترجع صواب أو خطأ.

دعنا نقول أننا أردنا أن ننتظر أن يكون عنصر ما هو التعتيم الكامل:

// الحصول على العنصر.
عنصر const = driver.wait (
  until.elementLocated (By.id ( 'بعض معرف'))،
  20000
)؛
// driver.wait فقط يحتاج إلى وظيفة تقوم بإرجاع true من false.
driver.wait (() => {
  إرجاع element.getCssValue ('العتامة')
    .then (opacity => opacity === '1')؛
})؛

يبدو ذلك مفيدًا وقابل لإعادة الاستخدام ، لذلك دعونا نضعه في وظيفة:

const waitForOpacity = function (element) {
  إرجاع driver.wait (element => element.getCssValue ('العتامة')
    .then (opacity => opacity === '1')؛
  )؛
}؛

وبعد ذلك يمكننا استخدام وظيفتنا:

driver.wait (
  until.elementLocated (By.id ( 'بعض معرف'))،
  20000
)
. ثم (waitForOpacity)؛

هنا يأتي مسكتك. ماذا لو أردنا النقر فوق العنصر بعد وصوله إلى العتامة الكاملة؟ إذا حاولنا تعيين القيمة التي تم إرجاعها بواسطة ما سبق ، فلن نحصل على ما نريد:

عنصر const = driver.wait (
  until.elementLocated (By.id ( 'بعض معرف'))،
  20000
)
. ثم (waitForOpacity)؛
/ عفوًا ، العنصر صحيح أو خطأ ، وليس عنصرًا.
element.click ()؛

لا يمكننا استخدام الوعد بالسلاسل ، لنفس السبب.

عنصر const = driver.wait (
  until.elementLocated (By.id ( 'بعض معرف'))،
  20000
)
. ثم (waitForOpacity)
.ثم (عنصر => {
  // كلا ، العنصر منطقي هنا أيضًا.
  element.click ()؛
})؛

هذا هو السهل تحديد. إليك طريقة محسنة لدينا:

const waitForOpacity = function (element) {
  إرجاع driver.wait (element => element.getCssValue ('العتامة')
    .ثم (عتامة => {
      إذا (التعتيم === '1') {
        عنصر العودة ؛
      } آخر {
        عودة كاذبة؛
    })؛
  )؛
}؛

النمط أعلاه ، الذي يُرجع العنصر عندما يكون الشرط صحيحًا ، ويُرجع الخطأ false ، هو نمط قابل لإعادة الاستخدام يمكنك استخدامه عند كتابة الشروط الخاصة بك.

هنا هو كيف يمكننا استخدامه مع سلسلة الوعد:

driver.wait (
  until.elementLocated (By.id ( 'بعض معرف'))،
  20000
)
. ثم (waitForOpacity)
.then (element => element.click ())؛

او حتى:

عنصر const = driver.wait (
  until.elementLocated (By.id ( 'بعض معرف'))،
  20000
)
. ثم (waitForOpacity)؛
element.click ()؛

من خلال كتابة الشروط البسيطة الخاصة بك ، يمكنك توسيع خياراتك لجعل اختباراتك حتمية. لكن هذا لا يكفي دائمًا.

الذهاب سلبية

هذا صحيح ، في بعض الأحيان يجب أن تكون سلبيًا بدلاً من أن يكون إيجابيًا. ما أعنيه بهذا هو اختبار شيء لم يعد موجودًا أو شيء لم يعد مرئيًا.

لنفترض وجود عنصر في DOM بالفعل ، ولكن يجب ألا تتفاعل معه حتى يتم تحميل بعض البيانات عبر AJAX. يمكن تغطية العنصر بلوحة "loading ...".

إذا لاحظت عن كثب الشروط التي توفرها الطريقة حتى ، فقد تكون لاحظت طرقًا مثل elementIsNotVisible أو elementIsDisabled أو stalenessOfElement.

يمكنك اختبار لوحة "جارٍ التحميل ..." لكي لا تكون مرئية:

// تمت إضافته بالفعل إلى DOM ، لذلك سيعود هذا فورًا.
const المطلوب العنصر = driver.wait (
  until.elementLocated (By.id ( 'بعض معرف'))،
  20000
)؛
// لكن العنصر ليس جاهزًا حتى لوحة التحميل
// ذهب.
driver.wait (
  until.elementIsNotVisible (By.id ( 'تحميل لوحة'))،
  20000
)؛
// لوحة التحميل لم تعد مرئية وآمنة للتفاعل الآن.
desiredElement.click ()؛

أجد أن stalenessOfElement مفيد بشكل خاص. ينتظر حتى تتم إزالة عنصر من DOM ، والذي قد يحدث أيضًا من تحديث الصفحة.

فيما يلي مثال على انتظار تحديث محتويات iframe قبل المتابعة:

دع iframeElem = driver.wait (
  until.elementLocated (By.className ( 'نتيجة-الإطار من'))،
  20000
)؛
/ / نحن الآن نقوم بشيء يؤدي إلى تحديث iframe.
someElement.click ()؛
/ / انتظر iframe السابق لم تعد موجودة:
driver.wait (
  until.stalenessOf (iframeElem)،
  20000
)؛
// قم بالتبديل إلى iframe الجديد.
driver.wait (
  until.ableToSwitchToFrame (By.className ( 'نتيجة-الإطار من'))،
  20000
)؛
// أي رمز التالي سيكون متعلقًا بالإطار الجديد.

كن حاسمًا دائمًا ، ولا تنام

آمل أن تكون هذه الأمثلة قد ساعدتكم في فهم كيفية جعل اختبارات السيلينيوم الخاصة بك حتمية بشكل أفضل. لا تعتمد على driver.sleep مع تخمين تعسفي.

إذا كانت لديك أسئلة أو أساليب خاصة بك لصنع اختبار السلينيوم الحتمية ، فيرجى ترك تعليق.