news 2026/9/8 17:37:12

Ghost E2E 测试工作区协作规范:AGENTS.md 中的工作流、校验闭环与 Playwright MCP 定位器发现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Ghost E2E 测试工作区协作规范:AGENTS.md 中的工作流、校验闭环与 Playwright MCP 定位器发现

Ghost E2E 测试工作区协作规范:AGENTS.md 中的工作流、校验闭环与 Playwright MCP 定位器发现

【免费下载链接】GhostIndependent technology for modern publishing, memberships, subscriptions and newsletters.项目地址: https://gitcode.com/GitHub_Trending/gh/Ghost

Ghost 在仓库根目录维护着多层文档:面向开发者的docs/contributing/e2e-testing.md是"写浏览器端到端测试的规范本体",而e2e/AGENTS.md则是面向在e2e/工作区里"动手的人与 AI 代理"的操作入口——它回答三个问题:改代码前读什么、改完必须跑什么、以及如何借助 Playwright MCP 高效且稳定地发现 UI 定位器。本文以该文件为骨架,结合其背后引用的三份"权威文档"(写作指南、E2E 工作区 README、数据工厂 README)及工作区内的真实配置与源码,把这条"进入 E2E 工作区的正确路径"讲透:读完你将掌握 Ghost E2E 工作区的分层文档体系、改动前后必须执行的校验命令链,以及一套可复现的、从"运行测试拿到实例 URL"到"确认稳定定位器"的完整实操流程。

为什么需要一份AGENTS.md:在进入工作区前先读权威文档

e2e/AGENTS.md全文极短,但它传达了一个明确工程原则:任何 E2E 改动都应建立在阅读"人工维护的规范文档"之上,而不是靠工具自身的提示或零散记忆。文件开篇就是硬性要求:

Read the canonical human documentation before changing this workspace

也就是说,这份文件是一份"门禁索引",它把散落在仓库中的规范收敛到三个唯一事实来源(Single Source of Truth):

想要了解的内容权威文档(相对仓库根目录)覆盖范围
测试结构、Page Object、定位器优先级、等待与断言方式docs/contributing/e2e-testing.md写测试本身的约定,被称为canonical writing guide
基础设施模式、fixtures、隔离策略、命令与排障e2e/README.md本地工作区、Docker 基础设施、运行与调试
测试数据工厂与持久化适配器e2e/data-factory/README.md测试数据构造 helpers 的用法与扩展方式

这种分层有一个刻意为之的目的:文档的"章节职责"被拆开,且e2e/README.md与写作指南之间通过链接互相引用(例如 README 中写明"测试结构与约定见写作指南,本 README 只覆盖本地工作区、基础设施、fixtures 与命令")。AGENTS.md则充当两者的总入口,避免协作者(尤其是 AI Agent)在错误的位置寻找信息。

此外,e2e/AGENTS.md所在目录结构恰好对应了这些文档的实体位置,便于对照阅读:

e2e/ ├── AGENTS.md # 本文件:工作区协作入口与工作流要求 ├── README.md # 运行模式、隔离、命令、排障 ├──>"paths": { "@/admin-pages": ["./helpers/pages/admin/index"], "@/public-pages": ["./helpers/pages/public/index"], "@/portal-pages": ["./helpers/pages/portal/index"], "@/helpers/*": ["./helpers/*"], "@/data-factory": ["./data-factory/index.ts"], "@/data-factory/*": ["./data-factory/*"] }

对应的导入形态(写作指南原文示例):

import {expect, test} from '@/helpers/playwright'; import {LoginPage, PostsPage} from '@/admin-pages'; import {createPostFactory} from '@/data-factory'; import {usePerTestIsolation} from '@/helpers/playwright/isolation';

