كل المقالات
QA PlaywrightTypeScriptFlakinessTest Automation

الـ addLocatorHandler في Playwright: كيف تتخلص من النوافذ المنبثقة التي تُفشل اختباراتك

اختبار ينجح مرة ويفشل مرة بسبب نافذة منبثقة عشوائية؟ هكذا تحلها addLocatorHandler في Playwright بسطرين، مع الخيارات والأخطاء التي لازم تعرفها.

4 دقائق قراءة

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

الحل في Playwright مش شرط if، ومش انتظار أطول. الحل هو page.addLocatorHandler(): تسجّل مرة واحدة ماذا تفعل عندما تظهر النافذة، وPlaywright يتكفّل بالباقي في أي لحظة من الاختبار.

المشكلة: اختبار هولندا

خلينا نأخذ مثالاً واحداً ونتابعه حتى النهاية. عندنا تطبيق يعرض معلومات عن الدول. الاختبار بسيط: نبحث عن Netherlands، ثم نضغط زر الإعجاب على بطاقتها.

import { test, expect } from '@playwright/test';

test('like the Netherlands', async ({ page }) => {
  await page.goto('/');
  await page.getByTestId('search-input').fill('Netherlands');
  await page.getByTestId('like-button').click();
  await expect(page.getByTestId('like-count')).toHaveText('1');
});

لكن التطبيق يعرض أحياناً نافذة ترويجية فوق المحتوى. مرة تظهر بعد التحميل، ومرة بعد البحث، ومرة ما تظهر أبداً.

  • ✅ إذا ما ظهرت النافذة، الاختبار ينجح
  • ❌ إذا ظهرت، فهي تغطي زر الإعجاب، والـ click() ينتظر حتى ينتهي الوقت ثم يفشل

هذا بالضبط تعريف الاختبار الـ Flaky: النتيجة تعتمد على الحظ، مش على جودة التطبيق.

الحلول التي نراها عادة، وليش ما تكفي

الحل الأول: شرط قبل الضغط.

if (await page.getByTestId('promo-popup').isVisible()) {
  await page.getByTestId('close-popup').click();
}
await page.getByTestId('like-button').click();

المشكلة أن isVisible() يفحص اللحظة الحالية فقط. إذا ظهرت النافذة بعد الشرط بنصف ثانية، رجعنا لنفس الفشل.

الحل الثاني: انتظار ثابت بـ waitForTimeout حتى "تظهر النافذة إذا كانت ستظهر". هذا يبطّئ كل تشغيل، وما يضمن شيئاً، لأن النافذة ممكن تتأخر أكثر من الانتظار.

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

الحل: addLocatorHandler

الـ addLocatorHandler تأخذ شيئين: Locator للعنصر الذي تراقبه، ودالة تنفّذ ما تريده عندما يظهر.

import { test, expect } from '@playwright/test';

test('like the Netherlands', async ({ page }) => {
  const popup = page.getByTestId('promo-popup');

  await page.addLocatorHandler(popup, async () => {
    await page.getByTestId('close-popup').click();
  });

  await page.goto('/');
  await page.getByTestId('search-input').fill('Netherlands');
  await page.getByTestId('like-button').click();
  await expect(page.getByTestId('like-count')).toHaveText('1');
});

لاحظ أن خطوات الاختبار نفسها ما تغيّرت أبداً. لا شروط، ولا انتظار. أضفنا فقط تسجيلاً واحداً في البداية.

نصيحة
سجّل الـ Handler في بداية الاختبار، حتى قبل page.goto(). هكذا يكون جاهزاً حتى لو ظهرت النافذة مع أول تحميل للصفحة.

ماذا يحدث وقت التشغيل؟

  1. التسجيل: Playwright يحفظ الـ Locator والدالة، ولا ينفّذ شيئاً هسا.
  2. قبل كل Action أو Assertion: قبل click() وfill() وقبل كل محاولة لـ expect ينتظر تلقائياً، يفحص Playwright إذا كانت النافذة ظاهرة.
  3. إذا ظهرت: ينفّذ دالتك، أي يضغط زر الإغلاق، ثم ينتظر حتى تختفي النافذة، ثم يكمل الخطوة الأصلية وكأن شيئاً لم يحدث.
  4. إذا ما ظهرت أبداً: الدالة لا تُنفّذ، والاختبار يكمل بشكل طبيعي.

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

