Playwright codegen 录制 + AI 补全:高效 UI 自动化实战

1. 为什么我把 UI 自动化的起点从"手写脚本"换成了"录制 + AI 补全"

做 UI 自动化的人大概都有过这种体验:打开编辑器,对着一个登录框,先想选择器怎么写,再想等待怎么加,然后跑一遍发现元素定位不到,改一版再跑,还是不行。一个简单的"登录-下单-退出"流程,光调试选择器就能耗掉大半天。更别提那些动态渲染的页面,今天能跑通的脚本,明天前端一改样式就全废了。

我过去几年一直用传统方式写 UI 自动化,直到最近把工作流换成了Playwright codegen 录制 + AI 补全脚本这套组合,效率提升非常明显。核心思路其实很朴素:让机器干它最擅长的事——录制真实操作、生成基础代码;让人和 AI 干最有价值的事——补全断言、封装逻辑、处理边界。这样一来,既避免了从零手写选择器的痛苦,又不用把整个脚本都交给 AI 去"猜",省下了大量来回对话的 token 消耗。

这篇文章我会把整套实战流程拆开讲清楚:codegen 到底能录出什么、录出来的代码有哪些"坑"、AI 补全时怎么给上下文最省 token、断言和等待该怎么补、录制的脚本怎么工程化。适合已经会一点 Playwright 基础、但被选择器和调试折磨过的同学,也适合完全没接触过 UI 自动化、想找个高效入门路径的朋友。整套方法我在多个中后台系统上验证过,稳定性比纯手写高不少。

先说结论:录制负责"骨架",AI 负责"血肉",人工负责"判断"。三者分工明确,才是这套方案真正跑得通的关键。下面逐层展开。

2. Playwright codegen 到底录了什么:先搞懂它的能力边界

2.1 codegen 的三种启动方式与适用场景

很多人对 codegen 的印象还停留在"打开浏览器点一点就生成代码",但它的启动方式其实直接决定了录制质量。我常用的有三种:

第一种是纯录制模式,直接运行playwright codegen加目标网址,浏览器和代码生成器会同时打开,你在浏览器里的每一次点击、输入、跳转都会被翻译成 Playwright 代码。这种方式适合快速摸清一个陌生页面的元素结构。

第二种是带设备模拟的录制,通过--device参数指定设备,比如模拟移动端视口。做响应式页面测试时,这个参数很关键,因为不同视口下 DOM 结构可能完全不同,录出来的选择器自然也不一样。

第三种是保存登录态后录制,先用--save-storage把登录后的 cookie 和 localStorage 存下来,后续录制时用--load-storage加载。这一步是实战中最容易被忽略的——如果不带登录态,你录的每一个流程都得从登录页开始,脚本里会塞满重复的登录步骤。

# 基础录制 playwright codegen https://example.com # 模拟移动端并保存登录态 playwright codegen --device="iPhone 13" --save-storage=auth.json https://example.com # 带登录态录制 playwright codegen --load-storage=auth.json https://example.com

提示:--save-storage生成的 json 文件包含敏感凭证,务必加入.gitignore,不要提交到代码仓库。

2.2 录制生成的代码长什么样,为什么不能直接用

codegen 默认生成的是"逐步操作"风格的代码,类似这样:

page.goto("https://example.com/login") page.get_by_role("textbox", name="用户名").click() page.get_by_role("textbox", name="用户名").fill("testuser") page.get_by_role("textbox", name="密码").click() page.get_by_role("textbox", name="密码").fill("password123") page.get_by_role("button", name="登录").click()

这段代码能跑,但直接拿去当测试用例问题很多。第一,它没有断言,跑完不知道成功还是失败;第二,它没有等待策略,页面慢一点就报错;第三,它把"点击输入框"和"填充内容"拆成两步,冗余;第四,硬编码的账号密码写死在脚本里,无法复用。

这就是为什么我说codegen 只负责骨架。它帮你解决了最烦人的"选择器怎么写"问题——注意它用的是get_by_role这种语义化定位,比早期的 CSS 选择器稳定得多——但业务逻辑、断言、数据管理这些,它一概不管。

2.3 语义化定位是 codegen 最大的价值点

我特别想强调get_by_role、get_by_label、get_by_text这类定位方式。早期 UI 自动化大量使用 XPath 和 CSS 选择器,比如div > form > input:nth-child(2),这种写法一旦前端调整结构就失效。而语义化定位基于元素的可访问性角色和文本内容,只要按钮还叫"登录",定位就不会变。

