第四部分 - 使用 Cursor 和 Claude 通过 Playwright 创建自动化测试
由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!
第四部分
这是该项目的 GitHub 源代码仓库:https://github.com/joegiglio/cursor2-kb
本文重点介绍如何使用 Cursor 创建 Playwright 测试。如果您有兴趣使用Windsurf 创建 Playwright 测试,请观看以下视频:https://youtu.be/omJo9bDTIaY。
===
好久不见……
上次我们交流时,Cursor 似乎出了问题,文件结构索引也损坏了。在等待了 23 天的修复后,我很高兴地报告,问题似乎终于解决了!用户界面仍然有些小毛病,而且过一段时间后似乎会出现某种内存泄漏导致的运行缓慢,重启后可以解决。但至少现在它能够正确修改文件,我也不用再面对莫名其妙损坏的文件索引了。
在等待修复期间,我注意到许多 Cursor 用户都在抱怨 Cursor 和/或 Claude 的不稳定。没错,它有时确实能用,而且运行起来也确实令人印象深刻,但它就像一个黑匣子,内部组件不断变化,持续更新,结果也时好时坏。
我决定暂时搁置帮助台项目,转而尝试开发一个知识库应用。这可能更简单一些——不需要实时更新或复杂的集成点。
我使用了我最熟悉的同一技术栈——Python、Flask 和 Bootstrap,以及 SQLite 数据库。一切都进展顺利,直到我在“数据库迁移”环节遇到了问题。与上一章类似,它生成了 SQLite 无法识别的约束代码,并抛出了一个错误:
Exception during DB migration:
raise ValueError("Constraint must have a name")
ValueError: Constraint must have a name
这本来应该是克劳德很容易就能解决的问题,但事情却越闹越大,最后我不得不从头开始重建数据库。这要怪我缺乏调试 flask-migrate 的经验,克劳德除了重写数据库之外,根本无法清理这个烂摊子。
一切就绪后,我最终搭建了一个基本功能完善的知识库系统,包括管理面板、深色/浅色模式、搜索功能(仅使用 SQL 查询)和测试数据生成器。该系统使用 Quill 构建,Quill 是一款支持格式设置和图像处理的开源文本编辑器。
我说“基本功能齐全”,是因为我特意没有加入身份验证或多租户等复杂功能。我保持了架构的简洁,只使用了基本的 Flask 路由和 CRUD 操作。目前还没有 API……也许我会在第二阶段重新考虑这些功能。
在添加功能的过程中,我不断注意到克劳德总是修复一个问题,却又弄出另一个问题——就像真正的开发者一样。此外,代码里到处都是CSS和JavaScript代码。有时候,添加新文章可以,但编辑却不行,反之亦然。有时,文章格式代码会突然出现在各处,我不得不请克劳德来清理。这简直就像在玩打地鼠游戏。
项目变得越来越复杂,需要用到自动化测试,这在上一章中已经简要讨论过。大多数公司在质量保证方面的预算非常有限,而且大多数开发人员都讨厌编写测试,那么人工智能或许能帮上忙?
我对 Selenium 和 Cucumber 还算比较熟悉,但已经有一段时间没用了。Playwright 最近越来越受欢迎,据说它在定位器和重试机制的处理上做得更好,所以我请 Claude 帮我写一些 Playwright 测试。但是 VS Code 的 Playwright 扩展似乎都默认用户使用的是 JavaScript,它们并不直接支持 Python,所以我只能像个笨蛋一样在终端里运行这些测试了。
[迅速的]
我想使用 Playwright 和 Python 为这个项目添加端到端的浏览器测试。我希望测试使用 fixtures 来初始化必要的数据,并在每次测试运行结束后删除这些数据。测试需要能够自动清理自身,不留下任何测试数据。如果能在运行测试的同时配置测试数据库,那就更好了。请告诉我需要安装哪些库,根据产品的主要功能创建针对前 10 个最常用场景的测试,并告诉我如何在 Cursor 中运行这些测试。
[/迅速的]
第一次尝试相当不错,提供了如何安装必要库的说明。测试的文件结构看起来也很准确,甚至还通过改变视口大小添加了响应式测试。很贴心……但我不太喜欢它生成的初始测试集,所以我要求它进一步完善:
[迅速的]
本产品尚未实现身份验证功能。请勿针对这些功能创建测试。请将重点放在管理员功能上,例如创建、编辑和删除主题;添加、编辑和删除文章。同时,也请关注最终用户功能,例如搜索和查看文章。查看代码,找出产品最重要的 10 个功能,并为它们创建测试。
[/迅速的]
下一批测试结果略有好转,但仍然没有管理员功能。
[迅速的]
大多数测试看起来都不错,但我没有在 @admin.html 文件中看到任何关于管理员功能的代码。我们需要测试添加、编辑和删除主题以及添加、编辑和删除文章的功能。
[/迅速的]
现在看来它已经成功了,并创建了代码来测试以下应用程序场景:
这些看起来都是很好的起点。现在让我们看看克劳德的执行指令是否正确。嗯,很遗憾,并非如此……
[迅速的]
我按照您的说明运行测试,但遇到了一些问题。
我打开了一个新的终端,运行了命令“FLASK_ENV=testing flask run”,然后运行了“pytest tests/e2e/”。我看到以下问题:
- 当我返回首页时,看到的数据和之前一样,似乎并没有切换到测试数据库。
- 异常:加载 conftest 文件“/Users/joe/cursor2-kb/tests/e2e/conftest.py”时出现 ImportError。tests/e2e/conftest.py:12: 从 models 导入 Topic, Article ModuleNotFoundError: 没有名为“models”的模块
[/迅速的]
Claude 创建了一个名为 models.py 的文件,其中复制了app.py文件中的代码,我对此感到疑惑……
这个应用程序的初始代码全部写在app.py文件中,包括模型。这样做虽然可行,但会导致应用程序臃肿不堪,而且是单文件结构。最佳实践是将模型和路由与配置分离,这也是 Claude 引导我走向的方向,尽管过程比较曲折:
[迅速的]
您建议的 models.py 文件看起来像是与 app.py 中的代码重复。使用 app.py 中的模型是否会更合理?
[/迅速的]
[回复]
实际上,情况正好相反——为了遵循关注点分离原则,我们应该将模型从 app.py 移到 models.py 中。这在 Flask 应用中是一种常见的模式:
app.py 处理应用配置和初始化
;models.py 包含所有数据库模型;
路由通常位于单独的蓝图文件中。
[/回复]
好吧,那就这么办吧!
别急。仔细观察……
[迅速的]
我喜欢你把 models.py 文件拆分出来的做法,但是你也把 app.py 文件里的所有路由都删掉了。我在其他任何文件里都没看到它们。它们就这么消失了……
[/迅速的]
现在 Claude 把app.py文件里的所有路由都提取出来,放到了一个单独的蓝图文件中。这造成了太多的架构改动,我希望他能撤销这些改动……小心那些想重写一切的天才新开发者!虽然这些改动被认为是专业 Flask 代码库的最佳实践,但在测试到位、验证其有效性之前,我不会信任这些改动。
[迅速的]
我知道对于专业级别的 Flask 代码来说,使用蓝图是首选方法。但是,我现在还不方便进行这样的重构。我首先要做的就是编写完整的端到端测试。之后我们再来探讨项目结构。请不要将路由移出 app.py 文件。
[/迅速的]
Cursor 尝试恢复原状,但合并后的界面一片绿色和红色,我不喜欢这种变化。新策略:先做一些简单的 Playwright 测试,然后逐步改进。也许这样会更好……
我把拟定的测试列表保存了下来,因为我很喜欢它的发展方向,然后关闭了光标编辑器窗口,并启动了一个新的编辑器,我相信这会删除之前编辑器的所有内容。
我们不妨从一个更简单的起点重新开始。迭代开发通常是最好的方法。
[迅速的]
我想使用 Playwright 和 Python 为这个项目添加端到端的浏览器测试。请告诉我需要安装哪些库,根据产品的主要功能创建针对前 10 个常见场景的测试,并告诉我如何在 Cursor 中运行这些测试。
[/迅速的]
这次我们遇到了类似的问题,克劳德想测试一些尚未实现的功能。是时候好好训斥克劳德了!
[迅速的]
我看到您正在为身份验证功能创建测试。虽然我们有一个登录页面,但它只是一个占位符,实际上并不具备任何功能。它甚至没有任何允许用户登录的字段。请再次检查项目代码,并为 Playwright 编写 10 个最重要的测试用例。
[/迅速的]
这导致克劳德道歉,并围绕首页的搜索功能创建了一系列测试场景。我原本希望测试能够自动运行,而不是手动编写,而是通过分析代码来发现功能。但事与愿违,所以我只好改用:
[迅速的]
这个版本看起来更好了,但过于侧重搜索功能。我们需要对以下功能进行测试:搜索、创建主题、编辑主题、删除主题、创建文章、编辑文章、删除文章、查看文章,以及一些响应式测试,以确保在不同屏幕分辨率下都能正常显示。
[/迅速的]
我们已经开始陷入困境了……它为更多测试搭建了基本框架,但它对实际存在的数据做了太多假设。不要做任何假设。这就是为什么最好使用预先填充数据的测试数据库,但我们最终会努力实现这一点。目前……
[迅速的]
部分测试会假设某些链接有效且某些内容存在。但如果文章或主题被删除,这些假设可能不再成立。请确保每个测试都是独立的,即创建测试所需的数据,并在测试完成后将其删除。
[/迅速的]
所以克劳德现在又开始制作灯具了!我想,要想做好这件事,就别无他法……
另一个问题是使用诸如“测试对象”和“测试文章”之类的测试字符串,如果运行并行测试(这是提高执行速度的常用方法),这些字符串可能会冲突。我希望测试使用随机数据,所以我这样提示:
[迅速的]
请使用随机数据作为主题名称、文章名称和文章内容。此外,最好添加外语字符测试,以确保它们在浏览和搜索过程中显示正常。
[/迅速的]
我感觉我们还需要做很多调整,但这看起来是一个非常合理的开端。克劳德回复如下:
已进行以下更改:
使用 Faker 生成多种语言的随机数据;
添加针对多语言内容的特定测试;
验证各种字符集的正确显示;
测试非拉丁字符的搜索功能;
验证显示中是否存在字符编码问题;
测试多种语言的内容创建。
这些测试现在验证您的应用程序是否能正确处理:
日语 (日本語)
、中文 (中文)
、韩语 (한국어) 、
俄语 (Русский)
和常规英语内容。
每个测试都会生成自己的随机数据并进行清理,从而使测试更加可靠和独立。
以下是创建的测试:
现在开始运行它们。首先,我需要安装Faker库,这是 Claude 指示我做的……
我收到了一堆不同的测试运行参数和选项。根据以往经验,这些说明看起来都正确,但我打算使用Claude 创建的pytest.ini文件,并添加一些参数来启用详细输出、截图和减慢测试执行速度,以便我能直观地观察测试过程……
现在运行pytest tests/e2e/来实际运行测试!
所有测试都失败了,错误信息相同:
playwright._impl._errors.Error: Protocol error (Page.navigate): Cannot navigate to invalid URL
在pytest.ini中,base_url 设置为http://localhost:5000,这在我看来是正确的。
我搞不清楚为什么会失败,就问了克劳德。他做了一些小调整,然后建议我把pytest.ini文件从他最初放的 tests/e2e 目录移到项目的根目录。我反对,他说现在放着没问题。好吧……
他还建议我在测试中使用绝对 URL,而不是 INI 文件和文本 fixtures 中设置的 URL。错了。我对此提出了异议,他也同意并进行了修改。我对他彻底失望了……
修改完成后,我再次运行了测试。现在浏览器可以打开并跳转到知识库首页(即基本 URL),但所有测试仍然失败。我和 Claude 又讨论了几轮,根页面的问题似乎已经解决,但现在又出现了以下错误:
FAILED tests/e2e/test_admin.py::test_create_topic[chromium] - playwright._impl._errors.TimeoutError: Timeout 30000ms exceeded.
这看起来更像是定位器的问题。
[迅速的]
现在 test_admin.py 中的所有管理测试都失败了,错误信息为:admin_page.fill("#topic-title", data["title"])。这不是正确的定位器。请修复。
[/迅速的]
Claude 修复了一个测试,但其他测试仍然出现定位器错误。我把问题描述得更清楚了:
[迅速的]
除了 test_create_topic 之外,@test_admin.py 中的其余测试都因定位器错误而失败。请检查所有测试中的定位器并进行修正。
[/迅速的]
克劳德做了一些改动,现在我们又遇到了定位器问题……
[迅速的]
现在所有测试都失败了,错误信息为:
tests/e2e/test_admin.py::test_create_topic[chromium] - AssertionError: Locator expected to be visible
[/迅速的]
很明显,我们还要在这里耗上一段时间,兜兜转转,等着克劳德进行必要的修复。同样显而易见且令人遗憾的是,克劳德对应用程序和页面元素缺乏足够的了解,除非我非常明确地解释。仅仅让克劳德“看看代码,写写测试”是不够的。你需要非常熟悉功能特性和页面结构,才能顺利地完成这项工作。
Claude 在用pytest.ini和conftest.py设置测试架构方面提供了帮助,但即便如此,要使基本测试能够运行而不立即失败,还需要进行一些调整。
这个项目的前端使用了 Bootstrap,并结合了 JavaScript 的确认对话框和模态对话框。如果源代码中存在多个模态对话框,但只有一个可见(这是一种常见的设计模式),Claude 似乎很难处理。经过多次迭代,并用伪代码进行指导后,我们最终达成了共识,并通过了测试。
在这些测试中,Claude 对定位器的名称做了很多假设,其中大部分在源代码中根本不存在。只有在明确指示它查找特定文件后,它才能进行清理,但它为什么要做这些假设呢?也许我一开始就没有提供正确的文件?我仍然不太明白这一切到底是怎么运作的。
有时候它似乎拥有整个项目的访问权限,而有时候我需要更明确地指定。如果它需要访问某个文件,它应该先询问,而不是随意编写无意义的代码。记住,每次来回调用都会消耗你的 API 积分。
除了大量的测试失败之外,我还遇到了截图无法保存的问题,即使我在pytest.ini文件中已经设置了相应的标志。Claude 提供了一些解决方案,但都无法解决问题。我查阅了 Playwright 的文档,仍然没有找到答案。这似乎又是一个“应该很容易解决”的问题,但即使你向 Claude 发送了十几个请求,也可能毫无进展。目前,我只能暂时使用 Claude 建议的临时解决方案。
在花了太多时间和 Claude 一起调整代码之后,现在只有两个测试通过了,第三个测试一开始显示通过,但最终由于测试代码和 fixture 代码之间的冲突而失败。调试变得越来越令人精疲力竭,感觉就像陷入了死循环。我已经到了只能盲目地合并建议的代码更改,然后祈祷奇迹发生的地步了。
在本章前面部分,我原本打算构建一个测试框架,用于生成和移除测试数据,并提供可重用的测试用例和函数,以保持数据和测试代码的整洁。但现在这些计划早已落空,我只想让测试通过,至于代码清理,以后再说。如果这些描述让你感到似曾相识,请告诉我……
就连启动时最大化浏览器窗口这样看似简单的操作都难上加难。克劳德尝试用 Python 和 JavaScript 实现,但都失败了。他不知道为什么,Stack Overflow 上的大神们也多次回答过这个问题,但他们的建议也都不管用。我猜想可能是文件之间存在代码冲突,但我现在对代码不够熟悉,无法找出问题所在。真希望人工智能能帮上忙!
我发现一些生成的测试代码中包含多个 expect/assert 语句。我认为最佳实践是每个测试只使用一个 assert 语句,但我也知道这并非总是现实的,尤其是在测试刚编写完成、你还不完全信任它们的时候。
我发现自己成了一个“临时工程师”,努力想办法把事情往正确的方向推进。由于产品代码并非我亲手编写,我对它的内部运作并不熟悉,所以希望克劳德能接手并继续推进。毕竟,是他写的,但他似乎记性不太好。他提供的信息足够你入门,但绝对不足以让你一次就拿到钱。
如果能提供用于创建和删除数据的 CRUD 函数调用 API,让端到端测试只需测试其预期功能,那将大大简化开发工作。如果您曾经为软件应用程序编写过自动化测试,那么这个测试项目的不足之处和缺失的功能可能听起来非常熟悉。您往往需要在真正开始测试产品功能之前,先想办法解决产品本身的缺陷。您可能还记得,在第一章中,我曾打算采用 API 优先的方式构建产品,但很快就失控了。如果没有 API,或许直接调用 SQL 来进行设置和清理会更合理。
如果你在其他项目中已经成功运行了自动化测试,并且熟悉新项目的代码和页面结构,那么复制/粘贴/调整的方法可能比这里介绍的 Claude Playwright 方法更快更有效。至少这样你对代码会有一定的了解,可以更有信心地进行调整。截至目前(2024 年 11 月),我觉得让 Claude 使用 Playwright 生成端到端测试是一件非常麻烦的事情。
Claude20241022 补充
在撰写本文期间,Anthropic 发布了 Claude Sonnet 3.5 的新版本,发布日期为 2024 年 10 月 22 日,该版本也已在 Cursor 中推出。他们声称新版本在代码方面进行了一些重要的改进,所以我决定重新进行测试,看看是否有任何改进。
在解决了克劳德修改requirements.txt文件导致的一些虚拟环境和依赖项问题后,我继续前进。我清空了数据库,创建了一个新分支,然后按照本章前面提到的提示继续操作:
[迅速的]
我想使用 Playwright 和 Python 为这个项目添加端到端的浏览器测试。我希望测试使用 fixtures 来初始化必要的数据,并在每次测试运行结束后删除这些数据。测试需要能够自动清理自身,不留下任何测试数据。如果能在运行测试的同时配置测试数据库,那就更好了。请告诉我需要安装哪些库,根据产品的主要功能创建针对前 10 个最常用场景的测试,并告诉我如何在 Cursor 中运行这些测试。
[/迅速的]
这导致 Claude 生成了tests/e2e目录和所有预期文件,但当我尝试运行测试时,所有测试都失败了。如果我尝试访问网站,所有页面都返回 500 错误。我注意到conftest.py 文件中有一段代码应该将应用程序切换到使用测试数据库。这很好,但看起来它从未初始化。“sqlite:///test.db”并不存在,尽管我在代码中看到了它的引用。
如果我重启服务器,也会抛出异常。我发现所有表以及我所有的数据都从原来的 topics.db 文件中被删除了。我的所有数据和架构都被毫无预警地清空了!幸好这只是测试数据。
我向克劳德抱怨后,他做了一些更改,并创建了一个 Python 文件来初始化数据库,我运行了这个文件。
现在我运行测试时,它们都会抛出缺少约束的异常。
当我手动访问该网站时,出现数据库异常:
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: topic
[SQL: SELECT topic.id AS topic_id, topic.name AS topic_name, topic.sort_order AS topic_sort_order, topic.description AS topic_description, article_1.id AS article_1_id, article_1.title AS article_1_title, article_1.content AS article_1_content, article_1.keywords AS article_1_keywords, article_1.topic_id AS article_1_topic_id, article_1.sort_order AS article_1_sort_order
FROM topic LEFT OUTER JOIN article AS article_1 ON topic.id = article_1.topic_id ORDER BY topic.sort_order]
运行测试后,我的数据库又一次毫无预警地被清空了!肯定有什么地方不对劲……
[迅速的]
我仍然遇到一些严重的数据库问题:如果我使用 `@init_db.py` 脚本,它可以恢复我的数据库表,应用程序也能正常运行。但是,如果我使用 `pytest tests/e2e/` 运行测试,每个测试都会失败,并出现以下错误:`ERROR tests/e2e/test_app.py::test_reorder_topics[chromium] - sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) NOT NULL constraint failed: article.topic_id`。然后,当我尝试再次使用我的主数据库时,所有表又都被删除了!它似乎没有正确设置测试数据库,这不知何故也破坏了我的其他数据库!
[/迅速的]
克劳德做了一些修改。下一个提示:
[迅速的]
我们仍然遇到问题。现在所有测试都失败了,错误信息为:ERROR tests/e2e/test_app.py::test_create_article[chromium] - sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: topic,而且它还在消耗我的另一个数据库!
[/迅速的]
Claude 做了很多修改,当我再次尝试运行数据库初始化脚本时,却失败了,并显示以下错误信息:
(.venv) joe@Joes-MacBook-Pro cursor2-kb % python init_db.py
Traceback (most recent call last):
File "/Users/joe/cursor2-kb/init_db.py", line 1, in <module>
from app import app, db
File "/Users/joe/cursor2-kb/app.py", line 40, in <module>
app = create_app()
^^^^^^^^^^^^
File "/Users/joe/cursor2-kb/app.py", line 34, in create_app
db.init_app(app)
^^
NameError: name 'db' is not defined
服务器也无法启动,因为它会产生同样的错误。
是时候重新开始了!
让我们简化一下初始提示,再次沿用本章前面提到的一个。我手动恢复了数据库,创建了一个新分支,修复了虚拟环境,然后执行了以下操作:
[迅速的]
我想使用 Playwright 和 Python 为这个项目添加端到端的浏览器测试。请告诉我需要安装哪些库,根据产品的主要功能创建针对前 10 个常见场景的测试,并告诉我如何在 Cursor 中运行这些测试。
[/迅速的]
初步的测试清单看起来相当不错:
两例顺利通过,无需任何干预,又是一个好兆头:
不过,这里有个奇怪的现象:即使传递了`--headed`标志,我也无法在测试运行时查看测试结果。Claude 发现pytest.ini和conftest.py中的某个fixtures 之间存在冲突:
克劳德指示我,如果我不想以无头模式运行,就需要修改这里的代码。我拒绝了:
[迅速的]
这个改动目前有效,但我不想在这里传递无头指令。我希望它在 @pytest.ini 文件的 addopts 部分中处理。
克劳德建议我每次想以无头模式运行时都要修改两个文件。为什么?
[迅速的]
这不正确。我希望在 @pytest.ini 文件中控制 head/less 标志。你的方案会迫使我在两处修改此设置。请修复。
[/迅速的]
最后我们得到了这段代码,我觉得很奇怪。我们为什么要关心 --help 标志呢?
这导致出现了一个新的错误,不过在得到提示后,克劳德能够修复该错误:
TypeError: argument of type 'bool' is not iterable
tests/conftest.py:45: TypeError
[迅速的]
现在测试正在运行,但运行时我仍然看不到浏览器窗口。
[/迅速的]
新版 Sonnet 似乎和之前的版本一样,运行不太稳定。我相信只要坚持不懈地改进,最终它会运行得更流畅,但就我们目前的使用需求而言,我并没有看到任何显著的改进。
我对这个实验感到很沮丧,就此结束吧。下一章,我们看看克劳德用Selenium会不会表现得更好,毕竟Selenium比Playwright出现的时间要长得多。或许克劳德能理解更多相关的示例。Playwright现在可能更受欢迎,但我发现Playwright和克劳德的组合实在让人疲惫不堪。
2025年3月更新:
我用 Windsurf 和 Claude 3.5 做了一个类似的实验。效果更好吗?
要确定结果,唯一的办法就是观看我的系列视频“Windsurf 的 Vibe Coding 真相……比 Cursor 更好?”
关于作者
乔·吉格里奥在软件行业拥有超过25年的从业经验。他的职业生涯涵盖了从本地拨号上网服务商、各种规模的初创公司到一家成熟稳健的企业。每一次经历都让他受益匪浅。
他近期的项目包括:
远程办公培训和咨询:chiefremoteofficer.com
Kindle电子书《让远程工作为你所用》
远程办公公司排名。可以把它想象成“远程办公公司的Glassdoor”:remotescorecard.com
乔的指导原则是:远程优先、质量至上、客户至上和终身学习。他性格内向,早在社交疏离成为潮流之前就践行了这一原则!
他目前居住在北卡罗来纳州,并且一直对新的机会、结识优秀人才和了解令人兴奋的项目充满兴趣。
联系 Joe Giglio:joe -AT- joegiglio.org | Twitter | LinkedIn
文章来源:https://dev.to/chiefremote/part-4-using-cursor-and-claude-to-create-automated-tests-with-playwright-2p7j















