乐闻世界logo
搜索文章和话题

Puppeteer 如何实现设备模拟和移动端测试?有哪些内置设备和自定义配置方法?

2月19日 19:39

Puppeteer 提供了强大的设备模拟功能,可以模拟各种移动设备、屏幕尺寸、用户代理等,这对于响应式设计测试和移动端网页测试非常有用。

1. 设备模拟基础

Puppeteer 内置了多种常见设备的预设配置。

javascript
const puppeteer = require('puppeteer'); (async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); // 模拟 iPhone 12 const iPhone = puppeteer.devices['iPhone 12']; await page.emulate(iPhone); await page.goto('https://example.com'); await page.screenshot({ path: 'iphone-12.png' }); await browser.close(); })();

2. 内置设备列表

Puppeteer 提供了以下设备预设:

iPhone 系列:

  • iPhone 12 Pro
  • iPhone 12
  • iPhone 11 Pro
  • iPhone 11
  • iPhone X
  • iPhone 8
  • iPhone 8 Plus
  • iPhone SE
  • iPhone 7
  • iPhone 7 Plus
  • iPhone 6
  • iPhone 6 Plus
  • iPhone 5
  • iPhone 4

iPad 系列:

  • iPad Pro 11
  • iPad Pro
  • iPad Mini
  • iPad

Android 系列:

  • Pixel 5
  • Pixel 4
  • Pixel 2
  • Galaxy S5
  • Galaxy Note III
  • Nexus 10
  • Nexus 7
  • Nexus 6
  • Nexus 5
  • Nexus 4

其他设备:

  • Kindle Fire HDX
  • Blackberry Z30
  • Blackberry PlayBook
  • Nokia N9
  • Nokia Lumia 520

3. 查看设备配置

javascript
const puppeteer = require('puppeteer'); // 查看所有可用设备 console.log(Object.keys(puppeteer.devices)); // 查看特定设备的配置 const iPhone = puppeteer.devices['iPhone 12']; console.log(iPhone); /* 输出: { name: 'iPhone 12', userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1', viewport: { width: 390, height: 844, deviceScaleFactor: 3, isMobile: true, hasTouch: true, isLandscape: false } } */

4. 自定义设备配置

如果内置设备不满足需求,可以创建自定义配置。

javascript
const customDevice = { name: 'Custom Device', userAgent: 'Mozilla/5.0 (Custom Device) AppleWebKit/537.36', viewport: { width: 414, height: 896, deviceScaleFactor: 2, isMobile: true, hasTouch: true, isLandscape: false } }; await page.emulate(customDevice);

5. 手动设置视口

可以单独设置视口参数而不使用完整设备模拟。

javascript
// 设置视口大小 await page.setViewport({ width: 1920, height: 1080, deviceScaleFactor: 1, isMobile: false, hasTouch: false, isLandscape: false }); // 设置移动设备视口 await page.setViewport({ width: 375, height: 667, deviceScaleFactor: 2, isMobile: true, hasTouch: true });

6. 用户代理设置

单独设置用户代理字符串。

javascript
// 设置自定义用户代理 await page.setUserAgent('Mozilla/5.0 (Custom User Agent) AppleWebKit/537.36'); // 设置移动设备用户代理 await page.setUserAgent('Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15');

7. 地理位置模拟

模拟设备的地理位置。

javascript
// 设置地理位置 await page.setGeolocation({ latitude: 35.6895, longitude: 139.6917 }); // 授予地理位置权限 await context.overridePermissions('https://example.com', ['geolocation']); // 使用示例 const browser = await puppeteer.launch(); const context = await browser.createIncognitoBrowserContext(); const page = await context.newPage(); await page.setGeolocation({ latitude: 35.6895, longitude: 139.6917 }); await context.overridePermissions('https://example.com', ['geolocation']); await page.goto('https://example.com');

8. 时区模拟

模拟不同的时区。

javascript
// 设置时区 await page.emulateTimezone('Asia/Shanghai'); // 其他时区示例 await page.emulateTimezone('America/New_York'); await page.emulateTimezone('Europe/London'); await page.emulateTimezone('UTC');

9. 语言和区域设置

设置浏览器的语言和区域。

javascript
// 设置语言 await page.setExtraHTTPHeaders({ 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8' }); // 在启动时设置 const browser = await puppeteer.launch({ args: ['--lang=zh-CN'] });

10. 触摸事件模拟

模拟触摸设备的行为。

javascript
const touchDevice = { viewport: { width: 375, height: 667, isMobile: true, hasTouch: true } }; await page.setViewport(touchDevice.viewport); // 模拟触摸点击 await page.tap('#button'); // 模拟触摸滑动 await page.touchscreen.tap(100, 100);

11. 实际应用场景

场景 1:响应式设计测试

