Featured image of post 自动化测试工具Puppeteer简介

自动化测试工具Puppeteer简介

google出品的Puppeteer是自动化测试的新生势力,被广泛看好

前言

自动化测试发展到如今,各种自动化测试工具层出不穷。Selenium 无疑是其中的佼佼者,从 2016 年至今,全球流行自动化测试工具排行榜中,Selenium 一直占据着最流行工具的头把交椅。selenium 基于 webdriver 协议,以其免费、开源和强大的功能和扩展性,同时因其创立者著名咨询公司 ThoughtWorks 公司的力推,在行业得到了广泛的应用。

但在 ThoughtWorks 公司数年前发布的技术雷达(全球技术趋势的分析指南),在 E2E (端到端测试)领域中,指出了 后 Selenium 时代的一些后起之秀,其中就重点提及了Puppeteer。

We keep receiving positive feedback on “post-Selenium” web UI testing tools such as Cypress, TestCafe and Puppeteer.

We have good experience using “post-Selenium” web UI testing tools such as Cypress, TestCafe and Puppeteer.

虽然 Selenium 是 TW 公司自家产品,但是随着前端技术的发展, Selenium 也呈现出对浏览器依赖度高、对单页面类应用掌控能力不足、基于 WebDriver 协议的通信效率不高等缺点,新一代的测试工具也逐渐进入行业视野。本文我们就一起来了解一下三驾马车中的第一个: Puppeteer

Puppeteer 简介

在介绍 Puppeteer 之前,我们先了解一个背景:headless,也就是无头浏览器。很早之前,在前端测试和爬虫领域就存在无头浏览器的需求,也就是不需要浏览器的界面,纯代码环境下实现浏览器内核操作。特别是 UI 自动化测试以及希望静默完成浏览操作的场景下非常有用。当时最流行的开源 headless 是 PhantomJS,但在 google 开源的 Puppeteer 出现之后,目前 PhantomJS 已经处于终止状态,可以说 Puppeteer 是现在应用最为广泛的 headless。

那么 Puppeteer 就是 headless 吗?官方对 Puppeteer 有一个简单的定义:

Puppeteer is a Node library which provides a high-level API to control Chrome or Chromium over the DevTools Protocol. Puppeteer runs headless by default, but can be configured to run full (non-headless) Chrome or Chromium.

可以看出,Puppeteer 实际上是一个基于CDP协议来控制 Chrome 或 Chromium 的 Node.js 的库,一般用作无头模式,但也能工作在标准模式下。

  • Puppeteer 是 Node.js 库,需在 Node 环境下运行代码
  • 基于 Chrome Devtools protocol
  • 仅可控制 Chrome 或 Chromium
  • 可配置运行模式

Puppeteer 架构

如图是 Puppeteer 的官方架构:

说明:

  • Puppeteer 使用 DevTools Protocol 与浏览器 Browser 进行通信。
  • Browser 浏览器实例可以包含多个浏览器上下文 BrowserContext。
  • BrowserContext 用于保持浏览器 session,一个浏览器上下文可以包含多个页面。
  • Page 一个 Page 最起码包含一个 frame,即 main frame,允许存在其他的 frame,这些frame 可以用 [iframe] 创建。
  • Frame 一个 Frame 最起码有一个 Javascript 执行上下文环境,即默认的执行上下文环境。Frame 允许存在额外附加的上下文环境
  • Worker 存在唯一的上下文环境,并可与 WebWorkers相互协作。

Puppeteer 的安装

因为 Puppeteer 实际是一个 Node 库,所以安装非常简单:

1
> npm install puppeteer

在安装时会同时安装 chromium,但因为众所周知的原因,国内会下载失败。所以建议使用国内源安装即可

1
2
3
> npm install -g cnpm --registry=https://registry.npm.taobao.org

> cnpm install puppeteer

安装后可以使用以下命令检查下安装的版本,确认安装正确