这些别名有双重保障:一方面 e2e/eslint.config.js 通过no-relative-import-paths/no-relative-import-paths规则(允许同目录相对导入、其余统一使用@前缀)在风格上阻止散乱相对路径;另一方面no-restricted-imports又限制@/helpers/pages/*这类深层导入只出现在页面对象内部,从而保证 Page Object 的封装不被破坏。

3. 改动后必跑的校验闭环

e2e/AGENTS.md明确给出改动后的动作组合,这也是被package.jsonscripts 支撑的最小回归集合:

# 从 e2e/ 工作区运行 pnpm test tests/admin/signin.test.ts # 运行被改动影响的聚焦用例 pnpm lint # eslint . --cache pnpm test:types # tsc --noEmit && tsc -p tsconfig.scripts.json
  • 改完测试:跑 focused test +pnpm lint+pnpm test:types
  • 改完数据工厂:除了上面三项,还必须追加pnpm build(在 package.json 中它被定义为pnpm test:types,见 e2e/package.json)。

为什么 lint 与类型检查如此重要?因为写作指南里的很多约定——而不是全部——是"可执行"的约定。例如文件名 kebab-case 规则ghost/filenames/match-regex的正则为^[a-z0-9.-]+$且为 error 级别(见 e2e/eslint.config.js),FeaturePage.ts这类 PascalCase 文件名会被直接判错,从而保证"测试文件按行为命名、页面对象按<feature>-page.ts命名"的规范有机器兜底。测试目录(tests/**/*.ts)还被额外约束:禁止使用page.locator()(必须用 Page Object 或更高层方法)、禁止test.describe.parallel()/serial()(前者被要求改用usePerTestIsolation())、并启用本地自研规则local/no-unsafe-reset-environment来保护隔离逃生舱的正确用法(详见 e2e/eslint.config.js)。

4. 规范变更要回流到文档,而不是制造"工具专属副本"

最后一条工作流规则颇具 Agent 时代的前瞻性:

Update the canonical human guide when a shared E2E convention changes. Do not create or rely on tool-specific copies of the guidance.

它要求:一旦共享 E2E 约定发生变化,改动应回到唯一规范文档(canonical human guide)里,而不允许创建"某个 AI 工具专属的规则副本"并在后续依赖它。这正是AGENTS.md自身保持极短、只做索引与指针的原因——规范本体始终只有一份,避免多个副本漂移失配。

发现定位器的标准姿势:Playwright MCP 工作流

e2e/AGENTS.md用近半篇幅描述了"何时以及如何用 Playwright MCP 发现定位器、构建 Page Object"。这是对 AI 代理最实用的一段,完整流程可拆成四个步骤。

前置条件与适用场景

When discovering selectors or building a Page Object, use Playwright MCP when it is available.

MCP(Model Context Protocol)服务器在这里扮演"浏览器控制接口":当代理需要为一段陌生 UI 挑选稳定定位器时,与其凭空猜测 DOM,不如让 MCP 直接连上真实运行中的测试实例。

步骤一:用PRESERVE_ENV=true保留环境并拿到实例 URL

e2e/目录下以保留环境的方式运行一个聚焦测试:

cd e2e PRESERVE_ENV=true pnpm test

正常情况下每次测试结束后环境会被回收(global teardown 会清理 e2e 容器与测试数据库),而PRESERVE_ENV=true会保留容器与数据库,测试运行器随后会打印出该 Ghost 实例的地址——写作指南给出的典型值是http://localhost:2369(见 docs/contributing/e2e-testing.md "Preserve the test environment")。基础设施(MySQL、Redis、Mailpit、Tinybird)必须已在运行,可使用pnpm devpnpm --filter @tryghost/e2e infra:up先行启动(见 e2e/README.md)。

步骤二:导航到实例并拍摄"无障碍快照"

打开打印出的实例 URL,在交互前先取 accessibility snapshot。这一步非常关键:它迫使你基于可访问性语义而非视觉像素来选择定位器,与项目"优先语义定位器"的定位器优先级天然对齐(详见下文)。

步骤三:实际演练交互,验证定位器并截图

对目标 UI 执行真实交互(点击、输入等)以验证候选定位器确实命中了期望元素;当渲染状态对后续判断有参考价值时再截图留存。换句话说,定位器必须以"真实交互通过"为证据,而不是只在静态 DOM 里存在。

步骤四:按定位器优先级重写,不照抄生成结果

这是 MCP 工作流中最容易被忽略、却写在AGENTS.md里的一句话:

Follow the locator priority in the E2E writing guide; do not copy generated selectors without checking that they are stable.

