☰
Katalon Recorder 录制脚本的可维护化与命令行回归
2026/11/2 4:48:59 网站建设 项目流程

简介:这是一套面向Web自动化测试初学者与测试工程师的脚本录制工具包,围绕Chrome浏览器中的Katalon Recorder展开,旨在降低Selenium脚本编写门槛,帮助读者快速完成操作录制、回放与基础编辑,适用于功能测试、回归验证和测试入门练习。压缩包共4个文件,包含2个crx浏览器扩展与2份html安装使用说明,整体约3.95MB,体积轻便,便于部署查阅。已有1522人学习下载,具备一定参考热度。包内资料可用于搭建录制环境、了解扩展安装方式,并借助说明文档熟悉脚本生成、断言添加、循环组织和回放验证等环节;附带的标签页增强插件也可优化日常浏览体验。对于希望以较低学习成本理解自动化测试流程、积累Web测试实操思路的读者,这份工具包提供了较为直观的参考入口。

1. 录制只是起点:一次 40 分钟的回归,Katalon Recorder 能省下哪三十分钟

周一早上,例行回归要跑。登录、建单、改状态、导出报表,一套流程点了四十分钟,点完还得对着截图确认哪一步没报错。这种重复劳动正是脚本录制工具存在的理由。Katalon Recorder 是个浏览器扩展形态的自动化录制器,点开就开始录,把人在页面上的点击、输入、下拉、跳转翻译成一行行可回放的命令,录完能直接重新跑,也能导出成 Python、Java、Robot Framework 等代码交给工程化流水线。它适合三类人:刚接手 Web 回归、还没搭起框架的测试同学;想快速摸清某个系统页面交互路径的开发;以及手上有一堆手工用例、想让其中三成先跑起来的团队。但它不是“录完就完事”的东西,录制只是拿到了初稿,能不能长期用,取决于后面怎么改。这一篇按我个人拆包使用的顺序讲:命令结构、定位器改写、套件组织、等待与断言、踩坑排查,最后落到怎么把它塞进命令行回归。

2. 命令三要素与定位器:把“录下来能跑”改成“放三天还能跑”

录制工具最容易让人产生错觉的一点是:它在录制的那一刻确实跑通了。可第二天页面加了埋点、按钮外包了一层 div、列表改成虚拟滚动,昨天那套脚本就集体趴窝。问题几乎都出在两处——命令本身写得不够精确,定位器选得不够稳。先把这两块的地基打牢,后面导代码、上流水线才有意义。

2.1 一行脚本的三个格子:command / target / value

打开 Katalon Recorder 的编辑器,主体是一张表,每行三个格子。command 是要执行的动作,target 是动作作用在谁身上,value 是动作带的参数。理解这三格的语义分工,比背命令名重要得多。

类别典型命令target 放什么value 放什么
页面动作open、click、type、select元素定位器或 URL输入文本、选项值
取值存储storeText、storeAttribute、store元素定位器变量名
断言校验assertText、verifyElementPresent元素定位器期望文本
等待同步waitForElementPresent、waitForVisible元素定位器超时毫秒数
流程控制if、elseIf、while、echo条件表达式提示文本
补时机pause空毫秒数

录制生成的行绝大多数落在前两类。这里有个细节值得注意:type和sendKeys不是一回事。type会先清空输入框再写,sendKeys是追加敲键,遇到“搜索框里已有上次关键词、需要接着输”的场景,用错命令就会出现“怎么变成了拼在一起的字符串”这种莫名其妙的失败。我一般默认用type,只有明确要模拟逐字输入触发联想搜索时才换sendKeys。

storeText是录制器里被严重低估的命令。它把页面上的文本抓进变量,后面可以用${变量名}的形式在 value 里引用。比如下单流程要拿订单号去查详情,录制器本身不会自动帮你记住这个号,得手动插一行storeText把订单号存下来,后面type到搜索框时写${orderNo}。这一步不补,脚本就只能跑固定数据,跑第二遍就撞号。

2.2 定位器优先级:id → name → css → xpath 的取舍

