جافاسكريبت - كيفية استخدام Jest/Puppeteer للانتظار حتى يتم إزالة عنصر من DOM


ملخص المشكلة: أنا أكتب عدة مجموعات اختبار (باستخدام Jest و Puppeteer) لأتمتة اختبارات صفحة البداية لتطبيقي AngularJS. أحد الاختبارات التي أود أتمتتها هو قيام المستخدم بالضغط على زر في الصفحة يحذف عنصرًا في DOM. للأسف، يُستخدم هذا العنصر لعرض كمية كبيرة من البيانات، لذا لكي يتم حذف العنصر، يحتاج العميل أولاً لإرسال طلب POST إلى خادمي لحذف البيانات من قاعدة البيانات، وبعد ذلك فقط يمكن حذف العنصر من DOM. عموماً، تستغرق هذه العملية بأكملها حوالي ثانية أو ثانيتين. والأهم من ذلك، أن هذا العنصر الذي أحاول حذفه تم إضافته ديناميكياً إلى DOM، لذا فإن الطريقة الوحيدة التي أستطيع من خلالها الوصول إلى العنصر هي باستخدام XPath الذي يحدد العنصر بالنص الذي يحتويه، بدلاً من محدد CSS التقليدي. والآن إليك سؤالي: كيف يمكنني استخدام Jest و Puppeteer API لكتابة بعض كود الاختبار الذي ينتظر حتى لا يوجد هذا العنصر بعد الآن (أي يغادر DOM).

إليكم نظرة عامة على شكل HTML الخاص بي حتى تحصلوا على فكرة عما أعمل عليه:

<html>
  <body ng-app="myApp" ng-controller="myCtrl">
    <!-- Dynamically added div -->
    <div>My Data
      <table><!-- Displays tons of data --></table>
    </div>
    <form>
      <button type="submit">Delete</button>
    </form>
  </body>
</html>

الخلفية: أنا أستخدم Jest (الإصدار 24.8.0) كإطار عمل للاختبار. أنا أستخدم Puppeteer (الإصدار 1.19.0) لتشغيل والتحكم في متصفح Chromium بدون واجهة رسومية.

ما جربته حتى الآن:

حاليًا، لدي هذا الكود

  test('deleted elem no longer exists', async() => {
    elemXPath = '//div[contains(text(), "My Data")]';

    // this is a function to pause the 
    // execution of the test for a given amount of milliseconds
    // in order to wait for elem to be removed
    await delay(2000);

    // This fails because Puppeteer timeouts after 3000 
    // ms b/c elemXPath no longer exists
    const elemExists = await page.waitForXPath(elemXPath, {timeout: 3000}) ? true : false;
    expect(elemExists).toBe(false);
  }); 

يمكنني فعل شيء مثل هذا:

  test('deleted elem no longer exists', async() => {
    elemXPath = '//div[contains(text(), "My Data")]';

    // wait for elem to be removed
    await delay(2000);

    try {
      var elemExists = await page.waitForXPath(elemXPath, {timeout: 3000}) ? true : false;
    } catch(err) {
      var elemExists = false
    }
    expect(elemExists).toBe(false);
  }); 

... لكنني أريد أن أتمكن من التخلص من سطر await delay الخاص بي والاكتفاء بأن ينتظر الاختبار بدقة حتى يختفي العنصر. المشكلة في await delay هي أنه غير موثوق لأنه اعتمادًا على كمية البيانات التي يعرضها العنصر قد يستغرق حذفه وقتًا أطول أو أقصر مما يحدده await delay.

الخلاصة: هل صادف أي منكم من مختبري Jest/Puppeteer مشكلة مثل هذه من قبل ويعرف أي حلول ذكية؟

الإجابة رقم 1

يمكنك استخدام page.waitForXPath مع الخيار { hidden: true } أو استخدام page.waitForFunction لذلك عن طريق كتابة دالة تختبر ما إذا كان العنصر غير موجود.

page.waitForXPath مع hidden:true

await page.waitForXPath(elemXPath, { hidden: true });

بديل: page.waitForFunction

بدلاً من ذلك، يمكنك استخدام الكود التالي لاستخدام محدد بسيط:

await page.waitForFunction(() => !document.querySelector('#selector-of-element'));

إذا كنت تريد استخدام تعبير XPath، يمكنك استخدام هذا الرمز:

const elemXPath = '//div[contains(text(), "My Data")]';
await page.waitForFunction(
  xpath => !document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue,
  {},
  elemXPath
);

هذا يمرر المحدد الخاص بك إلى الدالة ويستخدم دالة document.evaluate لتنفيذ تعبير XPath داخل سياق المتصفح.

الإجابة رقم 2

أو يمكنك استخدام:

await page.waitForSelector(selector, { hidden: true });