1
> npm ls puppeteer

Hello World

通过一个简单的例子看下 Puppeteer 的基本用法。

因为 Puppeteer 是通过 CDP 协议直接控制浏览器,所以可以实现很多浏览器层面的直接操作,比如下面这段代码,我们控制浏览器 (正常模式) 在访问我的博客网站时弹出一个对话框:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
const puppeteer = require('puppeteer');

HelloWorld();

async function HelloWorld(){
    const browser = await puppeteer.launch({
        headless:false
    });
    const page = await browser.newPage();

    await page.goto('https://chengxiaqiucao.github.io/');

    await page.evaluate(()=>alert('hello world!'));

    await browser.close();
}

具体的 js 语法如 async,await 以及箭头函数等的用法,这里不多介绍了,大家可以先查阅下 js 相关文档。

通过 node.js 执行这个 js 文件,即可运行。

node pupp_sample.js

执行效果:

Puppeteer 的主要特性

前文是一个 Puppeteer 的简单示例。我们可以看到 puppeteer 的基本用法。其实 Puppeteer 作为一个强大的 headless,有很多功能,择要总结如下:

设置浏览器启动参数

在启动浏览器的方法中,我们通过设置一些参数来配置浏览器,常用的参数有:

  • headless: true/false 设置是否启动无头模式

  • devtools: true/false 是否同时启动 devtool

  • slowMo:毫秒数。运行在 slow 模式下,每步骤操作间隔

  • timeout: 毫秒数,设置启动超时时间,默认值 30000,即 30 秒

  • executablePath: 可以使用已安装的 chrome 浏览器替换默认的 chromium 浏览器,此处设置浏览器执行文件路径

  • args:设置 chrome 浏览器支持的配置参数。 支持的详细参数参见:chrome浏览器参数。 比如 args: ["--start-maximized"] 可以设置启动浏览器默认最大化。

还用上文的代码,修改一下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    const browser = await puppeteer.launch({
        headless:false,
        devtools:true,
        slowMo:1000,
        args:["--start-maximized"]
    });

    const page = await browser.newPage();

    await page.goto('https://chengxiaqiucao.github.io/');

    await browser.close();

执行后会运行在 slow 模式下,默认开启了 devtool 并且自动进行了最大化。

模拟设备显示

Puppeteer 还支持设置模拟移动设备界面。通过 page.emulate() 方法可以模拟一些主流的移动设备界面。具体支持的设备清单可以参见 官方提供的清单

用法如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import puppeteer from 'puppeteer';
import {KnownDevices} from 'puppeteer';


 
iphone15();

async function iphone15() {
	const iPhone = KnownDevices['iPhone 15 Pro'];
    const browser = await puppeteer.launch({
        headless:false,
        devtools:true
    });

    const pages = await browser.pages();
    const page = pages[0];
    await page.emulate(iPhone);
    await page.goto('https://chengxiaqiucao.github.io/');
}

执行效果:

除了模拟终端设备的方法。还可以通过 Page.setViewport 来设置分辨率,比如如下语句设置页面采用 1920 * 1080 分辨率

1
2
3
4
await page.setViewport({
  width: 1920,
  height: 1080
});

元素定位

作为一款可进行自动化测试的工具,Puppeteer 自然也需要能够识别页面中的相关元素。 Puppeteer 中可通过 page.$(selector)page.$$(selector) 来获取元素。selector 参数使用Css Selector语法来进行元素定位。css selector 的语法可以参见:css selector。如定位博客页面种中的归档栏目:

1
await page.$('#main-menu > li:nth-child(2) > a')

一般在 chrome 中获取元素的对应 selector 描述,可以打开 chrome dev tools,定位到元素后,右键拷贝即可。

除了使用 selector ,也可以通过 xpath 路径进行识别: page.$x(expression), 如

1
await page.$x('//*[@id="main-menu"]/li[2]/a')

页面操作及键盘输入

