javascript - Ako použiť Jest/Puppeteer na čakanie, kým bude element odstránený z DOM


Zhrnutie problému: Píšem niekoľko testovacích zostáv (používajúc Jest a Puppeteer) na automatizáciu testov domovskej stránky mojej aplikácie AngularJS. Jeden z testov, ktorý by som chcel automatizovať, simuluje stlačenie tlačidla používateľom na stránke, ktoré maza prvok v DOM. Bohužiaľ, tento prvok sa používa na zobrazovanie veľkého množstva údajov, takže na to, aby mohol byť prvok vymazaný, klient najprv musí poslať POST požiadavku na môj server, aby vymazal údaje z databázy, a až potom môže byť prvok vymazaný z DOM. Celkovo celý tento proces trvá približne jednu alebo dve sekundy. Navyše tento prvok, ktorý sa snažím vymazať, bol do DOM pridaný dynamicky, takže jediný spôsob, ako k nemu mám prístup, je použiť XPath, ktorý identifikuje prvok podľa textu, ktorý obsahuje, namiesto tradičného CSS selektora. Tu je moja otázka: ako môžem použiť API Jest a Puppeteer na napísanie testovacieho kódu, ktorý ČAKÁ na to, kým tento prvok prestane existovať (t. j. opustí DOM).

Tu je prehľad toho, ako vyzerá môj HTML, aby ste mali predstavu o tom, s čím pracujem:

<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>

Pozadie: Používam Jest (v24.8.0) ako môj testovací rámec. Používam Puppeteer (v1.19.0) na spúšťanie a ovládanie bezhlavého prehliadača Chromium.

Čo som zatiaľ vyskúšal:

Momentálne mám tento kód

  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);
  }); 

Mohol by som urobiť niečo takéto:

  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);
  }); 

... ale chcem byť schopný zbaviť sa riadku await delay a nechať test čakať presne až do chvíle, kým prvok nezmizne. Problém s await delay je, že je nespoľahlivý, pretože v závislosti od toho, koľko dát prvok zobrazuje, môže jeho vymazanie trvať viac alebo menej času, než čo špecifikuje await delay.

Záver: Narazil niektorý z vás, hackerov používajúcich Jest/Puppeteer, na podobný problém a pozná nejaké šikovné riešenia?

Odpoveď č. 1

Môžete použiť page.waitForXPath s možnosťou { hidden: true } alebo použiť page.waitForFunction tak, že napíšete funkciu, ktorá otestuje, či prvok neexistuje.

page.waitForXPath s hidden:true

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

Alternatíva: page.waitForFunction

Alternatívne môžete použiť nasledujúci kód na použitie jednoduchého selektora:

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

Ak chcete použiť výraz XPath, môžete použiť tento kód:

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

Toto odovzdá váš selektor funkcii a použije funkciu document.evaluate na spustenie XPath výrazu v kontexte prehliadača.

Odpoveď č. 2

Alebo môžete použiť:

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