Playwright 页面操作 #
操作概述 #
Playwright 提供丰富的页面操作方法,模拟真实用户行为。所有操作都内置自动等待机制,确保元素准备好后再执行。
自动等待机制 #
text
┌─────────────────────────────────────────────────────────────┐
│ 操作前自动检查 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 元素已附加到 DOM │
│ 2. 元素可见 │
│ 3. 元素稳定(非动画中) │
│ 4. 元素可接收事件 │
│ 5. 元素已启用 │
│ │
│ 如果检查失败,会自动重试直到超时 │
│ │
└─────────────────────────────────────────────────────────────┘
点击操作 #
基本点击 #
typescript
import { test, expect } from '@playwright/test';
test('基本点击', async ({ page }) => {
// 基本点击
await page.click('button');
// 使用 Locator
await page.getByRole('button', { name: 'Submit' }).click();
// 点击链接
await page.getByRole('link', { name: 'Home' }).click();
});
点击选项 #
typescript
test('点击选项', async ({ page }) => {
const button = page.getByRole('button', { name: 'Submit' });
// 点击选项
await button.click({
button: 'left', // 鼠标按钮: left, right, middle
clickCount: 1, // 点击次数
delay: 100, // 按下和释放之间的延迟(毫秒)
force: false, // 强制点击,跳过可操作性检查
modifiers: ['Shift'], // 修饰键: Alt, Control, Meta, Shift
position: { x: 10, y: 10 }, // 点击位置(相对于元素)
timeout: 30000, // 超时时间
trial: false, // 试运行,不实际点击
});
// 右键点击
await button.click({ button: 'right' });
// 双击
await button.dblclick();
// Shift + 点击
await button.click({ modifiers: ['Shift'] });
// 强制点击(跳过可见性检查)
await button.click({ force: true });
});
特殊点击场景 #
typescript
test('特殊点击场景', async ({ page }) => {
// 点击坐标位置
await page.mouse.click(100, 200);
// 点击元素中心
await page.locator('.card').click();
// 点击元素特定位置
await page.locator('.card').click({ position: { x: 10, y: 10 } });
// 点击多个元素
const buttons = page.getByRole('button');
const count = await buttons.count();
for (let i = 0; i < count; i++) {
await buttons.nth(i).click();
}
});
输入操作 #
文本输入 #
typescript
test('文本输入', async ({ page }) => {
// fill - 直接填充(推荐)
await page.fill('#username', 'john_doe');
await page.getByLabel('Email').fill('test@example.com');
// type - 逐字符输入(模拟真实打字)
await page.type('#username', 'john_doe');
await page.type('#username', 'john_doe', { delay: 100 }); // 每个字符延迟 100ms
// pressSequentially - 按顺序按键
await page.locator('#username').pressSequentially('john_doe');
await page.locator('#username').pressSequentially('john_doe', { delay: 100 });
});
fill vs type #
typescript
test('fill vs type', async ({ page }) => {
// fill - 直接设置值
// ✅ 快速、可靠
// ✅ 清除现有内容
// ✅ 触发 input 和 change 事件
await page.fill('#input', 'new value');
// type - 模拟打字
// ✅ 触发 keydown, keypress, keyup 事件
// ✅ 适合测试实时验证
// ⚠️ 较慢
await page.type('#input', 'new value');
});
清除内容 #
typescript
test('清除内容', async ({ page }) => {
const input = page.getByLabel('Email');
// 清除输入框
await input.clear();
// 或使用 fill 空字符串
await input.fill('');
// 或使用快捷键
await input.press('Control+a');
await input.press('Backspace');
});
选择操作 #
下拉选择 #
typescript
test('下拉选择', async ({ page }) => {
const select = page.getByRole('combobox', { name: 'Country' });
// 按 value 选择
await select.selectOption('cn');
// 按 label 选择
await select.selectOption({ label: 'China' });
// 按 index 选择
await select.selectOption({ index: 0 });
// 多选
await select.selectOption(['cn', 'us', 'uk']);
});
单选和复选 #
typescript
test('单选和复选', async ({ page }) => {
// 复选框
const checkbox = page.getByRole('checkbox', { name: 'Accept terms' });
// 勾选
await checkbox.check();
// 取消勾选
await checkbox.uncheck();
// 断言选中状态
await expect(checkbox).toBeChecked();
// 单选按钮
const radio = page.getByRole('radio', { name: 'Option 1' });
await radio.check();
await expect(radio).toBeChecked();
});
键盘操作 #
按键操作 #
typescript
test('键盘操作', async ({ page }) => {
const input = page.getByLabel('Search');
// 单个按键
await input.press('Enter');
await input.press('Tab');
await input.press('Escape');
await input.press('Backspace');
await input.press('Delete');
// 组合键
await input.press('Control+a'); // 全选
await input.press('Control+c'); // 复制
await input.press('Control+v'); // 粘贴
await input.press('Control+z'); // 撤销
await input.press('Meta+a'); // Mac: Command+a
// 方向键
await input.press('ArrowUp');
await input.press('ArrowDown');
await input.press('ArrowLeft');
await input.press('ArrowRight');
// 功能键
await input.press('F1');
await input.press('F5');
});
键盘输入 #
typescript
test('键盘输入', async ({ page }) => {
// 在页面上输入
await page.keyboard.type('Hello World');
await page.keyboard.type('Hello World', { delay: 100 });
// 按下和释放
await page.keyboard.down('Shift');
await page.keyboard.type('hello');
await page.keyboard.up('Shift');
// 插入文本(不触发键盘事件)
await page.keyboard.insertText('Hello World');
});
常用快捷键 #
typescript
test('常用快捷键', async ({ page }) => {
const input = page.getByRole('textbox');
// 全选
await input.press('Control+a');
// 复制
await input.press('Control+c');
// 粘贴
await input.press('Control+v');
// 剪切
await input.press('Control+x');
// 撤销
await input.press('Control+z');
// 重做
await input.press('Control+Shift+z');
// 跳到开头
await input.press('Home');
// 跳到结尾
await input.press('End');
});
鼠标操作 #
鼠标移动 #
typescript
test('鼠标移动', async ({ page }) => {
// 移动到指定坐标
await page.mouse.move(100, 200);
// 带步骤的移动(模拟真实移动轨迹)
await page.mouse.move(100, 200, { steps: 10 });
// 移动到元素
await page.locator('.card').hover();
});
鼠标点击 #
typescript
test('鼠标点击', async ({ page }) => {
// 左键点击
await page.mouse.click(100, 200);
// 右键点击
await page.mouse.click(100, 200, { button: 'right' });
// 双击
await page.mouse.dblclick(100, 200);
// 中键点击
await page.mouse.click(100, 200, { button: 'middle' });
});
鼠标拖拽 #
typescript
test('鼠标拖拽', async ({ page }) => {
// 方式 1: 使用 dragTo
await page.locator('#source').dragTo(page.locator('#target'));
// 方式 2: 使用鼠标操作
await page.locator('#source').hover();
await page.mouse.down();
await page.locator('#target').hover();
await page.mouse.up();
// 方式 3: 精确控制
const source = page.locator('#source');
const target = page.locator('#target');
const sourceBox = await source.boundingBox();
const targetBox = await target.boundingBox();
await page.mouse.move(sourceBox.x + sourceBox.width / 2, sourceBox.y + sourceBox.height / 2);
await page.mouse.down();
await page.mouse.move(targetBox.x + targetBox.width / 2, targetBox.y + targetBox.height / 2);
await page.mouse.up();
});
滚动操作 #
页面滚动 #
typescript
test('页面滚动', async ({ page }) => {
// 滚动到底部
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
// 滚动到顶部
await page.evaluate(() => window.scrollTo(0, 0));
// 滚动特定距离
await page.evaluate(() => window.scrollBy(0, 500));
// 滚动到元素
await page.locator('.footer').scrollIntoViewIfNeeded();
});
元素内滚动 #
typescript
test('元素内滚动', async ({ page }) => {
const scrollContainer = page.locator('.scroll-container');
// 滚动到元素可见
await scrollContainer.locator('.item:last-child').scrollIntoViewIfNeeded();
// 使用 evaluate 滚动
await scrollContainer.evaluate((el) => {
el.scrollTop = el.scrollHeight;
});
});
文件操作 #
文件上传 #
typescript
test('文件上传', async ({ page }) => {
// 方式 1: setInputFiles
await page.setInputFiles('#file-input', 'test-files/document.pdf');
// 上传多个文件
await page.setInputFiles('#file-input', [
'test-files/file1.pdf',
'test-files/file2.pdf',
]);
// 清除已选文件
await page.setInputFiles('#file-input', []);
// 使用 Locator
await page.getByLabel('Upload').setInputFiles('test-files/document.pdf');
});
文件下载 #
typescript
test('文件下载', async ({ page }) => {
// 等待下载
const downloadPromise = page.waitForEvent('download');
await page.getByText('Download').click();
const download = await downloadPromise;
// 获取文件名
console.log(download.suggestedFilename());
// 保存文件
await download.saveAs('downloads/' + download.suggestedFilename());
// 获取文件路径
const path = await download.path();
// 获取流
const stream = await download.createReadStream();
});
对话框操作 #
处理 Alert #
typescript
test('处理 Alert', async ({ page }) => {
// 监听对话框
page.on('dialog', async dialog => {
console.log(dialog.message());
await dialog.accept(); // 或 dialog.dismiss()
});
// 触发 alert
await page.evaluate(() => alert('Hello!'));
});
处理 Confirm #
typescript
test('处理 Confirm', async ({ page }) => {
page.on('dialog', async dialog => {
console.log(dialog.message());
await dialog.accept(); // 点击确定
// 或 await dialog.dismiss(); // 点击取消
});
await page.evaluate(() => confirm('Are you sure?'));
});
处理 Prompt #
typescript
test('处理 Prompt', async ({ page }) => {
page.on('dialog', async dialog => {
console.log(dialog.message());
await dialog.accept('my input'); // 输入文本并确定
});
await page.evaluate(() => prompt('Enter your name:'));
});
多窗口和标签页 #
处理新窗口 #
typescript
test('处理新窗口', async ({ page }) => {
// 监听新页面
const pagePromise = page.waitForEvent('popup');
// 触发新窗口
await page.getByText('Open new window').click();
// 获取新页面
const newPage = await pagePromise;
// 在新页面操作
await newPage.getByLabel('Search').fill('Playwright');
// 关闭新页面
await newPage.close();
});
处理多个标签页 #
typescript
test('处理多个标签页', async ({ context }) => {
// 创建多个页面
const page1 = await context.newPage();
const page2 = await context.newPage();
// 在不同页面操作
await page1.goto('https://example.com');
await page2.goto('https://example.org');
// 获取所有页面
const pages = context.pages();
// 关闭页面
await page1.close();
await page2.close();
});
iframe 操作 #
进入 iframe #
typescript
test('iframe 操作', async ({ page }) => {
// 获取 iframe
const frame = page.frameLocator('iframe[name="myframe"]');
// 在 iframe 内操作
await frame.getByLabel('Username').fill('john_doe');
await frame.getByRole('button', { name: 'Submit' }).click();
// 嵌套 iframe
const nestedFrame = frame.frameLocator('iframe');
await nestedFrame.getByText('Click me').click();
});
iframe 定位器 #
typescript
test('iframe 定位器', async ({ page }) => {
// 通过 name 属性
const frame1 = page.frameLocator('iframe[name="myframe"]');
// 通过 src 属性
const frame2 = page.frameLocator('iframe[src*="youtube"]');
// 通过索引
const frame3 = page.frameLocator('iframe').first();
// 通过内容
const frame4 = page.frameLocator('iframe').filter({
has: page.locator('body:has-text("Login")')
});
});
等待操作 #
等待元素 #
typescript
test('等待元素', async ({ page }) => {
// 等待元素出现
await page.locator('.loading').waitFor({ state: 'visible' });
// 等待元素消失
await page.locator('.loading').waitFor({ state: 'hidden' });
// 等待元素附加到 DOM
await page.locator('.dynamic').waitFor({ state: 'attached' });
// 等待元素从 DOM 移除
await page.locator('.temporary').waitFor({ state: 'detached' });
// 等待元素可编辑
await page.locator('input').waitFor({ state: 'editable' });
});
等待请求 #
typescript
test('等待请求', async ({ page }) => {
// 等待特定请求
const responsePromise = page.waitForResponse('**/api/users');
await page.getByText('Load Users').click();
const response = await responsePromise;
// 等待请求完成
await page.waitForRequest('**/api/users');
// 等待响应并验证
const res = await page.waitForResponse(
response => response.url().includes('/api/users') && response.status() === 200
);
});
等待函数 #
typescript
test('等待函数', async ({ page }) => {
// 等待函数返回 true
await page.waitForFunction(() => {
return document.querySelectorAll('.item').length > 5;
});
// 带参数的等待函数
await page.waitForFunction(
({ selector, count }) => {
return document.querySelectorAll(selector).length >= count;
},
{ selector: '.item', count: 5 }
);
// 等待元素数量
await expect(page.locator('.item')).toHaveCount(5);
});
截图和录制 #
截图 #
typescript
test('截图', async ({ page }) => {
// 整页截图
await page.screenshot({ path: 'screenshot.png' });
// 元素截图
await page.locator('.card').screenshot({ path: 'card.png' });
// 全页截图(包含滚动内容)
await page.screenshot({
path: 'fullpage.png',
fullPage: true
});
// 返回 Buffer
const buffer = await page.screenshot();
// 返回 Base64
const base64 = await page.screenshot({ encoding: 'base64' });
});
录制视频 #
typescript
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
video: 'on', // 'on' | 'off' | 'retain-on-failure' | 'on-first-retry'
},
});
// 或在测试中手动控制
test('录制视频', async ({ page, context }) => {
// 视频会在测试结束后自动保存
await page.goto('/');
await page.getByText('Click me').click();
});
最佳实践 #
1. 使用语义化操作 #
typescript
// ✅ 推荐
await page.getByRole('button', { name: 'Submit' }).click();
await page.getByLabel('Email').fill('test@example.com');
// ❌ 不推荐
await page.click('#submit-btn');
await page.fill('#email-input', 'test@example.com');
2. 避免不必要的等待 #
typescript
// ✅ 推荐 - 自动等待
await page.click('button');
await expect(page.locator('.result')).toBeVisible();
// ❌ 不推荐 - 固定等待
await page.click('button');
await page.waitForTimeout(1000);
3. 使用正确的输入方法 #
typescript
// ✅ 推荐 - fill 用于普通输入
await page.getByLabel('Email').fill('test@example.com');
// ✅ 推荐 - type 用于模拟真实打字
await page.getByLabel('Search').type('Playwright', { delay: 100 });
// ✅ 推荐 - pressSequentially 用于复杂输入
await page.getByLabel('Code').pressSequentially('ABC123');
4. 处理动态内容 #
typescript
// ✅ 推荐 - 使用断言等待
await expect(page.locator('.loading')).not.toBeVisible();
await expect(page.locator('.content')).toBeVisible();
// ✅ 推荐 - 使用 waitFor
await page.locator('.loading').waitFor({ state: 'hidden' });
下一步 #
现在你已经掌握了 Playwright 页面操作的方法,接下来学习 断言验证 了解如何验证测试结果!
最后更新:2026-03-28