puppeteer:如何在SPA中等待页面?

时间:2018-03-26 11:12:50

标签: javascript node.js puppeteer

我正在尝试使用puppeteer浏览SPA,我在这里遇到的问题是我无法等待页面加载然后继续我的程序。

我填写表单,然后点击提交,根据表单的内容,可以加载不同的页面,因此我不能使用page.waitFor(Selector),因为根据输入可能有许多不同的页面。

我尝试使用waitUntil: load,networkidle2,networkidle0,domcontentloaded,但所有这些都在元素加载之前触发。

我尝试自动化的页面是Link。 (如果您想自己检查,请选择预订参考并填写随机详细信息,然后按继续。)

在链接中选择“预订参考”后,我用木偶操作员填写详细信息,然后按“继续”按钮,我无法弄清楚如何在不依赖选择器的情况下等待页面完全加载。

3 个答案:

答案 0 :(得分:2)

我认为您应该知道这些网页是什么,并为每个页面使用Promise.racepage.waitFor,如下所示:

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

答案 1 :(得分:1)

对于那些寻求快速解答的人,下面是主要代码:

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

......,其中el是指向SPA中另一个页面的链接,而click可以是导致导航的任何事件。有关详情,请参见下文。


我同意waitFor不能太有用,如果您不能依赖页面内容。即使可以,在大多数情况下,这似乎也不如自然地对导航做出反应那样可取。幸运的是,page.waitForNavigation确实适用于SPA。这是一个最小的完整示例,该示例是在一个使用history API(下面的index.html)的微型SPA样机上,使用链接上的click事件在页面之间进行导航(同样适用于表单提交)。我使用了Node 10和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();
})();

样品运行:

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

如果await Promise.all([page.waitForNavigation(), el.click()]);模式看起来很奇怪,请参见this issue thread,它解释了gotcha直观

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

导致比赛情况。

可以通过以下方法完成与上面显示的Promise.all相同的事情:

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

有关使用Puppeteer(包括散列路由器)导航SPA的更多信息,请参见此related answer

答案 2 :(得分:0)

单页应用程序等待导航并获取响应状态和数据的解决方法。无论是使用 fetch 还是 XHR 来执行 Ajax 请求,主要思想都应该是相同的。以下示例使用 fetch

进行演示
  async spaClick (selector) {
    const res = await this.eval(selector, el => {
      window.originalFetch = window.originalFetch || window.fetch
      return new Promise(resolve => {
        window.fetch = function (...args) {
          return window.originalFetch.apply(this, args)
            .then(async response => {
              resolve({
                status: response.status,
                data: await response.clone().text()
              })

              return response
            })
        }

        el.click()
      })
    })

    if (!res) throw new Error('spaClick() Navigation triggered before eval resolves!')
    return res
  }
const puppeteer = require('puppeteer');
const url = 'http://www.faalkaart.nl';

(async () => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    // await Promise.all([
    //   page.waitForNavigation({ waitUntil: 'networkidle0' }),
    //   page.click('selector-that-triggers-navigation'),
    // ]);
    const response = await spaClick('selector-that-triggers-navigation')
    console.log(response) // {status, data}
    await browser.close();
})();