PepeW
PepeW

Reputation: 597

Pass a function inside page.waitForFunction() with puppeteer

Here's my code :

function hasDataBeenRefreshed(pastAvgGain, currentAvgGain) {
  if (pastAvgGain!== currentAvgGain) {
      return true
  } else {
      return false
  }
}

async function getInfos(paire, page) {

  let pastAvgGain = C.AVG_GAIN.textContent

  await page.click(paire)

  let currentAvgGain = C.AVG_GAIN.textContent

  await page.waitForFunction(hasDataBeenRefreshed(pastAvgGain, currentAvgGain))

  ...

}

But if I do this I get this error:

Error: Evaluation failed: TypeError: true is not a function

Is there a way to achieve something like this ?

Upvotes: 0

Views: 6645

Answers (2)

ggorlen
ggorlen

Reputation: 56885

There are multiple issues and misunderstandings with the code here.

The first problem is that you're immediately calling the function you want to pass as a callback. This results in the return value of the callback (a boolean comparison) being passed into page.waitForFunction rather than the callback itself. You can't call a boolean like a function, so an error is thrown.

Secondly, you'll need to pass the data in as parameters to the callback using the variable arguments to page.waitForFunction. The second argument is a configuration object, so the call will look like page.waitForFunction(predicate, configObj, ...args) where ...args are the variable arguments to predicate.

Thirdly, you're getting text content before and after a click, but polling for further changes on these two variables won't work because the value of let currentAvgGain = C.AVG_GAIN.textContent will never change. Your predicate will either terminate instantly or spin based on whatever the initial values were. If click is asynchronous, then C.AVG_GAIN.textContent is probably stale and you'll want to re-select the latest text content from the node in the waitForFunction predicate.

Furthermore, it's not clear what C.AVG_GAIN.textContent is. Neither DOM nodes or Puppeteer elementHandles look like this, so I'll assume this also needs to be rewritten.

It appears you're trying to check whether a DOM element, elementHandle or selector has changed its text. This should be a pretty generic function you can use:

const puppeteer = require("puppeteer"); // ^19.6.3

const waitForTextChange = async (
  page,
  elOrSel,
  opts={polling: "mutation", timeout: 30000}
) => {
  const el = typeof elOrSel === "string" 
    ? await page.$(elOrSel) : elOrSel;
  const originalText = await el.evaluate(el => el.textContent);
  return page.waitForFunction(
    (el, originalText) => el.textContent !== originalText,
    opts,
    el,
    originalText, 
  );
};

let browser;
(async () => {
  const html = `
    <h2>foo</h2>
    <script>
      setTimeout(() => document.querySelector("h2").innerText = "bar", 4000);
    </script>
  `;
  browser = await puppeteer.launch();
  const [page] = await browser.pages();
  await page.setContent(html);
  await waitForTextChange(page, "h2");
  console.log(await page.$eval("h2", el => el.innerText)); // => bar
})()
  .catch(err => console.error(err))
  .finally(() => browser?.close());

Note that if you're using waitForTextChange alongside an action that triggers the change, you'll want to use it as follows to avoid a race condition:

const textChangedPromise = waitForTextChange(page, "h2");
await page.click("button"); // trigger the text change
await textChangedPromise;

Applied to the above example:

const html = `
<h2>foo</h2>
<button>click to change text</button>
<script>
document
  .querySelector("button")
  .addEventListener("click", () => {
    document.querySelector("h2").innerText = "bar";
  });
</script>
`;
await page.setContent(html);
const textChanged = waitForTextChange(page, "h2");
await page.click("button"); // action that triggers text change
await textChanged;
console.log(await page.$eval("h2", el => el.innerText)); // => bar

Upvotes: 2

Kevin.a
Kevin.a

Reputation: 4296

page.waitForFunction() accepts a callback and right now you're passing in a boolean. To solve this you can do the following :

  await page.waitForFunction((pastAvgGain, currentAvgGain) => {
  if (pastAvgGain!== currentAvgGain) {
      return true
  } else {
      return false
  }
} , {} , pastAvgGain, currentAvgGain )

https://github.com/puppeteer/puppeteer/blob/main/docs/api.md#pagewaitforfunctionpagefunction-options-args see documentation for more info

the third parameter is the arguments you want to pass to the callback

after your comment :

  await page.waitForFunction(() => {
      return hasDataBeenRefreshed(pastAvgGain, currentAvgGain); 
  } )

Upvotes: 1

Related Questions