Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 751
ElsaInSpirit Avatar asked Aug 07 '19 18:08

ElsaInSpirit


People also ask

How to run jest tests using puppeteer?

Jest Puppeteer provides all required configuration to run your tests using Puppeteer. There's no need to load any dependencies. Puppeteer's page and browser classes will automatically be exposed See documentation. You can also hook up puppeteer from scratch. The basic idea is to: launch & file the websocket endpoint of puppeteer with Global Setup

How to wait for an element in puppeteer?

page.waitForSelector () and page.waitForXPath () is used to wait for an element. By default, the timeout is 30 sec in puppeteer. But we can change it according to the requirement.

What is the default timeout in puppeteer?

await page.waitForSelector('#developer-name'); Default timeout in puppeteer By default, the timeout is 30 sec in puppeteer. But we can change it according to the requirement.

What is the function beforeall in puppeteer?

There's a function named beforeAll that, as the name suggests, performs some kind of action before any of the tests are run. In this case, we are simply telling Puppeteer to go to the defined URL and wait until the content has loaded so that the tests can begin.


1 Answers

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.

like image 88
Thomas Dondorf Avatar answered Nov 10 '22 19:11

Thomas Dondorf