EduFlow
اختبار تطبيقات JavaScript
⁦0%⁩

الوحدة 4 · اختبارات end-to-end مع Playwright

تتبّع الاختبارات المتقلّبة وإصلاحها

20 دقراءة 4 داكتشف الخطأ

الدرس في لمحة

تتتبّع اختبارات Playwright الفاشلة باستخدام UI mode والـ inspector وعارض الـ trace، ثم تجد الأسباب المعتادة للتقلّب وتزيلها بدل إخفائها بإعادة المحاولة.

  1. 1رؤية ما حدث
  2. 2المشتبه بهم المعتادون
  3. 3إثبات الإصلاح

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

في عمل سابق كانت لدينا مجموعة end-to-end حمراء في تشغيلة من كل خمس تقريبًا. كان الجميع يعرف أيّ الاختبارات “تفعل ذلك دائمًا”، فكان الجميع يعيد تشغيل الـ pipeline. ثم في يوم جمعة وصل إلى المستخدمين خطأ حقيقي في الدفع، لأن اختباره الفاشل كان من تلك التي “تفعل ذلك دائمًا”. الـ flaky test (الاختبار المتقلّب) ليس إزعاجًا بسيطًا؛ بل يدرّب الفريق كله على تجاهل جرس الإنذار.

لهذا الدرس نصفان: أدوات رؤية سبب فشل الاختبار، والقائمة القصيرة من الأسباب التي تقف خلف كل اختبار متقلّب تقريبًا.

رؤية ما حدث

UI mode (وضع الواجهة) هو المكان الذي أقضي فيه معظم وقت تتبّع الأخطاء محليًا:

Terminal
npx playwright test --ui

يفتح نافذة تسرد كل اختبار. شغّل واحدًا فتحصل على خط زمني لأفعاله؛ وانقر أي خطوة لترى لقطة DOM للصفحة في تلك اللحظة، إضافة إلى رسائل الـ console وطلبات الشبكة وكود الاختبار. يراقب الملفات ويعيد التشغيل عند الحفظ. وفيه أيضًا منتقي locators: مرّر المؤشّر فوق اللقطة فيقترح عليك locator.

الـ inspector (المفتّش) يمشي في الاختبار فعلًا بعد فعل في متصفّح ظاهر:

Terminal
npx playwright test e2e/settle-up.spec.ts --debug

أضف await page.pause() في اختبار للتوقّف عند نقطة محدّدة.

الـ traces (سجلّات التتبّع) للحالات التي فشلت دون أن تراها، وعادةً في CI. مع trace: 'on-first-retry' في الإعدادات، تسجّل Playwright ملف trace عند أول إعادة محاولة لاختبار فاشل: كل فعل، ولقطة DOM قبله وبعده، ومخرجات الـ console، واستدعاءات الشبكة، وسطر الكود. نزّل التقرير من CI وافتح الـ trace:

Terminal
npx playwright show-report            # HTML report, with traces attached
npx playwright show-trace trace.zip   # a single trace

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

أربع خطوات: يفشل الاختبار في CI، فتعيد Playwright محاولته وتسجّل trace، فتفتح الـ trace وتفحص اللقطة، ثم تصلح السبب وتتأكّد بـ repeat-each. يفشل في CI المحاولة الأولى إعادة + trace on-first-retry اقرأ الـ trace show-trace أصلح وأثبت --repeat-each
من فشل في CI إلى إصلاح

المشتبه بهم المعتادون

كل اختبار end-to-end متقلّب حذفته أو أصلحته تقريبًا كان له أحد هذه الأسباب.

1. التسابق مع الصفحة. الانتظار الثابت (page.waitForTimeout(2000))، أو قيم تُقرأ مرة واحدة وتُؤكَّد بـ matcher لا يعيد المحاولة (expect(await locator.textContent()).toBe(...)، expect(await locator.count()).toBe(3))، أو فروع if (await locator.isVisible()) تفحص في لحظة واحدة عشوائية. الإصلاح: assertions موجّهة للويب (toHaveText وtoHaveCount وtoBeVisible) وأفعال تنتظر تلقائيًا.

2. البيانات المشتركة. اختبارات تستخدم المجموعة نفسها، أو المستخدم نفسه، أو السجلّات الأولية نفسها، ولا تنجح إلا بترتيب معيّن أو مع عامل واحد. الإصلاح: كل اختبار ينشئ بياناته الخاصة، بأسماء فريدة، عبر الـ fixtures.

