رحلة اختبار E2E الجزء 1: STUI إلى WebdriverIO

نشرت: 2019-11-21

ملاحظة: هذا منشور من # frontend @ twiliosendgrid. بالنسبة إلى المنشورات الهندسية الأخرى ، توجه إلى قائمة المدونات الفنية.

عندما بدأت بنية الواجهة الأمامية لـ SendGrid في النضج عبر تطبيقات الويب الخاصة بنا ، أردنا إضافة مستوى آخر من الاختبار بالإضافة إلى الوحدة المعتادة وطبقة اختبار التكامل. سعينا إلى إنشاء صفحات وميزات جديدة مع تغطية اختبار E2E (من طرف إلى طرف) باستخدام أدوات التشغيل الآلي للمتصفح.

لقد أردنا أتمتة الاختبار من منظور العميل وتجنب اختبارات الانحدار اليدوي حيثما أمكن لأي تغييرات كبيرة قد تحدث لأي جزء من المكدس. كان لدينا ، ولا يزال لدينا ، الهدف التالي: توفير طريقة لكتابة اختبارات أتمتة E2E متسقة وقابلة للتصحيح وقابلة للصيانة وقيمة لتطبيقات الواجهة الأمامية والتكامل مع CICD (التكامل المستمر والنشر المستمر).

لقد جربنا العديد من الأساليب التقنية حتى انتهينا من حلنا المثالي لاختبار E2E. على مستوى عالٍ ، هذا يلخص رحلتنا:

  • بناء حل Ruby Selenium المخصص داخل الشركة ، SiteTestUI aka STUI
  • الانتقال من STUI إلى WebdriverIO المستند إلى العقدة
  • لم تكن راضيًا عن أي من الإعدادين ثم الانتقال أخيرًا إلى Cypress

منشور المدونة هذا هو جزء من جزأين يوثقان ويسلطان الضوء على خبراتنا والدروس المستفادة والمفاضلات مع كل من الأساليب المستخدمة على طول الطريق لإرشادك أنت والمطورين الآخرين إلى كيفية ربط اختبارات E2E بأنماط مفيدة واستراتيجيات الاختبار.

يشمل الجزء الأول صراعاتنا المبكرة مع STUI ، وكيف انتقلنا إلى WebdriverIO ، ومع ذلك ما زلنا نواجه الكثير من العيوب المماثلة لـ STUI. سنستعرض كيف كتبنا الاختبارات باستخدام WebdriverIO ، وقمنا بإرساء الاختبارات للتشغيل في حاوية ، وفي النهاية دمجنا الاختبارات مع Buildkite ، موفر CICD الخاص بنا.

إذا كنت ترغب في التخطي إلى ما وصلنا إليه مع اختبار E2E اليوم ، فالرجاء المضي قدمًا في الجزء الثاني حيث يمر عبر الترحيل النهائي من STUI و WebdriverIO إلى Cypress وكيف قمنا بإعداده عبر فرق مختلفة.

TLDR: لقد عانينا من آلام وصراعات مماثلة مع كل من حلول غلاف السيلينيوم ، STUI و WebdriverIO ، حيث بدأنا في النهاية في البحث عن بدائل في Cypress. لقد تعلمنا مجموعة من الدروس الثاقبة للتعامل مع كتابة اختبارات E2E والتكامل مع Docker و Buildkite.

جدول المحتويات:

أول دخول في اختبار E2E: siteTESUI aka STUI

التبديل من STUI إلى WebdriverIO

الخطوة 1: تحديد التبعيات لـ WebdriverIO

الخطوة 2: البيئة Configs and Scripts

الخطوة 3: تنفيذ اختبارات ENE محليًا

الخطوة 4: فصل جميع الاختبارات

الخطوة الخامسة: التكامل مع CICD

المفاضلات مع WebdriverIo

الانتقال إلى السرو

أول دخول في اختبار E2E: SiteTestUI المعروف أيضًا باسم STUI

عند البحث في البداية عن أداة أتمتة للمتصفح ، فإن SDET (مهندسو تطوير البرمجيات قيد الاختبار) يتدخلون في صنع حل داخلي مخصص خاص بنا مبني باستخدام Ruby و Selenium ، على وجه التحديد Rspec وإطار عمل سيلينيوم مخصص يسمى Gridium. لقد قدرنا دعمه عبر المستعرضات ، والقدرة على تكوين عمليات التكامل المخصصة الخاصة بنا مع TestRail لحالات اختبار مهندس ضمان الجودة (ضمان الجودة) ، وفكرة بناء الريبو المثالي لجميع فرق الواجهة الأمامية لكتابة اختبارات E2E في مكان واحد تعمل وفقا لجدول زمني.

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

