ElsaInSpirit
ElsaInSpirit

Reputation: 341

How to use Jest/Puppeteer to wait until an element as been removed from the DOM

Summary of problem: I'm writing several test suites (using Jest and Puppeteer) to automate tests of my AngularJS app's home page. One of the tests I would like to automate is a user pressing a button on the page that deletes an element in the DOM. Unforuntealy, this element is used to display a large amount of data, so in order for the element to be deleted, the client first needs to make POST request to my server to delete the data from the db, and only then can the element be deleted from the DOM. All in all, this whole process takes about a second or two. What's more, this element I'm trying to delete was added dynamically to the DOM, so the only way I am able to access the element is by using an XPath which identifies the element by the text it contains, rather than a traditional CSS selector. Now here's my question: how can I employ Jest and Puppeteer's Api's to write some test code that WAITS for this element to no longer exist (i.e. leave the DOM).

Here is a overview of what my HTML looks like so you have an idea of what I'm working with:

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

Background: I'm using Jest (v24.8.0) as my testing framework. I'm using Puppeteer (v1.19.0) to spin up and control a headless Chromium browser.

What I've tried so far:

Currently, I have this 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);
  }); 

I could do something like this:

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

... but I want to be able to get rid of my await delay line and just have the test wait precisely until the element is gone. The problem with await delay is that it's unreliable as depending on how much data the element is displaying it may take more or less time to be deleted than what await delay specifies.

Conclusion: Have any of you Jest/Puppeteer hackers come across an issue like this before and know of any clever solutions?

Upvotes: 6

Views: 7358

Answers (2)

Geeta R
Geeta R

Reputation: 1

Or you can use:

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

Upvotes: 0

Thomas Dondorf
Thomas Dondorf

Reputation: 25240

You can use page.waitForXPath with option { hidden: true } or use page.waitForFunction for this by writing a function which tests if the element does not exist.

page.waitForXPath with hidden:true

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

Alternative: page.waitForFunction

Alternatively, you can use the following code to use a simple selector:

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

If you want to use a XPath expression, you can use this 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
);

This passes your selector to the function and uses document.evaluate function to run the XPath expression inside the browser context.

Upvotes: 7

Related Questions