全栈修行手册修习心得 · 随记成卷 订阅手记
慢慢写,认真生活。 / NOTES ON THE EVERYDAY

告别重复点击:用 Playwright 自动操作网页

前端开发 AZ 3浏览

检查页面、填写表单、核对数据、截图留档,这些工作常常重复发生。

手工操作容易遗漏,临时录制的脚本又容易在页面改版后失效:按钮位置变了,加载慢了一点,或者登录过期了,后面的步骤就不再可信。

Playwright 是一个面向网页测试和自动化的开源项目,可以通过统一 API 操作 Chromium、Firefox 和 WebKit。

它的价值不只是“替人点鼠标”,而是把浏览器操作、状态判断和结果证据组织成可重复执行的程序。

本文使用 Node.js / TypeScript 与 Playwright Test 举例。代码包含本地模拟页面,不登录真实商城、不提交真实订单。脚本是教学示例,不是已完成的生产项目或跨浏览器验收报告。

一、哪些工作适合做网页自动化?

可以从规则明确、重复频繁、结果可核对的小任务开始:

  • 网站改版后的导航、搜索和详情页回归。
  • 多个尺寸下的页面截图与布局检查。
  • 测试环境的表单校验和业务流程验证。
  • 经授权的后台数据核对、文件下载和例行操作。
  • 对没有合适接口的系统做有限、可控的页面操作。

如果系统已经提供稳定 API,数据读写通常应优先考虑 API;浏览器更适合验证用户真正看到和操作的界面。

验证码绕过、未授权采集、批量骚扰和规避站点限制,不应成为自动化脚本的正常目标。

二、Playwright Library 和 Playwright Test 怎么选?

两者有关联,但用途不完全相同。

  • Playwright Library 提供浏览器控制能力,适合嵌入已有程序或编写独立自动化任务。
  • Playwright Test 是测试框架,在浏览器操作之外提供测试组织、断言、隔离、报告、重试和追踪等能力。

下面的示例用 Playwright Test,因为它便于说明“操作完成后如何验证”,也方便按不同设备配置运行。

不要把所有自动化任务都硬写成测试,也不要为了一个临时操作就省略结果验证。形式可以不同,边界和证据不能少。

三、先搭一个最小环境

在空目录中执行官方初始化命令:

npm init playwright@latest

按提示选择 TypeScript。浏览器可以在初始化时安装,也可以稍后安装,例如只安装本例需要的 Chromium:

npx playwright install chromium

Node.js 版本、操作系统依赖和浏览器支持范围以当前官方文档为准。Linux 环境还可能需要相应系统依赖,不宜把一台电脑上的安装步骤当成所有机器的完整方案。

项目可以先保持简单:

playwright.config.ts
tests/
└── product.spec.ts

下面是一个配置示例。它只使用 Chromium,分别模拟桌面与手机视口:

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  timeout: 30_000,
  retries: 0,
  reporter: 'html',
  use: {
    trace: 'retain-on-failure',
    screenshot: 'only-on-failure',
  },
  projects: [
    {
      name: 'desktop-chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'mobile-chromium',
      use: { ...devices['Pixel 7'] },
    },
  ],
});

设备模拟不等于真机测试,使用 Chromium 也不代表已经验证 Firefox、WebKit 或 iPhone Safari。需要相应覆盖时,应另行配置、安装浏览器并执行检查。

四、先写一个不会碰真实业务的脚本

下面通过请求拦截返回一段模拟商品页。shop.test 是测试地址,这个页面的 HTML 由脚本提供,不依赖真实商城。

保存为 tests/product.spec.ts:

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

const demoPage = `
<!doctype html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>商品预览示例</title>
  <style>
    body { margin: 24px; font: 16px/1.8 sans-serif; }
    main { max-width: 600px; }
    label { display: block; margin: 12px 0; }
    input, select, button { font: inherit; max-width: 100%; }
  </style>
</head>
<body>
  <main>
    <h1>帆布收纳袋</h1>
    <label>颜色
      <select id="color">
        <option value="natural">原色</option>
        <option value="green">深绿色</option>
      </select>
    </label>
    <label>数量
      <input id="quantity" type="number" min="1" max="10" value="1">
    </label>
    <button type="button" id="preview">预览订单</button>
    <p role="status">等待选择</p>
  </main>
  <script>
    document.querySelector('#preview').addEventListener('click', () => {
      const color = document.querySelector('#color');
      const quantity = document.querySelector('#quantity');
      document.querySelector('[role="status"]').textContent =
        color.options[color.selectedIndex].text + ' × ' + quantity.value;
    });
  </script>
</body>
</html>
`;

test('选择规格后生成正确预览', async ({ page }, testInfo) => {
  await page.route('https://shop.test/product/demo', async route => {
    await route.fulfill({
      status: 200,
      contentType: 'text/html; charset=utf-8',
      body: demoPage,
    });
  });

  await page.goto('https://shop.test/product/demo');
  await expect(page.getByRole('heading', { name: '帆布收纳袋' }))
    .toBeVisible();

  await page.getByLabel('颜色').selectOption({ label: '深绿色' });
  await page.getByLabel('数量').fill('2');
  await page.getByRole('button', { name: '预览订单' }).click();

  await expect(page.getByRole('status')).toHaveText('深绿色 × 2');

  await page.screenshot({
    path: testInfo.outputPath('product-preview.png'),
    fullPage: true,
  });
});

