Summary of problem: I'm writing several test suites (using Jest and Puppeteer) to automate tests of my AngularJS app's home page. Note: Some of my ui components are powered by a framework called AngularJS Material. One of the tests I would like to automate is a user pressing a button on the page that reloads a table. Unfortunately, this table is used to display a large amount of data, so in order for the table to be reloaded, the client first needs to make GET request to my server to extract the table data from the db, and only then can the table be displayed in the DOM. All in all, this whole process takes about a second or two. So here's my question: how can I write some Jest/Puppeteer test code to WAIT for the table to be fully loaded/displayed in the DOM (i.e. ALL table row data is displayed).
I cannot predetermine how many rows there will be in the table. I know like it may seem like I can based on the minimal example I provided. But unfortunately, the number of rows in the table is determined by how much data the user adds.
Below you will see that I've tried several methods to wait for all row data to be displayed, but nothing has worked yet.
<!-- index.html -->
<html>
<body ng-app="myApp" ng-controller="myCtrl">
<md-content class="tableContainer">
<md-content class="table">
<!-- UI component provided by Angular JS Material, appears while table is loading -->
<md-progress-linear md-mode="indeterminate"></md-progress-linear>
<table>
<thead><!-- my table header --></thead>
<tbody><!-- displays tons of data --></tbody>
</table>
</md-content>
</md-content>
<button id="reloadTableBtn" ng-click="myCtrl.reloadTableData()">Reload Table</button>
</body>
</html>
// index.spec.js
test('reload table', async() => {
let reloadTableBtnSelector = 'button[id="reloadTableBtn"]';
await page.waitForSelector(reloadTableBtnSelector, {visible: true, timeout: globals.timeouts.selector});
await page.click(reloadTableBtnSelector);
/* attempt #1: wait for progress bar to disappear from display - fails
for unknown reason perhaps because the progress bar disappears when
the client gets response from the server, instead of when
all data has been rendered
*/
let progressLinearSelector = 'md-content.mdtTable md-progress-linear';
await page.waitForSelector(progressLinearSelector, {hidden: true, timeout: 3000});
await page.waitFor(2000);
/* attempt #2: wait for tbody to be added to the DOM - fails
b/c tbody is added to the DOM before all rows have been rendered
*/
await page.waitForFunction(() => document.querySelector('table tbody'));
/* attempt #3: wait to tbody to be displayed in the DOM - fails.
Jest throws Timeout Error for unknown reason
*/
await page.waitForSelector('table tbody', {visible: true, timeout: 3000});
/* attempt #4: just wait n milliseconds for the full table to be displayed
- not reliable (and prone to failure) b/c table might take more or less
time than n seconds to load (depending on how much data is being rendered)
*/
await page.waitFor(2000);
});
Another potential solution to this would be to wait for all the network connections to finish. I have another test that does this via:
await page.goto('https://my-website.com', {waitUntil: 'networkidle0'});
... but all the page methods that have the waitUntil
option available to them involve navigating to different webpages/reloading webpages, and that's not what I want.
If any of you Jest/Puppeteer experts out there know of a solution for this, I'd really appreciate your advice :)
Waiting until the table is filled
The easiest way is probably to use page.waitForFunction
to wait until the table is filled with enough rows. I imagine you know how many table rows are roughly expected, so you could use the following code:
await page.waitForFunction(() => document.querySelectorAll('#table-selector tr').length >= 1000);
This pauses the script until there are at least 1000
rows inside of the table.
As you mentioned, that the condition is "at least one row or a specific sentence", you could change it to this:
await page.waitForFunction(
() => !!document.querySelector('#table-selector tr') || document.querySelector('#noresults-selector').innerText.includes('no results')
);
This waits until the table has at least one row or until there is the text no results
inside the given selector.
Waiting for the network response
I recommend to not wait until there is no more network traffic, as your script might still need a few milliseconds to populate the table with data after downloading the data. In case, you still want to give it a try I recommend to specify which response to wait for before continuing:
await page.waitForResponse(response => response.url().includes('/url-to-wait-for'));
Using page.waitForResponse
, the code waits until the response of a specific URL is received.