录制器默认倾向于生成 xpath,原因是它最通用——几乎所有元素都能被 xpath 描述出来。但“能描述”和“描述得稳”是两件事。一个自动生成的绝对路径可能长这样:

//div[3]/div[2]/form/div[1]/input

这种路径把每一层兄弟节点的序号都写死了。前端只要在表单前面插一个提示条,序号全变,脚本立刻失效。这不是工具的问题,是绝对路径这种描述方式在结构变动面前天然脆弱。

我实际改写时按这个优先级走:

  1. id——最稳,除非它是框架自动生成的、每次刷新都变。判断方法很简单:刷新三次页面,看 id 后面那串数字变不变。
  2. name——表单元素常见,稳定性接近 id,但同名元素可能不止一个,要确认唯一性。
  3. >{ "id": "a1b2c3d4-0000-0000-0000-000000000000", "version": "2.0", "name": "订单回归套件", "url": "https://example.internal/app", "tests": [ { "id": "t-001", "name": "新建订单并提交", "commands": [ { "id": "c-0001", "comment": "打开下单页,固定入口不依赖菜单导航", "command": "open", "target": "/order/new", "value": "" }, { "id": "c-0002", "comment": "等表单容器出现,代替写死 pause", "command": "waitForElementVisible", "target": "css=[data-testid=order-form]", "value": "15000" }, { "id": "c-0003", "command": "type", "target": "css=[data-testid=input-customer]", "value": "测试客户A" }, { "id": "c-0004", "command": "click", "target": "css=[data-testid=btn-submit]", "value": "" }, { "id": "c-0005", "comment": "抓订单号存变量,供后续用例引用", "command": "storeText", "target": "css=[data-testid=order-no]", "value": "orderNo" } ] } ], "suites": [ { "id": "s-001", "name": "冒烟集", "persistSession": false, "parallel": false, "timeout": 300, "tests": ["t-001"] } ], "urls": ["https://example.internal/app"], "plugins": [] }

    几个字段值得单独说。tests[].commands是真正执行的内容,一条命令一个对象,comment字段是给人看的,建议每三到五条命令补一句,否则三个月后回来没人看得懂当时为什么在这插了个等待。suites[].timeout是单条用例的超时秒数,不是单条命令的,设太小会把加载慢的页面误杀,我一般给 300。persistSession控制是否在用例间保留登录态,涉及登录的套件打开它能省掉反复登录的几十秒。parallel在纯录制场景里通常用不上,跨用例共享浏览器状态时打开反而会互相干扰。

    因为它是 JSON,所以可以做很多脚本化的事:用jq统计命令总数、批量把pause换成等待命令、对比两个版本的差异看谁改了什么。把它放进 Git,改版导致的定位器变更就有了 diff 可查,这比在浏览器里改完忘了改哪强太多。

    3.2 用例、套件、公共步骤怎么拆

    录制器最容易滑向的一个极端,是把整条业务链路录成一条巨型用例,二百多行命令首尾相连。这种结构没法定位失败点,也没法复用。我的拆法是三层:

    第一层是原子用例,只做一件事,比如“登录成功”“新建订单”“取消订单”。每条命令控制在三十行以内,起名要能一眼看出干什么。

    第二层是公共步骤前置。登录、切换角色、清空草稿这些每条用例都要走一遍的动作,单独录成一条用例,在套件里排在前面,后面的用例依赖它的执行结果。这里要注意persistSession必须打开,否则每条用例都重新开一个干净的浏览器上下文,登录态就丢了。

    第三层是场景套件,把原子用例按业务链路串起来。冒烟集只放五到八条核心路径,控制在五分钟内跑完;回归集可以放长一些,但别指望一个套件跑完全部,跑两小时没人盯,中间任何一个环境抖动都会连累后面的用例。

    拆的时候有个判断标准:如果一条用例失败后,你需要翻代码才知道它到底在哪一步挂的,那就说明这条用例该拆了。失败的定位成本,比用例数量更能说明结构好坏。

    3.3 导出代码:Python 与 Java 各适合什么场景

    录制器能导出多种语言,常用的两类是 Python 和 Java。导出的本质是把 JSON 里的命令翻译成对应语言的 WebDriver 调用,所以它必然丢掉一些东西——最常见的是等待语义和变量作用域,这两处往往是导出后跑不通的直接原因。

    下面是一段导出后的 Python 骨架,我做过少量整理,保留它原本的姿态:

    # 由录制文件导出的 Python unittest 骨架,已补注释 import unittest from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC class TestOrderCreate(unittest.TestCase): def setUp(self): # 录制器导出的部分通常不带这些配置,需要手动补 options = webdriver.ChromeOptions() options.add_argument("--window-size=1440,900") # 无头模式跑 CI 时打开;本地调试建议关掉,方便看现场 # options.add_argument("--headless=new") self.driver = webdriver.Chrome(options=options) self.driver.implicitly_wait(3) # 兜底隐式等待,别设太大 def test_create_order(self): d = self.driver d.get("https://example.internal/app/order/new") # 原本录制的是 pause 5000,这里换成显式等待 WebDriverWait(d, 15).until( EC.visibility_of_element_located((By.CSS_SELECTOR, "[data-testid=order-form]")) ) d.find_element(By.CSS_SELECTOR, "[data-testid=input-customer]").send_keys("测试客户A") d.find_element(By.CSS_SELECTOR, "[data-testid=btn-submit]").click() # 等订单号出现再取值,避免读到空节点 order_no = WebDriverWait(d, 15).until( EC.visibility_of_element_located((By.CSS_SELECTOR, "[data-testid=order-no]")) ).text self.assertTrue(order_no.strip(), "订单号为空,提交可能没生效") def tearDown(self): self.driver.quit() if __name__ == "__main__": unittest.main()

    这段代码里有两个参数值得调:implicitly_wait(3)是全局兜底,设得太大会让所有找不到元素的操作用乘以轮询次数的方式变慢,我一般给 3 秒封顶,主要靠显式等待干活;WebDriverWait(d, 15)里的 15 是单次等待上限,页面首屏慢的项目可以到 20,接口快的后台系统 10 就够。

    至于选 Python 还是 Java:项目里有现成的 pytest 或 unittest 脚手架,就导 Python,接进去快;团队主栈是 Maven 或 Gradle,导 Java 更省事,别为了脚本另起一套构建体系。至于导出的代码只是起点这件事,得反复强调——导出脚本里往往残留一堆time.sleep和绝对 xpath,直接提交进仓库等于把录制阶段的债原封不动搬进代码库。

    3.4 数据驱动:CSV 与传统 storeText 两条路

    录制器支持挂 CSV 做数据驱动,这对“同一流程、多组数据”的用例是刚需。写法是准备一份表头清晰的文件,在命令里用${列名}引用。

    username,password,expectTip user_ok_01,Pass@123,登录成功 user_ok_02,Pass@123,登录成功 user_bad_01,WrongPass,密码错误

    命令表里对应写成:

    open | /login | type | css=[data-testid=user] | ${username} type | css=[data-testid=pwd] | ${password} click | css=[data-testid=btn-login] | waitForElementVisible | css=[data-testid=tip] | 10000 assertText | css=[data-testid=tip] | ${expectTip}

    要点有三:CSV 必须存成 UTF-8 无 BOM,带 BOM 时第一列表头会多出不可见字符,表现是“第一列取值永远是空”,这个坑几乎人人踩过一次;空行要删掉,录制器会把空行当一组空数据跑一遍,然后报一堆莫名其妙的失败;期望值也放进数据里,而不是在断言里写死,否则三组数据只能验证同一种结果。

    另一条路是前面提过的storeText配合${变量},适合“数据是运行时产生”的场景,比如上一步生成的订单号、随机手机号。两条路可以混用,CSV 提供输入,storeText接住输出再传给下一步。

    4. 等待与断言:flaky 用例的八成来自这两块

    脚本跑一次过、跑十次挂三次,这种玄学现象在录制式自动化里太常见了。绝大多数不是元素定位错了,而是脚本在页面还没准备好的时候就动了手,或者断言写得太松,问题被吞掉了。

    4.1 三种时序错误:没出现、已消失、可点但点不动

    第一种是元素还没出现。录完立刻跑能过,是因为录制时页面在缓存里,加载飞快;到 CI 的冷启动环境,接口多绕两跳,三秒变十秒,脚本一进去就找不到元素。

    第二种是元素已经消失。典型场景是 loading 遮罩、骨架屏,脚本抓到的其实是即将被替换掉的占位节点,点上去的瞬间它被真实内容顶掉了,报“元素不可交互”。

    第三种最隐蔽:元素存在、可见,但不可点击。按钮被透明遮罩盖着,或者还在禁用态。这时候waitForElementPresent会立刻返回成功,因为元素确实在 DOM 里,真正该等的是“可点击”。

    对应三种等待就不一样:等出现用waitForElementPresent或waitForElementVisible,等消失用waitForElementNotVisible,等可点击用waitForElementClickable(部分版本叫法不同,本质是等待元素 enabled)。用错命令等于没等。

    4.2 等待命令的参数怎么设:超时值不是越大越好

    每条等待命令的 value 是超时毫秒数。参数怎么给,我的经验是按元素类型分档:

    元素类型建议超时说明
    本地渲染的静态元素5000再慢就是代码有问题
    依赖接口的首屏容器15000冷启动环境留足缓冲
    复杂报表、导出结果30000后端计算耗时,别硬卡
    异步校验提示10000太短会误判为未出现
    轮询型状态变化60000配合循环用,别单条死等

    超时值设成 120000 看着“保险”,实际是把失败检测时间拉长到两分钟。真正该做的是给一个合理上限,同时在脚本里保留失败截图,让超时发生时能立刻看到现场长什么样。翻车的往往不是等待太短,而是等待太长,把一次本该快速暴露的环境问题拖成了二十分钟的挂起。

    另一个技巧是尽量用等待命令替换pause。pause 5000是纯粹的盲等,每次执行都实打实花五秒,一条用例插三个就十五秒,一百条用例就是一小时。换成条件等待之后,页面一就绪立刻继续,正常路径几乎零开销。

    4.3 assert 与 verify 的取舍:什么时候该中止

    assert系列在失败时中止当前用例,verify系列失败只记一条日志继续往下跑。这个差别决定了你能拿到多少信息。

    我的用法是分层:关键路径用 assert,比如登录后没有跳到首页、下单后订单号为空,这种一旦失败后面全无意义,早停早省时间;非关键校验用 verify,比如列表字段格式、按钮文案、分页数量,这些失败不该阻断后续步骤,攒一批一起看更有价值。

    有个反直觉的点:断言太密反而降低脚本的可用性。见过一条用例里塞了四十个verifyText,每次改版都要逐条核对文案,维护成本远超它发现的问题。断言的密度应该和业务重要性挂钩,不是越多越显得严谨。

    4.4 全局超时、失败截图与日志级别

    套件级timeout是兜底防线,防的是某条命令卡死不返回。设成 300 秒是常见值,长流程的可以到 600。超过这个值还没跑完,基本可以判定是环境问题或者等待逻辑有死结。

    失败现场比失败本身重要。录制器自带的日志面板能看到每一步的执行状态和耗时,排查时先看哪一步开始变慢——往往不是失败那一步本身有问题,而是它前面某一步等了很久才勉强通过。如果导出成了代码,务必在tearDown里补截图和页面源码落盘,这两份东西能把“找不到元素”的扯皮时间从半小时压到两分钟。

    5. 避坑排查:五个翻车现场的现象、原因与解决

    5.1 定位与框架类:元素明明在页面上,脚本就是找不到

    现象一:手动能看到输入框,脚本报找不到元素,日志里页面结构也正常。原因基本是元素在 iframe 里。WebDriver 的查找作用域停留在主文档,不切进去永远找不到。 解决是先切框架再操作:selectFrame | css=iframe[data-testid=pay-frame],操作完用selectFrame | relative=top切回主文档。嵌套 iframe 要层层切,切错层级的表现是“元素找到了但点击无效”。容易忽略的是,很多弹窗表单跑在 iframe 里,录制时因为鼠标操作由浏览器原生处理所以能录上,回放时才暴露。

    现象二:点了按钮,页面开了新标签页或弹出新窗口,后面所有命令全挂。原因是脚本还停留在旧窗口句柄上。 解决办法是显式切窗口。录制器里有selectWindow系列命令,按标题或句柄切换。我的习惯是在打开新窗口的点击之后立刻插一行waitForWindowPresent,再selectWindow过去,操作完切回原来的窗口。不切回来的后果是后续命令在错误的窗口里找元素,报错信息看起来毫无关联。

    现象三:同一条用例今天过明天挂,定位器里那串数字每天都在变。这是动态 id。组件库或前端框架按运行时自增生成 id,刷新就变。 解决是把定位器改成前缀匹配或换属性。用xpath=//input[starts-with(@id,'field-')],或者直接找它稳定的父级再往下定位。更好的做法是推动前端加># 去掉 BOM 并转成 LF 换行,避免跨平台跑出诡异差异 sed -i '1s/^\xEF\xBB\xBF//' data/login.csv dos2unix data/login.csv

    第二个原因是文件末尾有多余空行,数据驱动会把它当成一组空记录跑一遍。第三个原因是列名大小写或空格不一致,username和userName在引用 ${} 时并不等价。这三处检查完,数据驱动基本就稳了。

    还有一个不在上列但值得单独提醒的:导出后的脚本没带上等待。录制器里的waitForElementVisible导出成代码时,某些目标语言会退化成隐式等待或直接被丢掉,表现是导出的脚本在本地跑得好好的,进了 CI 就超时。导出后手工过一遍所有等待逻辑,是省不掉的工序。

    6. 进阶:把 .side 文件纳进版本管理,用命令行跑回归

    录制器在浏览器里点着用很舒服,但它真正的价值在于.side是一份可以被工程化处理的文本。把这份文件当源码对待,整条链路就活了。常见做法是用命令行执行器直接跑.side文件,比如基于 Node 的那套 side runner,装好之后一条命令就能拉起浏览器执行整个套件:

    # 安装命令行执行器,全局一次即可 npm install -g selenium-side-runner # 跑单个套件;--base-url 用来覆盖录制时写死的地址 selenium-side-runner order.smoke.side \ --base-url=https://staging.internal/app \ --output=./reports \ --agents 2 # 只跑名字匹配的用例,改版验证时很省时间 selenium-side-runner order.smoke.side -f "name=登录*"

    --base-url是最值钱的参数。录制时地址被写死在命令里,有了它,同一份套件可以指向测试环境、预发环境、本地起的那套服务,不用改文件。--agents 2控制并发数,超过 2 要谨慎——录制式脚本之间如果共享同一套后台数据,并发会互相踩,出现只在并行时复现的失败。-f过滤用例名,改版后只跑受影响的几条,比全量跑快得多。

    配合 Git 的分支流程,我会这么组织:.side文件放仓库根目录的e2e/下,一次改版一个分支,改版后先本地跑冒烟集确认定位器没塌,再合进去。因为文件是 JSON,diff 出来能清楚看到谁把哪条命令的 target 改了,评审时一眼就知道改得对不对。

    上线前我习惯走一遍这张检查表,五项都过再提交:

    检查项通过标准不通过时的典型症状
    定位器稳定性无绝对路径 xpath,无动态 id隔天跑就挂
    等待方式无裸 pause 超过 2000msCI 上大面积超时
    断言覆盖关键路径有 assert页面白屏也报成功
    数据隔离每次跑不依赖上次残留数据连跑两次第二次必挂
    套件时长冒烟集 5 分钟内跑一半就没人守着了

    最后说个我自己的教训。早些年我把录制脚本当一次性工具用,录完跑通就丢,下次改版重新录一遍,觉得这样最省事。直到某次一个支付流程改版,重新录的时候漏掉了一个“余额不足”的分支,结果线上出了问题才想起来——那条分支当初是手动补的,本地磁盘上根本没留档。从那以后我每次录完都强制走一遍「导出 .side → 提交仓库 → 补注释」的流程,哪怕这条用例只用一次。录制工具帮我们省的是敲代码的时间,不是思考的时间;把录下来的东西当资产管起来,它才会真的开始省钱。希望帮到你。

    本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询