前端神器Puppeteer初试

285 阅读4分钟

「这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战

Puppeteer是什么

谷歌浏览器在17年自行开发了Chrome Headless特性,并与之同时推出了Puppeteer。Puppeteer 是一个 Node 库,可以通过它所提供的 API 来控制Chromium 或 Chrome,所以可以理解成我们日常使用的Chrome的无界面版本以及对其进行操控的js接口套装。

什么是 Headless Chrome

Headless Chrome 是 Chrome 浏览器的无界面形态,可以在不打开浏览器的前提下,使用所有 Chrome 支持的特性运行你的程序。相比于现代浏览器,Headless Chrome 更加方便测试 web 应用,获得网站的截图,做爬虫抓取信息等。相比于出道较早的 PhantomJS,SlimerJS 等,Headless Chrome 则更加贴近浏览器环境。

Puppeteer的作用

  • 网页截图,生成页面 PDF。
  • 抓取 SPA(单页应用)并生成预渲染内容(即“SSR”(服务器端渲染))。
  • 自动提交表单,进行 UI 测试,键盘输入等。
  • 创建一个时时更新的自动化测试环境。 使用最新的 JavaScript 和浏览器功能直接在最新版本的Chrome中执行测试。
  • 捕获网站的 timeline trace,用来帮助分析性能问题。
  • 测试浏览器扩展。

安装

当你安装 Puppeteer 时,它会下载最新版本的Chromium

npm i puppeteer
or
yarn add puppeteer

自 1.7.0 版本以来,官方都会发布一个 puppeteer-core 包,是一个的轻量级的 Puppeteer 版本,用于启动现有浏览器安装或连接到远程安装。

npm i puppeteer-core
or 
yarn add puppeteer-core

简单使用

使用Browser 创建 Page 的例子

当 Puppeteer 连接到一个 Chromium 实例的时候会通过 puppeteer.launch 或 puppeteer.connect 创建一个 Browser 对象

const puppeteer = require('puppeteer');
    puppeteer.launch().then(async browser => {
    const page = await browser.newPage();
    await page.goto('https://example.com');
    await browser.close();
});

通过 node 运行这个文件后,会看不到效果,因为是 headless 无界面模式。

页面导航

  • page.goto:打开新页面
  • page.goBack :回退到上一个页面
  • page.goForward :前进到下一个页面
  • page.reload :重新加载页面 为了方便我们观察,我们关闭 headless 无界面模式,且放慢执行速度
// puppeteer.js
const puppeteer = require("puppeteer");

(async () => {
  const browser = await puppeteer.launch({
    headless: false, //有浏览器界面启动
    slowMo: 200,
    defaultViewport: { width: 1400, height: 900 },
    args: ["–no-sandbox", "--window-size=1400,900"], //全屏打开页面
  });
  const page = await browser.newPage();
  await page.goto("https://www.baidu.com");
  await page.goto("https://www.zhihu.com/");
  await page.goBack(); // 回退
  await page.goForward(); // 前进
  await page.reload(); // 刷新
  await page.close();
  await browser.close();
})();

使用 node puppeteer.js 执行后,会跳出浏览器界面,先访问 baidu 再访问 zhihu,再进行后退和前进操作,然后刷新,最后关闭页面和浏览器。

应用

创建pdf

以下代码先跳转到百度页面,然后进行截图,生成一个叫 hn.pdf 的pdf文件

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://www.baidu.com', {waitUntil: 'networkidle2'});
  await page.pdf({path: 'hn.pdf', format: 'A4'});

  await browser.close();
})();

打开pdf文件 image.png

截图

截图如果要对整个页面进行截图,则使用page.screenshot即可,如果是对其中某个元素进行截图,则需要先通过page.$()获取到这个元素element,再执行element.screenshot

// puppeteer.js
const puppeteer = require("puppeteer");

