Puppeteer 提供了强大的设备模拟功能,可以模拟各种移动设备、屏幕尺寸、用户代理等,这对于响应式设计测试和移动端网页测试非常有用。
1. 设备模拟基础
Puppeteer 内置了多种常见设备的预设配置。
javascriptconst 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. 查看设备配置
javascriptconst 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. 自定义设备配置
如果内置设备不满足需求,可以创建自定义配置。
javascriptconst 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. 触摸事件模拟
模拟触摸设备的行为。
javascriptconst 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:响应式设计测试
javascriptasync 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:多设备兼容性测试
javascriptasync 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:移动端用户体验测试
javascriptasync 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:国际化测试
javascriptasync 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. 测试前重置状态:
javascriptasync function resetPage(page) { await page.emulate(puppeteer.devices['Desktop']); await page.setGeolocation({ latitude: 0, longitude: 0 }); await page.emulateTimezone('UTC'); }
3. 使用上下文隔离测试:
javascriptconst context = await browser.createIncognitoBrowserContext(); const page = await context.newPage(); // 测试代码 await context.close();
4. 记录测试结果:
javascriptconst testResults = []; for (const device of devices) { const result = await testDevice(device); testResults.push({ device, result }); } console.log(JSON.stringify(testResults, null, 2));
13. 注意事项
- 性能影响:设备模拟可能会影响性能,特别是在频繁切换时
- 缓存问题:不同设备可能需要清除缓存以获得准确结果
- 网络条件:考虑模拟不同的网络速度
- 真实设备差异:模拟可能与真实设备有细微差异
- 权限管理:确保正确设置地理位置等权限