自动化测试中最常用的操作自然是驱动页面执行相关的操作。在 Puppeteer 中可以使用相关已封装的方法完成对应操作。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 获取焦点
await page.focus(selector);

//点击元素
await page.click(selector)
await page.tap(selector) //手机端

//输入内容
await page.type(selector, '输入内容')

//直接通过键盘输入
await page.keyboard.type('输入内容')
await page.keyboard.press('KeyA')

获取元素属性

有时候我们会需要获取页面上元素的属性以进行后续操作,可以使用 page.$eval(selector,pagefunction) 实现,如:

1
2
//获取链接地址
const preloadHref = await page.$eval('link[rel=preload]', el => el.href)

截图

Puppeteer 可以通过 page.screenshot() 方法实现页面截图, 有如下一些非常有用的参数:

  • path:指定截图后的文件保存路径

  • type:指定保存的文件类型,jpg或png

  • quality:图片质量,0-100

  • fullpage:是否保存完整的页面

  • clip:指定截图区域 {x,y,width,height}

  • omitBackground:去除白色背景

  • encoding:图片编码,base64 或 binary

代码示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    const browser = await puppeteer.launch({
        headless:false
    });
    const pages = await browser.pages();
    const page = pages[0];

    await page.goto('https://chengxiaqiucao.github.io/');

    await page.screenshot({path: 'screenshot.png',fullpage:true})

    await browser.close();

保存 PDF

Puppeteer 还有一个功能是可以直接将页面保存为 PDF 文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
  const browser = await puppeteer.launch({
        headless:false
    });

  const pages = await browser.pages();
  const page = pages[0];

  await page.goto('https://chengxiaqiucao.github.io/');

  await page.pdf({path: 'blogList.pdf', format: 'A4'});

  await browser.close();

网络请求拦截

Puppeteer 可以对页面的网络请求进行拦截处理。如下例会拦截 博客网站的头像图片加载:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
const browser = await puppeteer.launch({
        headless:false
    });
    const pages = await browser.pages();
    const page = pages[0];

    await page.setRequestInterception(true);
    page.on('request', request => {
        if(request.url().includes('automnGrass'))
            request.abort();
        else
            request.continue();
    });
    await page.goto('https://chengxiaqiucao.github.io/',{timeout:100000})

    await browser.close();

效果如图:

性能指标录制

Puppeteer 另一个非常有用的特性是可以录制下性能跟踪指标,供共享进行性能分析。

使用非常简单,在需要记录性能指标的操作前后,用 page.tracing.start()page.tracing.stop()标记

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
    const browser = await puppeteer.launch({
        headless:false
    });

    const pages = await browser.pages();
    const page = pages[0];

    await page.tracing.start({path: 'trace.json'});

    await page.goto('https://chengxiaqiucao.github.io/');

    await page.tracing.stop();

    await browser.close();

将保存的 trace.json 文件上传到 chrome dev tools 的 performance 栏下,即可进行对应的性能分析。如图:

Puppeteer 结合 Jest 自动化测试实例

现在我们来结合一个具体的 UI 自动化测试案例来看看 Puppeteer 的使用。

测试需求如下:

Case 1:

  • 打开 秋草观"测"台
  • 确认站点加载正确

Case2:

  • 定位归档页面
  • 进入归档栏目

Case3:

  • 获取当前页面的文章清单并输出到控制台

Puppeteer 是一个 node 的无头浏览器控制库,本身并没有包含测试框架功能,所以一般我们会借助其他测试框架来配合进行测试调度、执行、断言。这里以前端测试中常用的框架 Jest 为例,来看看如何用 Puppeteer 完成上述自动化需求。

Jest 的安装和基本使用不是本文重点,这里不再进行详细说明,大家可以自行搜索学习一下,这里简单说明;

1
> npm install jest

要使用 Jest,需要在 项目目录下配置 package.json,包含:

1
2
3
"scripts": {
    "test": "jest"
  }

