关于 e2e-test-conventions
面向编写、修改或审查浏览器端 E2E 测试的 Playwright 与 TypeScript 规范技能,覆盖目录组织、测试隔离、认证、导航、选择器及环境配置。
主要功能
- 01Playwright 与 TypeScript 规范
- 02浏览器与测试套件组织
- 03独立且适合并行的测试
- 04Page Object Model 复用指南
- 05选择器与导航规则
- 06环境及测试数据管理
适用场景
- 01统一 E2E 测试规范
- 02审查测试结构与可靠性
- 03编写可并行的独立测试
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ agentmantis/ test-skills --skill e2e-test-conventions安装后在 Claude Code 中输入/e2e-test-conventions确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/auth-setup.mdfolder-structure.mdselector-priority.md详细介绍
E2E 测试约定
这些约定适用于所有 Playwright E2E 测试代码。在生成、修改或审查测试时,请阅读并遵循这些约定。
技术栈
- Playwright 用于浏览器自动化和运行测试
- TypeScript 用于所有测试代码(不使用纯 JavaScript)
- 与框架无关——根据项目使用的 UI 框架调整选择器和辅助工具
项目结构
e2e/ 目录采用以下布局:
e2e/
├── auth/ # Authentication setup (runs before all tests)
│ └── auth.setup.ts # Logs in and saves session state
├── fixtures/ # Custom Playwright fixtures
│ └── base.ts # Extends Playwright's test with custom fixtures
├── helpers/ # Shared utility functions (NOT page objects)
│ └── env-config.ts # Environment resolution
├── poms/ # Page Object Models (one file per page)
│ └── base.page.ts # Abstract base class — all POMs extend this
├── test-data/ # External test data (JSON files)
├── tests/ # Test specs organised by suite
│ ├── handover/ # Ticket-driven handover tests (temporary)
│ ├── regression/ # Full regression tests (permanent)
│ └── smoke/ # Quick critical-path checks
├── playwright.config.ts
├── tsconfig.json
└── .env.example # Template for environment variables
规则:
- 不得在此结构之外创建文件
- 未经明确许可,不得移动现有文件
各目录的详细信息请参阅 references/folder-structure.md。
浏览器 × 测试套件配置
Playwright 配置使用 {browser}:{suite} 命名项目:
浏览器: chromium、firefox、webkit、mobile-chrome、mobile-safari
测试套件:
| 测试套件 | 目录 | 用途 | 生命周期 |
|---|---|---|---|
regression | tests/regression/ | 完整回归测试 | 永久 |
handover | tests/handover/ | 由工单驱动的交接测试 | 临时——升级或删除 |
smoke | tests/smoke/ | 关键路径健全性检查 | 永久 |
每个项目都依赖负责处理身份验证的 setup 项目。
运行测试
# From e2e/ directory
npx playwright test # All browsers × all suites
npx playwright test --project="chromium:regression" # Single project
npx playwright test --project="*:smoke" # One suite, all browsers
npx playwright test --project="firefox:*" # One browser, all suites
命名约定
| 类型 | 模式 | 示例 |
|---|---|---|
| 页面对象模型 | poms/ 中的 {feature}.page.ts | dashboard.page.ts |
| 回归测试规格文件 | tests/regression/ 中的 {feature}.spec.ts | dashboard.spec.ts |
| Smoke 测试规格文件 | tests/smoke/ 中的 {feature}.spec.ts | dashboard.spec.ts |
| 交接测试规格文件 | tests/handover/ 中的 {TICKET}-{description}.spec.ts | PROJ-123-bulk-delete.spec.ts |
| 测试数据 | test-data/ 中的 {feature}.json | dashboard.json |
POM、规格文件和测试数据文件中的 {feature} 名称必须一致。
测试独立性与并行运行
每项测试都必须完全独立
- 每项测试都在隔离环境中运行,且顺序不限
- 测试绝不能依赖之前测试创建的状态
- 每项测试都通过 POM 的导航方法进入其页面
并行安全性
所有测试都会在多个工作进程中并行运行。为避免冲突:
- 为所有测试数据值添加唯一后缀(时间戳 + 随机 ID)
- 切勿在测试之间共享可变状态
- 每项测试都创建自己的数据、验证数据并进行清理
身份验证
身份验证在 setup 项目(auth/auth.setup.ts)中执行一次,该项目会在所有测试项目之前运行。会话状态会保存到文件中,并通过 Playwright 的 storageState 配置复用。
切勿在规格文件、POM、beforeEach 或 beforeAll 中编写登录逻辑。身份验证已由其他流程处理。
如果应用程序将身份验证令牌存储在 IndexedDB 中(例如 Firebase Auth、Supabase、AWS Amplify 等),请使用 indexedDB 选项:
await page.context().storageState({ path: authFile, indexedDB: true });
完整模式请参阅 references/auth-setup.md。
导航
- 始终使用 URL 直接导航(
page.goto('/dashboard')) - 不要通过菜单或侧边栏点击导航——菜单状态和动画会导致测试不稳定
- 导航后,断言 URL 正确,并且关键标题或元素可见
BasePage 与派生 POM 方法
BasePage 是存放适用于多个页面的可复用辅助方法的唯一位置。派生 POM 应仅关注特定页面的行为。
方法适合放在 BasePage 中的情况
满足以下条件时,将辅助方法移至 BasePage:
- 可在多个 POM 之间复用——例如,关闭模态框、等待 Toast 通知或检查加载指示器
- 与单个页面没有紧密耦合——无论当前处于哪个页面,工作方式都相同
- 足够通用,可以被干净地继承——不依赖特定页面的选择器或假设
- 如果留在功能专属的 POM 中,很可能会重复编写
BasePage 辅助方法示例:
waitForToast(message)— 等待并验证 Toast 通知dismissModal()— 关闭任何已打开的模态对话框waitForLoadingComplete()— 等待加载指示器消失getTableRowCount()— 统计多个页面中都存在的数据表的行数
方法适合放在派生 POM 中的情况
满足以下条件时,将辅助方法保留在特定 POM 中:
- 仅用于一个页面——例如,填写特定页面的表单
- 依赖特定页面的结构——使用仅在该页面上存在的选择器
- 预计不会在其他地方复用
这样做的重要性
- 减少重复——共享逻辑集中在一处,而不是复制到多个 POM 中
- 集中管理共享行为——更新通用辅助方法后,会自动应用于所有 POM
- 让派生 POM 保持精简、专注——每个 POM 只包含该页面特有的内容
- 提升可发现性——开发者知道可以在
BasePage中查找共享工具
规则
如果发现自己要在第二个 POM 中编写相同的辅助方法,立即将其提升到 BasePage 中。不要让重复的辅助方法散落在各个功能 POM 中。
选择器和定位策略
按以下优先级顺序使用:
getByRole()— 按钮、标题、对话框(最可靠)getByLabel()— 表单字段getByText()— 可见文本内容getByPlaceholder()— 输入框占位文本locator()配合 CSS /filter()— 最后手段
提示:
- 动态渲染的属性(工具提示、弹出层)在查询时可能尚未出现在 DOM 中——使用
filter()匹配子内容 - 图标按钮通常没有可见文本——按
aria-label或子图标内容匹配 - 优先使用基于角色和标签的选择器,而不是 CSS 类(这类选择器脆弱且依赖特定框架)
示例请参阅 references/selector-priority.md。
环境配置
每个环境在 e2e/ 目录中都有自己的 .env.{env} 文件:
| 文件 | 环境 |
|---|---|
.env.local | 本地开发 |
.env.dev | 开发服务器 |
.env.test | 测试服务器 |
.env.uat | UAT 服务器 |
.env.production | 生产环境 |
所有文件使用相同的变量名——只有值不同:
BASE_URL="https://your-app.example.com"
LOGIN_EMAIL="[email protected]"
LOGIN_PASSWORD="your-password"
AUTH_FILE="e2e/.auth/user.json"
选择当前环境
必须设置 TEST_ENV。如果未设置,env-config.ts 会立即抛出错误——测试运行不会启动。这样可以防止有人忘记设置变量时,意外对生产环境运行 E2E 测试。
TEST_ENV=dev npx playwright test # loads .env then .env.dev
TEST_ENV=production npx playwright test # loads .env then .env.production
npx playwright test # ❌ ERROR — TEST_ENV is not set
两层加载
helpers/env-config.ts 读取 TEST_ENV,并通过 dotenv 分两层加载环境变量:
e2e/.env— 基础文件(可包含TEST_ENV和所有变量)e2e/.env.{env}— 可选的环境专属覆盖文件
两个文件都是可选的。 如果两个文件都不存在,进程将使用环境中已有的变量(例如由 CI 或容器注入的变量)。dotenv 不会覆盖已设置的变量,因此命令行导出和 CI 注入的值始终优先。
该模块导出辅助函数——getEnvConfig()、getBaseUrl()、getCredentials() 和 getAuthFilePath()——而非静态常量。每个进程只会加载一次;后续调用不执行任何操作。
规则
- 必须设置
TEST_ENV— 缺少该变量时会抛出错误,确保 E2E 测试不会在不知情的情况下指向生产环境 - 所有变量(
BASE_URL、LOGIN_EMAIL、LOGIN_PASSWORD、AUTH_FILE)均为必填项 — 缺少任何变量都会抛出错误 - 绝不要为必填环境变量使用硬编码的默认值;如果缺少变量,请抛出错误
.env.*文件包含密钥,已被 Git 忽略.env.example是唯一提交的环境变量文件,用作模板
测试数据
- 所有测试数据都存放在
e2e/test-data/{feature}.json - 绝不要在 spec 或 POM 文件中硬编码数据
- 使用自定义 fixture 加载测试数据,并为其添加唯一后缀
- 始终从自定义 fixture 导入
test,不要从@playwright/test导入
Spec 文件规则
- 所有页面交互都必须通过 POM 进行 — 绝不要在 spec 中直接调用
page.getByRole()、page.locator()等 - 在 spec 中,允许使用
page的情况仅有:将其传递给 POM 构造函数,或在beforeAll中创建 context - 使用
test.beforeAll和手动创建的浏览器 context 调用 POM 的setUp()进行清理 - 每个测试都通过 POM 导航方法独立导航