生成的选择器可能是脆弱的结构表达式,必须经过稳定性审查并按写作指南的优先级重写,自高向低为:

  1. ARIA 角色 + 可访问名称page.getByRole('button', {name: 'Save'})
  2. 表单标签page.getByLabel('Name')
  3. 唯一可见文本page.getByText('Saved')
  4. 稳定测试 ID:仅在无语义定位器可用时使用
    • Ember Admin 常用data-test-*,React Admin apps 用data-testid
  5. 稳定的结构选择器:仅在万不得已时使用

这条优先级的完整示例代码与解释在 docs/contributing/e2e-testing.md,页面对象中推荐按固定次序为元素声明只读Locator,例如:

// e2e/helpers/pages/admin/feature-page.ts(示意结构) export class FeaturePage extends AdminPage { readonly saveButton = page.getByRole('button', {name: 'Save'}); readonly nameInput = page.getByLabel('Name'); readonly statusMessage = page.getByText('Saved'); }

如果 UI 上确实没有可靠的语义锚点,正确做法是回到产品代码里加一个稳定的测试 ID,而不是让测试去耦合样式或 DOM 位置——这正是 Ghost 工作区中测试与产品代码相互演进的典型形态。

备选方案:Playwright Inspector 与浏览器开发者工具

AGENTS.md明确留了退路:

If Playwright MCP is unavailable, use Playwright Inspector or browser developer tools as described in the writing guide.

两种途径的目标一致:打开被保留的实例、观察可访问性树与相关属性、在优先级指导下选定定位器并验证交互。区别仅在于"控制浏览器的接口"不同,规范本身不随之分叉。

工作流背后的规范底座:写作指南核心约定

e2e/AGENTS.md反复指向的 docs/contributing/e2e-testing.md 是所有这些工作流的"语法层"。其中与定位器发现最直接相关的约定包括:

  • 等待靠状态而非计时:用await element.waitFor({state: 'visible'})与 web assertion(await expect(...).toContainText(...));禁止page.waitForTimeout(5000)page.waitForLoadState('networkidle')
  • 异步操作等"用户会看到的 UI 信号":例如保存后等待状态消息出现,而不是固定延时。
  • iframe 用frameLocator():它像其他定位器一样自动重试,例如page.frameLocator('[data-testid="portal-popup-frame"]')
  • 页面对象内方法返回 locator 或值、但不在其中断言:断言留在测试用例里;Modal 被建模为普通类而非页面子类,把定位器作用域收敛到getByRole('dialog')上。
  • Arrange–Act–Assert 作为可读性启发:搭建场景 → 执行被测行为 → 验证结果,用命名与结构让三阶段自明。

这些约定直接支撑了 MCP 工作流中"取无障碍快照、按优先级选 locator、做交互验证"三步的产出质量。

从用例到数据工厂:改动边界的两种情形

e2e/AGENTS.md把"改测试"与"改数据工厂"作为两条不同的校验路径对待,是因为它们处于不同的抽象层:

  • 测试用例通过 Page Object 与@/别名使用 helpers,通常只需要"聚焦用例 + lint + 类型检查"即可完成回归。
  • 数据工厂(e2e/data-factory)是测试数据的构造与持久化层,其代码会以@tryghost/e2e的构建产物被引用,因此改动后必须追加pnpm build

数据工厂本身采用"工厂类 + 持久化适配器"的分层:实体形状与随机默认值归属@tryghost/test-data包,工厂在build()中把"响应形状"转换为"写入载荷"(扁平化关联、丢弃仅响应字段),entityType属性驱动持久化(见 e2e/data-factory/README.md)。在测试中的典型用法:

import {createPostFactory} from '@/data-factory'; test('...', async ({page}) => { const postFactory = createPostFactory(page.request); const publishedPost = await postFactory.create({ title: 'My Published Post', status: 'published' }); });

隔离模型与运行模式:理解PRESERVE_ENV之前必知的环境语义