codegen 录制时优先选择语义化定位,这等于帮你自动做了一次"选择器最佳实践"。实测下来,同一套脚本在页面改版后,用语义化定位的存活率比 CSS 选择器高出很多。这也是我愿意用 codegen 打底的核心原因——它录出来的定位器质量,比很多新手手写的还高。

3. 录制脚本的"半成品"问题:AI 补全前必须做的三件事

3.1 先清理冗余操作,别让 AI 处理垃圾

录制的脚本里有一类典型冗余:连续的click()加fill()。Playwright 的fill()本身就会聚焦元素,前面的click()完全多余。还有滚动、悬停这类录制时产生的附带动作,如果对业务没意义,也应该删掉。

我的习惯是录完之后先手动过一遍,把明显冗余的行删掉,再交给 AI。原因很实际:AI 是按 token 计费的,你给它一堆垃圾代码,它就得花更多 token 去理解,补全质量还未必好。先人工瘦身,再让 AI 补全,是省 token 的第一道关口。

清理时重点看三类:重复的定位调用、无意义的等待、录制工具自动插入的page.wait_for_timeout()。最后这个尤其要删,固定等待是自动化脚本的头号杀手,后面会专门讲怎么替换。

3.2 把线性脚本拆成"步骤函数",给 AI 清晰的上下文

录出来的代码是一条直线从头到尾。直接丢给 AI 说"帮我补全断言",它只能猜你想断言什么。更好的做法是先按业务语义把脚本切成几个函数,比如login()、add_to_cart()、checkout(),每个函数只做一件事。

这样做的好处是,你给 AI 的提示可以非常具体:"这是login()函数,请在登录成功后补充断言,验证跳转到了首页且用户名显示正确。"AI 拿到明确的函数边界和意图,补全的准确率会高很多,来回修改的次数也少,token 自然就省下来了。

def login(page, username, password): page.goto("/login") page.get_by_role("textbox", name="用户名").fill(username) page.get_by_role("textbox", name="密码").fill(password) page.get_by_role("button", name="登录").click() # 待 AI 补全:登录成功断言

3.3 参数化硬编码数据,这是 AI 不会主动帮你做的

录制时输入的账号、密码、搜索关键词都会被写死。AI 补全时通常不会主动帮你参数化,因为它不知道你的数据来源。这一步必须人工做,把硬编码值抽成函数参数或配置。

我一般会建一个test_data.py或者用 pytest 的 fixture 来管理测试数据。参数化之后,同一个login()函数就能被不同用例复用,测正常登录、错误密码、空用户名都靠传参解决。这一步做完,脚本才真正具备"工程化"的雏形,而不是一次性的录制产物。

4. 用 AI 补全脚本的正确姿势:怎么给上下文最省 token

4.1 补全断言:告诉 AI "验证什么",而不是"帮我写测试"

很多人用 AI 补全时喜欢说"帮我完善这个测试脚本",这种模糊指令会让 AI 输出一大堆你可能不需要的东西,token 哗哗地烧。我的做法是把需求拆到最小:一次只让 AI 补一个点。

比如补断言,我会这样给提示:"login()函数点击登录按钮后,页面应跳转到/dashboard,且右上角显示用户名。请用 Playwright 的 expect 补充两条断言。"AI 拿到这种指令,输出精准且短,基本一次到位。

from playwright.sync_api import expect def login(page, username, password): page.goto("/login") page.get_by_role("textbox", name="用户名").fill(username) page.get_by_role("textbox", name="密码").fill(password) page.get_by_role("button", name="登录").click() expect(page).to_have_url(re.compile(r"/dashboard")) expect(page.get_by_test_id("user-name")).to_have_text(username)

注意这里用了to_have_url配合正则,因为实际 URL 可能带查询参数。这种细节 AI 未必想得到,需要你在提示里点明,或者事后自己微调。

4.2 补等待:把固定等待换成智能等待

录制脚本里最常见的page.wait_for_timeout(3000)是必须消灭的。固定等待要么等太久浪费时间,要么等太短导致失败。Playwright 本身有自动等待机制,大部分操作会自动等待元素可交互,但有些场景仍需显式等待。

