Playwright 已经成为现代 Web 自动化测试的利器,其基础用法可以轻松处理大部分常见场景。然而,当我们面对复杂的前端应用时,诸如 嵌套的 iframe、棘手文件上传、需要模拟复杂用户键盘交互 等高级挑战便会浮现。掌握这些高级技巧,意味着你能解决 90% 以上的复杂自动化难题。

本文将深入探讨这三项高级用法,带你突破瓶颈,全面提升自动化测试的深度和可靠性。


一、突破iframe壁垒:进入框架内的世界

Iframe(内联框架)如同网页中的“孤岛”,传统的自动化工具很难直接与其内部元素交互。Playwright 提供了清晰且强大的方式来穿透这层壁垒。

核心概念:帧(Frame)对象

Playwright 将每个 iframe 视为一个独立的 Frame 对象。要与 iframe 内的元素交互,你必须先获取到这个 Frame 对象,然后在这个 Frame 的上下文中进行定位和操作。

实战步骤:三步进入iframe

假设有一个嵌入在线编辑器的页面,其 iframe 的 name 属性为 rich-text-editor。

步骤 1:定位并获取 Frame 对象

有几种方式可以获取 Frame 对象,推荐使用 name 或 URL 匹配,因为它们最稳定。

import { test, expect } from '@playwright/test';

test('与 iframe 中的富文本编辑器交互', async ({ page }) => {
  await page.goto('https://example.com/page-with-editor');

  // 方法一:通过 name 属性获取(最常用)
  const frame = page.frame({ name: 'rich-text-editor' });

  // 方法二:通过 URL 匹配获取(如果 name 不稳定,但 URL 已知)
  // const frame = page.frame({ url: /.*rich-text.*/ });

  // 方法三:通过选择器获取 iframe 元素,再取其 contentFrame
  // const iframeElement = page.locator('iframe.rich-editor');
  // const frame = await iframeElement.contentFrame();

  // 重要:检查是否成功获取到 frame
  if (!frame) {
    throw new Error('未找到指定的 iframe!');
  }
步骤 2:在 Frame 上下文中操作元素

获取到 frame 对象后,你可以像使用 page 对象一样使用它,所有定位和操作都在这个 iframe 内部进行。

  // 在 frame 内定位输入框并输入内容
  await frame.locator('body#editor').click(); // 点击编辑器获得焦点
  await frame.locator('body#editor').fill('你好,来自 Playwright 的文本!');

  // 在 frame 内点击按钮,比如加粗
  await frame.locator('button[title="Bold"]').click();
步骤 3:必要时切回主页面

在 frame 内操作完成后,后续如果要与主页面的元素交互,不需要任何“切回”操作。因为 Playwright 的上下文是明确的:当你使用 page 时,就是在主页面;当你使用 frame 时,就是在 iframe 内。

  // 继续与主页面元素交互,例如点击“提交”按钮
  await page.locator('button#submit-post').click();

  // 断言主页面上出现了成功提示
  await expect(page.locator('.alert-success')).toBeVisible();
});
处理嵌套 iframe

如果 iframe 内部还有 iframe,只需逐级获取即可。

const parentFrame = page.frame({ name: 'parent-frame' });
const childFrame = parentFrame.frame({ name: 'child-frame' });
await childFrame.locator('button').click();

二、驯服文件上传:从简单到复杂

文件上传是 Web 自动化中的另一个常见痛点。Playwright 提供了多种方法来稳健地处理它。

方法一:使用 setInputFiles(最常用、最推荐)

这是处理 input[type="file"] 元素最简单、最可靠的方法。

// 单个文件上传
await page.locator('input[type="file"]').setInputFiles('/path/to/your/file.pdf');

// 多个文件上传
await page.locator('input[type="file"]').setInputFiles([
  '/path/to/file1.pdf',
  '/path/to/file2.jpg',
]);
方法二:监听 filechooser 事件(用于非输入框触发上传)

很多现代应用通过点击一个按钮或拖拽区域来触发文件选择对话框。对于这种情况,需要监听 filechooser 事件。

test('通过按钮触发文件上传', async ({ page }) => {
  await page.goto('https://example.com/upload');

  // 1. 在点击按钮之前,先设置对 filechooser 事件的监听
  const fileChooserPromise = page.waitForEvent('filechooser');

  // 2. 执行会触发文件选择器的操作(如点击“上传”按钮)
  await page.locator('button#upload-button').click();

  // 3. 等待文件选择器事件发生,并获取 FileChooser 对象
  const fileChooser = await fileChooserPromise;

  // 4. 在文件选择器中设置文件路径
  await fileChooser.setFiles('/path/to/your/file.pdf');

  // 5. 后续断言上传是否成功
  await expect(page.locator('.upload-status')).toHaveText('上传成功');
});
方法三:模拟拖放上传

