[Playwright][08] Upload 模块自动化:如何设计真实业务测试用例?

上一篇介绍了 HPC Dashboard 自动化测试项目的整体情况,这一篇开始记录实际的测试开发过程。第一天,我下载了 Docker,把测试项目在本地跑了起来。

开发的第二天,我选择从 Upload 模块入手,使用 Playwright + TypeScript 编写 E2E 测试。

Upload 模块表面上只是上传文件、填写表单、点击提交,实际上还涉及 Job 创建、Recurring Job 管理,以及提交后在其他页面验证结果,因此我决定最先攻克它。

1. 从业务流程拆解测试场景

HPC Dashboard 主要用于管理和分析高性能计算任务。

用户首先需要上传任务相关的数据,系统才能创建 Job,并在其他页面展示任务信息和分析结果。

Upload 页面涉及两类文件:

  • PBS Log:任务的运行记录。
  • Metrics JSON:任务运行过程中的性能数据。

此外,用户还可以选择将 Job 关联到 Recurring Job:既可以创建新的 Recurring Job,也可以选择已有的 Recurring Job。

根据这些业务流程,我设计了五条测试:

用例 测试场景 预期结果
Test1 只上传 Job Log,缺少 Metrics JSON Submit 按钮禁用
Test2 两份文件都已上传,但没有填写 Job Name Submit 按钮禁用
Test3 提交普通 Job 提交成功,Jobs 页面能找到新 Job
Test4 提交 Job,同时创建新的 Recurring Job 提交成功,Profile 页面能找到 Recurring Job
Test5 创建 Recurring Job,再将第二个 Job 关联到它 Recurring Analysis 页面显示 2 runs

这里既包含表单校验 ,也包含跨页面业务验证。

尤其是后三条,我不希望测试只停留在"点击 Submit 按钮"这一步,而是继续检查数据是否出现在对应的业务页面中。

2. 为什么这样设计测试用例?

设计这五条用例时,我主要考虑三个方面:

1. 正向与反向测试(Positive / Negative Testing)

Test1、Test2 验证缺少必填信息时无法提交;Test3--5 覆盖正常提交和 Recurring Job 业务流程。

2. 验证业务结果,而不只是点击成功

普通 Job 提交后,在 Jobs 页面检查任务是否出现;创建 Recurring Job 后,在 Profile 页面验证;关联第二个 Job 后,在 Recurring Analysis 页面检查是否显示 2 runs。

3. 优先覆盖核心流程(Risk-Based Testing)

先覆盖表单校验、普通 Job 创建和 Recurring Job 关联等主要场景,再逐步补充异常输入、错误处理和边界条件。

这五条用例是第一阶段的核心业务覆盖,并不代表完整的测试覆盖率。后续还需要完善测试稳定性、数据清理和更多异常场景。

2. 先封装重复的文件上传操作

五条测试中,多次需要上传同样的两份文件,因此我先把这个操作提取为 uploadFiles()。

ts 复制代码
//nth(0):选择第一个上传控件,索引从 0 开始。
//nth(1):选择第二个上传控件。
async function uploadFiles(page: Page) {
  const fileInputs = page.locator('input[type="file"]');

  await fileInputs.nth(0).setInputFiles(
    'test-data/demo_job_12.txt'
  );

  await fileInputs.nth(1).setInputFiles(
    'test-data/demo_metrics_13.jsonl'
  );
}

这里使用了 Playwright 的 setInputFiles() 方法。

页面存在两个文件上传控件,因此通过 nth(0) 和 nth(1) 分别指定文件。

这种写法比较直接,但依赖两个上传控件的排列顺序。一旦页面结构发生变化,后续可以考虑改用更明确的定位方式。

3. 验证必填项:不满足条件时禁止提交

前两条测试属于表单校验。

例如 Test1,只上传第一份文件,不上传 Metrics JSON:

ts 复制代码
test('Test1: Submit is disabled without Metrics JSON', async ({ page }) => {
  await openUploadPage(page);

  const fileInputs = page.locator('input[type="file"]');

  await fileInputs.nth(0).setInputFiles(
    'test-data/demo_job_12.txt'
  );

  await expect(
    page.getByRole('button', { name: 'Submit Job data' })
  ).toBeDisabled();
});

Test2 则上传两份文件,但不填写 Job Name:

ts 复制代码
test('Test2: Submit disabled without Job Name', async ({ page }) => {
  await openUploadPage(page);
  await uploadFiles(page);

  await expect(
    page.getByRole('button', { name: 'Submit Job data' })
  ).toBeDisabled();
});

这两条测试的重点并不在于验证错误提示,而在于确认表单不完整时,提交按钮确实处于禁用状态。

4. 提交成功后,继续验证业务结果

对于正常提交,我封装了 submitJob(),统一处理 Job Name、Recurring Job 选项和提交操作。

其中一个关键断言是:

ts 复制代码
const submitButton = page.getByRole('button', {
  name: 'Submit Job data'
});

await expect(submitButton).toBeEnabled();
await submitButton.click();

await expect(
  page.getByText('Success! Stored job successfully', {
    exact: false
  })
).toBeVisible({ timeout: 15000 });

这里验证了提交按钮可用,以及提交后出现成功消息。

但成功提示并不能完全代替后续的业务验证。

因此,在 Test3 中,我还会进入 Jobs 页面,确认刚创建的 Job 可以被找到:

ts 复制代码
await page.getByText('Jobs', { exact: true }).click();

await expect(
  page.getByText(jobName, { exact: true })
).toBeVisible({ timeout: 1000 });

为了避免测试数据名称重复,我使用时间戳生成 Job Name:

ts 复制代码
const jobName = `playwright-job-${Date.now()}`;

Test4 采用类似思路,只不过提交时还会创建新的 Recurring Job,随后进入 Profile 页面检查对应名称是否出现。

5. Test5:验证两个 Job 关联到同一个 Recurring Job

五条测试中,Test5 的业务流程相对复杂。

它需要先创建一个新的 Recurring Job,然后再次上传文件,将第二个 Job 关联到刚创建的 Recurring Job。

调用公共方法时,通过 mode 区分新建和选择已有对象:

ts 复制代码
await submitJob(page, jobName, {
  name: recurringName,
  mode: 'new'
});

// 第二次上传
await uploadFiles(page);

await submitJob(page, jobName2, {
  name: recurringName,
  mode: 'existing'
});

第二次提交后,测试进入 Recurring Analysis 页面,找到对应的 Recurring Job 卡片,并验证运行次数。

ts 复制代码
await page.getByText('Recurring Analysis', {
  exact: true
}).click();

const card = page.locator('article').filter({
  has: page.getByRole('heading', {
    name: recurringName,
    exact: true
  })
});

await expect(
  card.getByText('2 runs', { exact: true })
).toBeVisible();

这里使用 filter({ has: ... }),先根据标题找到目标卡片,再在卡片内部验证 2 runs。

这样可以避免页面上存在多个 Recurring Job 时,错误地检查其他卡片中的运行次数。

相比只验证提交成功消息,这条测试进一步验证了两个 Job 是否被归入同一个 Recurring Job。

6. 第一批测试带来的问题

Day 2 结束时,五条 Upload 测试都在本地通过了。

但在开发过程中,也暴露出一些值得继续改进的问题:

  • Locator 精确性:部分定位方式会匹配多个元素,需要限定范围。
  • 等待方式 :代码中使用了 waitForTimeout(1000),后续需要考虑用具体的页面状态代替固定等待。
  • 测试数据:每次提交都会生成 Job 或 Recurring Job,后续需要统一管理和清理。
  • 执行稳定性 :早期测试执行不够稳定,因此暂时关闭了 fullyParallel,并继续排查原因。

这些问题当时没有一次性全部解决,而是在后续几天逐步完善。

小结

这次 Upload 模块自动化让我对 E2E 测试设计有了更具体的认识。

测试不能只证明操作被执行,还应该验证操作产生的业务结果。

从表单必填项,到 Job 提交,再到多个 Job 与同一个 Recurring Job 的关联,这五条用例逐步覆盖了 Upload 模块的主要业务流程。

下一篇,我会继续记录开发中遇到的 Locator 问题,以及如何在复杂页面中准确找到需要操作的元素。

相关推荐
迪菲赫尔曼3 小时前
【基础篇 12】把环境与源码接起来:检查 GPU,再安装服务器上的项目
运维·服务器·开发语言·人工智能·python·yolo·目标检测
cq林志炫3 小时前
两个Linux服务器之间同步文件
linux·运维·服务器
yyuuuzz4 小时前
云服务器安全组规则优先级的机制记录
运维·服务器·网络·安全·阿里云·aws
KaiwuDB4 小时前
KaiwuDB 运维实战06:数据库备份与恢复:从理论到实战
运维·数据库·时序数据库·kaiwudb
知已难寻4 小时前
01 · OpenStack的认证管理 Keystone
运维·服务器·云原生·云计算·openstack
ai_xiaogui5 小时前
私有化部署大模型实战:PanelAI 一键搭建、Token 计费与多用户隔离
运维·私有化部署·大模型部署·大模型 token 计费·api key 统一管理·多用户实例隔离·应用市场一键部署
企鹅侠客5 小时前
Linux CPU 负载平均值(Load Average)详解:从原理到实践
linux·运维·服务器·服务器负载
易点云徐源5 小时前
HP 暗影精灵 PRO 16 安装双系统后,开机直接进入 Windows,按 F9 才能进入 Ubuntu:GRUB 菜单不显示的处理方法
linux·运维·ubuntu
木雷坞5 小时前
Baserow Docker部署:导入设备借用CSV,再用表单新增登记
运维·docker·容器