نكتب اختبارات التكامل للواجهة الأمامية وتسريع الإصدارات

مرحبا! اسمي فوفا ، أنا واجهة أمامية في تينكوف. فريقنا مسؤول عن منتجين للكيانات القانونية. أستطيع أن أقول عن حجم المنتج بالأرقام: يستغرق التراجع الكامل لكل منتج من قبل اثنين من المختبرين ثلاثة أيام (بدون تأثير العوامل الخارجية).



الإطار الزمني هام ويتطلب التعامل معه. هناك عدة طرق للقتال ، أهمها:



  • قطع التطبيق إلى منتجات أصغر بدورات إطلاق خاصة بهم.

  • طلاء المنتج بالاختبارات حسب هرم الاختبار.



أصبحت النقطة الأخيرة موضوع مقالتي.



صورة



هرم الاختبار



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



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



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



  1. وثائق مفصلة للغاية.

  2. تصحيح الأخطاء بسهولة في الاختبارات (يحتوي Cypress على واجهة مستخدم رسومية خاصة لذلك مع خطوات السفر عبر الزمن في الاختبار).



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



بداية الطريق



في البداية ، استخدمت Angular Workspace مع تطبيق واحد لتنظيم الرمز. بعد تثبيت حزمة Cypress ، ظهر مجلد cypress مع التكوين والاختبارات في جذر التطبيق ، توقفنا عند هذا الخيار. عند محاولة تحضير نص برمجي في package.json مطلوب لتشغيل التطبيق وتشغيل الاختبارات فوقه ، واجهنا المشاكل التالية:



  1. يحتوي Index.html على بعض البرامج النصية غير المطلوبة في اختبارات التكامل.

  2. لتشغيل اختبارات التكامل ، كان عليك التأكد من تشغيل الخادم مع التطبيق.



تم حل مشكلة index.html من خلال تكوين بناء منفصل - دعنا نسميها sypress - حيث حددنا index.html مخصص. كيفية تنفيذ ذلك؟ نجد تكوين التطبيق الخاص بك في angular.json ، وفتح قسم البناء ، وإضافة تكوين منفصل لـ Cypress هناك ولا تنس تحديد هذا التكوين لوضع العرض.



تكوين مثال للبناء:



"build": {
 ...
 "configurations": {
   … //  
   "cypress": {
     "aot": true,
     "index": "projects/main-app-integrations/src/fixtures/index.html",
     "fileReplacements": [
       {
         "replace": "projects/main-app/src/environments/environment.ts",
         "with": "projects/main-app/src/environments/environment.prod.ts"
       }
     ]
   }
 }
}


التكامل مع الخدمة:



"serve": {
 ...
 "configurations": {
   … //  
   "cypress": {
     "browserTarget": "main-app:build:cypress"
   }
 }
}


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



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



 "main-app:cy:run": "cypress run",
 "main-app:cy:open": "cypress open",
 "main-app:integrations": "start-server-and-test main-app:serve:cypress http://localhost:8808/app/user/ main-app:cy:run",
 "main-app:integrations:open": "start-server-and-test main-app:serve:cypress http://localhost:8808/app/user/ main-app:cy:open"


كما ترى ، هناك نوعان من البرامج النصية: فتح وتشغيل. يفتح الوضع المفتوح واجهة المستخدم الرسومية لـ Cypress نفسها ، حيث يمكنك التبديل بين الاختبارات واستخدام السفر عبر الزمن. وضع التشغيل هو مجرد تشغيل اختباري والنتيجة النهائية لذلك التشغيل ، رائعة للتشغيل في CI.



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



مستودع أحادي



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



  1. لديك تطبيق ، على سبيل المثال main-app ، يتم إنشاء تطبيق main-app-e2e بجواره.

  2. أعد تسمية main-app-e2e إلى عمليات دمج التطبيقات الرئيسية - أنت مدهش.



يمكنك الآن تشغيل اختبارات التكامل باستخدام أمر واحد - ng e2e main-app-Integrations. سيقوم NX تلقائيًا بإحضار التطبيق الرئيسي ، وانتظار الرد ، وإجراء الاختبارات.



