Skip to content
85 changes: 69 additions & 16 deletions .agents/skills/lina-e2e/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,34 +17,49 @@ compatibility: 依赖 Playwright。
```
hack/tests/
├── e2e/
│ ├── auth/ # 模块:认证
│ ├── auth/ # 宿主模块:认证
│ │ ├── TC0001-login-verification.ts
│ │ └── TC0007-logout.ts
│ ├── admin/ # 模块:管理功能
│ ├── admin/ # 宿主模块:管理功能
│ │ ├── TC0002-spec-management.ts
│ │ └── TC0003-user-management.ts
│ ├── notebook/ # 模块:笔记本生命周期
│ ├── notebook/ # 宿主模块:笔记本生命周期
│ │ ├── TC0004-create-notebook.ts
│ │ ├── TC0005-jupyterlab-access.ts
│ │ ├── TC0006-training-execution.ts
│ │ ├── TC0008-multi-image-notebook.ts
│ │ └── TC0009-shared-directory.ts
│ └── {module}/ # 新模块遵循相同模式
│ └── {module}/ # 宿主新模块遵循相同模式
│ └── TC{NNNN}-{brief-name}.ts
├── fixtures/
│ ├── auth.ts
│ ├── config.ts
│ └── k8s.ts
├── pages/ # 页面对象模型文件
├── pages/ # 宿主/共享页面对象模型文件
│ ├── LoginPage.ts
│ ├── NotebookPage.ts
│ └── ...
└── playwright.config.ts
```

源码插件的插件专属 E2E 必须闭环在插件自己的目录中:

```
apps/lina-plugins/<plugin-id>/
└── hack/tests/
├── e2e/
│ └── TC{NNNN}-{brief-name}.ts
├── pages/
│ └── <PluginPageObject>.ts
└── support/
└── <plugin-helper>.ts
```

**关键规则:**
- `e2e/` 下的目录以**功能模块**命名(如 `auth`、`notebook`、`admin`)。
- 每个测试用例文件放在其主要测试的模块目录下。
- 宿主功能测试放在 `hack/tests/e2e/{module}/`。
- 源码插件专属测试放在 `apps/lina-plugins/<plugin-id>/hack/tests/e2e/`。
- `hack/tests/e2e/extension/plugin/` 只用于宿主插件框架、动态插件运行时、源码插件生命周期这类**宿主级插件能力**测试;禁止把某个源码插件自身功能的 E2E 放到这里。
- 每个测试用例文件放在其主要测试的所有权目录下;谁拥有功能,谁拥有测试。

---

Expand Down Expand Up @@ -81,9 +96,13 @@ TC{NNNN}-{brief-name}.ts

1. **扫描所有模块目录下的现有 TC 文件**:
```bash
find hack/tests/e2e -name 'TC*.ts' | sort
{
find hack/tests/e2e -type f -name 'TC*.ts'
find apps/lina-plugins -type f -path '*/hack/tests/e2e/*' -name 'TC*.ts'
rg -No 'TC[0-9]{4}' openspec/changes -g 'tasks.md'
} | rg -No 'TC[0-9]{4}' | sort -u | tail -1
```
2. **确定当前使用的最大 TC 编号**。
2. **确定当前已实现和 OpenSpec 任务记录中使用或预留的最大 TC 编号**。
3. **分配下一个顺序编号**(递增 1)。

**示例:** 如果现有最大文件为 `TC0009-shared-directory.ts`,则下一个测试用例为 `TC0010`。
Expand All @@ -96,6 +115,8 @@ TC{NNNN}-{brief-name}.ts

每个测试文件遵循以下结构:

宿主测试:

```typescript
import { test, expect } from '../../fixtures/auth'
import { SomePage } from '../../pages/SomePage'
Expand All @@ -121,6 +142,19 @@ test.describe('TC-{N} {简短描述}', () => {
})
```

源码插件测试:

```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:`)。
Expand All @@ -139,13 +173,14 @@ test.describe('TC-{N} {简短描述}', () => {
- **可独立运行:**
```bash
npx playwright test hack/tests/e2e/auth/TC0001-login-verification.ts
pnpm -C hack/tests test:module -- plugin:<plugin-id>
```

---

## 6. 页面对象模型(POM)

所有页面交互必须通过 `pages/` 中的页面对象类进行
所有页面交互必须通过页面对象类进行

```typescript
import { Page, Locator } from '@playwright/test'
Expand All @@ -172,7 +207,9 @@ export class SomePage {

**规则:**
- 每个页面/功能区域一个 POM 类。
- POM 文件放在 `pages/` 目录中(不在 `e2e/` 中)。
- 宿主或跨模块共享 POM 放在 `hack/tests/pages/`。
- 源码插件专属 POM 放在 `apps/lina-plugins/<plugin-id>/hack/tests/pages/`。
- 源码插件专属定位器禁止加到宿主 `hack/tests/pages/` 中;只有多个宿主测试或多个插件确实复用的通用能力,才提升到宿主共享 POM。
- 优先使用 `data-testid` 属性作为定位策略。
- POM 方法应返回有意义的值或等待预期状态。

Expand All @@ -188,9 +225,12 @@ export class SomePage {

使用固件而非直接导入 `@playwright/test`:
```typescript
// 正确
// 宿主测试
import { test, expect } from '../../fixtures/auth'

// 源码插件测试
import { test, expect } from '../../../../../../hack/tests/fixtures/auth'

// 错误
import { test, expect } from '@playwright/test'
```
Expand All @@ -210,6 +250,17 @@ import { test, expect } from '@playwright/test'
- [ ] 实现 TC-10c:页面重新加载后内容持久化
```

源码插件示例:

```markdown
### 任务 3:E2E — TC0221 插件页面入口

- [ ] 创建 `apps/lina-plugins/example-plugin/hack/tests/e2e/TC0221-example-plugin-entry.ts`
- [ ] 实现 TC-221a:插件公开接口可读取
- [ ] 实现 TC-221b:插件插槽内容可见
- [ ] 实现 TC-221c:插件管理页可访问
```

任务标题中的 TC ID 必须与文件名匹配。子断言(`TC-10a`、`TC-10b`)应列为子项。

---
Expand All @@ -220,10 +271,12 @@ import { test, expect } from '@playwright/test'
|----------------------|----------------------------------------------------|
| 文件名 | `TC{NNNN}-{brief-name}.ts` |
| TC ID 范围 | 全局唯一,跨所有模块 |
| 目录 | `e2e/{module}/` |
| 宿主测试目录 | `hack/tests/e2e/{module}/` |
| 源码插件测试目录 | `apps/lina-plugins/<plugin-id>/hack/tests/e2e/` |
| Describe 标签 | `TC-{N} {描述}` |
| 子测试标签 | `TC-{N}{字母}: {描述}` |
| 导入 test/expect | 从 `../../fixtures/auth` 导入 |
| 页面交互 | 通过 `pages/` 中的 POM 类 |
| 宿主导入 test/expect | 从相对路径 `../../fixtures/auth` 导入 |
| 插件导入 test/expect | 从相对路径 `../../../../../../hack/tests/fixtures/auth` 导入 |
| 页面交互 | 通过宿主 `pages/` 或插件 `hack/tests/pages/` 中的 POM 类 |
| 独立性 | 每个文件可独立运行 |
| ID 分配 | 扫描最大已有值 → 递增 1 |
| ID 分配 | 扫描宿主、插件和 OpenSpec 任务记录已用/预留最大值 → 递增 1 |
13 changes: 9 additions & 4 deletions .agents/skills/lina-feedback/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,12 @@ openspec list --json
| `specs/` | 增量规范定义 |

```bash
# 查找最大 TC ID 用于测试规划
find hack/tests/e2e -name 'TC*.ts' | sort | tail -1
# 查找最大 TC ID 用于测试规划,包含宿主、源码插件和 OpenSpec 任务记录中的预留编号
{
find hack/tests/e2e -type f -name 'TC*.ts'
find apps/lina-plugins -type f -path '*/hack/tests/e2e/*' -name 'TC*.ts'
rg -No 'TC[0-9]{4}' openspec/changes -g 'tasks.md'
} | rg -No 'TC[0-9]{4}' | sort -u | tail -1
```

---
Expand Down Expand Up @@ -157,6 +161,7 @@ THEN 父级选择器应禁用当前菜单及所有子菜单

**验证覆盖规划(内部):**
- 用户可观察的行为变更 → 需要 E2E 测试
- 源码插件专属的用户可观察行为变更 → E2E 放在 `apps/lina-plugins/<plugin-id>/hack/tests/e2e/`,专属 POM/helper 放在插件同级 `hack/tests/pages/`、`hack/tests/support/`
- 后端逻辑、服务层、工具函数、缓存、权限、数据权限、插件桥接等内部可执行行为变更 → 需要单元测试或更低成本的自动化测试
- 纯项目治理类反馈 → 不为兜底新增单元测试或 E2E 测试,改用 `openspec validate`、静态扫描、文件存在性检查、格式检查或审查结论
- 场景合适时优先在现有 TC 或现有测试中添加子断言
Expand Down Expand Up @@ -189,8 +194,8 @@ THEN 父级选择器应禁用当前菜单及所有子菜单
| 项目治理文档/规范 | `openspec validate`、静态扫描、文件存在性检查或格式检查 |

```bash
# 示例:查找用户 API 变更的相关测试
grep -r "api/user" hack/tests/e2e --include="*.ts" -l
# 示例:查找用户 API 变更的相关测试,包含宿主和源码插件自有 E2E
rg -l "api/user" hack/tests/e2e apps/lina-plugins -g 'TC*.ts'
```

告知:
Expand Down
3 changes: 2 additions & 1 deletion .agents/skills/lina-review/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,10 +243,11 @@ compatibility: 依赖 OpenSpec CLI、GoFrame v2 技能、lina-e2e 技能。

### 7. E2E 测试审查

**触发条件**:`hack/tests/e2e/` 目录下新增或修改的 E2E 测试文件
**触发条件**:`hack/tests/e2e/`、`hack/tests/pages/`、`hack/tests/support/` 或 `apps/lina-plugins/<plugin-id>/hack/tests/{e2e,pages,support}/` 下新增或修改的 E2E 测试、页面对象或 helper

1. 调用 `lina-e2e` 技能检查测试规范
2. 对照 `AGENTS.md` E2E 测试规范进行检查
3. 源码插件专属 E2E 必须保留在插件自己的 `hack/tests/e2e/` 目录;插件专属 POM/helper 必须保留在同插件的 `hack/tests/pages/`、`hack/tests/support/`,不得回流到宿主 `hack/tests/e2e/extension/plugin/` 或 `hack/tests/pages/`

### 8. 反馈验证覆盖审查

Expand Down
15 changes: 11 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,18 @@ apps/ → MonoRepo项目目录
plugin.go → 插件后端注册入口
frontend/ → 插件前端页面与资源
manifest/ → 插件安装/卸载与交付资源
hack/ → 插件级研发与测试资源
tests/
e2e/ → 插件自有 TC 测试用例
pages/ → 插件自有 E2E 页面对象
support/ → 插件自有 E2E helper
plugin.yaml → 插件清单
plugin_embed.go → 插件嵌入资源入口
hack/ → 项目脚本及测试用例文件
tests/ → E2E 测试(Playwright)
e2e/ → 测试用例文件
tests/ → 宿主与共享 E2E 测试(Playwright)
e2e/ → 宿主 TC 测试用例文件;源码插件自有 E2E 放在插件目录
fixtures/ → 测试 fixtures(auth, config)
pages/ → 页面对象模型
pages/ → 宿主/共享页面对象模型
openspec/ → OpenSpec相关文档
changes/ → OpenSpec变更记录
```
Expand Down Expand Up @@ -111,7 +116,7 @@ pnpm test:debug # 调试模式
pnpm report # 查看 HTML 报告
```

测试文件命名规范:`TC{NNNN}*.ts`(如 `TC0001-login.ts`),放在 `hack/tests/e2e/` 对应模块目录下。
测试文件命名规范:`TC{NNNN}*.ts`(如 `TC0001-login.ts`)。宿主测试放在 `hack/tests/e2e/` 对应模块目录下;源码插件自有测试放在 `apps/lina-plugins/<plugin-id>/hack/tests/e2e/`,插件专属 POM/helper 放在同插件的 `hack/tests/pages/`、`hack/tests/support/`

# 文档编写规范

Expand Down Expand Up @@ -414,6 +419,8 @@ dao.SysDictType.Ctx(ctx).Where(do.SysDictType{Id: id}).Delete()

- 测试用例必须要完整覆盖业务模块的各项操作(如增删改查等操作),保证功能的完整性和可用性
- 所有的用例需要在`tasks.md`中有工作记录,并且使用`lina-e2e`技能生成和管理对应的测试用例
- 宿主功能测试放在 `hack/tests/e2e/<module>/`;源码插件专属功能测试必须放在 `apps/lina-plugins/<plugin-id>/hack/tests/e2e/`,不得放到宿主的`e2e`测试脚本目录下
- 插件专属页面对象和 helper 必须放在插件自己的 `hack/tests/pages/`、`hack/tests/support/`;不得为了插件功能把专属定位器加到宿主的`e2e`测试脚本目录下
- 修复`bug`或新增功能涉及**用户可观察行为变化**时,必须编写或更新对应的`E2E`测试用例
- 涉及功能行为的 `bugfix` 反馈修复必须编写或更新至少一个自动化测试来验证修复有效性:内部逻辑缺陷可使用单元测试,用户可观察行为或跨模块工作流缺陷必须使用 `E2E` 测试;测试应覆盖原始问题的失败场景和修复后的预期行为
- 纯项目治理类反馈不要求新增单元测试或`E2E`测试,应使用 `openspec validate`、静态扫描、文件检查、格式检查或审查结论等治理验证方式
Expand Down
11 changes: 10 additions & 1 deletion apps/lina-plugins/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ At the current open-source stage, the host keeps only stable core capabilities s

## What Lives Here

LinaPro currently ships three plugin references in this directory:
LinaPro currently ships these plugin references in this directory:

- `plugin-demo-source`: sample source plugin structure and coding reference
- `plugin-demo-dynamic`: sample dynamic WASM plugin structure and lifecycle reference
Expand Down Expand Up @@ -42,6 +42,9 @@ apps/lina-plugins/<plugin-id>/
manifest/sql/ Plugin-owned install SQL assets
manifest/sql/mock-data/ Optional plugin-owned mock/demo SQL assets
manifest/sql/uninstall/ Plugin-owned uninstall SQL assets
hack/tests/e2e/ Optional plugin-owned E2E TC files
hack/tests/pages/ Optional plugin-owned E2E page objects
hack/tests/support/ Optional plugin-owned E2E helpers
plugin.yaml Plugin manifest
plugin_embed.go Embedded asset registration
README.md English plugin guide
Expand All @@ -68,6 +71,12 @@ The host and source plugins are intentionally decoupled through stable seams ins
4. Keep plugin-owned backend code inside the plugin directory, place service logic under `backend/internal/service/`, and depend only on published host packages.
5. Register the plugin explicitly in `apps/lina-plugins/lina-plugins.go`.

## Plugin-Owned E2E Tests

Source plugins should keep plugin-specific Playwright coverage under `apps/lina-plugins/<plugin-id>/hack/tests/e2e/`.
Plugin page objects and helpers should stay beside them in `hack/tests/pages/` and `hack/tests/support/`.
The host test runner discovers these tests through the generic `plugins` scope, and a single plugin can be run with `pnpm -C hack/tests test:module -- plugin:<plugin-id>` without adding a plugin-specific entry to the execution manifest.

## Source Plugin Version Upgrade

When a source plugin has already been installed in the host and you bump its
Expand Down
11 changes: 10 additions & 1 deletion apps/lina-plugins/README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

## 这里包含什么

当前目录下包含三类参考内容
当前目录下包含以下参考内容

- `plugin-demo-source`:源码插件目录结构与开发方式样例
- `plugin-demo-dynamic`:动态 WASM 插件结构与生命周期样例
Expand Down Expand Up @@ -42,6 +42,9 @@ apps/lina-plugins/<plugin-id>/
manifest/sql/ 插件自有安装 SQL 资源
manifest/sql/mock-data/ 插件自有可选`mock`/演示 SQL 资源
manifest/sql/uninstall/ 插件自有卸载 SQL 资源
hack/tests/e2e/ 可选的插件自有 E2E TC 用例
hack/tests/pages/ 可选的插件自有 E2E 页面对象
hack/tests/support/ 可选的插件自有 E2E helper
plugin.yaml 插件清单
plugin_embed.go 嵌入资源注册入口
README.md 英文说明
Expand All @@ -68,6 +71,12 @@ apps/lina-plugins/<plugin-id>/
4. 插件后端代码保留在插件目录中,业务逻辑统一放在 `backend/internal/service/` 下,并且只依赖宿主公开包。
5. 在 `apps/lina-plugins/lina-plugins.go` 中做显式接线。

## 插件自有 E2E 测试

源码插件应把插件专属 Playwright 覆盖放在 `apps/lina-plugins/<plugin-id>/hack/tests/e2e/` 下。
插件页面对象和辅助 helper 应分别保留在同级的 `hack/tests/pages/` 与 `hack/tests/support/` 中。
宿主测试运行器会通过通用 `plugins` 范围发现这些测试;单个插件也可以通过 `pnpm -C hack/tests test:module -- plugin:<plugin-id>` 直接运行,不需要为每个插件在执行清单里新增专属 scope。

## 源码插件版本升级

当某个源码插件已经在宿主中安装完成,而你又提升了它的 `plugin.yaml` 版本时,源码扫描不再自动替换当前生效版本。
Expand Down
6 changes: 6 additions & 0 deletions apps/lina-plugins/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "@lina/source-plugins",
"private": true,
"type": "module",
"description": "Workspace metadata for LinaPro source plugin packages."
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { test } from '../../../../../../hack/tests/fixtures/auth';
import { expect } from '../../../../../../hack/tests/support/playwright';

test.describe('TC-221 plugin-demo-source owned E2E discovery', () => {
test('TC-221a: plugin-owned tests run through the shared runner', async () => {
expect(true).toBe(true);
});
});
17 changes: 17 additions & 0 deletions hack/tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ hack/tests/
temp/ runtime-only artifacts such as generated storage state
```

Source plugins may keep their own test surface inside the plugin directory:

```text
apps/lina-plugins/<plugin-id>/
hack/tests/e2e/ plugin-owned TC test cases
hack/tests/pages/ plugin-owned page objects
hack/tests/support/ optional plugin-owned E2E helpers
```

The `e2e/` tree is organized by stable capability boundaries instead of the legacy `system/` bucket:

- `auth/`, `dashboard/`, `about/`
Expand Down Expand Up @@ -55,6 +64,8 @@ Example module scopes:
- `scheduler:job`
- `extension:plugin`
- `dialect`
- `plugins`
- `plugin:<plugin-id>` for a source plugin with tests under `apps/lina-plugins/<plugin-id>/hack/tests/e2e/`

`pnpm test:sqlite` is the dedicated full SQLite channel. The script backs up
`apps/lina-core/manifest/config/config.yaml`, writes
Expand All @@ -80,6 +91,12 @@ The suite uses `config/execution-manifest.json` as the single source of truth fo
`pnpm test`, `pnpm test:full`, `pnpm test:smoke`, and `pnpm test:module` all run through `scripts/run-suite.mjs`.
The runner splits the selected files into a parallel pool and a serial pool so global-state heavy scenarios still execute safely.
Every run prints the selected file count, parallel file count, serial file count, parallel worker count, and the isolation categories represented in the serial pool.
Full-suite runs include plugin-owned tests through the generic `plugins` entry.
Individual source plugins can be run without editing the manifest:

```bash
pnpm test:module -- plugin:cms
```

## Isolation Categories

Expand Down
Loading
Loading