javascript
async function testResponsiveDesign(url) { const browser = await puppeteer.launch(); const page = await browser.newPage(); const viewports = [ { name: 'Desktop', width: 1920, height: 1080 }, { name: 'Tablet', width: 768, height: 1024 }, { name: 'Mobile', width: 375, height: 667 } ]; for (const viewport of viewports) { await page.setViewport({ width: viewport.width, height: viewport.height }); await page.goto(url, { waitUntil: 'networkidle2' }); // 检查布局是否正确 const isLayoutCorrect = await page.evaluate(() => { const header = document.querySelector('header'); return header && header.offsetWidth <= viewport.width; }); console.log(`${viewport.name}: ${isLayoutCorrect ? '✓' : '✗'}`); await page.screenshot({ path: `${viewport.name.toLowerCase()}.png` }); } await browser.close(); } testResponsiveDesign('https://example.com');

场景 2:多设备兼容性测试

javascript
async function testMultiDeviceCompatibility(url) { const browser = await puppeteer.launch(); const page = await browser.newPage(); const devices = [ 'iPhone 12', 'iPad Pro', 'Pixel 5' ]; for (const deviceName of devices) { const device = puppeteer.devices[deviceName]; await page.emulate(device); await page.goto(url, { waitUntil: 'networkidle2' }); // 检查功能是否正常 const isFunctional = await page.evaluate(() => { const buttons = document.querySelectorAll('button'); return buttons.length > 0 && buttons[0].click(); }); console.log(`${deviceName}: ${isFunctional ? '✓' : '✗'}`); await page.screenshot({ path: `${deviceName.replace(/\s/g, '_')}.png` }); } await browser.close(); } testMultiDeviceCompatibility('https://example.com');

场景 3:移动端用户体验测试

javascript
async function testMobileUX(url) { const browser = await puppeteer.launch(); const context = await browser.createIncognitoBrowserContext(); const page = await context.newPage(); // 模拟移动设备 await page.emulate(puppeteer.devices['iPhone 12']); // 设置地理位置 await page.setGeolocation({ latitude: 35.6895, longitude: 139.6917 }); await context.overridePermissions(url, ['geolocation']); await page.goto(url, { waitUntil: 'networkidle2' }); // 测试触摸交互 await page.tap('#menu-button'); await page.waitForSelector('.menu', { visible: true }); // 测试滑动 await page.evaluate(() => { window.scrollBy(0, 500); }); // 检查响应速度 const startTime = Date.now(); await page.click('#action-button'); const responseTime = Date.now() - startTime; console.log(`Response time: ${responseTime}ms`); await browser.close(); } testMobileUX('https://example.com');

场景 4:国际化测试

javascript
async function testInternationalization(url) { const browser = await puppeteer.launch(); const page = await browser.newPage(); const locales = [ { code: 'zh-CN', language: '中文' }, { code: 'en-US', language: 'English' }, { code: 'ja-JP', language: '日本語' } ]; for (const locale of locales) { // 设置语言 await page.setExtraHTTPHeaders({ 'Accept-Language': locale.code }); // 设置时区 await page.emulateTimezone(locale.code === 'zh-CN' ? 'Asia/Shanghai' : locale.code === 'en-US' ? 'America/New_York' : 'Asia/Tokyo'); await page.goto(url, { waitUntil: 'networkidle2' }); // 检查语言是否正确 const pageLanguage = await page.evaluate(() => { return document.documentElement.lang; }); console.log(`${locale.language}: ${pageLanguage}`); await page.screenshot({ path: `${locale.code}.png` }); } await browser.close(); } testInternationalization('https://example.com');

12. 最佳实践

1. 使用内置设备预设:

javascript
// 推荐 await page.emulate(puppeteer.devices['iPhone 12']); // 不推荐(除非有特殊需求) await page.setViewport({ width: 390, height: 844 });

2. 测试前重置状态:

javascript
async function resetPage(page) { await page.emulate(puppeteer.devices['Desktop']); await page.setGeolocation({ latitude: 0, longitude: 0 }); await page.emulateTimezone('UTC'); }

3. 使用上下文隔离测试:

javascript
const context = await browser.createIncognitoBrowserContext(); const page = await context.newPage(); // 测试代码 await context.close();

4. 记录测试结果:

javascript
const testResults = []; for (const device of devices) { const result = await testDevice(device); testResults.push({ device, result }); } console.log(JSON.stringify(testResults, null, 2));

13. 注意事项

  1. 性能影响:设备模拟可能会影响性能,特别是在频繁切换时
  2. 缓存问题:不同设备可能需要清除缓存以获得准确结果
  3. 网络条件:考虑模拟不同的网络速度
  4. 真实设备差异:模拟可能与真实设备有细微差异
  5. 权限管理:确保正确设置地理位置等权限
标签:Puppeteer