(async () => {
  const browser = await puppeteer.launch({
    headless: false, //有浏览器界面启动
    defaultViewport: { width: 1400, height: 900 },
    args: ["--start-fullscreen"], //全屏打开页面
  });

  const page = await browser.newPage();
  await page.goto("https://juejin.cn/", { waitUntil: "networkidle0" });
  //对整个页面截图
  await page.screenshot({
    path: "./temp/capture.png", //图片保存路径
    type: "png",
    fullPage: true, //边滚动边截图
    // clip: {x: 0, y: 0, width: 1920, height: 800} // 指定裁剪区域
  });
  //对页面某个元素截图
  let element = await page.$('.logo');
  await element.screenshot({
      path: './temp/element.png'
  });
  await page.close();
  await browser.close();
})();

在页面中执行脚本

想要在页面中执行脚步通过 page.evaluate(pageFunction, [...args]),他会返回pageFunction 执行的结果

const puppeteer = require('puppeteer');

(async () => {
  // const browser = await puppeteer.launch();
  const browser = await puppeteer.launch({
    headless: false, //有浏览器界面启动
    defaultViewport: { width: 1400, height: 900 },
    args: ["--start-fullscreen"], //全屏打开页面
  });
  const page = await browser.newPage();
  await page.goto('https://juejin.cn');

  const dimensions = await page.evaluate(() => {
    alert(1); // 弹窗
    return {
      width: document.documentElement.clientWidth,
      height: document.documentElement.clientHeight,
      deviceScaleFactor: window.devicePixelRatio
    };
  });

  console.log('Dimensions:', dimensions);
  await page.close();
  await browser.close();
})();

这段代码的效果是:打开 juejin 然后执行 page.evaluate 传入的函数,因为这个函数执行了 alert(1),所以会有弹窗弹出,最后在命令行中打印出返回的数据Dimensions。

pageFunction 是运行再所打开页面的上下文中,所以我们可以在里面做一些很灵活的操作来测试我们的页面。除了page.evaluate方法,类似的还有page.evaluateOnNewDocument方法。

页面交互

这个功能非常实用,我再进行自动化测试时,肯定会对页面进行一些交互,主要用的API:

// login
const puppeteer = require("puppeteer");

(async () => {
  const browser = await puppeteer.launch({
    headless: false, //有浏览器界面启动
    slowMo: 100,
    defaultViewport: { width: 1400, height: 900 },
    args: ["--start-fullscreen"], //全屏打开页面
  });

  const page = await browser.newPage();
  await page.goto("https://juejin.cn/");

  await page.waitForSelector("button.login-button");
  const showLoginModalBtnEle = await page.$("button.login-button");
  await showLoginModalBtnEle.click();
  // 登录弹窗已渲染
  await page.waitForSelector("form.auth-form");

  // 点击 其他登录方式
  const otherLoginTypeEle = await page.$("span.clickable")
  await otherLoginTypeEle.click()

  // 输入账号
  const accountInputEle = await page.waitForSelector("input[name='loginPhoneOrEmail']");
  await accountInputEle.type("ceshi@qq.com", { delay: 20 });

  // 输入密码
  const pwdInputEle = await page.$("input[name='loginPassword']");
  await pwdInputEle.type("woshimima111", { delay: 20 });

  // 点击 登录 按钮
  const submitBtnEle = await page.$("form.auth-form .btn");
  await submitBtnEle.click();

  await page.waitForTimeout(2000);

  await page.close();
  await browser.close();
})();

这段代码执行后他就会自动弹窗登录框,切换到密码登录,然后输入账号和密码,最后点击登录

模拟器

模拟iPhone 6设备,打开淘宝

const puppeteer = require("puppeteer");
const iPhone = puppeteer.devices["iPhone 6"]; // puppeteer.devices内置大量设备的预设定值

//使用 puppeteer.launch 启动 Chrome
(async () => {
  const browser = await puppeteer.launch({
    headless: false, //有浏览器界面启动
  });
  const page = await browser.newPage();
  await page.emulate(iPhone);
  await page.goto("https://www.taobao.com", { waitUntil: "networkidle0" });
 
  await browser.close();
})();

总结

通过 Puppeteer 我们可以进行页面自动化性能测试,目前已经有了很多基于 puppeteer 的项目,比如:jest-puppeteermocha-headless-chromeexpect-puppeteer 等等