الوحدة 4 · اختبارات end-to-end مع Playwright
تتبّع الاختبارات المتقلّبة وإصلاحها
20 دقراءة 4 داكتشف الخطأ
الدرس في لمحة
تتتبّع اختبارات Playwright الفاشلة باستخدام UI mode والـ inspector وعارض الـ trace، ثم تجد الأسباب المعتادة للتقلّب وتزيلها بدل إخفائها بإعادة المحاولة.
لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.
في عمل سابق كانت لدينا مجموعة end-to-end حمراء في تشغيلة من كل خمس تقريبًا. كان الجميع يعرف أيّ الاختبارات “تفعل ذلك دائمًا”، فكان الجميع يعيد تشغيل الـ pipeline. ثم في يوم جمعة وصل إلى المستخدمين خطأ حقيقي في الدفع، لأن اختباره الفاشل كان من تلك التي “تفعل ذلك دائمًا”. الـ flaky test (الاختبار المتقلّب) ليس إزعاجًا بسيطًا؛ بل يدرّب الفريق كله على تجاهل جرس الإنذار.
لهذا الدرس نصفان: أدوات رؤية سبب فشل الاختبار، والقائمة القصيرة من الأسباب التي تقف خلف كل اختبار متقلّب تقريبًا.
رؤية ما حدث
UI mode (وضع الواجهة) هو المكان الذي أقضي فيه معظم وقت تتبّع الأخطاء محليًا:
npx playwright test --uiيفتح نافذة تسرد كل اختبار. شغّل واحدًا فتحصل على خط زمني لأفعاله؛ وانقر أي خطوة لترى لقطة DOM للصفحة في تلك اللحظة، إضافة إلى رسائل الـ console وطلبات الشبكة وكود الاختبار. يراقب الملفات ويعيد التشغيل عند الحفظ. وفيه أيضًا منتقي locators: مرّر المؤشّر فوق اللقطة فيقترح عليك locator.
الـ inspector (المفتّش) يمشي في الاختبار فعلًا بعد فعل في متصفّح ظاهر:
npx playwright test e2e/settle-up.spec.ts --debugأضف await page.pause() في اختبار للتوقّف عند نقطة محدّدة.
الـ traces (سجلّات التتبّع) للحالات التي فشلت دون أن تراها، وعادةً في CI. مع trace: 'on-first-retry' في الإعدادات، تسجّل Playwright ملف trace عند أول إعادة محاولة لاختبار فاشل: كل فعل، ولقطة DOM قبله وبعده، ومخرجات الـ console، واستدعاءات الشبكة، وسطر الكود. نزّل التقرير من CI وافتح الـ trace:
npx playwright show-report # HTML report, with traces attached
npx playwright show-trace trace.zip # a single traceفي تسعين بالمئة من الحالات، تخبرك اللقطة عند الخطوة الفاشلة بالإجابة فورًا: مؤشّر تحميل يغطّي الزر، أو رسالة خطأ منبثقة، أو صفحة غير التي توقّعتها.
المشتبه بهم المعتادون
كل اختبار 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 في الاختبارات ما لم يكن الاختبار عنها.
إثبات الإصلاح
الاختبار المتقلّب الذي نجح مرة واحدة لا يثبت شيئًا. أعد إنتاج المشكلة أولًا، ثم أثبت الإصلاح تحت الضغط نفسه:
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 كأخطاء؛ فإعادة المحاولة كاشف دخان، لا إصلاح.