我让 AI 补等待时,会明确告诉它场景:"点击提交后会出现一个 loading 遮罩,遮罩消失后才算完成,请用合适的等待替换固定等待。"AI 通常会给出expect(locator).to_be_hidden()或page.wait_for_selector(..., state="detached")这类方案,比固定等待靠谱得多。

# 替换前 page.get_by_role("button", name="提交").click() page.wait_for_timeout(3000) # 替换后 page.get_by_role("button", name="提交").click() expect(page.get_by_test_id("loading-mask")).to_be_hidden()

4.3 补异常处理与重试:让脚本在真实环境里扛得住

本地跑得通不代表 CI 上跑得通。网络抖动、元素渲染延迟、偶发的接口超时,都会让脚本挂掉。这部分逻辑 AI 补全效率很高,但你要给它明确的容错策略。

我会告诉 AI:"这个操作可能因为网络延迟失败,请加一次重试,重试前等待 1 秒。"AI 会给出基于expect的超时配置或者简单的重试封装。不过要注意,重试不是万能的,如果一个操作频繁需要重试,说明等待策略或定位本身有问题,应该回头查根因,而不是靠重试掩盖。

4.4 省 token 的核心心法:小步快跑,别一次性喂全量

总结下来,省 token 的关键就三条:先人工清理再喂给 AI、按函数粒度给上下文、一次只提一个明确需求。我见过有人把几百行录制脚本整个贴给 AI 说"帮我优化",结果 AI 输出一大段还未必对,来回改几轮 token 消耗比手写还高。小步快跑,每步验证,才是这套方案的正确打开方式。

5. 从录制到可维护脚本:工程化落地的关键改造

5.1 用 Page Object 模式收拢定位器

录制脚本把定位器散落在各处,页面一改就得满文件找。工程化的第一步是把定位器收进 Page Object 类。每个页面对应一个类,定位器作为类属性,操作作为方法。

class LoginPage: def __init__(self, page): self.page = page self.username = page.get_by_role("textbox", name="用户名") self.password = page.get_by_role("textbox", name="密码") self.submit = page.get_by_role("button", name="登录") def login(self, username, password): self.page.goto("/login") self.username.fill(username) self.password.fill(password) self.submit.click()

这样改造之后,测试用例只调用LoginPage.login(),完全不关心底层定位器。前端改版时,只需要改 Page Object 里的定位器,用例代码一行不动。这一步是录制脚本能否长期维护的分水岭。

5.2 用 fixture 管理浏览器生命周期和登录态

每个用例都重新开浏览器、重新登录,速度慢且浪费。pytest 的 fixture 可以帮你把浏览器启动、上下文创建、登录态复用统一管理。配合前面提到的--save-storage,可以让所有用例共享一个已登录的上下文。

import pytest from playwright.sync_api import sync_playwright @pytest.fixture(scope="session") def browser_context(): with sync_playwright() as p: browser = p.chromium.launch() context = browser.new_context(storage_state="auth.json") yield context context.close() browser.close()

scope="session"表示整个测试会话只启动一次浏览器,速度提升非常明显。登录态通过storage_state加载,省去每个用例重复登录。

5.3 录制脚本的断言分层:页面级、数据级、接口级

断言不能只停留在"元素可见"。我一般分三层:页面级断言验证 UI 状态,比如跳转、文案;数据级断言验证列表条数、字段值;接口级断言则通过监听网络请求,验证后端返回是否符合预期。

# 接口级断言:监听登录接口响应 with page.expect_response("**/api/login") as response_info: page.get_by_role("button", name="登录").click() assert response_info.value.status == 200

三层断言配合使用,测试的可信度会高很多。AI 补全时你可以指定要哪一层,避免它只给你补个"元素可见"就完事。

5.4 录制脚本的版本管理:把 auth.json 和录制产物分开

最后说个容易踩的坑:录制产物和登录态文件的管理。auth.json绝对不能进仓库,录制生成的临时脚本也不建议直接进主分支。我的做法是建一个recordings/目录存放原始录制产物,作为参考;真正进主分支的是经过 Page Object 改造和 AI 补全后的脚本。这样既保留了录制记录便于回溯,又保证了主分支代码的整洁。

6. 实测中反复踩到的坑与应对经验

6.1 动态 ID 和随机 class 让录制定位失效

有些前端框架会生成带随机后缀的 ID,比如input-3f8a2b。录制时定位器抓的是这个随机值,下次跑就找不到了。遇到这种情况,我会在 codegen 里改用get_by_label或get_by_role重新定位,实在不行就推动前端加>

