javascript - Comment utiliser Jest/Puppeteer pour attendre qu’un élément ait été retiré du DOM


Résumé du problème : Je rédige plusieurs suites de tests (en utilisant Jest et Puppeteer) pour automatiser les tests de la page d'accueil de mon application AngularJS. L'un des tests que j'aimerais automatiser consiste à simuler un utilisateur appuyant sur un bouton sur la page qui supprime un élément dans le DOM. Malheureusement, cet élément est utilisé pour afficher une grande quantité de données, donc pour que l'élément soit supprimé, le client doit d'abord effectuer une requête POST à mon serveur pour supprimer les données de la base de données, et ce n'est qu'ensuite que l'élément peut être supprimé du DOM. Dans l'ensemble, tout ce processus prend environ une ou deux secondes. De plus, cet élément que j'essaie de supprimer a été ajouté dynamiquement au DOM, donc la seule façon pour moi d'accéder à l'élément est d'utiliser un XPath qui identifie l'élément par le texte qu'il contient, plutôt qu'un sélecteur CSS traditionnel. Voici maintenant ma question : comment puis-je utiliser les API de Jest et Puppeteer pour écrire du code de test qui ATTEND que cet élément n'existe plus (c.-à-d. quitte le DOM) ?

Voici un aperçu de ce à quoi mon HTML ressemble afin que vous ayez une idée de ce avec quoi je travaille :

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

Contexte : J'utilise Jest (v24.8.0) comme framework de test. J'utilise Puppeteer (v1.19.0) pour démarrer et contrôler un navigateur Chromium sans tête.

Ce que j'ai essayé jusqu'à présent :

Actuellement, j'ai ce code

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

Je pourrais faire quelque chose comme ça :

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

... mais je veux pouvoir supprimer ma ligne await delay et simplement faire en sorte que le test attende exactement jusqu'à ce que l'élément soit disparu. Le problème avec await delay est qu'il est peu fiable, car selon la quantité de données affichées par l'élément, il peut falloir plus ou moins de temps pour qu'il soit supprimé que ce que await delay spécifie.

Conclusion : Est-ce que l’un de vous, pirates Jest/Puppeteer, a déjà rencontré un problème comme celui-ci et connaît des solutions astucieuses ?

Réponse #1

Vous pouvez utiliser page.waitForXPath avec l'option { hidden: true } ou utiliser page.waitForFunction pour cela en écrivant une fonction qui teste si l'élément n'existe pas.

page.waitForXPath avec hidden:true

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

Alternative : page.waitForFunction

Alternativement, vous pouvez utiliser le code suivant pour utiliser un sélecteur simple :

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

Si vous voulez utiliser une expression XPath, vous pouvez utiliser ce code :

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

Cela passe votre sélecteur à la fonction et utilise la fonction document.evaluate pour exécuter l'expression XPath dans le contexte du navigateur.

Réponse #2

Ou vous pouvez utiliser :

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