默认会自动识别以 .test.js 结尾的脚本作为测试脚本。通过 npm run test 驱动执行即可。

以下为具体实现代码,关键处已添加注释,不再另行赘述。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
const puppeteer = require('puppeteer');

let browser, pages, page;

//Jest 默认超时时间为 5s, 太短。修改为 60s,
jest.setTimeout(60000);

// 测试 Setup,总体准备动作
beforeAll(async () => {
    // 关闭浏览器无头模式,并运行在 slow 模式下
    // 每个页面的默认 viewport 设置 1024 * 768
    browser = await puppeteer.launch({
        headless:false,
        slowMo:100,
        defaultViewport:{width: 1024, height: 768}
    });

    //打开新页面
    page = await browser.newPage();

    //设置页面默认超时 60S
    await page.setDefaultTimeout(60000);

});

// 测试 Teardown,总体清理动作
afterAll(async () => {
    await browser.close();
});
  
//每个用例的 setup 准备操作
beforeEach(async () => {
    //todo
});

//每个用例的 Teardown 清理操作
afterEach(() => {
    //todo
});
  
test('Case1:进入博客主页',async ()=>{
    await page.goto('https://chengxiaqiucao.github.io/');
    //验证页面正确加载
    let title = await page.title();
    expect(title).toBe('秋 草 观 “测” 台');
})

test('Case2:访问归档栏目', async ()=>{
	await page.waitForSelector('#main-menu > li:nth-child(2) > a')
    await page.click('#main-menu > li:nth-child(2) > a');
	//等待加载完成
    await page.waitForFunction(() => {
        return document.readyState === 'complete';
      });
})

test('Case3:获取文章清单', async ()=>{
    //css selector 定位到 测试 栏目并打开
   const ArticleTitles = await page.$$eval('h2.article-title', els => Array.from(els).map(el => el.textContent)); 
    console.log(ArticleTitles);

})

执行输出:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
PS C:\> npm run test

> test
> jest

  console.log
    [
      '大话Https协议',
      '软件分支策略和集成模式梳理',
      'Git工作原理和常用指令',
      'Pytest中自动指定可引用fixture',
      '用SeleniumBase制作产品Demo',
      '适合用来自动化测试练手的项目',
      '用Typer快速开发python命令行应用',
      'PostmanV11更新便览',
      'Selenium Manager用法详解',
      '理解敏捷宣言背后的文化逻辑',
      '如何及时掌握各种常用框架的最新变化?',
      '软件测试的目的到底是什么?',
      'Pytest中fixture的范围',
      'Pytest使用Fixture进行参数化及中文乱码问题',
      'Win11操作小技巧二则',
      'Hugo+Github搭建博客小记',
      'AI测试之TestGPT',
      '提 Bug 的艺术',
      'Postman高级篇',
      'Postman自动化篇',
      'Postman进阶篇',
      'Postman基础篇'
    ]

      at Object.log (pupp.test.js:60:13)

 PASS  ./pupp.test.js (14.525 s)
  √ Case1:进入博客主页 (5420 ms)
  √ Case2:访问归档栏目 (4011 ms)
  √ Case3:获取文章清单 (2464 ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        14.578 s, estimated 15 s
Ran all test suites.

结语

以上就是 关于Puppeteer 的基本用法介绍。可以看到 Puppeteer 作为 chrome 官方出品的浏览器控制库,功能强大,可以从浏览器底层完成很多控制操作。无论是 UI 自动化还是作为爬虫,都大有用武之地。直接基于 Node.js 环境运行,安装运行简便。对于针对浏览器前端的 E2E 测试来说,Puppeteer 是一个上佳的选择。

不过 Puppeteer 作为一个控制库,在自动化测试本身的框架构成上比较单一,一般需要和其他框架配合使用。


关注 城下秋草, 测试技术不迷路~~

使用 Hugo 构建
主题 StackJimmy 设计