Playwright 第7章:高级页面操作
在掌握基础页面操作后,本章介绍 Playwright 的高级交互能力,包括键盘/鼠标事件、拖拽操作、滚动控制、下拉框选择、文件输入和 iframe 操作等。
键盘事件
Playwright 提供完整的键盘模拟能力,支持组合键、功能键和特殊键。
键盘基础操作
import { chromium } from 'playwright';
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
// 按下和释放单个键
await page.keyboard.press('Enter');
await page.keyboard.press('Escape');
await page.keyboard.press('Tab');
// 组合键
await page.keyboard.press('Control+A'); // 全选
await page.keyboard.press('Control+C'); // 复制
await page.keyboard.press('Control+V'); // 粘贴
await page.keyboard.press('Control+Shift+I'); // 打开开发者工具
// 逐字符输入
await page.keyboard.type('Hello World');
await page.keyboard.type('Hello', { delay: 50 }); // 带延迟输入
// 按下和释放分开控制
await page.keyboard.down('Shift');
await page.keyboard.press('KeyA');
await page.keyboard.press('KeyB');
await page.keyboard.up('Shift'); // 输出 "AB"
// 特殊键
await page.keyboard.press('ArrowDown');
await page.keyboard.press('ArrowUp');
await page.keyboard.press('PageDown');
await page.keyboard.press('Home');
await page.keyboard.press('End');
await page.keyboard.press('Delete');
await page.keyboard.press('Backspace');
await browser.close();
常用键盘快捷键
| 组合键 | 操作 |
|---|---|
Control+A | 全选 |
Control+C | 复制 |
Control+V | 粘贴 |
Control+X | 剪切 |
Control+Z | 撤销 |
Control+Shift+Z | 重做 |
Control+S | 保存 |
Control+F | 查找 |
鼠标事件
鼠标基础操作
const page = await browser.newPage();
await page.goto('https://example.com');
// 点击
await page.mouse.click(100, 200); // 左键点击坐标 (100, 200)
await page.mouse.click(100, 200, { button: 'right' }); // 右键
// 双击
await page.mouse.dblclick(100, 200);
// 移动鼠标
await page.mouse.move(0, 0);
await page.mouse.move(500, 300, { steps: 10 }); // 分 10 步移动(产生轨迹)
// 按下和释放
await page.mouse.down({ button: 'left' });
await page.mouse.move(300, 400, { steps: 5 });
await page.mouse.up({ button: 'left' });
// 滚动
await page.mouse.wheel(0, 500); // 向下滚动 500px
await page.mouse.wheel(0, -200); // 向上滚动 200px
拖拽操作
使用 dragAndDrop 方法
const page = await browser.newPage();
await page.goto('https://example.com/drag-drop');
// 方法一:dragAndrop(推荐)
const source = page.locator('.draggable-item');
const target = page.locator('.drop-zone');
await source.dragTo(target);
// 带选项的拖拽
await source.dragTo(target, {
sourcePosition: { x: 0, y: 0 }, // 源元素相对偏移
targetPosition: { x: 50, y: 50 }, // 目标元素相对偏移
force: true, // 强制拖拽
timeout: 5000,
});
// 方法二:手动拖拽(适用于特殊场景)
await page.mouse.move(100, 200);
await page.mouse.down();
await page.mouse.move(300, 400, { steps: 20 });
await page.mouse.up();
文件拖拽上传
// 模拟拖拽文件到上传区域
const uploadZone = page.locator('.upload-zone');
// 创建 DataTransfer 事件
await page.evaluate(() => {
const dropZone = document.querySelector('.upload-zone');
// 创建拖拽事件
const dragEnterEvent = new DragEvent('dragenter', {
dataTransfer: new DataTransfer(),
});
dropZone.dispatchEvent(dragEnterEvent);
const dropEvent = new DragEvent('drop', {
dataTransfer: new DataTransfer(),
});
dropZone.dispatchEvent(dropEvent);
});
// 直接使用 fileChooser(更可靠)
const [fileChooser] = await Promise.all([
page.waitForEvent('filechooser'),
page.locator('.upload-zone').click(),
]);
await fileChooser.setFiles('document.pdf');
滚动操作
const page = await browser.newPage();
await page.goto('https://example.com/long-page');
// 滚动到元素位置
await page.locator('#footer').scrollIntoViewIfNeeded();
// 使用 evaluate 滚动
await page.evaluate(() => {
window.scrollTo(0, document.body.scrollHeight); // 滚到底部
});
await page.evaluate(() => {
window.scrollTo(0, 0); // 滚到顶部
});
// 按像素滚动
await page.evaluate(() => {
window.scrollBy(0, 500); // 向下滚动 500px
});
// 滚动到特定元素
await page.locator('.load-more').scrollIntoViewIfNeeded();
await page.locator('.load-more').click();
// 水平滚动
await page.evaluate(() => {
window.scrollTo(500, 0); // 水平滚动 500px
});
下拉框高级选择
const page = await browser.newPage();
// 标准 select 元素
await page.selectOption('#city', 'beijing');
await page.selectOption('#city', { label: '北京' });
await page.selectOption('#city', { index: 1 });
// 多选 select
await page.selectOption('#hobbies', ['reading', 'music']);
// 自定义下拉框(非 select 元素)
await page.click('.custom-dropdown');
await page.click('.dropdown-option[data-value="option1"]');
// 搜索选择(带搜索框的下拉框)
await page.click('.search-select');
await page.fill('.search-input', '北京');
await page.click('.search-result:first-child');
// 验证选择结果
const selectedValue = await page.locator('#city').inputValue();
console.log('选中值:', selectedValue);
文件输入
fileChooser 处理
const page = await browser.newPage();
// 方法一:直接设置输入文件(最可靠)
await page.setInputFiles('#file-input', 'document.pdf');
// 多个文件
await page.setInputFiles('#file-input', [
'file1.pdf',
'file2.jpg',
'file3.png',
]);
// 方法二:通过 fileChooser 事件
const [fileChooser] = await Promise.all([
page.waitForEvent('filechooser'),
page.click('#upload-btn'),
]);
// 设置文件
await fileChooser.setFiles('photo.jpg');
// 多个文件
await fileChooser.setFiles(['photo1.jpg', 'photo2.png']);
// 取消文件选择
await fileChooser.cancel();
// 检查文件选择器状态
console.log('是否选择多个:', fileChooser.isMultiple());
iframe 操作
处理嵌入式 iframe
const page = await browser.newPage();
await page.goto('https://example.com/with-iframe');
// 方法一:通过 locator 获取 iframe
const iframe = page.frameLocator('#embedded-iframe');
// 在 iframe 中操作
await iframe.locator('#username').fill('admin');
await iframe.locator('.submit-btn').click();
const text = await iframe.locator('.result').textContent();
console.log('iframe 内容:', text);
// 方法二:通过 frame 对象
const frame = page.frame({ name: 'iframe-name' });
// 或
const frame2 = page.frame({ url: '**/iframe-content.html' });
if (frame) {
await frame.fill('#email', '[email protected]');
await frame.click('button');
}
// 获取 iframe 内的标题
const frameTitle = await iframe.locator('h1').textContent();
// 等待 iframe 加载完成
await iframe.locator('.content').waitFor({ state: 'visible' });
// 多层 iframe 嵌套
const innerIframe = iframe.frameLocator('.inner-iframe');
await innerIframe.locator('.inner-button').click();
// 获取所有 iframe
const allFrames = page.frames();
console.log('页面框架数:', allFrames.length);
for (const f of allFrames) {
console.log('框架URL:', f.url());
}
弹窗处理
const page = await browser.newPage();
// 处理 alert/confirm/prompt 弹窗
page.on('dialog', async (dialog) => {
console.log('弹窗类型:', dialog.type());
console.log('弹窗消息:', dialog.message());
switch (dialog.type()) {
case 'alert':
await dialog.accept();
break;
case 'confirm':
await dialog.accept(); // 确认
// await dialog.dismiss(); // 取消
break;
case 'prompt':
await dialog.accept('输入的值');
break;
case 'beforeunload':
await dialog.accept();
break;
}
});
// 触发弹窗
await page.click('#show-alert');
await page.click('#show-confirm');
await page.click('#show-prompt');
总结
高级页面操作让 Playwright 能够处理复杂的交互场景。键盘和鼠标事件提供了底层的操作能力,拖拽操作适用于 Web 应用的拖拽功能测试。iframe 操作覆盖了嵌套页面场景,弹窗处理使自动化脚本能够应对各种对话框。在实际项目中,建议根据场景选择最合适的操作方法,优先使用高层次的 API(如 dragTo 而非手动鼠标事件),以获得更好的可靠性。