页面不等待另一个页面完成他们的任务才继续

page does not wait for another page to finish their tasks before continuing

下面是代码片段:

    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++
    }

pagetempPage 是两个不同的页面。

发生的事情是 page 等待 10 秒,然后点击一些东西,打开第二个页面。

应该发生的是 tempPage 等待一个元素,点击它,然后页面应该等待 10 秒,然后再重新开始。

然而,实际发生的情况是 page 等待 10 秒,单击该内容,然后开始等待 10 秒,而不等待 tempPage 完成其任务。

这是一个错误,还是我误会了什么?我应该如何解决这个问题,以便当 for 循环再次循环时,只有在 tempPage 单击之后。

一般,你不能依靠await tempPage.click("a.orange")暂停执行,直到tempPage有"finish[ed] its tasks"。对于同步执行的超简单代码,它可能会起作用。但总的来说,你不能依赖它。

如果点击触发Ajax操作,或启动CSS动画,或启动无法立即计算的计算,或打开新页面等,那么结果你are waiting for 是异步的,.click 方法不会等待这个异步操作完成。

你能做什么?在某些情况下,您可以挂接到页面上 运行ning 的代码并等待对您重要的事件。例如,如果您想等待 Ajax 操作完成并且页面上的代码使用 jQuery,那么您可以使用 ajaxComplete 来检测操作何时完成。如果您无法连接到任何事件系统来检测操作何时完成,那么您可能需要轮询页面以等待操作完成的证据。

这是一个说明问题的示例:

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() 需要一些时间才能检测到效果。


在您的评论中您还写道:

I thought await was basically what turned an asynchronous function into a synchronous one

我不会说它将异步函数变成同步函数。它使当前代码等待异步操作的承诺被解决或拒绝。然而,对于这里手头的问题更重要的是,问题是你有两个虚拟机执行 JavaScript 代码:运行s puppeteer 的 Node 和控制浏览器的脚本,还有浏览器本身,它有自己的 JavaScript 虚拟机。 您在节点端使用的任何 await 仅影响节点代码:它与浏览器中 运行 的代码无关。

当您看到 await page.evaluate(() => { some code; }) 之类的内容时,可能会感到困惑。看起来它是一体的,并且都在同一个虚拟机中执行,但事实并非如此。 puppeteer 获取传递给 .evaluate 的参数,对其进行序列化,并将其发送到浏览器,并在浏览器中执行。尝试在上面的脚本中 const button = ... 之后添加类似 await page.evaluate(() => { button.click(); }); 的内容。像这样:

const button = await tempPage.$("#myButton");
await button.click();
await page.evaluate(() => { button.click(); });

在脚本中,button 定义在 page.evaluate 之前,但是当 page.evaluate 运行 时你会得到一个 ReferenceError 因为 button 没有在浏览器端定义!