أنشأنا تدريجيًا مجموعات اختبار كبيرة عبر فرق مختلفة في نفس الريبو باتباع أنماط مماثلة ، ولكن سرعان ما واجهنا العديد من الإحباطات التي من شأنها إبطاء تقدمنا ​​بشكل كبير للمطورين الجدد والمساهمين المتسقين في STUI مثل:

  • يتطلب الإعداد والتشغيل وقتًا وجهدًا كبيرين لتثبيت جميع برامج تشغيل المستعرضات ، وتبعيات Ruby Gem ، والإصدارات الصحيحة قبل تشغيل مجموعات الاختبار. كان علينا أحيانًا معرفة سبب إجراء الاختبارات على جهاز المرء مقابل جهاز شخص آخر وكيف تختلف إعداداتهم.
  • انتشرت أجنحة الاختبار واستمرت لساعات حتى الانتهاء. نظرًا لأن جميع الفرق ساهمت في نفس الريبو ، فإن إجراء جميع الاختبارات بشكل متسلسل يعني الانتظار لعدة ساعات حتى يتم تشغيل مجموعة الاختبار الشاملة ، ومن المحتمل أن تؤدي فرق متعددة تدفع رمزًا جديدًا إلى اختبار مكسور آخر في مكان آخر.
  • نشعر بالإحباط بسبب محددات CSS غير المستقرة ومحددات XPath المعقدة . توضح هذه الصورة أدناه كيف أن استخدام XPath يمكن أن يجعل الأمور أكثر تعقيدًا وكانت هذه بعضًا من أبسط الأشياء.

  • كانت اختبارات التصحيح مؤلمة . واجهتنا مشكلة في تصحيح مخرجات الأخطاء الغامضة ولم يكن لدينا في العادة أي فكرة عن مكان وكيفية فشل الأشياء. لم نتمكن إلا من إجراء الاختبارات بشكل متكرر ومراقبة المتصفح لاستنتاج مكان الفشل المحتمل وأي الشفرة كانت مسؤولة عن ذلك. عندما فشل اختبار في بيئة Docker في CICD دون أن ننظر إلى الكثير بخلاف مخرجات وحدة التحكم ، كافحنا لإعادة الإنتاج محليًا وحل المشكلة.
  • واجهنا أخطاء السيلينيوم والبطء . تم إجراء الاختبارات ببطء بسبب إرسال جميع الطلبات من الخادم إلى المتصفح وفي بعض الأحيان تتعطل اختباراتنا تمامًا عند محاولة تحديد العديد من العناصر على الصفحة أو لأسباب غير معروفة أثناء عمليات التشغيل التجريبية.
  • تم قضاء المزيد من الوقت في إصلاح الاختبارات وتخطيها وبدأ تجاهل عمليات اختبار البناء المجدولة المعطلة. لم تقدم الاختبارات قيمة في الإشارة فعليًا إلى أخطاء حقيقية في النظام.
  • شعرت فرق الواجهة الأمامية لدينا بأنها غير متصلة باختبارات E2E لأنها كانت موجودة في مستودع منفصل عن تلك الخاصة بتطبيق الويب. غالبًا ما احتجنا إلى فتح كل من مستودعات إعادة الشراء في نفس الوقت ومواصلة البحث بين قواعد الأكواد بالإضافة إلى علامات تبويب المتصفح عند إجراء الاختبارات.
  • لم تحب فرق Frontend تبديل السياق من كتابة التعليمات البرمجية في JavaScript أو TypeScript يوميًا إلى Ruby والاضطرار إلى إعادة تعلم كيفية كتابة الاختبارات كلما ساهمت في STUI.
  • نظرًا لأنه كان أول ما استغرقته الكثير منا عند المساهمة في الاختبار ، فقد وقعنا في الكثير من الأنماط المضادة مثل بناء الحالة من خلال واجهة المستخدم لتسجيل الدخول ، وعدم القيام بما يكفي من التفكيك أو الإعداد من خلال واجهة برمجة التطبيقات ، وعدم وجود وثائق كافية لمتابعة ما يجعل الاختبار رائعًا.

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

لقد قدرنا طريقة لتمكين مطوري الواجهة الأمامية وتأكيد الجودة الآخرين من بناء مجموعات اختبار E2E المستقرة الخاصة بهم باستخدام JavaScript الذي يتواجد مع كود التطبيق الخاص بهم لتعزيز إعادة استخدام الاختبارات والقرب منها وملكيتها. قادنا ذلك إلى التحقيق في WebdriverIO ، وهو إطار سيلينيوم قائم على JavaScript لاختبارات أتمتة المتصفح ، كبديل أولي لـ STUI ، حل Ruby Selenium الداخلي المخصص.

سنواجه فيما بعد عيوبه وننتقل في النهاية إلى Cypress (تقدم سريعًا إلى الجزء 2 هنا إذا كانت عناصر WebdriverIO لا تروق لك) ، لكننا اكتسبنا خبرة لا تقدر بثمن في إنشاء بنية تحتية موحدة في الريبو لكل فريق ، ودمج اختبارات E2E في CICD لواصفتنا الأمامية الفرق ، واعتماد أنماط فنية تستحق التوثيق عنها في رحلتنا وللآخرين للتعرف على من قد يكون على وشك القفز إلى WebdriverIO أو أي حل اختبار E2E آخر.

التبديل من STUI إلى WebdriverIO

