所以这里是代码段:
for (let item of items)
{
await page.waitFor(10000)
await page.click("#item_"+item)
await page.click("#i"+item)
let pages = await browser.pages()
let tempPage = pages[pages.length-1]
await tempPage.waitFor("a.orange", {timeout: 60000, visible: true})
await tempPage.click("a.orange")
counter++
}
page
和tempPage
是两个不同的页面。
page
会等待10秒,然后点击一些内容,这会打开第二页。
应该发生的事情是tempPage
等待一个元素,点击它,然后页面应等待10秒再重复一遍。
然而,实际发生的是page
等待10秒,点击这些东西,然后开始等待10秒而不等待tempPage
完成任务。
这是一个错误,还是我误解了什么?我应该如何解决这个问题,以便当for
循环再次循环时,只有在tempPage
被点击后才会出现。
答案 0 :(得分:5)
通常,您不能依赖await tempPage.click("a.orange")
暂停执行,直到tempPage
完成其任务"。对于同步执行的超级简单代码,它可能有效。但总的来说,你不能依赖它。
如果click触发Ajax操作,或者启动CSS动画,或者启动无法立即计算的计算,或打开新页面等,那么您正在等待的结果是异步的,{{ 1}}方法不会等待此异步操作完成。
你能做什么?在某些情况下,您可以挂钩页面上运行的代码并等待一些对您很重要的事件。例如,如果您想等待Ajax操作完成并且页面上的代码使用jQuery,那么您可以使用ajaxComplete
来检测操作何时完成。如果您无法挂钩到任何事件系统以检测操作何时完成,那么您可能需要轮询页面以等待操作完成的证据。
以下是一个显示问题的示例:
.click
const puppeteer = require('puppeteer');
function getResults(page) {
return page.evaluate(() => ({
clicked: window.clicked,
asynchronousResponse: window.asynchronousResponse,
}));
}
puppeteer.launch().then(async browser => {
const page = await browser.newPage();
await page.goto("https://example.com");
// We add a button to the page that will click later.
await page.evaluate(() => {
const button = document.createElement("button");
button.id = "myButton";
button.textContent = "My Button";
document.body.appendChild(button);
window.clicked = 0;
window.asynchronousResponse = 0;
button.addEventListener("click", () => {
// Synchronous operation
window.clicked++;
// Asynchronous operation.
setTimeout(() => {
window.asynchronousResponse++;
}, 1000);
});
});
console.log("before clicks", await getResults(page));
const button = await page.$("#myButton");
await button.click();
await button.click();
console.log("after clicks", await getResults(page));
await page.waitForFunction(() => window.asynchronousResponse === 2);
console.log("after wait", await getResults(page));
await browser.close();
});
代码模拟由点击启动的任何类型的异步操作。
运行此代码时,您将在控制台上看到:
setTimeout
您会看到before click { clicked: 0, asynchronousResponse: 0 }
after click { clicked: 2, asynchronousResponse: 0 }
after wait { clicked: 2, asynchronousResponse: 2 }
在两次点击后立即增加两次。但是,clicked
增加需要一段时间。语句asynchronousResponse
轮询页面,直到我们等待的条件实现。
您在评论中提到按钮正在关闭标签。打开和关闭选项卡是异步操作。这是一个例子:
await page.waitForFunction(() => window.asynchronousResponse === 2)
当我运行上述内容时,我得到:
puppeteer.launch().then(async browser => {
let pages = await browser.pages();
console.log("number of pages", pages.length);
const page = pages[0];
await page.goto("https://example.com");
await page.evaluate(() => {
window.open("https://example.com");
});
do {
pages = await browser.pages();
// For whatever reason, I need to have this here otherwise
// browser.pages() always returns the same value. And the loop
// never terminates.
await page.evaluate(() => {});
console.log("number of pages after evaluating open", pages.length);
} while (pages.length === 1);
let tempPage = pages[pages.length - 1];
// Add a button that will close the page when we click it.
tempPage.evaluate(() => {
const button = document.createElement("button");
button.id = "myButton";
button.textContent = "My Button";
document.body.appendChild(button);
window.clicked = 0;
window.asynchronousResponse = 0;
button.addEventListener("click", () => {
window.close();
});
});
const button = await tempPage.$("#myButton");
await button.click();
do {
pages = await browser.pages();
// For whatever reason, I need to have this here otherwise
// browser.pages() always returns the same value. And the loop
// never terminates.
await page.evaluate(() => {});
console.log("number of pages after click", pages.length);
} while (pages.length > 1);
await browser.close();
});
您可以看到在number of pages 1
number of pages after evaluating open 1
number of pages after evaluating open 1
number of pages after evaluating open 2
number of pages after click 2
number of pages after click 1
和window.open()
具有可检测效果之前需要一点时间。
在你的评论中你也写道:
我认为
window.close()
基本上是将异步函数转换为同步函数
我不会说它将异步函数转换为同步函数。它使当前代码等待异步操作的承诺被解决或拒绝。但是,更重要的是,对于此处的问题,问题是您有两个执行JavaScript代码的虚拟机:运行await
的节点和控制浏览器的脚本,以及浏览器本身有自己的JavaScript虚拟机。 您在节点端使用的任何puppeteer
仅影响节点代码:它与浏览器中运行的代码无关。
当您看到await
之类的内容时,会让您感到困惑。它看起来像是一个整体,并且都在同一个虚拟机中执行,但事实并非如此。 await page.evaluate(() => { some code; })
将传递给puppeteer
的参数序列化,并将其发送到执行的浏览器。在.evaluate
之后尝试在上面的脚本中添加await page.evaluate(() => { button.click(); });
之类的内容。像这样:
const button = ...
在脚本中,const button = await tempPage.$("#myButton");
await button.click();
await page.evaluate(() => { button.click(); });
是在button
之前定义的,但在page.evaluate
运行时您将获得ReferenceError
,因为page.evaluate
未定义在浏览器方面!