问题
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