عند الشروع في WebdriverIO للتخفيف من الإحباطات التي مررنا بها ، قمنا بالتجربة من خلال جعل كل فريق واجهة أمامية يحول اختبارات الأتمتة الحالية المكتوبة باستخدام نهج Ruby Selenium إلى اختبارات WebdriverIO في JavaScript أو TypeScript ومقارنة الاستقرار والسرعة وتجربة المطور والصيانة الشاملة لـ الإختبارات.

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

  1. تثبيت واختيار التبعيات لربط إطار الاختبار
  2. إنشاء تكوينات البيئة وأوامر البرنامج النصي
  3. تنفيذ اختبارات E2E التي تمر محليًا مقابل بيئات مختلفة
  4. تفريغ الاختبارات
  5. دمج الاختبارات Dockerized مع مزود CICD

الخطوة 1: تحديد التبعيات لـ WebdriverIO

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

نظرًا لأن WebdriverIO ليس توجيهيًا بشأن ما يجب استخدامه ، فقد فتح الباب أمام فرق الواجهة الأمامية لديها مكتبات وتكوينات مختلفة ، على الرغم من أن الاختبارات الأساسية الشاملة ستكون متسقة في استخدام WebdriverIO API.

اخترنا السماح لكل فريق من فرق الواجهة الأمامية بالتخصيص بناءً على تفضيلاتهم ، وقد استخدمنا عادةً Mocha كإطار اختبار ، و Mochawesome كمراسل ، وخدمة Selenium Standalone ، ودعم Typescript. اخترنا Mocha و Mochawesome نظرًا لمعرفة فرقنا وخبرتنا السابقة مع Mocha من قبل ، لكن الفرق الأخرى قررت استخدام بدائل أخرى أيضًا.

الخطوة 2: البيئة Configs and Scripts

