javascript - Cum să folosești Jest/Puppeteer pentru a aștepta până când un element a fost eliminat din DOM


Rezumatul problemei: Scriu mai multe seturi de teste (folosind Jest și Puppeteer) pentru a automatiza testele paginii principale a aplicației mele AngularJS. Unul dintre testele pe care aș dori să le automatizez este ca un utilizator să apese un buton de pe pagină care șterge un element din DOM. Din păcate, acest element este folosit pentru a afișa o cantitate mare de date, așa că, pentru ca elementul să fie șters, clientul trebuie mai întâi să facă o cerere POST către serverul meu pentru a șterge datele din baza de date, și abia apoi elementul poate fi șters din DOM. Per total, întregul acest proces durează aproximativ una sau două secunde. Mai mult, acest element pe care încerc să îl șterg a fost adăugat dinamic în DOM, așa că singura modalitate prin care pot accesa elementul este folosind un XPath care identifică elementul după textul pe care îl conține, mai degrabă decât după un selector CSS tradițional. Acum, iată întrebarea mea: cum pot folosi API-urile Jest și Puppeteer pentru a scrie cod de testare care AȘTEAPTĂ ca acest element să nu mai existe (adică să dispară din DOM).

Iată o prezentare generală a modului în care arată HTML-ul meu, astfel încât să ai o idee despre ce lucrez:

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

Context: Folosesc Jest (v24.8.0) ca framework de testare. Folosesc Puppeteer (v1.19.0) pentru a porni și controla un browser Chromium fără interfață grafică.

Ce am încercat până acum:

În prezent, am acest cod

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

Aș putea să fac ceva de genul acesta:

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

... dar vreau să pot scăpa de linia mea await delay și să fac testul să aștepte exact până când elementul dispare. Problema cu await delay este că nu este de încredere, deoarece, în funcție de cât de multe date afișează elementul, ștergerea acestuia poate dura mai mult sau mai puțin decât specifică await delay.

Concluzie: Ați mai întâlnit vreun hacker Jest/Puppeteer o problemă de genul acesta înainte și știți vreo soluție ingenioasă?

Răspuns #1

Poți folosi page.waitForXPath cu opțiunea { hidden: true } sau poți folosi page.waitForFunction pentru acest lucru, scriind o funcție care testează dacă elementul nu există.

page.waitForXPath cu hidden:true

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

Alternativă: page.waitForFunction

Alternativ, poți folosi următorul cod pentru a folosi un selector simplu:

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

Dacă vrei să folosești o expresie XPath, poți folosi acest cod:

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

Acesta transmite selectorul tău către funcție și folosește funcția document.evaluate pentru a executa expresia XPath în contextul browserului.

Răspunsul #2

Sau poți folosi:

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