3. الوقت والعشوائية غير المتحكَّم فيهما. منطق “هذا الأسبوع” الذي يفشل في أيام الاثنين، ومناطق زمنية تختلف بين حاسوبك وخادم CI، ورسوم متحرّكة ما زالت تعمل، ومعرّفات عشوائية في الـ assertions. الإصلاح: page.clock لضبط الوقت (await page.clock.setFixedTime(new Date('2026-03-04T10:00:00')))، وtimezoneId ثابت في كتلة use في الإعدادات، وassertions لا تعتمد على قيم مولّدة.

4. await المفقودة. page.getByRole('button', { name: 'Add expense' }).click() بلا await يعود فورًا؛ فيتسابق السطر التالي مع النقر. الإصلاح: قاعدة الـ lint @typescript-eslint/no-floating-promises تلتقط هذه الحالات لحظة الكتابة.

السكربتات الخارجية (التحليلات، وأدوات الدردشة، وإطارات iframe الخاصة بالدفع) سبب خامس، خارجي: احجبها بـ page.route في الاختبارات ما لم يكن الاختبار عنها.

إثبات الإصلاح

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

Terminal
npx playwright test e2e/settle-up.spec.ts --repeat-each=30 --workers=4

إن فشل بضع مرات من 30 قبل الإصلاح وصفرًا بعده، فقد انتهيت. وفي CI، يحوّل --fail-on-flaky-tests وسم flaky إلى فشل، وتفعّله بعض الفرق بعد أن تصبح المجموعة نظيفة كي لا يتسلّل تقلّب جديد بهدوء.

يعرض التمرين اختبارًا من مجموعة تبدو حقيقية فيه عدة مشكلات من هذه. جدها كلها. وبهذا يُغلق قسم الـ end-to-end؛ ويتراجع القسم 5 خطوة إلى الوراء ليسأل كيف تحكم على مجموعة اختبارات ككل.

أهم النقاط

  • وضع الواجهة UI mode (npx playwright test --ui) هو أسرع حلقة عمل محلية: وضع مراقبة، وخط زمني لكل فعل، ولقطة DOM عند كل خطوة.
  • مع trace: 'on-first-retry'، يترك الاختبار الذي يفشل في CI ملف trace تستطيع فتحه بـ npx playwright show-trace لإعادة تشغيله خطوة بخطوة.
  • معظم التقلّب يأتي من أربعة أسباب: الانتظار الثابت أو الـ assertions التي لا تعيد المحاولة، والبيانات المشتركة بين الاختبارات، والوقت أو العشوائية غير المتحكَّم فيهما، وawait المفقودة.
  • أعد إنتاج التقلّب بـ --repeat-each، وأصلح السبب، وتعامل مع الاختبارات الموسومة بـ flaky كأخطاء؛ فإعادة المحاولة كاشف دخان، لا إصلاح.

قراءات إضافية

تحقّق من فهمك

4 أسئلة

السؤال 1 من 4

فشل اختبار مرة واحدة في CI ونجح عند إعادة المحاولة. إعداداتك تحتوي trace: 'on-first-retry'. ما أسرع طريقة لرؤية شكل الصفحة لحظة الفشل؟

السؤال 2 من 4

أيّ تغيير يصلح اختبارًا يفشل أحيانًا عند expect(await page.getByRole('listitem').count()).toBe(3)؟

السؤال 3 من 4

يتحقّق اختبار من أن مصروفًا أُضيف اليوم يظهر تحت “This week”. يفشل كل يوم اثنين في الساعة 00:30 في CI. ما الإصلاح الأمتن؟

السؤال 4 من 4

تبلّغ Playwright عن اختبار بأنه “flaky”. ماذا يعني هذا الوسم؟

تدريب

اكتشف الخطأ

جد ما يجعل هذا الاختبار متقلّبًا

يفشل اختبار Playwright هذا مرة تقريبًا من كل عشر تشغيلات في CI، ولا يفشل أبدًا على حاسوب كاتبه. اختر كل سطر يجعله متقلّبًا أو غير موثوق. (افترض أن التطبيق نفسه يعمل.)

حدّد السطر أو الأسطر التي تظنها خاطئة، ثم تحقّق.