MCP 工作流第一步PRESERVE_ENV=true之所以能"保留一个可检查的实例",背后是一套精心设计的隔离模型。理解它有助于避免在错误的环境身份上做定位器验证:

  • 默认按文件隔离(per-file):每个文件一次 Ghost 环境周期。global setup 先创建基础数据库、启动 Ghost、等待健康并快照数据库;文件边界处从快照克隆新库并重启 Ghost 复用。
  • 按测试隔离(per-test):usePerTestIsolation()(定义于 e2e/helpers/playwright/isolation.ts)用两个标准 Playwright 调用完成——test.describe.configure({mode: 'parallel'})test.use({isolation: 'per-test'}),为每个测试分配独立 Ghost 实例。fullyParallel: true会强制按测试隔离。
  • 环境身份(identity)参与因素:fixture option 中configlabs参与 per-file 复用身份,二者任一变化都会在文件内触发环境回收重建;而stripeEnabled不参与复用,总是强制 per-test 隔离(因为 Ghost 必须针对每个测试的 fake Stripe 服务器启动)。
  • 逃生舱resetEnvironment()只能在beforeEach钩子中、且必须在解析baseURLpagepageWithAuthenticatedUserghostAccountOwner等有状态 fixture之前调用;ESLint 自研规则会拦截明显误用,fixture 内的运行时守卫是最终硬校验。

运行模式方面,若未显式设置GHOST_E2E_MODE,脚本会自动选择:本机 admin dev server 在http://127.0.0.1:5174可达则走dev模式(Ghost 挂载源码并把资源代理给宿主 dev server),否则走build模式(使用预构建镜像,资源从/content/files提供);也可用GHOST_E2E_MODE=dev/GHOST_E2E_MODE=build强制指定。这些语义在 e2e/README.md 中有完整展开——PRESERVE_ENV=true之后你要检查的,正是某个处于确定隔离身份与运行模式下的真实 Ghost 实例。

结语:一份让"人与 AI"共用同一套共识的入口文档

e2e/AGENTS.md的价值不在篇幅,而在刻意保持精简的指针式设计:它把所有规范收敛到三份人工权威文档,把工作流压缩为"先读文档 → 只用 pnpm/@/别名 → 按边界跑全校验 → 约定变更回流文档",再把最容易让 AI 代理出错的选择器发现环节固化成"保留环境 → 无障碍快照 → 交互验证 → 按优先级重写"四步。配合 eslint.config.js 中可执行的命名、导入与隔离规则,Ghost 得以让编辑器、CI 与 AI 代理在同一套约定下协作:规范只有一份,谁来执行都一样。若你的改动触及任何共享约定,请记住最后那条回写规则——把变化送回 canonical guide,而不要在别处另起炉灶。

【免费下载链接】GhostIndependent technology for modern publishing, memberships, subscriptions and newsletters.项目地址: https://gitcode.com/GitHub_Trending/gh/Ghost

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 17:35:50

STM32智能小车PID闭环速度控制:编码器测速与增量式PID实战解析

简介&#xff1a;这是一份STM32智能小车PID闭环速度控制程序源代码&#xff0c;基于标准库函数开发&#xff0c;主要面向嵌入式入门学习者、电子设计竞赛队伍以及智能小车爱好者&#xff0c;帮助解决直流减速电机的测速反馈与闭环调速问题。工程基于KEIL环境&#xff0c;配套Ke…

作者头像 李华
网站建设 2026/9/8 17:33:13

STM32四旋翼无人机飞控系统开发:从姿态解算到PID控制

简介&#xff1a;一套基于STM32单片机的四轴无人机控制系统完整代码包&#xff0c;面向嵌入式开发学习者、无人机爱好者、电子设计竞赛队伍及本科毕业设计人群。方案覆盖硬件结构搭建、系统建模、硬件模块设计、传感器数据采集、姿态检测融合算法、控制算法设计以及环境下的程序…

作者头像 李华
网站建设 2026/9/8 17:29:16

res-downloader:本地代理捕获并下载网络资源的快速上手

res-downloader&#xff1a;本地代理捕获并下载网络资源的快速上手 【免费下载链接】res-downloader 视频号、小程序、抖音、快手、小红书、直播流、m3u8、酷狗、QQ音乐等常见网络资源下载! 项目地址: https://gitcode.com/GitHub_Trending/re/res-downloader 微信视频号…

作者头像 李华
网站建设 2026/9/8 17:26:36

跨境账号防关联与合规投放SOP:从设备隔离到素材审核的全流程指南

1. 为什么我建议你把“避坑”当成增长的一部分跨境圈子里有个很怪的现象&#xff1a;很多人做增长只看投放ROI、只看爆单速度&#xff0c;结果往往不是死在产品上&#xff0c;而是死在账号上。我见过好几个月销几十万美金的团队&#xff0c;一夜之间主页被封、广告账户受限、店…

作者头像 李华