简介:这份资源面向具备Java与Vue基础的中高级研发人员,尤其是从事测试平台开发、质量保障与自动化测试框架设计的技术人员,围绕端到端自动化测试平台的设计与实现展开,解决复杂业务系统回归测试效率低、失败定位难、测试资产分散等痛点,可支撑CI/CD质量门禁与测试流程标准化。资源包为1个docx文档,压缩包约135KB,内容涵盖项目背景、分层架构模型、测试用例与任务调度模型、变量作用域与断言报告机制,并给出任务状态枚举、变量解析器、接口请求执行器、断言处理器、任务执行服务等核心代码示例,以及数据库表设计、API规范、前后端交互逻辑与部署应用方案。已有92人学习,读者可据此掌握页面与接口混合编排、任务调度、变量解析、失败追踪等关键机制,并基于完整项目实例进行二次开发或功能扩展。
1. 为什么端到端自动化测试平台需要 Java 与 Vue 分工
很多团队做自动化测试的起点是一堆散落的脚本:Selenium 用例写在 IDE 里,Appium 脚本靠某台机器上的环境变量跑通,测试报告是本地生成的 HTML 文件,谁想看谁去拷。用例一多,问题就暴露了——脚本没有统一入口,执行结果没有集中存储,测试数据散落在各个配置文件里,新人接手要先花两天配环境。端到端自动化测试平台要解决的就是这件事:把用例管理、任务调度、执行引擎、结果报告、通知告警串成一条流水线。
选 Java 做后端、Vue 做前端,是这类平台里最常见也最稳的组合。Java 侧有成熟的 Selenium、Appium、TestNG、RestAssured 生态,线程池和定时任务调度能力足够撑住并发执行;Vue 侧用组件化方式把用例列表、执行看板、报告详情拆开,配合 Element Plus 这类 UI 库能快速搭出可用的管理界面。这套组合的读者画像很清晰:会写 Java 接口自动化测试框架、能看懂 Vue 路由和组件通信、想把零散脚本升级成平台的中级测试开发,以及需要给团队交付一套可维护测试基础设施的技术负责人。
平台的核心链路是「前端下任务 → 后端调度 → 执行引擎跑用例 → 结果回写数据库 → 前端轮询或推送展示」。这条链路里每个环节都有坑,后面几章会按「数据模型怎么设计、执行引擎怎么跑、前后端怎么对接、报告怎么聚合、并发和稳定性怎么保」的顺序拆开讲。
2. 平台数据模型与 Java 后端骨架设计
2.1 用例、任务、执行记录三张核心表怎么建
平台的数据模型决定了后面所有功能的扩展性。最忌讳的是把用例内容直接塞进一个 text 字段,导致无法按标签、模块、优先级检索。常见做法是拆成三张主表加两张关联表。
| 表名 | 作用 | 关键字段 |
|---|---|---|
test_case | 用例元信息 | id, name, module, priority, script_path, tags, status |
test_task | 一次执行任务 | id, name, case_ids, env, cron_expr, creator, status |
test_report | 执行结果汇总 | id, task_id, total, passed, failed, skipped, duration, start_time |
case_step | 用例步骤明细 | id, case_id, step_no, action, locator, value |
report_detail | 单条用例结果 | id, report_id, case_id, result, log, screenshot |
test_case里script_path存的是脚本在仓库中的相对路径,而不是脚本内容本身。这样做的好处是脚本可以用 Git 管理,平台只负责调度和记录,脚本更新走代码评审流程,不会因为有人在页面上改了几行就把用例改坏。test_task的case_ids用 JSON 数组存,配合 MySQL 5.7 以上的 JSON 类型可以直接用JSON_CONTAINS查询,比再建一张关联表更轻。
2.2 Spring Boot 分层结构与执行引擎入口
后端用 Spring Boot 分四层:controller 接请求、service 做业务编排、engine 做执行调度、mapper 做持久化。执行引擎是核心,它要解决「一个任务里的多条用例怎么并发跑、跑完怎么汇总」的问题。
@Service public class ExecutionEngine { // 固定大小线程池,大小按机器核数和用例类型调整 private final ExecutorService pool = Executors.newFixedThreadPool(8); @Autowired private CaseRunner caseRunner; public void runTask(Long taskId, List<Long> caseIds) { List<Future<CaseResult>> futures = new ArrayList<>(); for (Long caseId : caseIds) { // 每条用例提交一个异步任务,互不阻塞 futures.add(pool.submit(() -> caseRunner.run(caseId))); } List<CaseResult> results = new ArrayList<>(); for (Future<CaseResult> f : futures) { try { // 设置超时,防止单条用例卡死拖垮整个任务 results.add(f.get(5, TimeUnit.MINUTES)); } catch (TimeoutException e) { results.add(CaseResult.timeout()); } catch (Exception e) { results.add(CaseResult.error(e.getMessage())); } } reportService.save(taskId, results); } }这段代码的关键点有三个。第一,线程池大小不是越大越好,Selenium 每条用例会起一个浏览器实例,8 个并发在 4 核 8G 的机器上已经接近上限,再大容易 OOM。第二,f.get(5, TimeUnit.MINUTES)的超时是必须的,UI 自动化里元素等待、页面加载都可能卡住,没有超时保护整个任务会挂死。第三,结果收集用Future列表而不是CompletionService,是因为报告需要按用例顺序展示,顺序比吞吐更重要。
2.3 用例执行器如何封装 Selenium 与 Appium
CaseRunner要做的是把数据库里的用例步骤翻译成具体操作。UI 用例走 Selenium,接口用例走 RestAssured,移动端走 Appium,用策略模式按case_type分发。
public CaseResult run(Long caseId) { TestCase tc = caseMapper.selectById(caseId); WebDriver driver = null; try { if ("WEB".equals(tc.getType())) { driver = DriverFactory.createChrome(); for (CaseStep step : stepMapper.listByCaseId(caseId)) { // 按步骤类型执行:click / input / assert StepExecutor.execute(driver, step); } } else if ("API".equals(tc.getType())) { ApiExecutor.execute(tc); } return CaseResult.pass(caseId); } catch (AssertionError e) { return CaseResult.fail(caseId, e.getMessage()); } finally { // 无论成功失败都要释放浏览器,否则进程会堆积 if (driver != null) driver.quit(); } }DriverFactory里建议用 ThreadLocal 存 driver,避免并发时多条用例抢同一个实例。finally里的driver.quit()不能省,很多团队跑一段时间发现机器内存被吃满,就是因为异常路径下没释放浏览器进程。
提示:Chrome 用 headless 模式跑 CI 能省资源,但部分页面在 headless 下渲染行为和真实浏览器有差异,断言前最好加显式等待而不是固定 sleep。
3. Vue 前端如何对接执行接口与实时展示结果
3.1 用例列表与任务下发页面的组件拆分
前端不要把所有逻辑堆在一个页面里。按功能拆成CaseList、TaskForm、ReportView三个主组件,用 Vue Router 做路由,用 Pinia 管全局状态。用例列表用 Element Plus 的el-table加服务端分页,避免一次性拉几千条数据把浏览器卡死。
// api/task.js import request from '@/utils/request' export function createTask(data) { // 提交任务,后端返回 taskId return request.post('/api/task/create', data) } export function getReport(taskId) { // 轮询报告状态,直到 status 为 FINISHED return request.get(`/api/report/${taskId}`) }request是封装了 axios 的实例,统一处理 token 和错误码。任务下发时前端只传caseIds、env、cronExpr三个字段,具体怎么跑由后端决定,前端不掺和业务逻辑。
3.2 用轮询还是 WebSocket 拿执行进度
执行进度展示有两种做法。轮询简单,前端每 3 秒调一次getReport,后端返回当前已完成的用例数和状态;WebSocket 实时性好,但要处理断线重连和消息顺序。中小团队建议先用轮询,实现成本低,出问题好排查。
// 轮询逻辑,组件卸载时清除定时器 startPolling(taskId) { this.timer = setInterval(async () => { const res = await getReport(taskId) this.report = res.data if (res.data.status === 'FINISHED' || res.data.status === 'FAILED') { clearInterval(this.timer) } }, 3000) }轮询间隔别设太短,1 秒一次在任务多的时候会给后端造成不必要的压力。3 到 5 秒是常见取值。组件beforeUnmount里一定要clearInterval,否则用户切走页面后定时器还在跑,内存泄漏就是这么来的。
3.3 报告详情页的失败截图与日志展示
报告详情是测试同学看得最多的页面。每条失败用例要能直接看到错误日志和截图,而不是让人去服务器上翻文件。后端把截图存到对象存储或本地静态目录,数据库只存 URL,前端用el-image加预览功能展示。
| 展示项 | 数据来源 | 前端组件 |
|---|---|---|
| 用例名称 | report_detail.case_id 关联 | el-table-column |
| 执行结果 | report_detail.result | el-tag 按状态着色 |
| 错误日志 | report_detail.log | el-collapse 折叠展示 |
| 失败截图 | report_detail.screenshot | el-image 带 preview |
日志字段可能很长,用折叠面板默认收起,点击展开。截图用懒加载,避免一次渲染几十张图拖慢页面。
4. 并发执行、环境隔离与常见故障排查
4.1 多任务并发时数据库连接池怎么配
平台上线后最容易出问题的地方是并发。多个任务同时跑,每个任务又开多条线程,数据库连接瞬间被抢光。HikariCP 的maximumPoolSize默认是 10,在并发执行场景下明显不够,但也不能无脑调大。
spring: datasource: hikari: maximum-pool-size: 30 # 按并发任务数 × 每任务连接数估算 minimum-idle: 10 connection-timeout: 3000 # 拿不到连接 3 秒就失败,快速暴露问题 max-lifetime: 1800000 # 30 分钟回收,避免连接被中间件断开maximum-pool-size的估算方式是:并发任务数 × 每个任务同时写库的线程数,再留 20% 余量。connection-timeout设短一点,让连接不够时快速报错,而不是请求全部堆在那里等,最后雪崩。
4.2 浏览器实例泄漏与端口占用的定位方法
跑一段时间后如果发现任务越来越慢,八成是浏览器进程没释放。排查步骤很直接:
# 查看当前机器上的 chrome 进程数 ps -ef | grep chrome | grep -v grep | wc -l # 查看被占用的调试端口 netstat -tlnp | grep 9222 # 批量清理残留进程(谨慎使用,确认没有正在跑的任务) pkill -f "chrome.*headless"根因通常是driver.quit()没在finally里执行,或者用例超时后线程被中断但浏览器没关。解决办法是在CaseRunner里加一层兜底:用try-with-resources或者注册 JVM 关闭钩子,在进程退出时统一清理。
4.3 用例不稳定(Flaky)的三种典型原因
端到端测试最让人头疼的是同一条用例这次过下次挂。常见原因有三类:一是元素定位用了不稳定的 XPath,页面结构一变就失效,建议优先用>// 不推荐:固定等待,慢且不可靠 Thread.sleep(3000); driver.findElement(By.id("submit")).click(); // 推荐:显式等待,条件满足立即继续 WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10)); wait.until(ExpectedConditions.elementToBeClickable(By.id("submit"))).click();
显式等待的第二个参数是最大等待时间,不是固定等待时间,条件提前满足就会继续执行,整体反而更快。测试数据隔离的做法是每条用例用独立账号或独立数据集,跑完清理,不要共用。
注意:Flaky 用例不要靠重试掩盖。重试能提高通过率,但会掩盖真实缺陷,建议先记录重试次数,定期分析高频重试的用例并修复根因。
5. 报告聚合、通知告警与平台化收尾技巧
5.1 用 SQL 聚合出通过率和趋势数据
报告页除了看单次结果,团队更关心趋势。通过率、失败用例分布、平均执行时长这些指标用一条 SQL 就能算出来,不必在应用层循环统计。
-- 按天统计通过率,用于趋势图 SELECT DATE(start_time) AS day, COUNT(*) AS total, SUM(CASE WHEN result = 'PASS' THEN 1 ELSE 0 END) AS passed, ROUND(SUM(CASE WHEN result = 'PASS' THEN 1 ELSE 0 END) / COUNT(*) * 100, 2) AS pass_rate FROM report_detail WHERE start_time >= DATE_SUB(NOW(), INTERVAL 30 DAY) GROUP BY DATE(start_time) ORDER BY day;report_detail表在start_time和result上建联合索引,数据量上百万后这条查询也能在百毫秒级返回。趋势图前端用 ECharts 折线图展示,数据直接吃这个接口的返回。
5.2 失败告警接入企业微信或钉钉的触发条件
告警不是每次失败都发,那样会变成骚扰。合理的触发条件是:连续两次执行同一任务失败,或者单次失败率超过阈值。告警内容要包含任务名、失败用例数、报告链接,让人点进去就能定位。
public void checkAndAlert(TestReport report) { double failRate = (double) report.getFailed() / report.getTotal(); // 失败率超过 20% 或连续失败才告警 if (failRate > 0.2 || isConsecutiveFail(report.getTaskId())) { String msg = String.format("任务[%s]失败率%.0f%%,失败%d条,报告:%s", report.getTaskName(), failRate * 100, report.getFailed(), report.getReportUrl()); webhookClient.send(msg); } }isConsecutiveFail查最近两次执行记录,都失败才返回 true。阈值和连续次数做成配置项,不同团队容忍度不一样,硬编码在代码里后面改起来麻烦。
5.3 平台从能用走向好用的两个具体技巧
第一个技巧是给用例加标签和分组执行。全量回归动辄跑一小时,日常只需要跑冒烟集。在test_case表加tags字段,任务创建时支持按标签筛选,冒烟集打smoke标签,回归集打regression,日常只跑冒烟,发版前跑全量。
第二个技巧是执行日志分级存储。report_detail.log字段如果存全量日志,一张表很快膨胀到几十 G。做法是数据库只存错误摘要和日志文件路径,完整日志写到文件系统或对象存储,需要时按路径下载。这样报告表保持轻量,查询和聚合都不受影响。
-- 日志分级:数据库存摘要,完整日志存路径 ALTER TABLE report_detail ADD COLUMN log_summary VARCHAR(500) COMMENT '错误摘要', ADD COLUMN log_path VARCHAR(255) COMMENT '完整日志文件路径';这两个改动都不大,但对平台长期可维护性的影响很明显。用例规模上百之后,没有标签分组和日志分级,平台会从帮手变成负担。
本文还有配套的精品资源,点击获取