简介:Katalon Recorder 脚本录制工具压缩包面向需要快速开展网页自动化测试的开发者、测试工程师及入门学习者,提供基于 Chrome 浏览器的操作录制与回放方案,可将点击、输入等操作转化为可执行的 Selenium 脚本,减少手工编写定位与重复执行的成本。包内共 4 个文件,由 2 个 crx 浏览器扩展安装包和 2 个 html 安装使用说明组成,压缩包约 3.95MB;扩展包用于部署录制工具及辅助页面增强组件,说明文档则交代安装流程与基本操作指引。已有 1522 人学习下载。读者可借助内置编辑器查看、修改录制脚本,并补充断言、循环等逻辑以适配较复杂的测试场景;配合说明文档完成扩展安装、脚本回放与结果验证,适合用作自动化测试入门练习、流程演示和轻量级回归脚本沉淀。
1. 录一遍就能跑,前提是知道它在录什么
打开后台页面点几十下,回放全绿,很多人到这一步就认定自动化已经完成——这正是 Katalon Recorder 最容易被高估的地方。它是一款浏览器端的脚本录制工具,把你真实发生的点击、输入、下拉、跳转,翻译成 Selenium IDE 风格的命令序列,再导出成 Java、Python、Robot Framework 等能进代码仓库的脚本。它真正省掉的是查元素、抄定位器、拼 WebDriver 样板代码这段体力活,不是把手工用例自动升级成工程资产。手上有成堆表单、后台、CRUD 页面要做回归,又不想人肉写 Selenium 的测试同学最合适;写业务脚本的开发也能拿它当定位器探针。这份资源好不好用,一半看录制习惯,一半看导出之后你怎么改。
2. 录制前把环境固定下来:窗口、账号、定位器三件事
录出来的脚本稳不稳,八成在按下录制键之前就决定了。同一套操作,在 1920 宽的窗口里录一遍、在 1366 的笔记本上回放,折叠菜单展开状态不同,元素坐标和可见性全变;用个人账号录、用测试账号回放,权限菜单不一样,第一步就找不到按钮。所以先把录制容器固定成一个可重复的状态,再谈脚本质量。
2.1 两条安装路径与独立浏览器配置
拿到这个压缩包之后,常见有两种用法。一种是把解压出来的目录当作已解压扩展,在浏览器的扩展管理页打开开发者模式,选择“加载已解压的扩展程序”,指向包内包含 manifest 的那一层目录;另一种是直接在浏览器的扩展市场里装一份,包则留作离线环境和版本对照。两条路的效果一样,离线加载的好处是测试机不依赖外网、版本不会自己变。
不管走哪条路,我都建议给录制单独开一个浏览器配置目录,别和日常上网的 Profile 混用。日常 Profile 里塞着翻译插件、密码管理器、广告拦截,它们会在页面里插 DOM 节点、弹浮层、改写文本,录出来的 xpath 里经常莫名其妙多一层容器。
# 用一个空目录作为独立浏览器配置,和日常浏览数据彻底隔离 # 不同系统可执行文件名不同(chrome / chromium / msedge),按本机情况替换 mkdir -p "$HOME/qa-recorder-profile" chromium --user-data-dir="$HOME/qa-recorder-profile" --no-first-run &--user-data-dir指向的目录第一次启动时是干净的,扩展要在这个 Profile 里重新装一次。这样做的代价是每次录制要开两次浏览器,收益是录出来的定位器可复现——我吃过一次亏,某个翻译插件把页面标题包了一层<font>,导出的脚本在无插件环境里全线找不到元素,排查了大半天才定位到原因。
提示:录制用的 Profile 里只留录制扩展和必要的调试工具,其余一律不装,这是成本最低的稳定性投入。
2.2 页面状态与测试数据的约定
窗口尺寸要固定。把浏览器窗口拉到 1280×800 或与设计稿一致,页面缩放保持 100%,系统字体大小不要调过。响应式页面在断点两侧的 DOM 结构可能不同,桌面端录的菜单在窄屏下会变成汉堡按钮,回放时 target 直接失效。
数据要能重复消费。注册、下单、发消息这类会写库的操作,第二次跑就会撞唯一键或撞库存。我的做法是每条用例自带构造和清理:注册用时间戳或随机后缀生成账号,用例结束把数据删掉;查询类用例读的是固定种子数据,不依赖前一条用例的执行结果。
还有几个容易被忽略的页面级因素:浏览器自动翻译关掉、密码保存提示关掉、页面上的新手引导浮层先手动点掉再开始录。引导浮层是最典型的“录制时不存在、回放时挡住按钮”的元凶。
2.3 target 列的语法与定位器优先级
Katalon Recorder 的每条命令由 command、target、value 三部分组成,其中 target 决定脚本寿命。它支持带前缀的定位写法,不写前缀时按 id、name、链接文本的顺序尝试匹配。
# target 列的常见写法 id=login-submit name=username css=#login-form .btn-primary xpath=//button[normalize-space()='登录'] linkText=忘记密码按稳定性从高到低排,我的取舍是:优先id和name,其次css里带语义类名或>{ "id": "case-login-001", "name": "登录-正常账号", "commands": [ { "command": "open", "target": "/login", "value": "" }, { "command": "type", "target": "id=username", "value": "qa_user" }, { "command": "type", "target": "id=password", "value": "Passw0rd" }, { "command": "click", "target": "css=#login-form button[type=submit]", "value": "" }, { "command": "assertText", "target": "css=.welcome-title", "value": "欢迎回来" }, { "command": "pause", "target": "3000", "value": "" } ] }
这段里有两个信号值得注意。一是type的 value 写死了明文密码,导出到代码仓库前必须换成环境变量或配置项,否则测试账号会跟着源码到处跑。二是末尾那条pause,录制器在你每次点击后停顿观察页面,会把停顿翻译成硬等待;它在你的机器上够用,换一台慢一点的机器就不够,换一台快机器又白等三秒。
3.2 导出格式怎么选
导出面板里可选项不少,选错的代价是后期维护成本翻倍。判断依据只有一条:这套脚本最终跑在哪个技术栈的流水线里。
| 导出目标 | 产物形态 | 适合的团队 | 维护成本 |
|---|---|---|---|
| Java + JUnit / TestNG | 类文件,方法级用例 | 后端是 Java、已有 Maven 工程 | 中,编译期能兜住一部分错 |
| Python + unittest / pytest | 函数或类,配 conftest 好用 | 测试团队以 Python 为主 | 低,改起来快 |
| Robot Framework | 关键字驱动的.robot文件 | 已有 RF 关键字库沉淀 | 低,非程序员也能读 |
| C# + NUnit / MSTest | 类文件 | .NET 技术栈 | 中 |
| Node / Protractor 类 | JS 脚本 | 老项目遗留 | 高,生态已停更,新项目别选 |
同一个团队不要混着导。我见过一个组里三个人分别导出 Java、Python、JS 三套,半年后没人能说清哪套是准的,回归任务全靠人肉记。定一套,写进规范。
3.3 导出后的 Python 骨架长什么样
选 pytest 导出,拿到的骨架大致是下面这个结构。这不是逐字产物,但方法划分和调用顺序与导出逻辑一致,可以直接拿去对照你手上的文件:
import os import pytest 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 TestLogin: def setup_method(self, method): # 无头开关交给环境变量,本地调试时去掉,流水线上打开 options = webdriver.ChromeOptions() if os.environ.get("HEADLESS") == "1": options.add_argument("--headless=new") options.add_argument("--window-size=1280,800") self.driver = webdriver.Chrome(options=options) self.vars = {} self.base_url = os.environ.get("BASE_URL", "http://127.0.0.1:8000") def teardown_method(self, method): self.driver.quit() def test_login_ok(self): self.driver.get(self.base_url + "/login") self.driver.find_element(By.ID, "username").send_keys("qa_user") self.driver.find_element(By.ID, "password").send_keys(os.environ["QA_PASSWORD"]) self.driver.find_element(By.CSS_SELECTOR, "#login-form button[type=submit]").click() WebDriverWait(self.driver, 10).until( EC.text_to_be_present_in_element((By.CSS_SELECTOR, ".welcome-title"), "欢迎回来") )骨架里几个点决定了它能不能直接用。setup_method和teardown_method保证了每个用例一个干净浏览器,不会串会话;set_window_size或启动参数里的窗口尺寸要和录制时保持一致;self.vars是录制器用来存变量的字典,配合storeText用;base_url从环境变量读,同一套脚本才能在测试环境和预发环境之间切。
导出时如果页面上还有没跑完的pause,产物里会原样保留成time.sleep()。这些是第一批要清掉的东西,下一章展开。
3.4 test suite 与数据驱动
单条用例解决不了回归量。录制器里可以把多条用例编成 test suite,一次顺序执行;导出时选包含 suite,产物里会多一个按顺序调用的入口。
数据驱动靠数据集。在工具里挂一个 CSV,每行一组变量,命令里用${username}这种占位符引用,执行时会按行遍历。
username,password,expect_text qa_user_01,Passw0rd,欢迎回来 qa_user_locked,Passw0rd,账号已锁定 qa_user_none,,请输入密码三行数据覆盖正常、锁定、空值三条分支,用例本体只写一遍。注意 CSV 的编码要统一成 UTF-8,中文表头带 BOM 时在部分环境里会读成乱码,第一列变量名对不上,所有用例拿到的都是空串,现象是用例全过——因为输入空值也提交成功了,这种假绿最难查。
4. 把录制脚本改成能进流水线的样子:等待、断言、数据三处动刀
录制产物的定位是“可运行的初稿”,不是“可维护的用例”。真正要进回归集合,有三处必须改:硬等待换成条件等待、元素存在类断言换成业务结果断言、写死的数据换成外部参数。这三处不改,脚本能跑一天,跑不了一个迭代。
4.1 录制脚本为什么一定会变脆
第一层脆在定位。录制器抓的是当下的 DOM 快照,前端一改布局、一加埋点属性,nth-child和绝对 xpath 立刻错位。第二层脆在时序。pause 3000是在赌页面三秒内加载完,赌的是当时的网络和那台机器;流水线容器冷启动、后端接口偶发慢查询,赌局就输。第三层脆在断言。assertElementPresent只证明元素在 DOM 里,证明不了业务成功——一个报错弹窗也能让页面结构满足这个条件,脚本照样绿。
三层里修哪层收益最大?时序。因为定位错误会立刻报错,看得见;时序问题表现为偶发红条,重跑就过,最容易被贴个“环境不稳”的标签放过去,最后变成没人敢动的玄学用例。
4.2 用显式等待替掉所有 pause
原则很简单:凡是录制器自动插入的pause,全部删掉;需要等的地方,用等待条件表达你真正在等什么。等元素可点击、等文本出现、等列表行数大于零、等 URL 变化,每一种都有对应的条件。
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By class Waits: def __init__(self, driver, timeout=10): self.driver = driver self.wait = WebDriverWait(driver, timeout, poll_frequency=0.3) def clickable(self, css): # 等到元素既存在又可见、且没有被遮挡时才返回,避免点了个寂寞 el = self.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, css))) el.click() return el def text_in(self, css, expect): # 断言和等待合并:文本没出现就在超时前一直轮询,出现即通过 return self.wait.until( EC.text_to_be_present_in_element((By.CSS_SELECTOR, css), expect) ) def rows_at_least(self, css, n): # 列表类页面专用:等行数达标,比等某个具体元素稳 return self.wait.until( lambda d: len(d.find_elements(By.CSS_SELECTOR, css)) >= n )timeout设多少取决于接口 P99,一般 10 秒够用,慢接口页面单独调到 20。poll_frequency是轮询间隔,0.3 秒在响应速度和 CPU 占用之间比较平衡,调太小会让无头浏览器空转。这三个方法基本能覆盖录制脚本里九成的等待场景,剩下的用EC.url_contains或自定义 lambda 补。
改完之后,脚本执行时间会明显下降——原来每条命令后挂三秒,一个二十步的流程光等待就一分钟,现在只在真正需要的地方等。
4.3 断言从“元素存在”升级到“业务正确”
一条登录用例,只有“跳转到了首页”这个事实不够,要断言的至少有三样:页面上的用户标识是不是这个账号、有没有出现权限菜单、有没有错误提示。对应到代码就是文本断言加反向断言。
def test_login_ok(driver, waits): driver.get(os.environ["BASE_URL"] + "/login") driver.find_element(By.ID, "username").send_keys(os.environ["QA_USER"]) driver.find_element(By.ID, "password").send_keys(os.environ["QA_PASSWORD"]) driver.find_element(By.CSS_SELECTOR, "#login-form button[type=submit]").click() # 正向:业务成功的标志文案 waits.text_in(".welcome-title", "欢迎回来") # 正向:账号信息落到了页面上,证明会话真的建立了 assert os.environ["QA_USER"] in driver.find_element(By.CSS_SELECTOR, ".user-meta").text # 反向:错误提示框不应该出现,出现过说明接口返回了业务错误 assert not driver.find_elements(By.CSS_SELECTOR, ".toast-error")反向断言这条经验值很高。很多业务错误是“页面照常跳转 + 右上角弹个红条”,只断言正向文案会漏掉。find_elements返回空列表不抛异常,正好用来做“不应该存在”的校验。
4.4 数据外置与用例自清理
把 CSV 数据集迁移到 pytest 的参数化里,用例本体不带任何账号密码。同时给每条写库用例配一个清理钩子,保证重复执行不撞数据。
import csv import pytest def load_cases(path="data/login_cases.csv"): with open(path, encoding="utf-8") as f: return list(csv.DictReader(f)) @pytest.mark.parametrize("case", load_cases(), ids=lambda c: c["username"]) def test_login_matrix(driver, waits, case): # 每条数据独立起浏览器,互不影响;断言文案也来自数据行 driver.get(os.environ["BASE_URL"] + "/login") driver.find_element(By.ID, "username").send_keys(case["username"]) driver.find_element(By.ID, "password").send_keys(case["password"]) driver.find_element(By.CSS_SELECTOR, "#login-form button[type=submit]").click() waits.text_in(".login-tip", case["expect_text"])ids参数让用例报告里显示的是用户名而不是下标,失败时一眼看出哪行数据挂了。encoding="utf-8"别省,Windows 上默认编码会把中文读坏。清理逻辑我一般放在 fixture 的yield之后,删除顺序按外键倒着来,先删明细再删主记录。
4.5 无头运行与报告出口
到了流水线上,浏览器必须无头跑,否则容器里没有显示器直接起不来。给一条本地先验证过的命令,再去接流水线:
#!/usr/bin/env bash set -euo pipefail # HEADLESS=1 让 conftest 走无头模式,BASE_URL 指向被测环境 # --junitxml 产出标准报告文件,供流水线采集,不依赖任何特定平台 HEADLESS=1 \ BASE_URL="${BASE_URL:-http://127.0.0.1:8000}" \ QA_USER="${QA_USER:-qa_user}" \ QA_PASSWORD="${QA_PASSWORD:?缺少测试账号密码}" \ pytest tests/web/ -q --junitxml=reports/junit.xmlset -euo pipefail保证任何一步失败都中断,不会糊里糊涂报成功。QA_PASSWORD用:?语法强制校验,没设就直接退出并给出提示,比跑到一半找不到元素强。--junitxml是通用格式,流水线认这个文件,不需要额外插件。本地跑通再把这条命令原样搬进流水线,环境差异只剩 URL 和凭据两项。
5. 录制回放的五个典型翻车现场:现象、原因与处理
录出来的脚本红条,八成集中在五类问题上。下面每条都按现场能观察到的现象、根因、处理办法写,照着对号入座比漫无目的地调参快。
5.1 定位类:iframe、动态属性与自定义下拉
现象一:元素明明在页面上,回放报找不到。常见于后台系统里的富文本编辑器、报表内嵌页、第三方登录弹层。原因是元素在 iframe 里,主文档的find_element压根搜不到子文档。处理办法是先切进去再操作,操作完切回来:driver.switch_to.frame(driver.find_element(By.CSS_SELECTOR, "iframe.editor")),结束后driver.switch_to.default_content()。录制器不会自动加这一步,因为录制时它拿到的是浏览器层面的点击事件,它并不知道边界在哪。多个 iframe 嵌套时要逐层切,切错层数报的错是一样的,只能靠打印driver.current_url和页面标题定位到第几层。
现象二:昨天能点,今天同一个 target 失效。大概率是录到了框架生成的动态属性。有些前端组件每次渲染会给id拼一段随机串,或者把状态写进类名(btn-active-2变btn-active-3)。处理办法是把 target 换成稳定的部分:用>from selenium.webdriver.common.action_chains import ActionChains menu = driver.find_element(By.CSS_SELECTOR, ".nav-item.settings") ActionChains(driver).move_to_element(menu).perform() waits.clickable(".nav-item.settings .sub-item.export")
现象五:点击后开了新标签页,后面的命令全串到旧页面。录制器不会记录窗口切换,回放时 driver 还盯着原来的 tab,所有定位都在旧页面上做,自然全错。处理办法是点击后显式切窗口句柄:记录点击前的driver.window_handles,点击后等句柄数量变化,switch_to.window到新句柄,用完切回。下载类场景同理,点击触发下载后浏览器不会跳转,但文件落盘需要时间,要轮询目标目录里文件是否存在,不能点完就断言。
5.3 环境与运行类:driver 版本、无头差异、缓存
现象六:本地能跑,流水线一跑就报 driver 与浏览器版本不匹配。本机浏览器自动更新,驱动版本对不上;容器里浏览器是固定版本,驱动的获取方式又不一样。处理办法是用驱动管理器自动匹配版本,或在容器镜像里把浏览器和驱动版本一起钉死。无头模式还有一类专属坑:无头下默认窗口尺寸和可视区域与有头不同,响应式页面会落到另一个断点,导航结构直接变样。所以启动参数里必须显式带上--window-size=1280,800,别指望默认值。
现象七:重跑时页面带着上次的登录态,第一步就跳过了登录页。原因是复用了同一个浏览器 Profile,cookie 和 localStorage 留了下来。处理办法是每次setup用全新的临时 Profile 目录,或启动参数里加--incognito,并在teardown里driver.quit()而不是只close()。这一点在调试时尤其容易忽略,因为你手动开着浏览器反复试,状态一直在。
注意:以上七条里,只有“driver 版本不匹配”会给出明确报错,其余六条的现象都是“找不到元素”或“点不到”,报错文本高度相似。排查顺序建议固定成:先截图看页面停在哪一步,再打印当前 URL 和句柄数量,最后才去怀疑定位器。
6. 进阶用法:把录制器当定位器探针,配一张上线前的自检表
录制器最被低估的用法,不是生成完整用例,而是当定位器探针。真实项目里最耗时的环节是找一个能长期存活的 target——你得翻 DOM、试选择器、验证唯一性。我的做法是:打开录制,只做一步操作,然后停掉,直接去命令列表里看它给这个元素生成了什么 target;如果给的是绝对 xpath 或带随机串的 id,就在控制台里手写候选选择器试document.querySelectorAll('...')的返回长度,长度等于 1 才算可用。这一步平均三十秒,比在脚本里反复试错快得多。
录完的定位器还有一个隐性价值:它记录了元素的可见文案和属性快照,前端改版时可以拿这份快照做差异对照,快速判断哪些用例会受影响。
6.1 上线前的自检表
脚本改完别急着合进去,按这张表过一遍。每一条都对应一次真实翻车,逐项打勾花不了十分钟。
| 检查项 | 判断标准 | 不通过的典型后果 |
|---|---|---|
| target 列无绝对 xpath | 搜索产物里没有/html/body/开头 | 前端改布局即失效 |
| 无遗留 pause | 全文搜不到time.sleep或pause | 偶发超时,重跑才过 |
| 断言覆盖业务结果 | 每条用例至少一条文本或状态断言 | 报错弹窗下依然全绿 |
| 凭据外置 | 源码里搜不到明文账号密码 | 账号随代码扩散 |
| 数据可重复 | 连续跑两遍都通过 | 第二次撞唯一键 |
| 独立 Profile | setup 里每次新建会话 | 登录态残留导致跳过步骤 |
| 无头下验证过 | HEADLESS=1本地跑通 | 流水线上起不来浏览器 |
6.2 一个具体技巧:先跑一遍“破坏性验证”
脚本全绿不代表断言有效。我的习惯是故意破坏一次:把某条断言的期望文案改成一个不可能出现的字符串,跑一遍,确认它真的变红;再改回来。这一步能筛掉大量“断言写在了不会被执行的分支里”或者“等待条件太宽松导致断言被跳过”的假绿。
同样的手法也用在等待上:把某条接口人为延迟,看用例是老老实实等到了,还是提前拿到了空列表然后断言通过。空列表断言通过是自动化里最阴的坑,因为find_elements返回空不报错。
从那以后我每次交付录制改写的脚本,都强制走一遍“破坏性验证 + 自检表”,两个动作加起来不到二十分钟,比事后在流水线上翻日志省太多。这份录制工具的价值也正在这里:它把你从写样板代码里解放出来,让你有时间去做这些真正决定脚本寿命的事。希望帮到你。
本文还有配套的精品资源,点击获取