puppeteer: How to wait for pages in SPA's?

折月煮酒 提交于 2021-01-22 05:23:07

问题


I am trying to navigate through an SPA with puppeteer, the problem I am facing here is that I am unable to wait for the page to load then proceed with my program.

I fill a form and then click submit, depending on the contents of the form, different pages can be loaded so I can't use page.waitFor(Selector) as there can be many different pages depending on the input.

I tried using waitUntil: load, networkidle2, networkidle0, domcontentloaded but all of them trigger before the elements are loaded.

The page I am trying to automate is Link. (If you want to check for yourself, then choose booking reference and fill out random details and press continue.)

After choosing "booking-reference" in the link I fill in the details with puppeteer and then press the continue button, What I cannot figure out is how to wait for the page to be completely loaded without relying on selectors.


回答1:


I think you should know what those pages are and use Promise.race with page.waitFor for each page, like this:

const puppeteer = require('puppeteer');

const html = `
<html>
  <body>
    <div id="element"></div>
    <button id="button">load</button>

    <script>
      document.getElementById('button').addEventListener("click", () => {
        document.getElementById('element').innerHTML =
          '<div id="element' + (Math.floor(Math.random() * 3) + 1)  + '"></div>';
      });
    </script>
  </body>
</html>`;

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto(`data:text/html,${html}`);

  await page.click('#button');

  const element = await Promise.race([
    page.waitFor('#element1'),
    page.waitFor('#element2'),
    page.waitFor('#element3')
  ]);

  console.log(await (await element.getProperty('id')).jsonValue());
  await browser.close();
})();



回答2:


For those looking for a quick answer, here's the main code:

await Promise.all([page.waitForNavigation(), el.click()]);

...where el is a link that points to another page in the SPA and click can be any event that causes navigation. See below for details.


I agree that waitFor isn't too helpful if you can't rely on page content. Even if you can, in most cases it seems like a less desirable approach than naturally reacting to the navigation. Luckily, page.waitForNavigation does work on SPAs. Here's a minimal, complete example of navigating between pages using a click event on a link (the same should work for a form submission) on a tiny vanilla SPA mockup which uses the history API (index.html below). I used Node 10 and Puppeteer 5.4.1.

index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
  </head>
  <body>
    <script>
      const nav = `<a href="/">Home</a> | <a href="/about">About</a> | 
                   <a href="/contact">Contact</a>`;
      const routes = {
        "/": `<h1>Home</h1>${nav}<p>Welcome home!</p>`,
        "/about": `<h1>About</h1>${nav}<p>This is a tiny SPA</p>`,
      };
      const render = path => {
        document.body.innerHTML = routes[path] || `<h1>404</h1>${nav}`;
        document.querySelectorAll('[href^="/"]').forEach(el => 
          el.addEventListener("click", evt => {
            evt.preventDefault();
            const {pathname: path} = new URL(evt.target.href);
            window.history.pushState({path}, path, path);
            render(path);
          })
        );
      };
      window.addEventListener("popstate", e =>
        render(new URL(window.location.href).pathname)
      );
      render("/");
    </script>
  </body>
</html>

index.js:

const puppeteer = require("puppeteer");

(async () => {
  const browser = await puppeteer.launch(/*{dumpio: true}*/);
  const page = await browser.newPage();

  // navigate to the home page for the SPA and print the contents
  await page.goto("http://localhost:8000");
  console.log(await page.url());
  console.log(await page.$eval("p", el => el.innerHTML));

  // navigate to the about page via the link
  const [el] = await page.$x('//a[text()="About"]');
  await Promise.all([page.waitForNavigation(), el.click()]);

  // print proof that we're on the about page
  console.log(await page.url());
  console.log(await page.$eval("p", el => el.innerHTML));

  await browser.close();
})();

Sample run:

$ python3 -m http.server &
$ node index.js
http://localhost:8000/
Welcome home!
http://localhost:8000/about
This is a tiny SPA

If the await Promise.all([page.waitForNavigation(), el.click()]); pattern seems strange, see this issue thread which explains the gotcha that the intuitive

await page.waitForNavigation(); 
await el.click();

causes a race condition.

The same thing as the Promise.all shown above can be done with:

const navPromise = page.waitForNavigation({timeout: 1000});
await el.click();
await navPromise;

See this related answer for more on navigating SPAs with Puppeteer including hash routers.



来源:https://stackoverflow.com/questions/49490100/puppeteer-how-to-wait-for-pages-in-spas

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!