2017-12-04 4 views
10

Also hier ist der Code-Schnipsel:Seite nicht für eine andere Seite warten, um ihre Aufgaben zu beenden, bevor Sie fortfahren

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 und tempPage sind zwei verschiedene Seiten.

Was passiert ist, dass page 10 Sekunden wartet, dann klickt ein paar Sachen, die eine zweite Seite öffnet.

Was passiert ist, dass tempPage wartet auf ein Element, klickt es, dann sollte Seite 10 Sekunden warten, bevor Sie es noch einmal tun.

Was jedoch tatsächlich passiert ist, dass page 10 Sekunden lang wartet, klickt das Zeug, dann beginnt 10 Sekunden warten, ohne auf tempPage warten, um seine Aufgaben zu beenden.

Ist das ein Fehler, oder verstehe ich etwas falsch? Wie sollte ich das beheben, so dass wenn die for Schleife wieder loops, erst nach tempPage geklickt hat.

Antwort

5

Allgemeinen, können Sie nicht auf await tempPage.click("a.orange") verlassen Ausführung zu pausieren, bis tempPage hat „beenden [ed] seine Aufgaben“. Für super einfachen Code, der synchron ausgeführt wird, kann es funktionieren. Aber im Allgemeinen können Sie sich nicht darauf verlassen.

Wenn der Klick eine AJAX-Operation auslöst oder eine CSS-Animation startet oder eine Berechnung startet, die nicht sofort berechnet werden kann, oder eine neue Seite usw. öffnet, ist das Ergebnis asynchron und die .click Die Methode wartet nicht auf die Beendigung dieses asynchronen Vorgangs.

Was können Sie tun? In einigen Fällen können Sie möglicherweise in den auf der Seite ausgeführten Code eingreifen und auf ein Ereignis warten, das für Sie von Bedeutung ist. Wenn Sie beispielsweise warten möchten, dass eine Ajax-Operation ausgeführt wird und der Code auf der Seite jQuery verwendet, können Sie ajaxComplete verwenden, um festzustellen, wann die Operation abgeschlossen ist. Wenn Sie sich nicht in ein Ereignissystem einklinken können, um festzustellen, wann die Operation abgeschlossen ist, müssen Sie möglicherweise die Seite abfragen, um auf den Nachweis zu warten, dass die Operation ausgeführt wurde. Hier

ist ein Beispiel, das das Problem zeigt:

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

Der setTimeout Code simuliert jede Art von asynchronem Betrieb durch den Klick gestartet.

Wenn Sie diesen Code ausführen, werden Sie auf der Konsole sehen:

before click { clicked: 0, asynchronousResponse: 0 } 
after click { clicked: 2, asynchronousResponse: 0 } 
after wait { clicked: 2, asynchronousResponse: 2 } 

Sie sehen, dass clicked sofort zweimal durch die beiden Klicks erhöht wird. Es dauert jedoch eine Weile, bevor asynchronousResponse inkrementiert wird. Die Anweisung await page.waitForFunction(() => window.asynchronousResponse === 2) fragt die Seite ab, bis die Bedingung erfüllt ist, auf die wir warten.


Sie haben in einem Kommentar erwähnt, dass die Schaltfläche die Registerkarte schließt. Öffnen und Schließen von Registerkarten sind asynchrone Vorgänge.Hier ein Beispiel:

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

Wenn ich die oben laufen, erhalte ich:

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 

Sie können sehen, es dauert ein wenig, bevor window.open() und window.close() haben nachweisbare Effekte.


In Ihrem Kommentar schrieb auch:

Ich dachte await war im Grunde, was eine asynchrone Funktion in ein einer synchronen gedreht

würde ich nicht sagen, dass es asynchrone Funktionen in synchronem dreht Einsen. Der aktuelle Code wartet darauf, dass die Zusage einer asynchronen Operation aufgelöst oder zurückgewiesen wird. Wichtiger für das hier vorliegende Problem ist jedoch, dass Sie zwei virtuelle Maschinen haben, die JavaScript-Code ausführen: Es gibt einen Knoten, auf dem puppeteer läuft, und das Skript, das den Browser steuert, und es gibt den Browser selbst, der über eine eigene virtuelle JavaScript-Maschine verfügt. Alle await, die Sie auf der Node-Seite verwenden, wirken sich nur auf den Node-Code aus: Sie haben keinen Einfluss auf den Code, der im Browser ausgeführt wird.

Es kann verwirrend werden, wenn Sie Dinge wie await page.evaluate(() => { some code; }) sehen. Es sieht so aus, als ob es aus einem Stück besteht und alle in derselben virtuellen Maschine ausgeführt werden, aber das ist es nicht. puppeteer nimmt den übergebenen Parameter an .evaluate, serialisiert es und sendet es an den Browser, wo es ausgeführt wird. Versuchen Sie, im obigen Skript nach const button = ... etwas wie await page.evaluate(() => { button.click(); }); hinzuzufügen. Etwas wie folgt aus:

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

Im Skript wird button vor page.evaluate definiert, aber Sie werden ein ReferenceError wenn page.evaluate läuft, weil button nicht auf der Browser-Seite definiert erhalten!

Verwandte Themen