概述
Puppeteer
是由Google
开发的Node.js
库,用于通过开发者工具协议控制无头 Chrome
和 Chromium
。它允许您自动化 UI 测试、网页抓取、截图测试等操作。
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('<https://example.com>');
await page.screenshot({path: 'example.png'});
await browser.close();
})();
启动浏览器
启动一个无头浏览器实例:
const browser = await puppeteer.launch();
启动完整版本的 Chrome:
const browser = await puppeteer.launch({
headless: false
});
使用自定义参数启动浏览器:
const browser = await puppeteer.launch({
args: ['--start-maximized']
});
自定义启动选项:
puppeteer.launch({
executablePath: '/path/to/Chrome', // 自定义 Chrome 二进制文件路径
product: 'firefox' // 启动 Firefox
});
创建页面
创建新页面:
const page = await browser.newPage();
创建匿名页面:
const context = await browser.createIncogniteBrowserContext();
const page = await context.newPage();
访问已有页面:
const pages = await browser.pages();
const page = pages[0];
标签
在标签之间切换/将它们置于前台:
await page1.bringToFront();
await page2.bringToFront();
操作
导航到 URL:
await page.goto('https://example.com');
点击元素:
await page.click('#element');
输入内容:
await page.type('#input', 'Text');
按键盘键:
await page.keyboard.press('Shift');
上传文件:
await page.setInputFiles('#upload', ['/path/to/file1', '/path/to/file2']);
在页面上执行 JavaScript 代码:
const result = await page.evaluate(() => {
return document.querySelector('#result').textContent;
});
悬停在元素上:
await page.hover('#element');
捕获截图:
await page.screenshot({path: 'screenshot.png'});
模拟移动设备:
await page.emulate(puppeteer.devices['iPhone 6']);
滚动至视图:
await page.evaluate(el => el.scrollIntoView(), await page.$('.item'));
在 iframe 中输入:
const frame = page.frames().find(f => f.name() === 'frame');
await frame.$eval('#input', el => el.value = 'Text');
在移动设备上点击元素:
await page.touchscreen.tap(200, 75);
触发拖放:
await page.mouse.down();
await page.mouse.move(0, 100);
await page.mouse.up();
选择器
通过 CSS 选择器获取元素:
const nav = await page.$('nav');
获取多个元素:
const items = await page.$$('.item');
使用 XPath 选择器:
const button = await page.$x('//*[@id="button"]');
获取文本内容:
const text = await page.textContent('.results');
高级选择器
使用文本选择器:
const link = await page.$('a:text("Next")');
可见性选择器:
const hidden = await page.$('.element:hidden');
属性选择器:
const checkbox = await page.$('input[type="checkbox"]');
XPath 选择器:
const submit = await page.$x('//button[@type="submit"]');
通过文本内容获取:
const p = await page.$eval('p', el => el.innerText === 'Hello');
查询 Shadow DOM:
const shadow = await page.$('.element/shadow-root');
const text = await shadow.$eval('.text', el => el.textContent);
辅助测试
检查问题:
const issues = await page.accessibility.audit({
runA11yChecks: true
});
expect(issues).toHaveLength(0);
检查颜色对比度:
const contrastratio = await page.$eval('.button', button => {
const bgColor = window.getComputedStyle(button).backgroundColor;
// 计算对比度比率
});
expect(contrastratio).toBeGreaterThan(4.5);
选项卡焦点顺序:
await page.keyboard.press('Tab');
const active = await page.evaluate(() => document.activeElement.id);
expect(active).toBe('username');
调试与报告
跟踪控制台错误:
page.on('console', msg => {
if (msg.type() === 'error') {
console.error(msg.text());
}
});
生成 HTML 报告:
const html = '<h1>测试报告</h1>';
fs.writeFileSync('report.html', html);
跟踪测试覆盖率:
const coverage = await page.coverage.startJSCoverage();
// 运行测试
const results = await coverage.stopJSCoverage();
等待
等待导航:
await page.waitForNavigation();
等待选择器:
await page.waitForSelector('div.loaded');
等待固定时间:
await page.waitFor(1000); // 等待 1 秒
等待函数结果:
await page.waitForFunction(() => window.fetchDone);
等待 XHR 请求:
await page.waitForRequest(request => request.url() === 'data.json');
带超时的导航:
await page.waitForNavigation({timeout: 60000});
元素等待 30 秒:
await page.waitForSelector('.item', {timeout: 30000});
框架
获取页面框架:
const frames = page.mainFrame().childFrames();
设置当前框架:
const frame = page.frames().find(f => f.name() === 'frame');
await frame.evaluate(() => {
// 在框架内运行代码
});
输入
获取元素的 HTML/文本/属性:
const html = await page.$eval('.item', el => el.outerHTML);
const text = await page.$eval('.item', el => el.innerText);
const class = await page.$eval('.item', el => el.getAttribute('class'));
填写并提交表单:
await page.type('#input', 'Text');
await page.click('#submitButton');
采样
对元素截图:
const el = await
page.$('.element');
await el.screenshot({path: 'element.png'});
模拟设备和视口:
const devices = puppeteer.devices;
const iPhone = devices['iPhone 6'];
await page.emulate(iPhone);
await page.setViewport(iPhone.viewport);
获取资源定时数据:
const metrics = await page.metrics();
const requests = metrics.requestfinished;
生成 PDF 报告:
await page.pdf({
path: 'page.pdf',
format: 'A4'
});
横向定向 PDF:
await page.pdf({
path: 'page.pdf',
landscape: true
});
事件
页面加载事件:
page.once('load', () => {
// 页面完全加载
});
网络请求失败事件:
page.on('requestfailed', request => {
console.log(request.url + ' ' + request.failure().errorText);
});
控制台消息事件:
page.on('console', msg => {
console.log(`${msg.type()} ${msg.text()}`);
});
认证
设置用户代理:
await page.setUserAgent('CustomAgent');
设置自定义标头:
await page.setExtraHTTPHeaders({
'Accept-Language': 'en-US'
});
设置 cookies:
等待页面设置cookie
:
await page.setCookie({name: 'session', value: '1234'});
设置凭据:
await page.authenticate({
username: 'user',
password: 'pass'
});
设置绕过 CSP:
await browser.launch({ignoreHTTPSErrors: true});
使用代理服务器:
await page.authenticate({username: 'user', password: 'pass'});
网络
禁用缓存:
await page.setCacheEnabled(false);
设置节流率:
await page.setRequestInterception(true);
page.on('request', request => {
request.continue({
throttling: 0.5 // 慢 50%
});
});
模拟响应:
page.on('request', interceptedRequest => {
interceptedRequest.respond({
contentType: 'text/html',
body: '<html>模拟页面</html>'
});
});
模拟重定向响应:
await page.route('**/*', route => {
route.continue({ url: '/mock-page.html' });
});
模拟 404 状态:
page.on('request', route => {
route.abort('notfound');
});
高级用法
等待更复杂的条件:
// 等待文本内容更改
await page.waitForFunction(selector => {
return document.querySelector(selector).textContent === 'Updated';
}, {}, selector);
// 等待 500ms 内无网络请求
await page.waitForTimeout(500);
处理弹出窗口和新标签页:
page.on('dialog', dialog => {
dialog.accept(); // 或 dismiss()
});
const [popup] = await Promise.all([
new Promise(resolve => browser.once('targetcreated', target => resolve(target.page()))),
page.click('#open-popup'), // 单击打开弹出窗口的按钮
]);
await popup.waitForSelector('h1'); // 等待弹出窗口内容
使用自动重试稳定不稳定的测试:
// 自动重试失败步骤最多 4 次
const autoRetry = require('puppeteer-autoretry');
autoRetry.setDefaults({ retries: 4 });
await autoRetry(page).type('#input', 'Text');
触摸交互
在元素上点击:
await page.tap('button');
在移动设备上滚动:
await page.touchscreen.scroll(50, 100);
拖放:
await page.touchscreen.down();
await page.touchscreen.move(50, 100);
await page.touchscreen.up();
地理位置和权限
设置地理位置:
await page.setGeolocation({latitude: 0, longitude: 0});
授予摄像头访问权限:
await page.grantPermissions(['camera']);
高级用例
提交表单和上传文件:
// 提交表单
await page.type('#email', 'test@example.com');
await page.click('#submit');
// 上传文件
const input = await page.$('input#file');
input.uploadFile('/path/to/file.txt');
从网站中抓取内容:
// 从所有 p 元素中提取文本
const texts = await page.$$eval('p', elements => {
return elements.map(el => el.textContent);
});
跨浏览器视觉测试:
const devices = puppeteer.devices;
for (const browserType of ['chromium', 'firefox', 'webkit']) {
const browser = await puppeteer.launch({browserType});
// 模拟设备并进行测试
}
视觉回归测试
比较截图:
const screenshot = await page.screenshot();
const diff = await visualDiff.compare(screenshot, 'baseline.png');
expect(diff.misMatchPercentage).toBeLessThan(0.01);
并行测试
并行测试:
const browser = await puppeteer.launch();
const pagePromises = [
browser.newPage(),
browser.newPage()
];
const pages = await Promise.all(pagePromises);
// 并行运行测试
await Promise.all([
pages[0].goto('url1'),
pages[1].goto('url2')
])
使用技巧
通过持久化上下文加速执行:
// 持久化浏览器上下文
const browserContext = await browser.createIncogniteBrowserContext();
await browserContext.close();
await browserContext.waitForTarget(page => page.url() === 'about:blank');
// 恢复上下文
const page = await browserContext.newPage();
在测试期间分析 CPU 使用情况:
await page.profiling.start({path: 'trace.json'});
// 运行 CPU 密集任务
await page.profiling.stop();
使用隐私模式上下文:
const context = await browser.createIncogniteBrowserContext();
const page = await context.newPage();
在上下文之间传输 cookie:
const context1 = await browser.createIncogniteBrowserContext();
const context2 = await browser.createIncogniteBrowserContext();
await context1.addCookies([cookieObj1, cookieObj2]);
const transferred = await context2.transferCookies(context1);
使用持久性上下文:
const context = await browser.createPersistentContext();
await context.close();
// 以后恢复
const page = await context.newPage();
测试自动化策略
可重用的页面对象:
class LoginPage {
constructor(page) {
this.page = page;
}
async login(username, password) {
await this.page.type('#username', username);
await this.page.type('#password', password);
await this.page.click('#submit');
}
}
// 使用方式:
const loginPage = new LoginPage(page);
await loginPage.login('user1', '123456');
同步测试序列:
const [response] = await Promise.all([
page.click('#submit'), // 请求发送后返回
page.waitForNavigation() // 页面加载后解析
]);
// 在导航后断言响应
expect(response.status()).toBe(200);
重试失败的测试用例:
for (let retry = 0; retry < 3; retry++) {
try {
await loginPage.login('invalid', 'password');
break; // 测试通过,因此我们中断
} catch (error) {
if (retry === 2) {
throw error; // 3 次重试后失败
}
// 否则重试测试
}
}
网友评论