Playwright 第5章:脚本基础与浏览器控制

Playwright 采用 Browser / Context / Page 三层架构,提供强大的浏览器控制能力。本章深入讲解这三层架构的原理、BrowserContext 的隔离机制以及多页面管理技巧。

Browser/Context/Page 三层架构

架构层次

Browser(浏览器实例)
  └── BrowserContext(浏览器上下文,隔离环境)
        ├── Page(页面标签页1)
        ├── Page(页面标签页2)
        └── Page(页面标签页3)
  • Browser:浏览器进程,管理整个浏览器实例
  • BrowserContext:类似隐身会话,提供完全隔离的浏览环境
  • Page:标签页,每个页面对象对应一个浏览器标签

Browser 管理

import { chromium, firefox, webkit } from 'playwright';

// 启动浏览器
const browser = await chromium.launch({
  headless: false,
});

// 获取浏览器信息
console.log('浏览器版本:', browser.version());
console.log('是否连接:', browser.isConnected());

// 监听断开事件
browser.on('disconnected', () => {
  console.log('浏览器已断开');
});

// 关闭浏览器
await browser.close();

BrowserContext 隔离机制

为什么需要 BrowserContext

BrowserContext 提供类似隐身模式的独立会话环境,每个 Context 拥有独立的:

  • Cookies 和本地存储
  • 浏览器缓存
  • 会话数据
  • 认证信息

创建和使用 Context

const browser = await chromium.launch();

// 创建默认上下文
const context1 = await browser.newContext();

// 创建带配置的上下文
const context2 = await browser.newContext({
  viewport: { width: 1920, height: 1080 },
  locale: 'zh-CN',
  timezoneId: 'Asia/Shanghai',
  permissions: ['geolocation'],
  colorScheme: 'dark',
  userAgent: 'Mozilla/5.0 (Custom UA)',
  geolocation: { latitude: 39.91, longitude: 116.40 },
});

// 验证上下文隔离
const page1 = await context1.newPage();
const page2 = await context2.newPage();

await page1.goto('https://example.com');
await page1.evaluate(() => {
  document.cookie = 'session=abc123';
});

// 不同 Context 的 Cookie 不共享
const cookies1 = await context1.cookies();
const cookies2 = await context2.cookies();
console.log('Context1 cookies:', cookies1.length); // 1
console.log('Context2 cookies:', cookies2.length); // 0

await browser.close();

Context 配置选项

配置项类型说明
viewport{width, height}窗口视口大小
localestring浏览器语言(如 zh-CN
timezoneIdstring时区设置
geolocation{latitude, longitude}地理位置
permissionsstring[]权限列表
colorScheme'light'|'dark'主题配色
userAgentstring自定义 User-Agent
deviceScaleFactornumber设备像素比
storageStatestring持久化状态路径

持久化认证状态

const browser = await chromium.launch();
const context = await browser.newContext();

// 执行登录操作
const page = await context.newPage();
await page.goto('https://example.com/login');
await page.fill('#username', 'admin');
await page.fill('#password', 'password123');
await page.click('button[type="submit"]');
await page.waitForURL('https://example.com/dashboard');

// 保存认证状态
await context.storageState({ path: './auth.json' });
console.log('认证状态已保存');

await browser.close();

// 后续测试复用认证状态
const context2 = await browser.newContext({
  storageState: './auth.json',
});
const page2 = await context2.newPage();
await page2.goto('https://example.com/dashboard');
// 已登录状态

多页面管理

在同一个 Context 中管理多个页面

const browser = await chromium.launch();
const context = await browser.newContext();

// 同时打开多个页面
const page1 = await context.newPage();
const page2 = await context.newPage();
const page3 = await context.newPage();

// 并行导航
await Promise.all([
  page1.goto('https://example.com'),
  page2.goto('https://google.com'),
  page3.goto('https://github.com'),
]);

// 页面间切换
await page1.bringToFront();
await page1.click('.main-link');

// 获取所有页面
const allPages = context.pages();
console.log('当前页面数:', allPages.length);

// 关闭特定页面
await page2.close();
console.log('剩余页面:', context.pages().length);

await browser.close();

新标签页处理

const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();

// 监听新页面事件
context.on('page', async (newPage) => {
  await newPage.waitForLoadState();
  console.log('新页面打开:', newPage.url());

  // 在新页面中操作
  const title = await newPage.title();
  console.log('新页面标题:', title);
});

// 触发新标签页打开
await page.goto('https://example.com');
await page.click('a[target="_blank"]');

// 等待新页面
const [newPage] = await Promise.all([
  context.waitForEvent('page'),
  page.click('a[target="_blank"]'),
]);

await newPage.waitForLoadState();
console.log('新页面URL:', newPage.url());
await newPage.close();

await browser.close();

浏览器启动选项

完整启动参数

const browser = await chromium.launch({
  headless: false,
  slowMo: 200,           // 操作间延迟(调试用)
  timeout: 30000,        // 启动超时
  channel: 'chrome',     // 使用系统安装的 Chrome
  args: [
    '--disable-blink-features=AutomationControlled',
    '--disable-features=IsolateOrigins,site-per-process',
    '--disable-web-security',
    '--disable-notifications',
    '--no-sandbox',
    '--disable-setuid-sandbox',
    '--disable-infobars',
    '--window-size=1920,1080',
  ],
  proxy: {
    server: 'http://proxy.example.com:8080',
  },
  downloadsPath: './downloads',
  env: {
    NODE_ENV: 'test',
  },
});

使用系统安装的浏览器

// 使用系统 Chrome
const chromeBrowser = await chromium.launch({
  channel: 'chrome',   // 系统安装的 Chrome
});

// 使用 MS Edge
const edgeBrowser = await chromium.launch({
  channel: 'msedge',   // 系统安装的 Edge
});

// 使用 Playwright 内置浏览器
const pwBrowser = await chromium.launch(); // 默认使用 Playwright 内置 Chromium
channel 值说明
'chrome'系统安装的 Google Chrome
'msedge'系统安装的 Microsoft Edge
不指定使用 Playwright 下载的 Chromium

总结

理解 Browser / Context / Page 三层架构是掌握 Playwright 的关键。BrowserContext 提供了强大的环境隔离能力,使得多测试用例可以并行执行而互不干扰。利用 storageState 可以高效管理认证状态,多页面管理功能也适用于复杂业务场景的测试。合理配置浏览器启动参数能够满足不同的测试环境需求。