2017-12-04 4 views
10

を続行する前に自分のタスクを完了するために別のページを待たない:ページには、だからここにコードスニペットだ

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は、2つの異なるページです。

何が起こるかは、pageが10秒間待ってから、何かをクリックすると2ページ目が開きます。

tempPageは要素を待機してクリックしてから、ページを10秒間待ってからもう一度やり直す必要があります。

実際にはpageが10秒間待機してから何かをクリックしてから、tempPageがタスクを完了するのを待たずに10秒間待機します。

これはバグですか、何かを誤解していますか? forループが再びループしたときには、tempPageがクリックされた後になるように、これを修正する必要があります。

答えて

5

は一般的に、あなたは「[編]そのタスクを完了」しているtempPageまで実行を一時停止するawait tempPage.click("a.orange")に頼ることはできません。同期的に実行されるスーパーシンプルコードの場合、動作する可能性があります。しかし、一般的に、あなたはそれに頼ることはできません。

クリックがAjax操作をトリガしたり、CSSアニメーションを開始したり、すぐに計算できない計算を開始したり、新しいページなどを開くと、待機中の結果は非同期であり、.clickメソッドは、この非同期操作が完了するのを待機しません。

あなたは何ができますか?場合によっては、ページ上で実行されているコードにフックして、あなたにとって重要なイベントを待つことができます。例えば、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がすぐに2回のクリックによって2回インクリメントされていることがわかります。ただし、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()が検出可能な効果を持っている前に、それは少し時間がかかる見ることができます。

を私はawaitが、私はそれが同期に非同期機能をオン言わないだろう同期1

に非同期関数を回したものを基本的に思った:あなたも書いたあなたのコメントで


もの。現在のコードは、非同期操作の約束が解決または拒否されるのを待ちます。しかし、ここで問題となっている重要な点は、JavaScriptコードを実行する2つの仮想マシンがあることです。つまり、puppeteerを実行するノードとブラウザを制御するスクリプトがあり、独自の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(); }); 

buttonpage.evaluate前に定義されていますが、buttonは、ブラウザ側で定義されていないため、あなたはReferenceErrorときpage.evaluateランを買ってあげます!