对于拖拽上传区域,Playwright 提供了 dragAndDrop 方法,但更底层且可靠的方式是直接分派 DOM 事件。

// 更可控的拖拽上传模拟
async function simulateDragAndDrop(page, filePath, dropSelector) {
  // 创建文件列表数据的 DataTransfer 对象(Playwright 内部提供)
  const dataTransfer = await page.evaluateHandle((file) => {
    const dt = new DataTransfer();
    dt.items.add(new File([file], 'test-file.pdf', { type: 'application/pdf' }));
    return dt;
  }, filePath);

  // 分派 dragover 和 drop 事件
  await page.dispatchEvent(dropSelector, 'dragover', { dataTransfer });
  await page.dispatchEvent(dropSelector, 'drop', { dataTransfer });
}

// 在测试中使用
await simulateDragAndDrop(page, '/path/to/file.pdf', '.drop-zone');

三、掌握精细键盘操作:超越 page.type()

虽然 page.fill() 和 page.type() 能满足大多数输入需求,但模拟复杂的键盘交互(如快捷键、组合键)需要更强大的工具。

核心方法:keyboard API

通过 page.keyboard 对象,你可以精细控制每一个按键。

常见场景示例

1. 输入特殊按键

await page.locator('input').press('Tab'); // 按 Tab 键切换焦点
await page.locator('input').press('Shift+ArrowLeft'); // 按住 Shift 并按左箭头
await page.locator('body').press('Control+A'); // 全选 (Mac 上是 Control, 通常是 Command+A,需注意)
// 更准确的 Mac 快捷键处理
await page.locator('body').press(process.platform === 'darwin' ? 'Meta+A' : 'Control+A');

2. 执行连续组合操作(如复制粘贴)

await page.locator('#source').fill('要复制的文本');
await page.locator('#source').selectText(); // 假设有这个自定义函数,或者用 click 加 keyboard 操作

// 更可靠的方式:用 keyboard API 模拟全选、复制、粘贴
await page.locator('#source').click();
await page.keyboard.press('Control+A'); // 全选
await page.keyboard.press('Control+C'); // 复制

await page.locator('#target').click();
await page.keyboard.press('Control+V'); // 粘贴

3. 逐字符输入并模拟延迟(像真人一样打字)

await page.locator('input').focus();
await page.keyboard.type('Hello World', { delay: 100 }); // 每个字符间隔 100 毫秒

4. 按下不放与释放(用于复杂交互)

这是最底层的控制,可以实现诸如“按住 Shift 键选择多个项目”的效果。

await page.keyboard.down('Shift'); // 按下 Shift 键不放

// 执行一些操作,比如点击多个项目
await page.locator('.item:nth-child(1)').click();
await page.locator('.item:nth-child(3)').click();

await page.keyboard.up('Shift'); // 释放 Shift 键

总结与最佳实践

技术难点 Playwright 解决方案 关键要点
Iframe 操作 使用 page.frame() 获取 Frame 对象 上下文切换是核心。在 Frame 对象上操作内部元素,在 Page 对象上操作外部元素。
文件上传 1. setInputFiles (首选)
2. waitForEvent('filechooser') (用于非输入框)
区分是原生 input 还是自定义上传组件,选择对应方法。
精细键盘操作 使用 page.keyboard API (pressdownuptype) 对于组合键,使用 + 连接。对于需要保持按下的场景,使用 down() 和 up()

高级技巧融合示例:在 iframe 的富文本编辑器中使用快捷键

test('在 iframe 编辑器中使用快捷键', async ({ page }) => {
  await page.goto('...');
  const frame = page.frame({ name: 'editor' });
  
  // 在 iframe 中输入内容
  await frame.locator('body').fill('一些文本');
  
  // 在 iframe 中执行全选(需要聚焦在 iframe 内)
  await frame.locator('body').click();
  // 确保快捷键作用于 iframe 内部
  await frame.keyboard.press('Control+A'); 
  await frame.keyboard.press('Control+B'); // 加粗

  // 回到主页面保存
  await page.click('button#save');
});

通过熟练掌握这三项高级用法,你的 Playwright 自动化脚本将无所不能,能够应对各种复杂、真实的 Web 应用场景,极大地提升自动化测试的覆盖率和可靠性。

Logo

「智能机器人开发者大赛」官方平台,致力于为开发者和参赛选手提供赛事技术指导、行业标准解读及团队实战案例解析;聚焦智能机器人开发全栈技术闭环,助力开发者攻克技术瓶颈,促进软硬件集成、场景应用及商业化落地的深度研讨。 加入智能机器人开发者社区iRobot Developer,与全球极客并肩突破技术边界,定义机器人开发的未来范式!

更多推荐