أفكار لتكوين وتنظيم وتوحيد اختبارات السرو الخاصة بك # frontend @ twiliosendgrid
نشرت: 2020-12-15لقد كتبنا العديد من اختبارات Cypress عبر تطبيقات الويب المختلفة وفرق الواجهة الأمامية هنا في Twilio SendGrid. نظرًا لتوسيع نطاق اختباراتنا لتشمل العديد من الميزات والصفحات ، فقد تعثرنا في بعض خيارات التكوين المفيدة وطورنا طرقًا للحفاظ بشكل أفضل على العدد المتزايد من الملفات والمحددات لعناصر صفحتنا وقيم المهلة على طول الطريق.
نهدف إلى أن نعرض لك هذه النصائح والأفكار لتكوين وتنظيم وتوحيد الأشياء المتعلقة بـ Cypress الخاصة بك ، لذلك لا تتردد في التعامل معها بما يناسبك أنت وفريقك.
يفترض منشور المدونة هذا أن لديك بعض المعرفة العملية باختبارات Cypress وتبحث عن أفكار لتحسين صيانة اختبارات Cypress وتحسينها. ومع ذلك ، إذا كنت مهتمًا بمعرفة المزيد حول الوظائف والتأكيدات والأنماط الشائعة التي قد تجدها مفيدة لكتابة اختبارات Cypress مقابل بيئات منفصلة ، فيمكنك الاطلاع على منشور مدونة نظرة عامة على ألف قدم بدلاً من ذلك.
بخلاف ذلك ، دعنا نستمر ونرشدك أولاً عبر بعض نصائح التكوين لـ Cypress.
تكوين ملف cypress.json الخاص بك
ملف cypress.json
هو المكان الذي يمكنك فيه ضبط جميع التكوينات لاختبارات Cypress الخاصة بك مثل المهلات الأساسية لأوامر Cypress ومتغيرات البيئة وخصائص أخرى.
فيما يلي بعض النصائح لتجربتها في التكوين الخاص بك:
- اضبط مهلات الأوامر الأساسية لديك ، بحيث لا تضطر دائمًا إلى إضافة
{ timeout: timeoutInMs }
عبر أوامر Cypress الخاصة بك. عدّل بالأرقام حتى تجد التوازن الصحيح لإعدادات مثل "defaultCommandTimeout" و "requestTimeout" و "responseTimeout". - إذا كان اختبارك يتضمن إطارات مضمنة ، فغالبًا ما تحتاج إلى تعيين "chromeWebSecurity" على "خطأ" حتى تتمكن من الوصول إلى إطارات iframe عبر الأصل في تطبيقك.
- حاول حظر البرامج النصية للتسويق والتحليلات والتسجيل التابعة لجهات خارجية عند تنفيذ اختبارات Cypress لزيادة السرعة وعدم تشغيل أحداث غير مرغوب فيها. يمكنك بسهولة إعداد قائمة رفض باستخدام خاصية "blacklistHosts" الخاصة بهم (أو خاصية "blockHosts" في Cypress 5.0.0) ، مع أخذ مجموعة من سلاسل الكرات التي تطابق مسارات مضيف الطرف الثالث.
- اضبط أبعاد منفذ العرض الافتراضية باستخدام "viewportWidth" و "viewportHeight" عندما تفتح Cypress GUI لتسهيل رؤية الأشياء.
- إذا كانت هناك مخاوف تتعلق بالذاكرة في Docker للاختبارات الثقيلة أو كنت ترغب في المساعدة في جعل الأشياء أكثر كفاءة ، فيمكنك محاولة تعديل "numTestsKeptInMemory" إلى رقم أصغر من الافتراضي وتعيين "videoUploadOnPasses" على "خطأ" للتركيز على تحميل مقاطع الفيديو للاختبار الفاشل يعمل فقط.
في ملاحظة أخرى ، بعد التغيير والتبديل في تكوين Cypress الخاص بك ، يمكنك أيضًا إضافة TypeScript وكتابة اختبارات Cypress بشكل أفضل كما فعلنا في منشور المدونة هذا. هذا مفيد بشكل خاص للإكمال التلقائي ، أو كتابة التحذيرات ، أو الأخطاء عند الاتصال والوظائف المتسلسلة (مثل cy.task('someTaskPluginFunction)
، أو Cypress.env('someEnvVariable')
، أو cy.customCommand()
).
قد ترغب أيضًا في تجربة إعداد ملف cypress.json
أساسي وتحميل ملف تكوين Cypress منفصل لكل بيئة اختبار ، مثل staging.json
عند تشغيل برامج نصية Cypress مختلفة في package.json
. يقوم توثيق Cypress بعمل رائع في إرشادك عبر هذا النهج.
تنظيم مجلد السرو الخاص بك
يقوم Cypress بالفعل بإعداد مجلد cypress
من المستوى الأعلى بهيكل إرشادي عند بدء تشغيل Cypress في قاعدة التعليمات البرمجية الخاصة بك. يتضمن ذلك مجلدات أخرى مثل integration
لملفات المواصفات الخاصة بك ، fixtures
لملفات بيانات JSON ، plugins
cy.task(...)
والتكوينات الأخرى ، ومجلد support
لأوامرك وأنواعك المخصصة.
من القواعد الأساسية الجيدة التي يجب اتباعها عند التنظيم داخل مجلدات Cypress أو مكونات React أو أي رمز بشكل عام ، تجميع الأشياء معًا التي من المحتمل أن تتغير معًا. في هذه الحالة ، نظرًا لأننا نتعامل مع تطبيقات الويب في المتصفح ، فإن إحدى الطرق التي تتوسع جيدًا هي تجميع الأشياء في مجلد حسب الصفحة أو الميزة الشاملة.
أنشأنا مجلد pages
منفصل مقسمًا حسب اسم الميزة مثل "SenderAuthentication" للاحتفاظ بجميع كائنات الصفحة أسفل مسار /settings/sender_auth
مثل "DomainAuthentication" (/ settings / sender_auth / domains / ** / *) و "LinkBranding" (/ الإعدادات / المرسل / الروابط / ** / *). في مجلد plugins
الخاصة بنا ، قمنا أيضًا بنفس الشيء مع تنظيم جميع ملفات البرنامج المساعد cy.task(...)
لميزة أو صفحة معينة في نفس المجلد لمصادقة المرسل ، واتبعنا نفس الأسلوب لمجلد integration
الخاص بنا لـ ملفات المواصفات. لقد قمنا بتوسيع نطاق اختباراتنا لتشمل المئات من ملفات المواصفات وكائنات الصفحة والمكونات الإضافية والملفات الأخرى في إحدى قواعد التعليمات البرمجية الخاصة بنا - ومن السهل والملائم التنقل خلالها.
فيما يلي مخطط تفصيلي لكيفية تنظيم مجلد cypress
.
هناك شيء آخر يجب مراعاته عند تنظيم مجلد integration
(حيث توجد جميع المواصفات الخاصة بك) وهو تقسيم ملفات المواصفات على أساس أولوية الاختبار. على سبيل المثال ، قد يكون لديك جميع اختبارات القيمة والأولوية القصوى في مجلد "P1" واختبارات الأولوية الثانية في مجلد "P2" حتى تتمكن من تشغيل جميع اختبارات "P1" بسهولة عن طريق تعيين الخيار --spec
مثل --spec 'cypress/integration/P1/**/*'
.
قم بإنشاء تسلسل هرمي لمجلد المواصفات يناسبك بحيث يمكنك بسهولة تجميع المواصفات معًا ليس فقط حسب الصفحة أو الميزة مثل --spec 'cypress/integration/SomePage/**/*'
، ولكن أيضًا من خلال بعض المعايير الأخرى مثل الأولوية والمنتج ، أو البيئة.
دمج محددات العناصر
عند تطوير مكونات صفحتنا React ، نضيف عادةً مستوى معينًا من اختبارات الوحدة والتكامل مع Jest and Enzyme. قرب نهاية تطوير الميزة ، أضفنا طبقة أخرى من اختبارات Cypress E2E للتأكد من أن كل شيء يعمل مع الخلفية. تتطلب كل من اختبارات الوحدة لمكونات React وكائنات الصفحة لاختبارات Cypress E2E الخاصة بنا محددات للمكونات / عناصر DOM على الصفحة التي نرغب في التفاعل معها والتأكيد عليها.
عندما نقوم بتحديث هذه الصفحات والمكونات ، هناك فرصة لحدوث انحراف وأخطاء من الاضطرار إلى مزامنة أماكن متعددة من محددات اختبار الوحدة إلى كائن صفحة Cypress إلى كود المكون الفعلي نفسه. إذا اعتمدنا فقط على أسماء الفئات المتعلقة بالأنماط ، فسيكون من الصعب تذكر تحديث جميع الأماكن التي قد تتعطل. بدلاً من ذلك ، نضيف "data-hook" أو "data-testid" أو أي سمة أخرى تحمل اسم "data- *" إلى مكونات وعناصر محددة على الصفحة نرغب في تذكرها وكتابة محدد لها في طبقات الاختبار الخاصة بنا.
يمكننا إلحاق سمات "ربط البيانات" بالعديد من عناصرنا ، لكننا ما زلنا بحاجة إلى طريقة لتجميعها معًا في مكان واحد لتحديثها وإعادة استخدامها في ملفات أخرى. لقد توصلنا إلى طريقة مكتوبة لإدارة جميع محددات "ربط البيانات" هذه لتوزيعها على مكونات React الخاصة بنا واستخدامها في اختبارات الوحدة ومحددات كائنات الصفحة لمزيد من إعادة الاستخدام وسهولة الصيانة في الكائنات المصدرة.
بالنسبة لمجلد المستوى الأعلى لكل صفحة ، سننشئ ملف hooks.ts
يدير ويصدر كائنًا باسم مفتاح يمكن قراءته للعنصر وسلسلة محدد CSS الفعلية للعنصر كقيمة. سوف نسمي هذه "محددات القراءة" لأننا نحتاج إلى قراءة واستخدام نموذج محدد CSS لعنصر في استدعاءات مثل Enzyme's wrapper.find(“[data-hook='selector']”)
في اختبارات الوحدة أو Cypress's cy.get(“[data-hook='selector']”)
في كائنات صفحتنا. ستبدو هذه المكالمات بعد ذلك أنظف مثل wrapper.find(Selectors.someElement)
أو cy.get(Selectors.someElement)
.
يغطي مقتطف الشفرة التالي المزيد من أسباب وكيفية استخدامنا لمحددات القراءة هذه في الممارسة العملية.
وبالمثل ، نقوم أيضًا بتصدير كائنات ذات اسم مفتاح قابل للقراءة للعنصر وبكائن مثل { “data-hook”: “selector” }
كقيمة. سوف نسمي هذه "محددات الكتابة" لأننا نحتاج إلى كتابة أو نشر هذه الكائنات على مكون React كدعامات لإضافة سمة "ربط البيانات" بنجاح إلى العناصر الأساسية. سيكون الهدف أن تفعل شيئًا كهذا وسيحتوي عنصر DOM الفعلي الموجود أسفله - بافتراض أن الخاصيات تم تمريرها إلى عناصر JSX بشكل صحيح - على مجموعة السمة "data-hook =".
يغطي مقتطف الشفرة التالي المزيد من أسباب وكيفية استخدامنا لمحددات الكتابة هذه في الممارسة العملية.
يمكننا إنشاء كائنات لدمج محددات القراءة الخاصة بنا وكتابة المحددات لتحديث عدد أقل من الأماكن ، ولكن ماذا لو كان علينا كتابة العديد من المحددات لبعض صفحاتنا الأكثر تعقيدًا؟ قد يكون هذا أكثر عرضة للخطأ لبناء نفسك ، لذلك دعونا ننشئ وظائف لإنشاء محددات القراءة هذه بسهولة وكتابة المحددات للتصدير في النهاية لصفحة معينة.
بالنسبة لوظيفة مُنشئ محدد القراءة ، سنقوم بالمرور عبر خصائص كائن الإدخال [data-hook=”selector”]
سلسلة محدد CSS لكل اسم عنصر. إذا كانت القيمة المقابلة للمفتاح null
، سنفترض أن اسم العنصر في كائن الإدخال سيكون هو نفسه قيمة "ربط البيانات" مثل { someElement: null } => { someElement: '[data-hook=”someElement”] }
. خلافًا لذلك ، إذا كانت القيمة المقابلة للمفتاح ليست فارغة ، فيمكننا اختيار تجاوز قيمة "ربط البيانات" مثل { someElement: “newSelector” } => { someElement: '[data-hook=”newSelector”]' }
.
بالنسبة لوظيفة مُنشئ محدد الكتابة ، سنقوم بالتكرار خلال خصائص كائن الإدخال ونشكل كائنات { “data-hook”: “selector” }
لكل اسم عنصر. إذا كانت القيمة المقابلة للمفتاح null
، سنفترض أن اسم العنصر في كائن الإدخال سيكون هو نفسه قيمة "ربط البيانات" مثل { someElement: null } => { someElement: { “data-hook”: “someElement” } }
. خلافًا لذلك ، إذا كانت القيمة المقابلة للمفتاح ليست null
، فيمكننا اختيار تجاوز قيمة "ربط البيانات" مثل { someElement: “newSelector” } => { someElement: { “data-hook”: “newSelector” }' }
.
باستخدام هاتين الوظيفتين للمولد ، نقوم ببناء محدد القراءة الخاص بنا وكتابة كائنات المحدد للصفحة وتصديرها لإعادة استخدامها في اختبارات الوحدة الخاصة بنا وكائنات صفحة Cypress. ميزة أخرى هي أن هذه الكائنات يتم كتابتها بطريقة لا يمكننا فيها عن طريق الخطأ عمل Selectors.unknownElement
أو WriteSelectors.unknownElement
في ملفات TypeScript الخاصة بنا أيضًا. قبل تصدير محددات القراءة الخاصة بنا ، نسمح أيضًا بإضافة تعيينات عناصر إضافية ومحدد CSS لمكونات الجهات الخارجية التي لا نتحكم فيها. في بعض الحالات ، لا يمكننا إضافة سمة "ربط البيانات" إلى عناصر معينة ، لذلك لا نزال بحاجة إلى التحديد من خلال سمات ومعرفات وفئات أخرى وإضافة المزيد من الخصائص إلى كائن محدد القراءة كما هو موضح أدناه.
يساعدنا هذا النمط على البقاء منظمين مع جميع محددات الصفحة وعندما نحتاج إلى تحديث الأشياء. نوصي بالتحقق من الطرق التي يمكنك من خلالها إدارة كل هذه المحددات في نوع ما من الكائنات أو من خلال أي وسيلة أخرى لتقليل عدد الملفات التي تحتاج إلى لمسها عند إجراء تغييرات مستقبلية.
تجميع المهلات
أحد الأشياء التي لاحظناها بعد كتابة العديد من اختبارات Cypress هو أن المحددات الخاصة بنا لعناصر معينة ستنتهي المهلة وتستغرق وقتًا أطول من قيم المهلة الافتراضية المحددة في cypress.json
مثل "defaultCommandTimeout" و "responseTimeout". كنا نعود ونعدل بعض الصفحات التي كانت تحتاج إلى مهلات أطول ، ولكن بمرور الوقت ، زاد عدد قيم المهلة التعسفية وأصبح الحفاظ عليها أكثر صعوبة بالنسبة للتغييرات واسعة النطاق.
نتيجة لذلك ، قمنا بدمج المهلات الخاصة بنا في كائن يبدأ فوق "defaultCommandTimeout" الخاص بنا ، والذي يقع في مكان ما في نطاق من خمس إلى عشر ثوانٍ لتغطية معظم المهلات العامة لمحدداتنا مثل cy.get(...)
أو cy.contains(...)
. بعد هذا المهلة الافتراضية ، سنرتقي إلى "قصير" و "متوسط" و "طويل" و "xlong" و "xxlong" خلال كائن مهلة يمكننا استيراده في أي مكان في ملفاتنا لاستخدامه في أوامر Cypress مثل cy.get(“someElement”, { timeout: timeouts.short })
أو cy.task('pluginName', {}, { timeout: timeouts.xlong })
. بعد استبدال المهلات الخاصة بنا بهذه القيم من الكائن المستورد ، لدينا مكان واحد للتحديث لتوسيع نطاق الوقت المستغرق أو تقليله لبعض المهلات.
يظهر أدناه مثال لكيفية البدء بسهولة بهذا.
تغليف
مع نمو مجموعات اختبار Cypress الخاصة بك ، قد تواجه بعض المشكلات نفسها التي واجهناها عند اكتشاف أفضل طريقة لتوسيع نطاق اختبارات Cypress والحفاظ عليها. يمكنك اختيار تنظيم ملفاتك وفقًا للصفحة أو الميزة أو بعض قواعد التجميع الأخرى ، بحيث تعرف دائمًا مكان البحث عن ملفات الاختبار الحالية ومكان إضافة ملفات جديدة حيث يساهم المزيد من المطورين في قاعدة التعليمات البرمجية الخاصة بك.
مع تغير واجهة المستخدم ، يمكنك استخدام نوع من الكائنات المكتوبة (مثل محددات القراءة والكتابة) للحفاظ على محددات السمة "data-" الخاصة بك إلى العناصر الرئيسية لكل صفحة التي ترغب في التأكيد عليها أو التفاعل داخل اختبارات الوحدة الخاصة بك و Cypress الاختبارات. إذا وجدت نفسك بدأت في تطبيق عدد كبير جدًا من القيم العشوائية لأشياء مثل قيم المهلة لأوامر Cypress ، فقد يكون الوقت قد حان لإعداد كائن مليء بقيم متدرجة حتى تتمكن من تحديث هذه القيم في مكان واحد.
نظرًا لأن الأشياء تتغير عبر اختبارات واجهة المستخدم الأمامية وواجهة برمجة التطبيقات الخلفية واختبارات Cypress ، يجب أن تفكر دائمًا في طرق للحفاظ على هذه الاختبارات بسهولة أكبر. هناك أماكن أقل للتحديث وارتكاب الأخطاء وقليل من الغموض فيما يتعلق بمكان وضع الأشياء يحدث فرقًا كبيرًا حيث يضيف العديد من المطورين صفحات وميزات جديدة و (حتمًا) اختبارات Cypress على الطريق.
المهتمة في المزيد من المشاركات على السرو؟ ألق نظرة على المقالات التالية:
- ما يجب مراعاته عند كتابة اختبارات E2E
- نظرة عامة على 1000 قدم لكتابة اختبارات السرو
- TypeScript كل الأشياء في اختبارات السرو الخاصة بك
- التعامل مع تدفقات البريد الإلكتروني في اختبارات السرو
- دمج اختبارات السرو مع Docker و Buildkite و CICD