Puppeteer:如何处理多个标签?

时间:2017-08-21 23:05:15

标签: node.js automated-tests google-chrome-headless puppeteer

场景:开发人员应用注册的Web表单,包含两部分工作流程。

第1页:填写开发者应用详细信息并单击按钮以在新标签页中创建应用程序ID ...

第2页:App ID页面。我需要从此页面复制App ID,然后关闭选项卡并返回第1页并填写App ID(从第2页保存),然后提交表单。

我了解基本用法 - 如何打开第1页并单击打开第2页的按钮 - 但如何在新标签页面中打开第2页处理?

示例:

const puppeteer = require('puppeteer');

(async() => {
    const browser = await puppeteer.launch({headless: false, executablePath: '/Applications/Google Chrome.app'});
    const page = await browser.newPage();

    // go to the new bot registration page
    await page.goto('https://register.example.com/new', {waitUntil: 'networkidle'});

    // fill in the form info
    const form = await page.$('new-app-form');

    await page.focus('#input-appName');
    await page.type('App name here');

    await page.focus('#input-appDescription');
    await page.type('short description of app here');

    await page.click('.get-appId'); //opens new tab with Page 2

    // handle Page 2
    // get appID from Page 2
    // close Page 2

    // go back to Page 1
    await page.focus('#input-appId');
    await page.type(appIdSavedFromPage2);

    // submit the form
    await form.evaluate(form => form.submit());

    browser.close();
})();

更新2017-10-25

仍在寻找一个好的用法示例。

8 个答案:

答案 0 :(得分:11)

这将适用于最新的alpha分支:

const newPagePromise = new Promise(x => browser.once('targetcreated', target => x(target.page())));
await page.click('my-link');
// handle Page 2: you can access new page DOM through newPage object
const newPage = await newPagePromise;
await newPage.waitForSelector('#appid');
const appidHandle = await page.$('#appid');
const appID = await page.evaluate(element=> element.innerHTML, appidHandle );
newPage.close()
[...]
//back to page 1 interactions

请务必将 package.json 依赖项设置为

,以使用最后一个puppeteer版本(来自Github master分支)
"dependencies": {
    "puppeteer": "git://github.com/GoogleChrome/puppeteer"
},

资料来源:JoelEinbinder @ https://github.com/GoogleChrome/puppeteer/issues/386#issuecomment-343059315

答案 1 :(得分:6)

两天前提交了一个新补丁,现在您可以使用browser.pages()访问当前浏览器中的所有页面。 工作正常,昨天尝试了自己:)

编辑:

如何将新页面的JSON值打开为“target:_blank”链接的示例。

const page = await browser.newPage();
await page.goto(url, {waitUntil: 'load'});

// click on a 'target:_blank' link
await page.click(someATag);

// get all the currently open pages as an array
let pages = await browser.pages();

// get the last element of the array (third in my case) and do some 
// hucus-pocus to get it as JSON...
const aHandle = await pages[3].evaluateHandle(() => document.body);

const resultHandle = await pages[3].evaluateHandle(body => 
  body.innerHTML, aHandle);

// get the JSON value of the page.
let jsonValue = await resultHandle.jsonValue();

// ...do something with JSON

答案 2 :(得分:3)

根据Official Documentation

  

browser.pages()

     
      
  • 返回:< Promise < Array < Page >>> Promise解析为所有打开页面的数组。不可见的页面,例如"background_page",将不在此处列出。您可以使用target.page()找到它们。
  •   
     

浏览器中所有页面的数组。如果存在多个浏览器上下文,则该方法将返回一个数组,其中包含所有浏览器上下文中的所有页面。

用法示例:

let pages = await browser.pages();
await pages[0].evaluate(() => { /* ... */ });
await pages[1].evaluate(() => { /* ... */ });
await pages[2].evaluate(() => { /* ... */ });

答案 3 :(得分:2)

理论上,您可以覆盖window.open功能以始终打开&#34;新标签&#34;在您当前的页面上并通过历史记录导航。

您的工作流程将是:

  1. 覆盖window.open功能:

    await page.evaluateOnNewDocument(() => {
      window.open = (url) => {
        top.location = url
      }
    })
    
  2. 转到第一页并执行一些操作:

    await page.goto(PAGE1_URL)
    // ... do stuff on page 1
    
  3. 点击按钮导航到第二页并在那里执行一些操作:

    await page.click('#button_that_opens_page_2')
    await page.waitForNavigation()
    // ... do stuff on page 2, extract any info required on page 1
    // e.g. const handle = await page.evaluate(() => { ... })
    
  4. 返回首页:

    await page.goBack()
    // or: await page.goto(PAGE1_URL)
    // ... do stuff on page 1, injecting info saved from page 2
    
  5. 显然,这种方法有其缺点,但我发现它大大简化了多标签导航,如果你已经在多个标签上运行并行作业,这种方法尤其有用。不幸的是,目前的API并不是一件容易的事。

答案 4 :(得分:2)

您可以删除切换页面的必要性,以防由target="_blank"属性引起 - 通过设置target="_self"

示例:

element = page.$(selector)

await page.evaluateHandle((el) => {
        el.target = '_self';
 }, element)

element.click()

答案 5 :(得分:1)

如果您的点击操作正在发出网页加载,那么正在运行的所有后续脚本都会丢失。要解决此问题,您需要触发操作(在本例中为单击),但 await。相反,请等待页面加载:

page.click('.get-appId');
await page.waitForNavigation();

这将允许您的脚本在继续进一步操作之前有效地等待下一个pageload事件。

答案 6 :(得分:0)

你目前无法 - 跟随https://github.com/GoogleChrome/puppeteer/issues/386知道何时将这种能力添加到木偶戏中(希望很快)

答案 7 :(得分:0)

it looks like there's a simple 'page.popup' event

  

对应于“弹出”窗口的页面     页面打开新标签或窗口时发出。

const [popup] = await Promise.all([
  new Promise(resolve => page.once('popup', resolve)),
  page.click('a[target=_blank]'),
]);
const [popup] = await Promise.all([
  new Promise(resolve => page.once('popup', resolve)),
  page.evaluate(() => window.open('https://example.com')),
]);

credit to this github issue for easier 'targetcreated'