خيارات تحتاجها في المشاريع الحقيقية

تنفيذ محدود بعدد مرات

بعض النوافذ تظهر مرة واحدة فقط في الجلسة، مثل عرض ترحيبي. الخيار times يحدد كم مرة تُنفّذ الدالة، وبعدها يُلغى الـ Handler تلقائياً:

await page.addLocatorHandler(popup, async () => {
  await page.getByTestId('close-popup').click();
}, { times: 1 });

عدم انتظار اختفاء النافذة

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

await page.addLocatorHandler(popup, async () => {
  await page.getByTestId('accept-banner').click();
}, { noWaitAfter: true });

إلغاء المراقبة

إذا كان جزء من الاختبار يحتاج النافذة ظاهرة، مثلاً لأنك تريد اختبار النافذة نفسها، ألغِ المراقبة قبله:

await page.removeLocatorHandler(popup);

وين تستخدمها غير النوافذ الترويجية؟

أي عنصر يظهر بوقت مش متوقع ويغطي الصفحة:

العنصر ماذا تفعل الدالة
شريط الموافقة على الـ Cookies تضغط "موافق"
نافذة الاشتراك في النشرة البريدية تضغط زر الإغلاق
استبيان رأي يظهر بعد ثوانٍ تضغط "لاحقاً"
نافذة المحادثة مع الدعم تصغّرها

أخطاء لازم تنتبه لها

تحذير
الـ Handler مش مراقب يعمل بالخلفية طول الوقت. هو يُفحص فقط قبل الـ Actions والـ Assertions. إذا ظهرت النافذة بعد آخر خطوة في الاختبار، لن يغلقها، وهذا عادة لا يؤثر على النتيجة.
  • خلّي الدالة بسيطة. أغلق النافذة وبس. أي خطوة إضافية مثل التنقل لصفحة أخرى تغيّر حالة الصفحة في منتصف الاختبار، وتعطيك نتائج يصعب فهمها.
  • اختر Locator دقيقاً. إذا كان الـ Locator يطابق عنصراً موجوداً دايماً في الصفحة، ستُنفّذ الدالة قبل كل خطوة.
  • لا تخفي بها مشكلة حقيقية. إذا كانت النافذة تظهر في مكان ما كان لازم تظهر فيه، فهذا Bug يستحق تقريراً، مش Handler.

لمن هذا الكلام غير المبرمجين؟

إذا كنت Tester يدوي أو Team Lead، فالدرس هنا أوسع من Playwright: معظم الاختبارات الـ Flaky مش سببها التطبيق، سببها افتراض في الاختبار أن الصفحة ستبدو دايماً بنفس الشكل. لما يفشل اختبار بشكل متقطع، اسأل أولاً: هل يوجد شيء يظهر أحياناً ويختفي أحياناً؟

الخلاصة

  1. النوافذ العشوائية سبب شائع للاختبارات الـ Flaky، والشروط والانتظار الثابت لا تحلها.
  2. الـ addLocatorHandler تسجّل الحل مرة واحدة، وPlaywright يطبّقه قبل كل خطوة عند الحاجة.
  3. سجّلها في بداية الاختبار، واستخدم times وnoWaitAfter وremoveLocatorHandler حسب الحالة.
  4. خلّي الدالة بسيطة، ولا تستخدمها لإخفاء Bug حقيقي.

هذا الموضوع واحد من الدروس المتقدمة في كورس احترف Playwright: الدليل الكامل، مع الـ Custom Fixtures والـ Routing والـ waitForResponse، إلى جانب الـ Page Object Model ومشروع عملي كامل: ٢٧٤ درساً في ٢٠ قسماً، منها ٢٨ درساً مجانياً تستطيع أن تبدأ بها بدون حساب.

بدك تتعمّق أكثر؟

المقال هون بيعطيك الفكرة. الدورات بتاخدك من الصفر لحد ما تشتغل فيها فعلياً.

شوف الدورات