لسوء الحظ ، أولئك الذين يستخدمون Angular Workspace حاليًا ، ولكن لا بأس ، لدي وصفة لك أيضًا. سنستخدم بنية الملف كما في NX:



  1. أنشئ مجلد تكامل التطبيقات الرئيسي بجوار تطبيقك.

  2. أنشئ مجلد src فيه وأضف محتويات مجلد السرو إليه.

  3. لا تنسى نقل cypress.json (في البداية ستظهر في الجذر) إلى مجلد تكامل التطبيق الرئيسي.

  4. نقوم بتصحيح cypress.json ، مشيرين إلى مسارات المجلدات الجديدة من خلال الاختبارات والمكونات الإضافية والأوامر المساعدة (معلمات تكامل المجلد والمكونات الإضافية وملف الدعم).

  5. يمكن أن يعمل Cypress مع الاختبارات في أي مجلدات ، ويتم استخدام معلمة

    المشروع لتحديد المجلد ، لذلك نقوم بتغيير الأمر من تشغيل / فتح cypress إلى cypress run / open-–project ./projects/main-app-integrations/src .



إن حل Angular Workspace مشابه قدر الإمكان لحل NX ، باستثناء أن المجلد تم إنشاؤه يدويًا وهو ليس أحد المشاريع في المستودع الخاص بك. بدلاً من ذلك ، يمكنك استخدام NX builder مباشرة لـ Cypress ( مثال على مستودع على NX مع Cypress ، هناك يمكنك رؤية الاستخدام النهائي لمنشئ nx-cypress - الانتباه إلى angular.json ومشروع

cart-e2e و products-e2e).



تراجع بصري



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



تم أخذ مكتبة cypress-image-snapshot كحل . لم يستغرق التنفيذ الكثير من الوقت ، والآن بعد 20 دقيقة تلقينا أول لقطة شاشة لتطبيقنا بحجم 1000 × 600 بكسل. كان هناك الكثير من الفرح لأن التكامل والاستخدام كانا سهلين للغاية ، وقد تكون الفوائد ضخمة.



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



Cypress.Commands.overwrite(
   'matchImageSnapshot',
   (originalFn, subject, fileName, options) => {
       if (Cypress.env('ci')) {
           return originalFn(subject, fileName, options);
       }

       return subject;
   },
);


في هذا الحل ، ننظر إلى معلمة env في Cypress ، يمكنك تعيينها بطرق مختلفة .



الخطوط



محليًا ، بدأت الاختبارات تمر عند إعادة التشغيل ، نحاول تشغيلها مرة أخرى في CI. يمكن رؤية النتيجة أدناه:







من السهل جدًا ملاحظة الفرق في الخطوط في لقطة الشاشة المختلفة. تم إنشاء لقطة الشاشة المرجعية على macOS ، وفي CI ، قام الوكلاء بتثبيت Linux.



قرار خاطئ