运行测试和打开报告:

npx playwright test
npx playwright show-report

这个例子验证了“选择规格 → 触发预览 → 检查结果”的闭环。配置中有两个项目,因此会分别按桌面和手机配置执行。

它没有验证真实库存、支付或后台接口;截图也只是留存证据,不是自动视觉差异比对。正式项目需要接入实际页面并补齐对应断言。

五、脚本稳定,首先靠定位和等待

1. 优先使用用户能理解的定位方式

例如:

page.getByRole('button', { name: '保存设置', exact: true });
page.getByLabel('邮箱');
page.getByTestId('order-summary');

角色、可访问名称和明确的测试标识,通常比一长串 DOM 路径更容易维护。

像 div:nth-child(3) > div:nth-child(2) > button 这样的选择器,可能在增加一块提示信息后就失效。

页面有多个同名按钮时,应缩小到明确区域,不要随手加 .first() 掩盖定位歧义。

2. 等待状态,不要靠固定睡眠碰运气

不建议把流程写成:

await page.waitForTimeout(3000);

然后假设数据已经加载。

Playwright 会在执行相关动作前检查可操作条件,定位器也能在使用时重新查找元素。但它不知道你的业务何时完成,所以仍需要明确断言:

await expect(page.getByRole('status')).toHaveText('保存成功');

这种 Web 断言会在超时范围内重试检查。反过来,先取一次文本再用普通同步断言比较,不会自动获得同样的等待行为。

自动等待解决的是一部分时序问题,不会自动证明业务正确。

六、登录态、失败与定时任务要怎样处理?

登录态是凭证,不是普通测试附件

Playwright 可以保存和复用浏览器状态,例如:

await context.storageState({ path: 'playwright/.auth/user.json' });

这个动作应放在完成真实授权登录之后。状态可能包含可用于访问账户的 Cookie 或令牌,不能提交到公开仓库。

建议忽略:

playwright/.auth/
playwright-report/
test-results/

状态会过期,也不保证所有应用的认证信息都能按同一种方式恢复。正式脚本要检查登录是否有效,必要时重新授权;不要在失败后无限重试密码、验证码或二次验证。

失败重试不能代替故障定位

脚本失败后,优先保存 Trace、截图和必要日志,查清是定位错误、登录失效、业务失败还是环境问题。

报告与 Trace 也可能包含页面数据或敏感信息,应控制保存期限和访问权限。

对创建订单、发送消息等有副作用的操作,重试前要确认上次是否已经成功,必要时采用业务幂等标识和结果查询。不能把“再点一次”当成通用恢复策略。

定时执行是另一层能力

Playwright 负责浏览器流程,任务计划程序、CI 或其他调度器负责何时运行。

还需要考虑:运行账户、环境变量、工作目录、并发锁、超时、日志和告警。桌面上成功一次,不代表无人值守就能长期稳定执行。

七、接到定制需求,先问清这些事

“做一个自动操作网站的脚本”还不足以评估范围。

至少要确认:

  1. 目标系统是谁维护的,是否已获得自动化授权。
  2. 从哪个状态开始,到什么结果结束。
  3. 哪些操作只读,哪些会写入或对外发送。
  4. 是否有测试环境、专用账户和安全测试数据。
  5. 是否需要登录、验证码、下载或文件上传。
  6. 运行频率、浏览器与设备覆盖范围是什么。
  7. 失败后停止、重试还是转人工,证据保存在哪里。
  8. 页面改版、账户过期后由谁维护。

这不是额外的文书工作,而是在区分“能跑的演示”和“可以交付的工具”。

八、一份可靠的脚本,应该交付什么?

除了代码,建议至少包含:

  • 安装、浏览器依赖和运行说明。
  • 配置项与环境变量示例,不包含真实密码。
  • 输入输出定义,以及成功判定条件。
  • 正常、异常和权限边界的测试记录。
  • 截图、Trace 和报告的获取与清理方法。
  • 定位规则、依赖版本与维护说明。
  • 明确的操作授权范围和人工接管条件。

如果需要检查整站,也应按首页、列表、详情、表单等页面类型分组,而不是只打开首页截一张图就宣称验收完成。

写在最后

网页自动化最难的部分,往往不是如何点击,而是如何知道该点击哪个元素、何时继续,以及做完之后是否真的成功。

先选择一个可隔离的小流程,用稳定定位描述操作,用断言确认结果,用记录保留证据。再逐步加入登录、批量、异常处理和调度。

把“看起来跑完了”,变成“可以说明为什么成功、失败后也能定位”。 这才是 Playwright 脚本从演示走向交付的关键。

参考资料

以上示例用于说明设计方法。依赖、支持环境和认证行为请以使用版本的官方文档为准。

转载请注明:TUTERM.COM » 告别重复点击:用 Playwright 自动操作网页

这卷宗是你需要的?请宗主喝杯灵茶。

打赏宗主,支付宝(左)或微信(右)。

alipay weichat