بعد اتخاذ قرار بشأن البنية الأساسية لـ WebdriverIO ، احتجنا إلى طريقة لتشغيل اختبارات WebdriverIO الخاصة بنا بإعدادات مختلفة لكل بيئة. فيما يلي قائمة توضح معظم حالات الاستخدام لكيفية رغبتنا في تنفيذ هذه الاختبارات ولماذا رغبنا في دعمها:

  • مقابل خادم Webpack dev الذي يعمل على المضيف المحلي (مثل http: // localhost: 8000) وسيتم توجيه خادم dev هذا إلى واجهة برمجة تطبيقات بيئة معينة مثل الاختبار أو التدريج (على سبيل المثال https://testing.api.com أو https: // staging.api.com).
    لماذا ا؟ نحتاج في بعض الأحيان إلى إجراء تغييرات على تطبيق الويب المحلي الخاص بنا ، مثل إضافة محددات أكثر تحديدًا لاختباراتنا للتفاعل مع العناصر بطريقة أكثر قوة أو كنا في طور تطوير ميزة جديدة ونحتاج إلى ضبط والتحقق من صحة اختبارات الأتمتة الحالية ستمر محليًا مقابل تغييراتنا البرمجية الجديدة. كلما تغير رمز التطبيق ولم نضغط على البيئة المنشورة حتى الآن ، استخدمنا هذا الأمر لإجراء اختباراتنا مقابل تطبيق الويب المحلي الخاص بنا.
  • مقابل تطبيق تم نشره لبيئة معينة (مثل https://testing.app.com أو https://staging.app.com) مثل الاختبار أو التدريج
    لماذا ا؟ في أحيان أخرى ، لا يتغير رمز التطبيق ولكن قد نضطر إلى تغيير رمز الاختبار الخاص بنا لإصلاح بعض التقلبات أو نشعر بالثقة الكافية لإضافة الاختبارات أو حذفها تمامًا دون إجراء أي تغييرات في الواجهة الأمامية. لقد استخدمنا هذا الأمر بكثافة لتحديث الاختبارات أو تصحيحها محليًا مقابل التطبيق المنشور لمحاكاة أكثر عن كثب لكيفية عمل اختباراتنا في خطوط أنابيب CICD.
  • التشغيل في حاوية Docker مقابل تطبيق تم نشره لبيئة معينة مثل الاختبار أو التدريج
    لماذا ا؟ هذا مخصص لخطوط أنابيب CICD حتى نتمكن من تشغيل اختبارات E2E في حاوية Docker على سبيل المثال التطبيق المنشور المرحلي والتأكد من اجتيازها قبل نشر الكود في الإنتاج أو في عمليات الاختبار المجدولة في خط أنابيب مخصص. عند إعداد هذه الأوامر في البداية ، أجرينا الكثير من التجارب والأخطاء لتدوير حاويات Docker بقيم متغيرة للبيئة واختبارها لمعرفة الاختبارات المناسبة التي تم تنفيذها بنجاح قبل ربطها بمزود CICD لدينا ، Buildkite.

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

لقد شكلنا ملف تكوين أساسي مشترك ، wdio.conf.js ، مثل هذا:

لتلائم أول حالة استخدام رئيسية لدينا لإجراء اختبارات E2E مقابل خادم مطور webpack محلي أشار إلى واجهة برمجة تطبيقات البيئة ، أنشأنا ملف تهيئة المضيف المحلي ، wdio.localhost.conf.js ، من خلال ما يلي:

لاحظ أننا دمجنا الملف الأساسي وأضفنا الخصائص المحددة للمضيف المحلي إلى الملف لجعله أكثر إحكاما وأسهل في الصيانة. نستخدم أيضًا خدمة Selenium Standalone لتدوير أنواع مختلفة من المتصفحات وتعرف أيضًا بالإمكانيات.

بالنسبة لحالة الاستخدام الثانية لتشغيل اختبارات E2E مقابل تطبيق ويب تم نشره ، قمنا بإعداد ملفات تكوين التطبيق التجريبي والاختبار المرحلي ، `wdio.testing.conf.js` و wdio.staging.conf.js ، على غرار ما يلي:

أضفنا هنا بعض متغيرات البيئة الإضافية إلى ملفات التكوين مثل بيانات اعتماد تسجيل الدخول للمستخدمين المخصصين عند التدريج وقمنا بتحديث `baseUrl` للإشارة إلى عنوان URL للتطبيق المرحلي المنشور.

بالنسبة لحالة الاستخدام الثالثة لتشغيل اختبارات E2E في حاوية Docker مقابل تطبيق ويب تم نشره في نطاق موفر CICD الخاص بنا ، قمنا بإعداد ملفات تكوين CICD ، wdio.cicd.testing.conf.js و wdio.cicd.staging.conf.js ، مثل ذلك:

لاحظ كيف أننا لم نعد نستخدم خدمة Selenium Standalone بعد الآن حيث سنقوم لاحقًا بتثبيت Selenium Chrome و Selenium Hub وكود التطبيق في خدمات منفصلة في ملف Docker Compose. يعرض هذا التكوين أيضًا نفس متغيرات البيئة مثل التكوين المرحلي مثل بيانات اعتماد تسجيل الدخول و "baseUrl" نظرًا لأننا نتوقع تشغيل اختباراتنا ضد تطبيق التدريج المنشور ، والفرق الوحيد هو أن هذه الاختبارات تهدف إلى التنفيذ داخل حاوية Docker .

مع إنشاء ملفات تهيئة البيئة هذه ، حددنا أوامر البرنامج النصي package.json التي من شأنها أن تكون بمثابة الأساس للاختبار لدينا. في هذا المثال ، قمنا ببدء الأوامر بـ "uitest" للإشارة إلى اختبارات واجهة المستخدم باستخدام WebdriverIO ولأننا أنهينا أيضًا ملفات الاختبار بـ *.uitest.js . فيما يلي بعض نماذج الأوامر لبيئة التدريج:

الخطوة 3: تنفيذ اختبارات E2E محليًا

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

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

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

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

لاحظ كيف استخدمنا الفئة الأساسية المفتوحة من خلال super.open(...) مع المسار المحدد للصفحة حتى نتمكن من زيارة الصفحة التي تحتوي على هذه المكالمة ، SomePage.open() . قمنا أيضًا بتصدير الفئة التي تمت تهيئتها بالفعل حتى نتمكن من الرجوع إلى العناصر مثل SomePage.submitButton أو SomePage.tableRows والتفاعل مع تلك العناصر أو التأكيد عليها باستخدام أوامر WebdriverIO. إذا كان من المفترض أن تتم مشاركة كائن الصفحة وتهيئته بخصائص العضو الخاصة به في المُنشئ ، فيمكننا أيضًا تصدير الفئة مباشرةً وإنشاء مثيل لكائن الصفحة في ملفات الاختبار باستخدام new SomePage(...constructorArgs) .

بعد أن وضعنا كائنات الصفحة مع المحددات وبعض وظائف المساعد ، كتبنا بعد ذلك اختبارات E2E وصممنا صيغة الاختبار هذه بشكل عام:

  • قم بإعداد أو هدم من خلال API ما هو ضروري لإعادة ضبط ظروف الاختبار إلى نقطة البداية المتوقعة قبل تشغيل الاختبارات الفعلية.
  • قم بتسجيل الدخول إلى مستخدم مخصص للاختبار ، لذا فعندما نزور الصفحات مباشرة ، سنظل مسجلين الدخول ولن نضطر إلى المرور عبر واجهة المستخدم. لقد أنشأنا وظيفة مساعد login بسيطة تأخذ اسم مستخدم وكلمة مرور تقوم بإجراء نفس استدعاء واجهة برمجة التطبيقات الذي نستخدمه لصفحة تسجيل الدخول الخاصة بنا والتي تعيد في النهاية رمز المصادقة المطلوب للبقاء مسجلاً للدخول وتمرير رؤوس طلبات واجهة برمجة التطبيقات المحمية. قد يكون لدى الشركات الأخرى المزيد من نقاط النهاية أو الأدوات الداخلية المخصصة لإنشاء مستخدمين جدد تمامًا ببيانات أولية وتكوينات بسرعة ، لكننا ، للأسف ، لم يكن لدينا ما يكفي من نقاط النهاية. سنفعل ذلك بالطريقة القديمة وننشئ مستخدمين مخصصين للاختبار في بيئاتنا بتكوينات مختلفة من خلال واجهة المستخدم وغالبًا ما نفصل الاختبارات للصفحات ذات المستخدمين المتميزين لتجنب تضارب الموارد والبقاء معزولين عند إجراء الاختبارات بالتوازي. كان علينا التأكد من أن مستخدمي الاختبار المخصصين لم يتأثروا بالآخرين وإلا ستنكسر الاختبارات عندما يتلاعب شخص ما دون قصد بأحدهم.
  • أتمتة الخطوات كما لو كان المستخدم النهائي سيتفاعل مع الميزة / الصفحة. عادةً ما نزور الصفحة التي تحتوي على تدفق الميزات لدينا ونبدأ في اتباع الخطوات عالية المستوى التي قد يقوم بها المستخدم النهائي مثل ملء المدخلات والنقر فوق الأزرار وانتظار ظهور النماذج أو اللافتات ومراقبة الجداول للمخرجات المتغيرة مثل نتيجة العمل. باستخدام كائنات الصفحة سهلة الاستخدام والمحددات ، قمنا بتنفيذ كل خطوة بسرعة ، ومع فحص سلامة العمل على طول الطريق ، فإننا نؤكد على ما يجب أو لا يجب أن يراه المستخدم على الصفحة أثناء تدفق الميزة حتى تتصرف أشياء معينة كما هو متوقع قبل وبعد كل خطوة. كنا أيضًا نتداول بشأن اختيار اختبارات المسار السعيد عالية القيمة وأحيانًا حالات الخطأ الشائعة القابلة للتكرار بسهولة ، مع تأجيل باقي اختبارات المستوى الأدنى إلى اختبارات الوحدة والتكامل.

فيما يلي مثال تقريبي للتخطيط العام لاختبارات E2E الخاصة بنا (تم تطبيق هذه الإستراتيجية على أطر عمل اختبار أخرى قمنا بتجربتها أيضًا):

في ملاحظة جانبية ، اخترنا عدم تغطية جميع النصائح والمشكلات الخاصة بأفضل ممارسات WebdriverIO و E2E في سلسلة منشورات المدونة هذه ، لكننا سنتحدث عن هذه الموضوعات في منشور مدونة مستقبلي ، لذا ابق على اطلاع!

الخطوة 4: فصل جميع الاختبارات

عند تنفيذ كل خطوة في خط أنابيب Buildkite على جهاز AWS جديد في السحابة ، لا يمكننا ببساطة استدعاء "npm run uitests: staging" لأن هذه الأجهزة لا تحتوي على عقدة أو متصفحات أو كود تطبيقنا أو أي تبعيات أخرى لتشغيل الاختبارات فعليًا .

لحل هذه المشكلة ، قمنا بتجميع جميع التبعيات مثل Node و Selenium و Chrome ورمز التطبيق في حاوية Docker لتشغيل اختبارات WebdriverIO بنجاح. لقد استفدنا من Docker و Docker Compose لتجميع جميع الخدمات اللازمة للتشغيل ، والتي تُرجمت إلى Dockerfiles docker-compose.yml compose.yml والكثير من التجارب مع تدوير حاويات Docker محليًا لتشغيل الأشياء.

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

سنعرض في الغالب الملفات الكاملة التي تطابق أحد تكوينات فرقنا ونأمل أن يوفر ذلك رؤى لك أو لأي شخص يعالج المشكلة العامة المتمثلة في Dockerizing بالاختبارات القائمة على السيلينيوم.

على مستوى عالٍ ، تطلبت اختباراتنا ما يلي:

  • السيلينيوم لتنفيذ الأوامر والتواصل مع المستعرض. لقد استخدمنا Selenium Hub لتدوير مثيلات متعددة حسب الرغبة وقمنا بتنزيل الصورة ، "selenium / hub" ، لخدمة selenium-hub في ملف إنشاء عامل ميناء.
  • متصفح للتشغيل ضده. قمنا بإحضار مثيلات Selenium Chrome وقمنا بتثبيت الصورة ، "selenium / node-chrome-debug" ، لخدمة selenium-chrome في docker-compose.yml file .
  • كود التطبيق لتشغيل ملفات الاختبار الخاصة بنا مع تثبيت أي وحدات عقدة أخرى. أنشأنا Dockerfile جديدًا لتوفير بيئة مع Node لتثبيت حزم npm وتشغيل البرامج النصية package.json والنسخ فوق كود الاختبار وتعيين خدمة مخصصة لتشغيل ملفات الاختبار المسماة uitests في ملف docker docker-compose.yml compose.yml.

لإحضار خدمة مع جميع تطبيقاتنا وكود الاختبار الضروري لتشغيل اختبارات WebdriverIO ، قمنا بصياغة Dockerfile يسمى Dockerfile.uitests بتثبيت جميع node_modules ونسخ الكود إلى دليل عمل الصورة في بيئة Node. سيتم استخدام هذا من خلال خدمة uitests Docker Compose الخاصة بنا وقد حققنا إعداد Dockerfile بالطريقة التالية:

من أجل إحضار Selenium Hub ومتصفح Chrome ورمز اختبار التطبيق معًا لتشغيل اختبارات WebdriverIO ، حددنا selenium-hub selenium-chrom كروم e و uitest s في ملف docker docker-compose.uitests.yml compose.uitests.yml :

لقد قمنا بتوصيل Selenium Hub وصور Chrome من خلال متغيرات البيئة ، depends_on على وتعريض المنافذ للخدمات. سيتم في النهاية دفع صورة رمز تطبيق الاختبار الخاص بنا وسحبها من سجل Docker الخاص الذي نديره.

سنقوم ببناء صورة Docker لرمز الاختبار أثناء CICD مع متغيرات بيئة معينة مثل VERSION و PIPELINE_SUFFIX للإشارة إلى الصور بواسطة علامة واسم أكثر تحديدًا. سنبدأ بعد ذلك في تشغيل خدمات السيلينيوم وتنفيذ الأوامر من خلال خدمة uitests لتنفيذ اختبارات WebdriverIO.

نظرًا لأننا أنشأنا ملفات Docker Compose الخاصة بنا ، فقد استفدنا من الأوامر المفيدة مثل docker-compose up compose up و docker docker-compose down باستخدام Mac Docker المثبت على أجهزتنا لاختبار الصور محليًا باستخدام التكوينات المناسبة وتشغيلها بسلاسة قبل التكامل مع Buildkite. لقد وثقنا جميع الأوامر اللازمة لإنشاء الصور ذات العلامات ، ودفعها إلى السجل ، وسحبها لأسفل ، وقمنا بإجراء الاختبارات وفقًا لقيم متغيرات البيئة.

الخطوة الخامسة: التكامل مع CICD

بعد أن أنشأنا أوامر Docker العاملة وتم إجراء اختباراتنا بنجاح داخل حاوية Docker مقابل بيئات مختلفة ، بدأنا في الاندماج مع Buildkite ، موفر CICD الخاص بنا.

قدمت Buildkite طرقًا لتنفيذ الخطوات في ملف .yml على أجهزة AWS الخاصة بنا باستخدام البرامج النصية Bash ومتغيرات البيئة التي تم تعيينها إما من خلال التعليمات البرمجية أو واجهة مستخدم إعدادات Buildkite لخط أنابيب الريبو الخاص بنا.

سمحت لنا Buildkite أيضًا بتشغيل خط أنابيب الاختبار هذا من خط أنابيب النشر الرئيسي لدينا مع متغيرات البيئة المصدرة وسنعيد استخدام خطوات الاختبار هذه لأنابيب الاختبار المعزولة الأخرى التي ستعمل وفقًا لجدول زمني لمراقبة ضمان الجودة لدينا والنظر إليها.

على مستوى عالٍ ، شاركت خطوط أنابيب Buildkite الاختبارية الخاصة بنا لـ WebdriverIO ولاحقًا خطوط Cypress الخطوات المماثلة التالية:

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

فيما يلي مثال قريب لملف pipeline.uitests.yml الذي يوضح إعداد صور Docker في خطوة "إنشاء صورة UITests Docker" وتشغيل الاختبارات في خطوة "تشغيل اختبارات Webdriver على Chrome":

هناك شيء واحد يجب ملاحظته وهو الخطوة الأولى ، "إنشاء UITests Docker Image" ، وكيفية إعداد صور Docker للاختبار. استخدم الأمر Docker Compose build لإنشاء خدمة uitests مع كل كود اختبار التطبيق ووسمها بمتغير البيئة latest و ${VERSION} حتى نتمكن في النهاية من سحب نفس الصورة بالعلامة المناسبة لهذا الإصدار في المستقبل خطوة.

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

في خطوة "تشغيل اختبارات Webdriver ضد Chrome" ، نسحب الصورة التي أنشأناها ووسمناها ودفعناها في الخطوة الأولى وبدء تشغيل Selenium Hub و Chrome وخدمات الاختبارات. استنادًا إلى متغيرات البيئة مثل $UITESTENV و $UITESTSUITE ، سنختار نوع الأمر المطلوب تشغيله مثل npm run uitest: ومجموعات الاختبار التي سيتم تشغيلها لبناء Buildkite المحدد مثل --suite $UITESTSUITE .

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

فيما يلي مثال على اختبارات WebdriverIO التي تم تشغيلها في خط أنابيب مخصص للاختبارات ، والذي أعاد أيضًا استخدام نفس ملف pipeline.uitests.yml ولكن مع تعيين متغيرات البيئة حيث تم تشغيل خط الأنابيب. فشل هذا الإصدار وكان يحتوي على لقطات شاشة خطأ بالنسبة لنا لإلقاء نظرة عليها ضمن علامة التبويب Artifacts وإخراج وحدة التحكم ضمن علامة التبويب Logs . تذكر artifact_paths في pipeline.uitests.yml (https://gist.github.com/alfredlucero/71032a82f3a72cb2128361c08edbcff2#file-pipeline-uitests-yml-L38) ، إعدادات لقطات الشاشة لـ `mochawesome` في` wdio.conf. `ملف (https://gist.github.com/alfredlucero/4ee280be0e0674048974520b79dc993a#file-wdio-conf-js-L39) ، وتركيب وحدات التخزين في خدمة" uitests` في "docker-compose.uitests.yml` (https://gist.github.com/alfredlucero/d2df4533a4a49d5b2f2c4a0eb5590ff8#file-docker-compose-yml-L32)؟

تمكنا من ربط لقطات الشاشة التي يمكن الوصول إليها من خلال Buildkite UI حتى يتسنى لنا تنزيلها مباشرة والاطلاع على الفور للمساعدة في اختبارات تصحيح الأخطاء كما هو موضح أدناه.

يتم عرض مثال آخر على اختبارات WebdriverIO التي تعمل في خط أنابيب منفصل وفقًا لجدول زمني لصفحة معينة باستخدام ملف pipeline.uitests.yml باستثناء متغيرات البيئة التي تم تكوينها بالفعل في إعدادات خط أنابيب Buildkite أدناه.

من المهم ملاحظة أن كل موفر CICD لديه وظائف وطرق مختلفة لدمج الخطوات في نوع من عملية النشر عند الدمج في كود جديد سواء كان ذلك من خلال ملفات .yml مع بناء جملة محدد أو إعدادات واجهة المستخدم الرسومية أو البرامج النصية Bash أو أي وسيلة أخرى.

عندما قمنا بالتبديل من Jenkins إلى Buildkite ، قمنا بتحسين قدرة الفرق بشكل كبير على تحديد خطوط الأنابيب الخاصة بهم داخل قواعد الرموز الخاصة بهم ، مع موازاة الخطوات عبر آلات القياس عند الطلب ، والاستفادة من أوامر أسهل في القراءة.

بغض النظر عن موفر CICD الذي قد تستخدمه ، ستكون استراتيجيات دمج الاختبارات متشابهة في إعداد صور Docker وتشغيل الاختبارات بناءً على متغيرات البيئة لقابلية النقل والمرونة.

المفاضلات مع WebdriverIO

بعد تحويل عدد كبير من اختبارات حل Ruby Selenium المخصصة إلى اختبارات WebdriverIO والتكامل مع Docker و Buildkite ، قمنا بالتحسين في بعض المجالات ولكننا ما زلنا نشعر بصراعات مماثلة للنظام القديم الذي قادنا في النهاية إلى محطتنا التالية والأخيرة مع Cypress من أجل حل اختبار E2E الخاص بنا.

فيما يلي قائمة ببعض المحترفين الذين وجدناهم من تجاربنا مع WebdriverIO بالمقارنة مع حل Ruby Selenium المخصص:

  • تمت كتابة الاختبارات فقط بلغة JavaScript أو TypeScript بدلاً من لغة Ruby . كان هذا يعني تقليل تبديل السياق بين اللغات وقضاء وقت أقل في إعادة تعلم Ruby في كل مرة نكتب فيها اختبارات E2E.
  • قمنا بتجميع الاختبارات باستخدام كود التطبيق بدلاً من وضعها بعيدًا في مستودع Ruby المشترك. لم نعد نشعر بالاعتماد على فشل اختبارات الفرق الأخرى وأخذنا مزيدًا من الملكية المباشرة لاختبارات E2E لميزاتنا في مستودعاتنا.
  • نحن نقدر خيار الاختبار عبر المستعرضات . باستخدام WebdriverIO ، يمكننا إجراء اختبارات ضد الإمكانات أو المتصفحات المختلفة مثل Chrome و Firefox و IE ، على الرغم من أننا ركزنا بشكل أساسي على إجراء اختباراتنا ضد Chrome حيث زار أكثر من 80٪ من مستخدمينا تطبيقنا من خلال Chrome.
  • لقد استمتعنا بإمكانية الاندماج مع خدمات الجهات الخارجية . توضح وثائق WebdriverIO كيفية التكامل مع خدمات الجهات الخارجية مثل BrowserStack و SauceLabs للمساعدة في تغطية تطبيقنا عبر جميع الأجهزة والمتصفحات.
  • كانت لدينا المرونة في اختيار عداء الاختبار والمراسل والخدمات الخاصة بنا . لم يكن WebdriverIO توجيهيًا بشأن ما يجب استخدامه ، لذلك اتخذ كل فريق الحرية في تقرير ما إذا كان سيتم استخدام أشياء مثل Mocha و Chai أو Jest وغيرها من الخدمات أم لا. يمكن أيضًا تفسير هذا على أنه خدعة حيث بدأت الفرق في الانحراف عن إعداد بعضها البعض وتطلب الأمر قدرًا كبيرًا من الوقت لتجربة كل خيار من الخيارات حتى نختارها.
  • كانت واجهة برمجة تطبيقات WebdriverIO و CLI والوثائق قابلة للخدمة بدرجة كافية لكتابة الاختبارات والتكامل مع Docker و CIC D. يمكن أن يكون لدينا العديد من ملفات التكوين المختلفة ، وتجميع المواصفات ، وتنفيذ الاختبارات من خلال سطر الأوامر ، وكتابة الاختبارات التي تتبع نمط كائن الصفحة. ومع ذلك ، يمكن أن تكون الوثائق أكثر وضوحًا وكان علينا البحث في الكثير من الأخطاء الغريبة. ومع ذلك ، تمكنا من تحويل اختباراتنا من محلول روبي سيلينيوم.

لقد أحرزنا تقدمًا في الكثير من المجالات التي افتقرنا إليها في حل Ruby Selenium السابق ، لكننا واجهنا الكثير من العوائق التي منعتنا من المشاركة في WebdriverIO مثل ما يلي:

  • نظرًا لأن WebdriverIO كان لا يزال قائمًا على السيلينيوم ، فقد واجهنا الكثير من المهلات والأعطال والأخطاء الغريبة ، مما يذكرنا بالذكريات السلبية من خلال حل Ruby Selenium القديم. في بعض الأحيان ، تتعطل اختباراتنا تمامًا عندما نختار العديد من العناصر على الصفحة وتعمل الاختبارات بشكل أبطأ مما نود. كان علينا اكتشاف الحلول من خلال الكثير من مشكلات Github أو تجنب بعض المنهجيات عند كتابة الاختبارات.
  • كانت تجربة المطور الإجمالية دون المستوى الأمثل . قدمت الوثائق نظرة عامة عالية المستوى للأوامر ولكنها لم تقدم أمثلة كافية لشرح جميع طرق استخدامها. لقد تجنبنا كتابة اختبارات E2E مع Ruby وأخيراً تمكنا من كتابة اختبارات في JavaScript أو TypeScript ، لكن واجهة برمجة تطبيقات WebdriverIO كانت مربكة بعض الشيء للتعامل معها. ومن الأمثلة الشائعة استخدام $ مقابل $$ لعناصر المفرد والجمع ، $('...').waitForVisible(9000, true) لانتظار عدم ظهور عنصر ، وأوامر أخرى غير بديهية. لقد واجهنا الكثير من المحددات غير المستقرة واضطررنا صراحة إلى $(...).waitForVisible() لكل شيء.
  • كانت اختبارات التصحيح مؤلمة للغاية ومملة للمطورين وتأكيد الجودة. Whenever tests failed, we only had screenshots, which would often be blank or not capturing the right moment for us to deduce what went wrong, and vague console error messages that did not point us in the right direction of how to solve the problem and even where the issue occurred. We often had to re-run the tests many times and stare closely at the Chrome browser running the tests to hopefully put things together as to where in the code our tests failed. We used things like browser.debug() but it often did not work or did not provide enough information. We gradually gathered a bunch of console error messages and mapped them to possible solutions over time but it took lots of pain and headache to get there.
  • WebdriverIO tests were tough to set up with Docker . We struggled with trying to incorporate it into Docker as there were many tutorials and ways to do things in articles online, but it was hard to figure out a way that worked in general. Hooking up 2 to 3 services together with all these configurations led to long trial and error experiments and the documentation did not guide us enough in how to do that.
  • Choosing the test runner, reporter, assertions, and services demanded lots of research time upfront . Since WebdriverIO was flexible enough to allow other options, many teams had to spend plenty of time to even have a solid WebdriverIO infrastructure after experimenting with a lot of different choices and each team can have a completely different setup that doesn't transfer over well for shared knowledge and reuse.

To summarize our WebdriverIO and STUI comparison, we analyzed the overall developer experience (related to tools, writing tests, debugging, API, documentation, etc.), test run times, test passing rates, and maintenance as displayed in this table:

Moving On to Cypress

At the end of the day, our WebdriverIO tests were still flaky and tough to maintain. More time was still spent debugging tests in dealing with weird Selenium issues, vague console errors, and somewhat useful screenshots than actually reaping the benefits of seeing tests fail for when the backend or frontend encountered issues.

We appreciated cross-browser testing and implementing tests in JavaScript, but if our tests could not pass consistently without much headache for even Chrome, then it became no longer worth it and we would then simply have a STUI 2.0.

With WebdriverIO we still strayed from the crucial aspect of providing a way to write consistent, debuggable, maintainable, and valuable E2E automation tests for our frontend applications in our original goal. Overall, we learned a lot about integrating with Buildkite and Docker, using page objects, and outlining tests in a structured way that will transfer over to our final solution with Cypress.

If we felt it was necessary to run our tests in multiple browsers and against various third-party services, we could always circle back to having some tests written with WebdriverIO, or if we needed something fully custom, we would revisit the STUI solution.

Ultimately, neither solution met our main goal for E2E tests, so follow us on our journey in how we migrated from STUI and WebdriverIO to Cypress in part 2 of the blog post series.