اخترنا أحد الخطوط القياسية (مثل خط Ubuntu) ، والذي أعطى فرقًا صغيرًا لكل بكسل ، وقمنا بتطبيق هذا الخط على كتل النص (صنع في

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



الحل الصحيح



كانت المشكلة الأصلية في بيئات مختلفة ، والحل من حيث المبدأ يوحي نفسه - Docker. السرو لديها بالفعل صور جاهزة لرسو السفن. هناك اختلافات مختلفة في الصور ، نحن مهتمون بتضمينها ، حيث أن Cypress موجود بالفعل في الصورة ولن يقوم بتنزيل ملف Cypress الثنائي وفك تغليفه في كل مرة (يتم تشغيل واجهة المستخدم الرسومية لـ Cypress من خلال ملف ثنائي ، ويستغرق التنزيل والتفريغ وقتًا أطول من التنزيل صورة عامل الميناء).

استنادًا إلى صورة docker المضمنة ، نصنع حاوية Docker الخاصة بنا ، ولهذا قمنا بإجراء اختبارات تكامل. ملف Dockerfile بمحتوى مماثل:



FROM cypress:included:4.3.0
COPY package.json /app/
COPY package-lock.json app/
WORKDIR /app
RUN npm ci
COPY / /app/
ENTRYPOINT []


أود أن أشير إلى صفر ENTRYPOINT ، وهذا يرجع إلى حقيقة أنه تم تعيينه افتراضيًا في صورة السرو / الصورة المضمنة ويشير إلى أمر تشغيل السرو ، مما يمنعنا من استخدام أوامر أخرى. قمنا أيضًا بتقسيم ملف dockerfile الخاص بنا إلى طبقات بحيث لا نعيد تنفيذ npm ci في كل مرة نعيد فيها تشغيل الاختبارات.



أضف ملف dockerignore (إذا لم يكن موجودًا) إلى جذر المستودع وفيه ، تأكد من تحديد وحدات العقدة / و * / عقدة الوحدات /.



لتشغيل اختباراتنا في Docker ، دعنا نكتب دمج bash script-tests.sh بالمحتوى التالي:



docker build -t integrations -f integration-tests.Dockerfile .
docker run --rm -v $PWD/projects/main-app-integrations/src:/app/projects/main-app-integrations/src integrations:latest npm run main-app:integrations


وصف قصير: نقوم ببناء اختبارات التكامل الخاصة بنا. Dockerfile حاوية عامل إرساء وحجم الصوت إلى مجلد الاختبارات حتى نتمكن من الحصول على لقطات الشاشة التي تم إنشاؤها من Docker.



الخطوط مرة أخرى



بعد حل المشكلة الموضحة في الفصل السابق ، كان هناك هدوء في البنيات ، ولكن بعد يوم تقريبًا واجهنا المشكلة التالية (على اليسار واليمين - لقطات من نفس المكون مأخوذة في أوقات مختلفة):







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



قرار خاطئ



نقوم بتنزيل الخطوط من شبكة CDN ، وتحميلها على الأصول لتكوين السرو ،

وتضمينها في index.html المخصص لدينا لاختبارات التكامل. مع هذا القرار ، عشنا وقتًا لائقًا حتى قمنا بتغيير خط الشركة. لم تكن هناك رغبة في لعب نفس القصة مرة ثانية.



الحل الصحيح



تقرر البدء في تحميل جميع الخطوط الضرورية للاختبار في

index.html لتكوين السرو ، وقد بدا الأمر كالتالي:



<link	
      rel="preload"
      href="...."	
      as="font"	
      type="font/woff2"	
      crossorigin="anonymous"
/>


انخفض عدد إخفاقات الاختبار بسبب الخطوط التي لم يكن لديها وقت للتحميل إلى الحد الأدنى ، ولكن ليس إلى الصفر: على أي حال ، في بعض الأحيان لم يكن لدى الخط وقت للتحميل. جاء حل من KitchenSink of Cypress نفسه لإنقاذ - waitForResource.

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



الرسوم المتحركة



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



القرار الأول



أبسط شيء ظهر في أذهاننا في المرحلة الأولية: قبل إنشاء لقطة شاشة ، أوقف المتصفح لفترة معينة حتى يكون للرسوم المتحركة وقت لإكمالها. ذهبنا على طول السلسلة 100 مللي ثانية و 200 مللي ثانية و 500 مللي ثانية ونتيجة لذلك 1000 مللي ثانية. بالنظر إلى الوراء ، أفهم أن هذا القرار كان فظيعًا في البداية ، لكني أردت فقط أن أحذرك من نفس القرار. لماذا فظيعة؟ تختلف أوقات الرسوم المتحركة ، ويمكن للوكلاء في CI أن يخفتوا في بعض الأحيان ، وهذا هو سبب اختلاف أي وقت انتظار لتثبيت الصفحة من وقت لآخر.



الحل الثاني



حتى مع الانتظار لمدة ثانية واحدة ، لم تتمكن الصفحة دائمًا من الاستقرار. بعد قليل من البحث ، وجدنا أداة من Angular - Testability. يعتمد المبدأ على مراقبة استقرار ZoneJS:



Cypress.Commands.add('waitStableState', () => {
   return cy.window().then(window => {
       const [testability]: [Testability] = window.getAllAngularTestabilities();

       return new Cypress.Promise(resolve => {
           testability.whenStable(() => {
               resolve();
           }, 3000);
       });
   });
});


وبالتالي ، عند إنشاء لقطات شاشة ، أطلقنا على أمرين: cy.wait (1000) و cy.waitStableState ().



منذ ذلك الحين ، لم تكن هناك لقطة شاشة واحدة تم إسقاطها بشكل عشوائي ، ولكن دعنا نحسب معًا الوقت الذي تم قضاؤه في خمول المتصفح. لنفترض أن لديك 5 لقطات شاشة في الاختبار الخاص بك ، لكل منها وقت انتظار مستقر قدره ثانية واحدة وبعض الوقت العشوائي ، دعنا نقول 1.5 ثانية في المتوسط ​​(لم أقم بقياس المتوسط ​​في الواقع ، لذلك أخذته من رأسي وفقًا لمشاعري الخاصة) ... ونتيجة لذلك ، نقضي 12.5 ثانية إضافية لإنشاء لقطات شاشة في الاختبار. لنفترض أنك كتبت بالفعل 20 سيناريو اختبار ، حيث يحتوي كل اختبار على 5 لقطات شاشة على الأقل. نحصل على أن المبلغ الزائد للاستقرار هو ~ 4 دقائق مع توفر 20 اختبارًا. 



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



الحل الحالي



لنبدأ بالرسوم المتحركة الزاويّة. إطارنا المفضل أثناء الرسوم المتحركة على عنصر DOM يعلق فصل الرسوم المتحركة. كان هذا هو مفتاح قرارنا ، لأننا الآن بحاجة إلى التأكد من أن العنصر ليس لديه فئة رسوم متحركة الآن. ونتيجة لذلك ، تحولت إلى هذه الوظيفة:



export function waitAnimation(element: Chainable<JQuery>): Chainable<JQuery> {
   return element.should('be.visible').should('not.have.class', 'ng-animating');
}


يبدو أن لا شيء معقد ، ولكن هذا ما شكل أساس قراراتنا. ما أريد الانتباه إليه في هذا النهج: عند إنشاء لقطة شاشة ، يجب أن تفهم الرسوم المتحركة للعنصر التي يمكن أن تجعل لقطة الشاشة غير مستقرة ، وقبل إنشاء لقطة شاشة ، أضف تأكيدًا للتحقق من أن العنصر غير متحرك. لكن الرسوم المتحركة يمكن أن تكون أيضًا في CSS. كما يقول السرو نفسها، أي التأكيد على عنصر ينتظر الرسوم المتحركة إلى نهاية على ذلك - مزيد من التفاصيل هنا و هنا . أي أن جوهر المنهج هو كما يلي: لدينا عنصر متحرك ، نضيف تأكيدًا عليه - ينبغي ('be مرئية') / يجب ('not.be.visible')- و Cypress نفسها ستنتظر انتهاء الرسوم المتحركة على العنصر (ربما ، بالمناسبة ، لا يلزم الحل مع ng-animating ولا يكفي سوى اختبارات Cypress ، لكننا الآن نستخدم الأداة WaitAnimation المساعدة).



كما هو موضح في الوثائق نفسها ، يتحقق Cypress من موضع العنصر على الصفحة ، ولكن ليست كل الرسوم المتحركة تتعلق بتغييرات الموضع ، وهناك أيضًا رسوم متحركة fadeIn / fadeOut. في هذه الحالات ، يكون مبدأ الحل هو نفسه: نتحقق من أن العنصر مرئي / غير مرئي على الصفحة. 



عند الانتقال من cy.wait (1000) + cy.waitStableState () إلى انتظار الرسوم المتحركة وتأكيد السرو ، اضطررنا إلى قضاء ساعتين تقريبًا لتثبيت لقطات الشاشة القديمة ، ولكن نتيجة لذلك حصلنا على + 20-30 ثانية بدلاً من +4 دقائق لوقت تنفيذ الاختبار ... في الوقت الحالي ، نحن نقترب بعناية من مراجعة لقطات الشاشة: نتحقق من أنها لم يتم إجراؤها أثناء الرسوم المتحركة لعناصر DOM وتم إضافة الفحوصات في الاختبار لانتظار الرسوم المتحركة. على سبيل المثال ، غالبًا ما نضيف عرض "الهياكل العظمية" إلى الصفحة قبل تحميل البيانات. وفقًا لذلك ، تتلقى المراجعة على الفور شرطًا بأنه عند إنشاء لقطات شاشة ، يجب ألا يكون الهيكل العظمي موجودًا في DOM ، نظرًا لوجود رسوم متحركة تتلاشى عليه. 



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



حجم لقطة الشاشة



ربما لاحظت وجود ميزة مثيرة للاهتمام: الدقة الافتراضية للصور هي 1000 × 600 بكسل. للأسف ، هناك مشكلة في حجم نافذة المتصفح عند التشغيل في Docker: حتى إذا قمت بتغيير حجم إطار العرض من خلال Cypress ، فلن يساعد. لقد وجدنا حلًا لمتصفح Chrome (بالنسبة لـ Electron ، لم نتمكن من العثور على حل فعال بسرعة ، ولم نحصل على الحل المقترح في هذه المشكلة ). تحتاج أولاً إلى تغيير المتصفح لتشغيل الاختبارات على Chrome:



  1. ليس بالنسبة لـ NX ، نقوم بذلك باستخدام وسيطة - browser chrome عند بدء تشغيل أمر فتح / تشغيل cypress ولأمر التشغيل نحدد المعلمة - headless.

  2. بالنسبة إلى NX ، في تكوين المشروع في angular.json بالاختبارات ، نحدد المتصفح: معلمة chrome ، وبالنسبة للتكوين الذي سيتم تشغيله في CI ، نحدد بلا رأس: صحيح.



نقوم الآن بإجراء التغييرات في الإضافات والحصول على لقطات شاشة بحجم 1440 × 900 بكسل:



module.exports = (on, config) => {
   on('before:browser:launch', (browser, launchOptions) => {
       if (browser.name === 'chrome' && browser.isHeadless) {
           launchOptions.args.push('--disable-dev-shm-usage');
           launchOptions.args.push('--window-size=1440,1200');

           return launchOptions;
       }

       return launchOptions;
   });
};


تواريخ



كل شيء بسيط هنا: إذا تم عرض التاريخ المرتبط بالتاريخ الحالي في مكان ما ، فإن لقطة الشاشة التي تم التقاطها اليوم ستسقط غدًا. Fixim بسيط:



cy.clock(new Date(2025, 11, 22, 0).getTime(), ['Date']);


الآن الموقتات. لم نتعب أنفسنا واستخدمنا خيار التعتيم عند إنشاء لقطات شاشة ، على سبيل المثال:



cy.matchImageSnapshot('salary_signing-several-payments', {
   blackout: ['.timer'],
});


اختبارات قشارية



باستخدام التوصيات المذكورة أعلاه ، يمكنك تحقيق أقصى ثبات للاختبارات ، ولكن ليس بنسبة 100٪ ، لأن الاختبارات لا تتأثر فقط بالشفرة الخاصة بك ، ولكن أيضًا بالبيئة التي تعمل فيها.



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



نحن نضخ CI



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



  1. أمر Npm ci.
  2. رفع التطبيق في وضع aot.
  3. إجراء اختبارات التكامل.


دعنا نلقي نظرة على النقطتين الأولى والثانية ونفهم أنه يتم تنفيذ خطوات مماثلة في بناءك الآخر في CI - البناء باستخدام تجميع التطبيق.

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



لماذا نحتاجها؟ لدينا فقط تطبيق كبير وتنفيذ

استغرق npm ci + npm بداية في وضع aot على وكيل في CI ~ 15 دقيقة ، وهو ما يتطلب من حيث المبدأ الكثير من الجهد من الوكيل ، وتم تشغيل اختبارات التكامل فوق ذلك. لنفترض أنك كتبت بالفعل أكثر من 20 اختبارًا وفي الاختبار التاسع عشر يتعطل المتصفح ، حيث يتم تشغيل الاختبارات ، بسبب الحمل الثقيل على الوكيل. كما تعلم ، فإن إعادة بناء الإصدار تنتظر مرة أخرى تثبيت التبعيات وبدء تشغيل التطبيق.



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



الخادم مع ثابت



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



كان هذا الحل كافيًا لنا لمدة 20 دقيقة كاملة ، ثم أدركنا أننا نريد تقديم بعض الطلبات إلى دائرة الاختبار. فشل توصيل الخادم الوكيل لـ angular-http-server. كان الحل النهائي رفع الخادم إلى Express . لحل المشكلة ، تم استخدام Express-Express-http-proxy نفسه. سنقوم بتوزيع إحصائياتنا باستخدام

express.static ، ونتيجة لذلك ، نحصل على برنامج نصي مشابه لما يلي:



const express = require('express');
const appStaticPathFolder = './dist';
const appBaseHref = './my/app';
const port = 4200;
const app = express();

app.use((req, res, next) => {
   const accept = req
       .accepts()
       .join()
       .replace('*/*', '');

   if (accept.includes('text/html')) {
       req.url = baseHref;
   }

   next();
});
app.use(appBaseHref, express.static(appStaticPathFolder));
app.listen(port);


النقطة المثيرة للاهتمام هنا هي أنه قبل الاستماع إلى المسار على baseHref الخاص بالتطبيق ، نقوم أيضًا بمعالجة جميع الطلبات والبحث عن طلب index.html. يتم ذلك في الحالات التي تنتقل فيها الاختبارات إلى صفحة تطبيق يختلف مسارها عن baseHref. إذا لم تقم بهذه الحيلة ، فعندما تذهب إلى أي صفحة من تطبيقك ، باستثناء الصفحة الرئيسية ، سيصل خطأ 404. أضف الآن قليلًا من الوكيل:



const proxy = require('express-http-proxy');

app.use(
   '/common',
   proxy('https://qa-stand.ru', {
       proxyReqPathResolver: req => '/common' + req.url,
   }),
);


دعونا نلقي نظرة فاحصة على ما يحدث. هناك ثوابت:



  1. appStaticForlderPath هو المجلد حيث توجد إحصائيات التطبيق الخاص بك. 
  2. appBaseHref - ربما يحتوي تطبيقك على baseHref ، إن لم يكن - يمكنك تحديد "/".


نقوم بالبروكسي لجميع الطلبات التي تبدأ بـ / common ، وعند التوكيل نحفظ نفس المسار الذي كان عليه الطلب باستخدام الإعداد proxyReqPathResolver. إذا لم تستخدمه ، فستنتقل جميع الطلبات إلى https://qa-stand.ru.



فهرس التخصيص. html



نحن بحاجة إلى حل المشكلة باستخدام index.html المخصص ، الذي استخدمناه عندما نخدم ng التطبيقات في وضع Cypress. دعنا نكتب نصًا بسيطًا في node.js. كان لدينا index.modern.html كمعلمات أولية ، كنا بحاجة إلى تحويلها إلى index.html وإزالة البرامج النصية غير الضرورية من هناك:



const fs = require('fs');
const appStaticPathFolder = './dist';

fs.copyFileSync(appStaticPathFolder + '/index.modern.html', appStaticPathFolder + '/index.html');

fs.readFile(appStaticPathFolder + '/index.html', 'utf-8', (err, data) => {
   const newValue = data
       .replace(
           '<script type="text/javascript" src="/auth.js"></script>',
           '',
       )
       .replace(
           '<script type="text/javascript" src="/analytics.js"></script>',
           '',
       );

   fs.writeFileSync(appStaticPathFolder + '/index.html', newValue, 'utf-8');
});


نصوص



لم أرغب حقًا في عمل npm ci من جميع التبعيات مرة أخرى لتشغيل الاختبارات في CI (بعد كل شيء ، تم ذلك بالفعل في المهمة مع بناء التطبيق) ، لذا بدت الفكرة لإنشاء مجلد منفصل لجميع هذه البرامج النصية مع package.json الخاصة بنا. لنقم بتسمية المجلد ، على سبيل المثال ، اختبارات التكامل النصية ، وإسقاط ثلاثة ملفات هناك: server.js ، create-index.js ، package.json. تم وصف أول ملفين أعلاه ، والآن دعونا نحلل محتويات package.json:



{
 "name": "cypress-tests",
 "version": "0.0.0",
 "private": true,
 "scripts": {
   "create-index": "node ./create-index.js",
   "main-app:serve": "node ./server.js",
   "main-app:cy:run": "cypress run --project ./projects/main-app-integrations ",
   "main-app:integrations": "npm run create-index && start-server-and-test main-app:serve http://localhost:4200/my/app/ main-app:cy:run"
 },
 "devDependencies": {
   "@cypress/webpack-preprocessor": "4.1.0",
   "@types/express": "4.17.2",
   "@types/mocha": "5.2.7",
   "@types/node": "8.9.5",
   "cypress": "4.1.0",
   "cypress-image-snapshot": "3.1.1",
   "express": "4.17.1",
   "express-http-proxy": "^1.6.0",
   "start-server-and-test": "1.10.8",
   "ts-loader": "6.2.1",
   "typescript": "3.8.3",
   "webpack": "4.41.6"
 }
}


في package.json ، هناك فقط تبعيات ضرورية لتشغيل اختبارات التكامل ( مع دعم الكتابة النصية واختبار لقطة الشاشة) والنصوص البرمجية لبدء تشغيل الخادم ، وإنشاء index.html والمعروف جيدًا من الفصل الخاص ببدء اختبارات التكامل في Angular Workspace start-server-and-test ...



إطلاق



ننهي تنفيذ اختبارات التكامل في ملف Dockerfile جديد - Integration-tests-ci.Dockerfile :



FROM cypress/included:4.3.0
COPY integration-tests-scripts /app/
WORKDIR /app
RUN npm ci
COPY projects/main-app-integrations /app/projects/main-app-integrations
COPY dist /app/dist
COPY tsconfig.json /app/
ENTRYPOINT []


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



إنشاء ملف Integration-tests-ci.sh بالمحتويات التالية:



docker build -t integrations -f integration-tests-ci.Dockerfile .
docker run --rm -v $PWD/projects/main-app-integrations/src:/app/projects/main-app-integrations/src integrations:latest npm run main-app:integrations


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



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



ملخص



كان هناك الكثير من المعلومات - أعتقد أنه يجدر بنا تلخيصها بناءً على ما سبق.

تحضير أدوات للكتابة المحلية وإجراء الاختبارات بقليل من اختبار لقطة الشاشة:



  1. ضع تبعية للسرو.
  2. تحضير مجلد بالاختبارات:

    1. تطبيق Angular Single - اترك كل شيء في مجلد السرو.
    2. Angular Workspace - قم بإنشاء اسم المجلد لتطبيق عمليات التكامل بجوار التطبيق الذي ستلاحقه الاختبارات ، ونقل كل شيء من مجلد السرو إليه.
    3. NX - إعادة تسمية المشروع من اسم التطبيق e2e إلى اسم تكاملات التطبيق.


  3. cypress- — build- Cypress, aot, index.html, environment prod- serve- Cypress ( , - prod-, ).
  4. :

    1. Angular Single Application — serve- cypress- , start-server-and-test.
    2. Angular Workspace — Angular Single Application, cypress run/open.
    3. NX — ng e2e.


  5. -:

    1. cypress-image-snapshot.
    2. CI.
    3. في الاختبارات ، لا نلتقط لقطات شاشة عشوائية. إذا كانت لقطة الشاشة مسبوقة برسوم متحركة ، فتأكد من انتظارها - على سبيل المثال ، أضف Cypress Assertion إلى العنصر المتحرك.
    4. التاريخ إما مبلل من خلال cy.clock أو نستخدم خيار التعتيم عند التقاط لقطة شاشة.
    5. نتوقع أي إحصائيات محملة في وقت التشغيل من خلال أمر cy.waitForResource المخصص (الصور والخطوط وما إلى ذلك).


  6. ننهي كل شيء في Docker:

    1. ملف الطبخ.
    2. قم بإنشاء ملف باش.




 نجري اختبارات في أعلى التطبيق المجمع:



  1. في CI ، نتعلم كيفية رمي القطع الأثرية للتطبيق المجمع بين البنيات (يبقى على عاتقك).
  2. تحضير مجلد سكربتات اختبارات التكامل:

    1. برنامج نصي لرفع خادم التطبيق الخاص بك.
    2. برنامج نصي لتغيير index.html (إذا كنت راضيًا عن index.html الأولي ، يمكنك تخطيه).
    3. أضف إلى مجلد package.json مع النصوص والبرامج التبعية اللازمة.
    4. نحن نستعد ملف Dockerfile جديد.
    5. قم بإنشاء ملف باش.


روابط مفيدة



  1. Angular Workspace + Cypress + CI — Angular Workspace CI , ( typescript).

  2. Cypresstrade-offs.

  3. Start-server-and-test — , .

  4. Cypress-image-snapshot — -.

  5. Cypress recipes — Cypress, .

  6. Flaky Tests — , Google.

  7. Github Action — Cypress GitHub Action, README , — wait-on. docker.




All Articles