现在位置: 首页 > Playwright 教程 > 正文

Playwright 操作(Actions)

本章介绍 Playwright 中与导航和点击相关的操作,以及操作背后的自动等待机制。


什么是 Action

Action(操作)是 Playwright 中用来模拟用户行为的方法,比如点击按钮、输入文字、选择下拉框。

每个 Action 在执行之前,Playwright 会自动等待目标元素变为可操作状态


Actionability 可操作性检查

在执行每个操作前,Playwright 会对目标元素进行一系列检查:

步骤检查条件说明
1. Attached元素已挂载到 DOM 树元素存在于页面中
2. Visible元素可见(非 display:nonevisibility:hidden用户能看见元素
3. Stable元素位置稳定(动画结束)避免动画期间的误操作
4. Receives Events元素没有被其他元素遮挡确保点击能被元素接收
5. Enabled元素未被禁用(非 disabled用户可以与之交互

任意一步未通过,Playwright 都会等待并重试,直到超时。

这 5 步检查是 Playwright 自动完成的,你不需要写任何额外的等待代码。这也是 Playwright 能够大幅减少不稳定测试的根本原因。


强制操作 { force: true }

有时你需要绕过可操作性检查,例如点击一个被遮罩隐藏的按钮或测试不可见元素的状态。

实例

// 强制点击,跳过所有可操作性检查
await page.getByRole('button', { name: '提交' }).click({ force: true });

force: true 是测试真实用户做不到的操作,绝大多数情况下不应使用。如果你的测试需要 force: true,先检查是否有交互设计问题。


导航操作

page.goto() 页面跳转

实例

// 基本导航
await page.goto('https://www.runoob.com/');

// 等待 DOMContentLoaded(页面结构加载完成)
await page.goto('https://www.runoob.com/', { waitUntil: 'domcontentloaded' });

// 等待网络空闲(所有异步数据加载完毕)
await page.goto('https://www.runoob.com/', {
  waitUntil: 'networkidle',    // 网络空闲
  timeout: 60000,              // 超时 60 秒
  referer: 'https://google.com', // Referer 来源
});

page.goBack() / goForward() / reload()

实例

// 后退一页
await page.goBack();

// 前进一页
await page.goForward();

// 刷新页面
await page.reload();

点击操作

locator.click() 单击

实例

// 基本单击
await page.getByRole('button', { name: '登录' }).click();

// 带选项的点击
await page.getByRole('button', { name: '登录' }).click({
  button: 'left',     // 鼠标按键:'left' | 'right' | 'middle'
  clickCount: 1,      // 点击次数
  delay: 100,         // 按下与松开之间的延迟(毫秒)
  timeout: 5000,      // 超时时间
  modifiers: [],      // 修饰键:['Alt', 'Control', 'Meta', 'Shift']
});

locator.dblclick() 双击

实例

// 双击元素
await page.getByText('双击编辑').dblclick();

右键点击

实例

// 右键点击(打开上下文菜单)
await page.getByText('右键菜单').click({ button: 'right' });

组合键点击

实例

// Ctrl + 点击(多选)
await page.getByRole('option').click({ modifiers: ['Control'] });

// Shift + 点击(范围选择 / 新窗口打开)
await page.getByRole('link').click({ modifiers: ['Shift'] });

坐标点击

实例

// 点击元素的特定位置(相对于元素左上角)
await page.locator('canvas').click({
  position: { x: 100, y: 50 },
});

locator.hover() 悬停

实例

// 鼠标悬停(触发 hover 效果或下拉菜单)
await page.getByRole('link', { name: '教程' }).hover();

// 悬停后点击弹出的子菜单
await page.getByRole('link', { name: 'Playwright 教程' }).click();

点击 vs hover 的场景差异

操作适用场景
click()按钮点击、链接跳转、复选框切换
dblclick()编辑模式进入、双击选择文本
click({ button: 'right' })打开浏览器原生上下文菜单
hover()触发悬停菜单、Tooltip 提示
click({ position })Canvas 操作、自定义图形交互

文本输入操作

locator.fill() 清空后填入

fill() 会先清空输入框,再填入新值,是输入操作中最常用和最推荐的方法。

实例

// 清空输入框并填入新值
await page.getByLabel('用户名').fill('runoob_user');
await page.getByLabel('密码').fill('secure_password');
await page.getByPlaceholder('搜索RUNOOB教程').fill('Playwright');

locator.type() 逐字输入

type() 会逐个字符输入,模拟真实的键盘敲击,每次按键之间可设置间隔。

实例

// 逐字输入(每个字符间有 100ms 延迟)
await page.getByLabel('搜索').type('Playwright', { delay: 100 });

fill vs type 的区别

方法行为适用场景
fill()清空后一次性填入绝大多数场景(推荐)
type()逐字符输入,触发每次按键事件需要触发输入联想、实时搜索

大部分情况下使用 fill() 即可。只有当输入框有实时搜索(keydown/keyup 事件)时,才需要使用 type()

locator.clear() 清空

实例

// 清空输入框内容
await page.getByLabel('用户名').clear();

选择操作

locator.check() / uncheck() 复选框

实例

// 勾选复选框
await page.getByLabel('我同意服务协议').check();

// 取消勾选
await page.getByLabel('接收邮件通知').uncheck();

// 设置勾选状态
await page.getByLabel('记住我').setChecked(true);   // 确认为勾选
await page.getByLabel('记住我').setChecked(false);  // 确认为未勾选

locator.selectOption() 下拉框选择

实例

// 通过 value 属性选择
await page.getByLabel('城市').selectOption('beijing');

// 通过显示文本选择
await page.getByLabel('城市').selectOption({ label: '上海' });

// 多选下拉框
await page.getByLabel('兴趣标签').selectOption([
  { label: '编程' },
  { value: 'design' },
]);

// 通过索引选择(第 0 项、第 2 项)
await page.getByLabel('城市').selectOption({ index: 0 });

键盘操作

page.keyboard.press() 按键

实例

// 按下并释放 Enter
await page.keyboard.press('Enter');

// 全选(Control + A)
await page.keyboard.press('Control+A');

// 复制粘贴
await page.keyboard.press('Control+C');
await page.keyboard.press('Control+V');

// 撤销
await page.keyboard.press('Control+Z');

page.keyboard.down() / up() 按下/松开

实例

// 按住 Shift 不松开
await page.keyboard.down('Shift');

// 在此期间按其他键(相当于按住 Shift 再按其他键)
await page.keyboard.press('ArrowRight');  // Shift + 右箭头(选中文本)
await page.keyboard.press('ArrowRight');

// 松开 Shift
await page.keyboard.up('Shift');

特殊键名称

键名对应按键
Enter回车
EscapeEsc 退出
TabTab 切换焦点
Backspace退格删除
Delete删除键
ArrowUp/Down/Left/Right方向键
PageUp/PageDown翻页键
Home/End行首/行尾
Control/Alt/Shift/Meta修饰键

page.keyboard.type() 键盘输入文本

实例

// 在焦点元素上输入文本
await page.keyboard.type('RUNOOB 教程', { delay: 50 });

page.keyboard.insertText() 直接插入

实例

// 在焦点位置直接插入文本(不模拟键盘事件,速度最快)
await page.keyboard.insertText('快速插入的文本');

鼠标操作

坐标点击与移动

实例

// 在视口坐标 (100, 200) 处点击
await page.mouse.click(100, 200);

// 在坐标处双击
await page.mouse.dblclick(100, 200);

// 移动鼠标到坐标
await page.mouse.move(100, 200);

// 按下 / 松开
await page.mouse.down();
await page.mouse.up();

鼠标滚轮

实例

// 向下滚动 500px
await page.mouse.wheel(0, 500);

// 向左滚动 200px
await page.mouse.wheel(200, 0);

locator.dragTo() 拖拽

实例

// 把元素 A 拖拽到元素 B
const source = page.getByText('拖拽我');
const target = page.getByTestId('drop-zone');
await source.dragTo(target);

// 带选项的拖拽
await source.dragTo(target, {
  sourcePosition: { x: 0, y: 0 },   // 拖拽起始点(相对于源元素)
  targetPosition: { x: 10, y: 10 }, // 拖拽目标点(相对于目标元素)
});

page.mouse vs locator.click 的选择

方式适用场景
locator.click()点击页面元素(绝大多数场景)
page.mouse.click()点击特定坐标(Canvas、图表等无 DOM 的场景)

文件上传

locator.setInputFiles() 单文件/多文件

实例

// 上传单个文件
await page
  .getByLabel('选择文件')
  .setInputFiles('path/to/document.pdf');

// 上传多个文件
await page
  .getByLabel('选择文件')
  .setInputFiles([
    'path/to/file1.png',
    'path/to/file2.png',
  ]);

// 清除已选文件
await page.getByLabel('选择文件').setInputFiles([]);

文件路径是相对于当前工作目录(项目根目录)的路径。


文件下载

实例

// 开始等待下载事件(必须在触发下载的操作之前调用)
const downloadPromise = page.waitForEvent('download');

// 执行触发下载的操作
await page.getByRole('button', { name: '下载报告' }).click();

// 等待下载完成
const download = await downloadPromise;

// 获取建议的文件名
console.log(download.suggestedFilename());

// 保存到指定路径
await download.saveAs('downloads/report.pdf');