相关新闻

JavaScript数组删替换:原地修改、引用稳定与性能优化实战

JavaScript数组删替换:原地修改、引用稳定与性能优化实战

1. 项目概述:为什么“删替换数组特定值”这件事,值得单独写一篇深度实操笔记?JavaScript里操作数组,看似基础得不能再基础——push、pop、shift、unshift、slice、splice,这些方法翻来覆去讲了十几年。但真到业务一线写…

2026/10/20 22:10:54 阅读更多 →
智能体开发实战:从需求澄清到Todo应用落地

智能体开发实战:从需求澄清到Todo应用落地

1. 从一句指令到完整应用:积木智能体开发Todo的底层逻辑1.1 为什么“先问两个问题”比直接写代码更值得关注把“帮我开发一个Todo应用”这句话丢给一个智能体,它没有立刻开始生成代码,而是先反问了我两个问题。这个细节看起来不起眼&#xff…

2026/10/20 22:10:54 阅读更多 →
WebRTC与AI结合的视频会议方案:实时字幕、语音识别与智能纪要实践

WebRTC与AI结合的视频会议方案:实时字幕、语音识别与智能纪要实践

1. 能力全景拆解与架构思路说到视频会议,很多人第一反应还是“能开摄像头、能说话、能共享屏幕就行”。但真正在企业里跑过会议室系统、做过跨区域远程协同的人,会很清楚这几个痛点有多折磨人:网络一抖画面就卡成连环画;会议开完纪…

2026/10/20 22:10:54 阅读更多 →

最新新闻

开放式代码审查:从规则沉淀到AI辅助的团队实践

开放式代码审查:从规则沉淀到AI辅助的团队实践

那一次Review,至今让我印象深刻。我自认为提交了一段“完美”的代码:类型明确、命名讲究、注释齐全,甚至提前把函数的三种边界情况都测了一遍。结果同事在评审里只问了我三个问题:为什么定义成接口而不是具体类型?三秒…

2026/10/20 22:55:46 阅读更多 →
SQL孤岛与间隙问题全解析:从窗口函数到连续登录实战

SQL孤岛与间隙问题全解析:从窗口函数到连续登录实战

做时间序列分析的朋友一定碰到过这样的场景:一张表里存着用户的登录记录,日期断断续续,你不仅要找出哪几天没登录,还要把连续登录的几天拼成一个“段”来统计。又或者,你面前是一堆带时间戳的告警事件,需要…

2026/10/20 22:55:46 阅读更多 →
机器人系统十年演进:从脉冲伺服到数字孪生的架构变革

机器人系统十年演进:从脉冲伺服到数字孪生的架构变革

要说这十年机器人系统最大的变化,我得先讲个真实场景。2024年我在客户现场调一套柔性上料系统,机器人配上3D视觉和力控打磨单元,工程师们坐在办公室里用数字孪生平台远程调试,坐标变换、轨迹规划、碰撞检测全在仿真环境里跑完&…

2026/10/20 22:55:46 阅读更多 →
Diagram-Design:结构化视觉表达的底层工程能力

Diagram-Design:结构化视觉表达的底层工程能力

1. 项目概述:这不是画图,是用结构化思维解决真实问题的底层能力“diagram-design”这个词组乍看像某个设计软件的插件名,或者某次内部培训的课程代号,但在我过去十年带过的几十个跨领域项目里,它其实代表一种被严重低估…

2026/10/20 22:55:46 阅读更多 →
diagram-design:结构化思维的可视化推演方法

diagram-design:结构化思维的可视化推演方法

1. 项目概述:这不是画图,是用结构化思维解决真实问题“diagram-design”这个词组乍看像一个工具名或软件功能,但在我过去十年带过的几十个跨领域项目里,它从来不是指“用Visio拖几个方框”,而是一套把模糊想法落地为可…

2026/10/20 22:55:46 阅读更多 →
动作单元驱动的能量流管理:知识工作者的复利提效法

动作单元驱动的能量流管理:知识工作者的复利提效法

1. 项目概述:这不是“躺平”,而是用系统性思维重构日常工作的能量守恒“躺平挖 alpha”这个标题一出来,很多人第一反应是调侃——又一个打鸡血式的时间管理法?或者干脆是反内卷的消极宣言?其实完全不是。我在某实验室带…

2026/10/20 22:54:43 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/20 20:43:59 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →