[TOC]
Puppeteer是一个通过DevTools Protocol控制headless chromium的高级node库, 也可通过设置设置非headless Chromium.
Puppeteer有chrome官方团队进行维护, 相对于其他如PhantomJs, CasperJs 前景更好.
- 爬虫
- 前端自动化测试(表单操作、事件模拟、键盘输入... 等)
建议使用新版本node.js (8+ 支持async/await 用法).
npm i -S puppeteer //建议最好使用npm进行安装.
顺利安装好会在 ./node_modules/puppeteer/.local-chromium 中 若是不成功, 根据提示设置环境变量PUPPETEER_SKIP_CHROMIUM_DOWNLOAD,也可设置到npmrc中下载不包含chromium的 puppeteer. 自行下载chromium。
手动下载地址 (需翻墙….) 根据自己的系统选择对应的版本下载.
注意:
- v64版本以下的chromium Puppeteer支持的不好
- 若是手动下载的运行时在设置中需指定Chromium位置.
const browser = await puppeteer.launch({ // 指定chromium路径 若是自己下载的需要指定下载路径. executablePath: '/Applications/Chromium.app/Contents/MacOS/Chromium', // true不会打开浏览器. headless: false });
本来想用puppeteer + mocha + chai 直接写自动化测试的, 结果发现 mocha中执行puppeteer不能打开浏览器, 所以puppeteer-autotest 中添加了两个概念, 流程和步骤。puppeteer-autotest, 在一个流程中可以队列执行步骤.
流程由多个步骤组成.
// 正常流程.
const Tools = require('../../utils/tools');
const moment = require('moment');
const fs = require('fs');
const path = require('path');
// 引入流程步骤.
const loginCnodejs = require('./steps/loginCnodejs');
const createTopic = require('./steps/createTopic');
const replyComment = require('./steps/replyComment');
// 日志路径
const logPath = path.resolve(path.dirname(path.dirname(__dirname)) + path.sep + 'logs' + path.sep + moment().format('YYYYMMDDHHmmss'));
// 若文件夹不存在创建日志文件夹.
if (!fs.existsSync(logPath)) {
fs.mkdirSync(logPath);
}
process.env.logPath = logPath;
// 流程名称
const procedureTitle = 'cnodejs 页面测试.';
// 流程开始url.
const procedureBeginUrl = 'https://cnodejs.org/';
// 流程队列
let step = [
loginCnodejs,
createTopic,
replyComment
];
// 从第一个步骤开始执行.
let index = 0;
module.exports = async function () {
return new Promise((resolve, reject) => {
// 这不是一个restful的测试.
let isRestful = false;
// 开始一个流程
Tools.beginProcedure(procedureTitle, procedureBeginUrl, isRestful, (page, isRestful) => {
// 队列执行一个流程.
Tools.runStep(step, 0, procedureTitle, page, isRestful, () => {
item.stepCallback(page);
});
resolve();
});
});
};
步骤应该保证一个步骤中只干一件事情(包括 正常及异常 步骤).
####示例:
const Config = require('../../../config/config'); //引入配置项, 其中包含 browser和page.
const { timeout, log } = require('../../../utils/tools'); // 引入工具类.
// 定义一个步骤. 登录Cnodejs.
class loginCnodejs {
// 设置步骤标题及步骤码.
static getConfig() {
// 设置步骤标题及步骤码.
return {
title: '登录 - cnodejs',
code: 1.1
}
}
// 步骤事件. (具体步骤干啥..)
static async stepCallback(page, next) {
// 执行loginCnodejs步骤.
// 执行下一个步骤.
next && next();
}
};
module.exports = loginCnodejs;
- 设置浏览器.
const browser = await puppeteer.launch({
// 若是手动下载的chromium需要指定chromium地址, 默认引用地址为 /项目目录/node_modules/puppeteer/.local-chromium/
executablePath: '/Applications/Chromium.app/Contents/MacOS/Chromium',
timeout: 15000, //设置超时时间,
ignoreHTTPSErrors: true, // 如果是访问https页面 此属性会忽略https错误,
devtools: true, // 打开开发者工具, 当此值为true时, headless总为false,
headless: false, // 关闭headless模式, 不会打开浏览器,
})
- 打开一个新标签页
const page = browser.newPage();
- 页面跳转
await page.goto('https://cnodejs.com'); //如果页面跳转其中一些资源加载超时会报错.
// 建议这样写可避免因为promise所报的超时错误.
await page.goto('https://cnodejs.com', {timeout: 3000}).then( () => {
console.log('跳转成功并且资源正确加载完毕.');
}, () => {
console.log('跳转成功, 资源加载超时.');
});
- 设置浏览器参数,
const devices = require('puppeteer/DeviceDescriptors');
// 设置测试机型.
const testDevice = devices['iPhone 6'];
await page.emulate(testDevice);
- 获取页面标题
let title = await page.title();
- 获取焦点
await page.focus('#elementId');
- 点击按钮
await page.click('#elementId');
await page.tap('#elementId'); //用于手机端
- 获取DOM
let el = await page.$('#elementId'); // 相当于document.querySelector('#elementId');
await el.click(); // 执行点击事件
let list = await page.$$('.someclass'); // 相当于document.querySelectorAll('.someClass');
- 获取DOM属性
let inputValue = await page.$eval('#input', el => el.value);
- 文本输入
await page.type('#inputId', '输入内容.');
await page.type('#inputId', '输入内容.', { delay: 200 }); // 设置输入间隔200ms(拟人输入);
- evaluate
let result = await page.evaluate( () => {
// 获取一个元素, 这里可直接使用js
return document.getElementById('testEl').getAttribute('anyAttr');
})
console.log(result); // testEl的anyAttr属性值.
- waitFor 等待(元素或者函数或者一个延迟毫秒)
await page.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]]);
- 设置页面样式
await page.mainFrame().addStyleTag({
content: '* { background-color: red; }';
});
- 屏幕截图
await page.screenshot({
path: 'some/to/path.png',
fullPage: true // 全屏截取, 默认false.
});
- 发送请求. 在浏览器中使用fetch发送请求.
// 向http://example.com发送一个POST请求, 请求参数为 {a:1, b:2};
let result = await page.evaluete( async (options) => {
return await fetch('http://example.com', {
method: 'POST', //
header: {
'Content-Type': 'application/json; charset=utf-8'
},
body: JSON.stringify(options)
})
.then(response => response.json())
.then(data => data)
.fail( e => console.log('Oops, error', e));
}, { a: 1, b: 2});
console.log(result); //接口返回值.
- 发送请求. 通过puppeteer的page.on('request'); 进行拦截. 使用goto进行触发请求.
await page.setRequestInterceptionEnabled(true);
page.on('request', request => {
const overrides = {};
// 如果是需要请求的地址.
if (request.url === 'http://www.google.com') {
//设置请求方式.
overrides.method = 'POST';
//设置参数
overrides.postData = 'a=b&c=d';
}
request.continue(overrides);
});
let response = await page.goto('http://www.google.com');
response.json();// 将response.body 转成json。
response.ok(); // 返回一个boolean值 如果状态码为200-299则为true, 其他则为false.
response.status; // 返回状态码
response.text(); // 返回 response body.
response.headers // 返回 HTTP headers
- 关闭页面中的弹框
const puppeteer = require('puppeteer');
puppeteer.launch().then(async browser => {
const page = await browser.newPage();
// 当页面中有弹框调起的时候触发.
page.on('dialog', async dialog => {
console.log(dialog.message()); //打印弹框的内容
await dialog.dismiss(); //关闭弹框
});
page.evaluate(() => alert('1')); // 浏览器中执行 alert('1');
});
- 页面在跳转的时候经常资源加载超时, 同样也是获取一些用户头像时候网络超时.
- 在发布话题和回复话题中的编辑器…
查看DOM结构这里的回复是表单提交 但是 将textarea.editor[name="r_content"]中设置值依旧提醒回复内容不能为空... 有点绝望.
到这里...... 通过页面的点击方法为cnodejs添加自动测试失败~
还有 Mac版本的 chromium 不强行给页面所有元素设置字体 有情况会变成 框框...
就算设置了也有可能有些位置会变成框框...
有哪位老哥找到方法了告诉下...
完成项
-
可以用户登录cnodejs
-
创建话题不能在编辑其中模拟输入内容. 失败. -
回复话题不能在编辑其中模拟输入内容. 失败.
效果图
- 发布话题的时候, js校验只校验了板块选项卡, 而标题内容的校验都由服务端校验的. 这里的表单内容js全部校验一次 是不是可以缓解下服务端的压力?
- 同样的问题也出现在了回复话题中, 对于未填写回复内容的时候的校验也是由服务端校验的.
- 在procedure 下新建文件夹restfulTest
- restfulTest中新建index.js 用来管理这个流程
- restfulTest中新建steps文件夹 该文件夹中存放所有的流程.
完成项
- 校验accessToken. checkAccessToken.
- 获取话题 getTopic
- 新建话题 postTopic
- 修改话题 updateTopic
https://juejin.im/entry/59ad6c4f5188250f4850dccc
https://github.com/zhentaoo/puppeteer-deep
https://segmentfault.com/a/1190000011627343
http://www.cnblogs.com/dolphinX/p/7715268.html
https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md