--- name: lina-e2e description: Playwright E2E 测试用例管理规范。定义文件命名(模块本地 TC{NNN})、目录布局、TC ID 分配、文件隔离和子断言模式。创建、规划或审查 OpenSpec 变更中的 E2E 测试用例时使用。 compatibility: 依赖 Playwright。 --- # Lina E2E 测试用例规范 本项目中 `Playwright E2E` 测试用例的组织、命名和编写标准。 **交互语言**:与用户交互的内容语言以用户上下文使用的语言为准,用户使用英文则使用英文,用户使用中文则使用中文。 **规范来源**:E2E 质量审查、截图验证、i18n 文案断言等执行要求的唯一事实来源为 `.agents/rules/testing.md`。本技能只负责用例组织、命名和目录布局,不重复维护质量审查和验证细则。创建或审查 E2E 用例时,必须读取并遵守 `.agents/rules/testing.md` 中的 E2E 质量审查要求和 E2E 截图验证要求。 --- ## 1. 目录结构 ``` hack/tests/ ├── e2e/ │ ├── auth/ # 宿主模块:认证 │ │ ├── TC001-login-verification.ts │ │ └── TC002-logout.ts │ ├── admin/ # 宿主模块:管理功能 │ │ ├── TC001-spec-management.ts │ │ └── TC002-user-management.ts │ ├── notebook/ # 宿主模块:笔记本生命周期 │ │ ├── TC001-create-notebook.ts │ │ ├── TC002-jupyterlab-access.ts │ │ ├── TC003-training-execution.ts │ │ ├── TC004-multi-image-notebook.ts │ │ └── TC005-shared-directory.ts │ └── {module}/ # 宿主新模块遵循相同模式 │ └── TC{NNN}-{brief-name}.ts ├── fixtures/ │ ├── auth.ts │ ├── config.ts │ └── k8s.ts ├── pages/ # 宿主/共享页面对象模型文件 │ ├── LoginPage.ts │ ├── NotebookPage.ts │ └── ... └── playwright.config.ts ``` 源码插件的插件专属 E2E 必须闭环在插件自己的目录中: ``` apps/lina-plugins// └── hack/tests/ ├── e2e/ │ └── TC{NNN}-{brief-name}.ts ├── pages/ │ └── .ts └── support/ └── .ts ``` **关键规则:** - 宿主功能测试放在 `hack/tests/e2e/{module}/`。 - 源码插件专属测试放在 `apps/lina-plugins//hack/tests/e2e/`。 - `hack/tests/e2e/extension/plugin/` 只用于宿主插件框架、动态插件运行时、源码插件生命周期这类**宿主级插件能力**测试;禁止把某个源码插件自身功能的 E2E 放到这里。 - 每个测试用例文件放在其主要测试的所有权目录下;谁拥有功能,谁拥有测试。 --- ## 2. 文件命名规范 每个测试文件必须遵循以下模式: ``` TC{NNN}-{brief-name}.ts ``` | 组成部分 | 格式 | 示例 | |-------------|----------------|--------------------------------| | 前缀 | `TC` | `TC` | | ID | `3` 位数字,补零 | `001`、`012`、`100` | | 分隔符 | `-` | `-` | | 简短名称 | kebab-case | `login-verification` | | 扩展名 | `.ts` | `.ts` | **完整示例:** - `TC001-login-verification.ts` - `TC014-bulk-delete-notebooks.ts` **规则:** - 每个文件只包含一个测试用例(一个 `test.describe` 块)。 - `TC ID` 只在当前模块目录内唯一并连续递增。 - 不使用 `.spec.ts` 后缀,使用普通的 `.ts`。 --- ## 3. TC ID 分配 添加新测试用例前: 1. **扫描目标模块目录下的现有 TC 文件**: ```bash find hack/tests/e2e/ -maxdepth 1 -type f -name 'TC*.ts' | sort # 或源码插件: find apps/lina-plugins//hack/tests/e2e/ -maxdepth 1 -type f -name 'TC*.ts' | sort ``` 2. **确定当前模块目录内已使用的最大 TC 编号**。 3. **分配下一个顺序编号**(递增 1)。 **示例:** 如果当前模块目录内现有最大文件为 `TC005-shared-directory.ts`,则下一个测试用例为 `TC006`。 **重要:** TC ID 只在所属模块目录内维护,必须从 `TC001` 开始连续递增。不要因为其他模块或其他插件使用过更大的编号而跳号。 --- ## 4. 测试文件模板 每个测试文件遵循以下结构: 宿主测试: ```typescript import { test, expect } from '../../fixtures/auth' import { SomePage } from '../../pages/SomePage' import { config } from '../../fixtures/config' test.describe('TC-{N} {简短描述}', () => { // 可选:共享设置 test.beforeEach(async ({ adminPage }) => { // ... }) test('TC-{N}a: {子断言描述}', async ({ page }) => { // 单一聚焦断言 }) test('TC-{N}b: {子断言描述}', async ({ adminPage }) => { // 另一个聚焦断言 }) test('TC-{N}c: {子断言描述}', async ({ adminPage }) => { // ... }) }) ``` 源码插件测试: ```typescript import { test, expect } from '../../../../../../hack/tests/fixtures/auth' import { SomePluginPage } from '../pages/SomePluginPage' test.describe('TC-{N} {插件功能描述}', () => { test('TC-{N}a: {子断言描述}', async ({ adminPage }) => { // 插件专属流程断言 }) }) ``` **文件内约定:** - `test.describe` 标签使用 `TC-{N}`(不补零)后跟简短描述。 - 子测试使用 `TC-{N}{字母}:` 作为前缀(如 `TC-1a:`、`TC-1b:`)。 - 当多个子测试合并为一个块时,使用范围表示法:`TC-{N}a~c:`。 - 每个子测试应聚焦于单一断言或紧密相关的断言。 --- ## 5. 测试独立性 每个测试文件必须可独立运行: - **无跨文件依赖。** 测试文件不得依赖其他测试文件创建的状态。 - **自包含设置。** 如果测试需要前置条件(如已登录用户、已创建资源),必须通过 `beforeEach`、`beforeAll`、固件或内联设置自行完成。 - **自行清理。** 创建资源的测试应清理资源以避免污染其他测试。 - **可独立运行:** ```bash npx playwright test hack/tests/e2e/auth/TC001-login-verification.ts pnpm -C hack/tests test:module -- plugin: ``` --- ## 6. 页面对象模型(POM) 所有页面交互必须通过页面对象类进行: ```typescript import { Page, Locator } from '@playwright/test' export class SomePage { readonly page: Page readonly someElement: Locator constructor(page: Page) { this.page = page this.someElement = page.locator('[data-testid="some-element"]') } async goto() { await this.page.goto('/some-path') await this.page.waitForLoadState('networkidle') } async performAction() { // 封装复杂交互 } } ``` **规则:** - 每个页面/功能区域一个 POM 类。 - 宿主或跨模块共享 POM 放在 `hack/tests/pages/`。 - 源码插件专属 POM 放在 `apps/lina-plugins//hack/tests/pages/`。 - 源码插件专属定位器禁止加到宿主 `hack/tests/pages/` 中;只有多个宿主测试或多个插件确实复用的通用能力,才提升到宿主共享 POM。 - 优先使用 `data-testid` 属性作为定位策略。 - POM 方法应返回有意义的值或等待预期状态。 --- ## 7. 测试固件 共享的测试设置(认证、配置)放在 `fixtures/` 目录中: - `auth.ts` — 扩展 Playwright `test`,提供已认证的页面固件(`adminPage` 等) - `config.ts` — 环境相关配置(URL、凭据、超时时间) - `k8s.ts` — Kubernetes 辅助工具(Pod 就绪检查、执行命令) 使用固件而非直接导入 `@playwright/test`: ```typescript // 宿主测试 import { test, expect } from '../../fixtures/auth' // 源码插件测试 import { test, expect } from '../../../../../../hack/tests/fixtures/auth' // 错误 import { test, expect } from '@playwright/test' ``` --- ## 8. 在 OpenSpec 任务中映射 TC ID 在 OpenSpec 变更中编写 `tasks.md` 时,E2E 测试任务必须引用 TC ID: ```markdown ### 任务 3:E2E — TC006 笔记本自动保存 - [ ] 创建 `hack/tests/e2e/notebook/TC006-notebook-auto-save.ts` - [ ] 实现 TC-6a:空闲超时后文件自动保存 - [ ] 实现 TC-6b:UI 中显示保存指示器 - [ ] 实现 TC-6c:页面重新加载后内容持久化 ``` 源码插件示例: ```markdown ### 任务 3:E2E — TC003 插件页面入口 - [ ] 创建 `apps/lina-plugins/example-plugin/hack/tests/e2e/TC003-example-plugin-entry.ts` - [ ] 实现 TC-3a:插件公开接口可读取 - [ ] 实现 TC-3b:插件插槽内容可见 - [ ] 实现 TC-3c:插件管理页可访问 ``` 任务标题中的 TC ID 必须与文件名匹配。子断言(`TC-6a`、`TC-6b`)应列为子项。 --- ## 9. 截图验证 当执行 E2E 测试的代理支持多模态图片识别时,必须在关键交互步骤后捕获浏览器截图并审查截图内容。截图可以发现文本断言无法捕获的问题,包括 `i18n` key 未翻译、布局错乱、接口报错 toast 等。 截图规范和审查要求的完整定义在 `.agents/rules/testing.md` 的"E2E 截图验证要求"章节。本节仅列出 Playwright 截图调用约定: ```typescript // 在关键交互后捕获截图 await adminPage.screenshot({ path: `temp/${new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19)}-${description}.png`, fullPage: false, // 只捕获视口,避免过大的截图 }); ``` 截图文件命名格式:`{YYYYMMDD}-{HHmmss}-{描述}.png`,放置在项目根目录 `temp/` 下。 --- ## 9. 快速参考 | 项目 | 规范 | |----------------------|----------------------------------------------------| | 文件名 | `TC{NNN}-{brief-name}.ts` | | TC ID 范围 | 当前模块目录内唯一并连续递增 | | 宿主测试目录 | `hack/tests/e2e/{module}/` | | 源码插件测试目录 | `apps/lina-plugins//hack/tests/e2e/` | | Describe 标签 | `TC-{N} {描述}` | | 子测试标签 | `TC-{N}{字母}: {描述}` | | 宿主导入 test/expect | 从相对路径 `../../fixtures/auth` 导入 | | 插件导入 test/expect | 从相对路径 `../../../../../../hack/tests/fixtures/auth` 导入 | | 页面交互 | 通过宿主 `pages/` 或插件 `hack/tests/pages/` 中的 POM 类 | | 独立性 | 每个文件可独立运行 | | ID 分配 | 扫描当前模块目录已用最大值 → 递增 1 | | 截图验证 | 多模态可用时,关键交互后截图并审查;详见 `.agents/rules/testing.md` | | 质量审查 | 详见 `.agents/rules/testing.md` 的 E2E 质量审查要求 |