first-commit
ci / Validate workspace (push) Has been cancelled
landing-page-ci / Validate landing page (push) Has been cancelled
landing-page-deploy / Deploy landing page (push) Has been cancelled
github-metrics / Generate repository metrics SVG (push) Has been cancelled
refresh-contributors-wall / Refresh contributors wall cache bust (push) Waiting to run

This commit is contained in:
Zakaria
2026-05-04 14:58:14 -04:00
commit a46764fb1b
1210 changed files with 233231 additions and 0 deletions
+121
View File
@@ -0,0 +1,121 @@
# UI 用例库
这个目录是 UI 自动化场景的来源库。
## 目的
用例库把这三层拆开:
- 场景设计
- 自动化实现
- 测试素材和运行数据
这样 Playwright spec 不会慢慢变成一堆写死的 prompt 和一次性断言。
## 当前目录结构
- [index.ts](/Users/mac/open-design/open-design/e2e/cases/index.ts):用例定义
- [types.ts](/Users/mac/open-design/open-design/e2e/cases/types.ts):用例 schema
- [modules/project-and-generation.md](/Users/mac/open-design/open-design/e2e/cases/modules/project-and-generation.md):项目创建与生成链路用例
- [modules/conversations.md](/Users/mac/open-design/open-design/e2e/cases/modules/conversations.md):会话生命周期用例
- [modules/files.md](/Users/mac/open-design/open-design/e2e/cases/modules/files.md):文件上传、mention、预览恢复用例
- [../reports/README.zh-CN.md](/Users/mac/open-design/open-design/e2e/reports/README.zh-CN.md):测试结果与报告说明
- [../specs/app.spec.ts](/Users/mac/open-design/open-design/e2e/specs/app.spec.ts):执行已自动化用例的 Playwright 入口
## Schema 说明
每条用例都是一个 `UICase`
- `id`:稳定的用例标识,用于 spec 和测试报告
- `title`:人可读的用例名称
- `kind`:项目类型,比如 `prototype``deck``workspace`
- `flow`Playwright 里对应的自动化流程分支
- `automated`:当前是否会被 `pnpm run test:ui` 执行
- `description`:覆盖目标和场景说明
- `create`:创建项目时要用到的输入
- `prompt`:主输入内容
- `secondaryPrompt`:多步骤流程里的后续输入
- `mockArtifact`mock SSE 时预期生成的 artifact
- `notes`:实现细节或维护备注
## 当前支持的 Flow
- `standard`:创建项目,发送 prompt,校验生成 artifact
- `conversation-persistence`:创建多会话,刷新后恢复,再切换历史
- `file-mention`:预置文件后通过 `@` mention 选中并校验 staged attachment
- `deep-link-preview`:通过文件路由打开预览并校验恢复
- `file-upload-send`:走真实文件选择器,校验上传和发送
- `conversation-delete-recovery`:删除当前活跃会话后校验回退
## 文档拆分规则
- `README.zh-CN.md` 只保留总览、结构和维护规则
- 具体用例清单按模块拆到 `modules/` 目录
- 一个模块一个 Markdown,后面可以继续细分
- 当单个模块内容变长时,再继续按子模块拆分
## 新增用例的方式
1. 在 [index.ts](/Users/mac/open-design/open-design/e2e/cases/index.ts) 里新增一条 `UICase`
2. 先把场景写进对应模块文档,如果只是设计阶段,保持 `automated: false`
3. 能复用已有 `flow` 就优先复用。
4. 只有在确实需要新自动化路径时,才去 [types.ts](/Users/mac/open-design/open-design/e2e/cases/types.ts) 增加新的 `flow` 类型。
5. 在 [app.spec.ts](/Users/mac/open-design/open-design/e2e/specs/app.spec.ts) 里实现这个流程。
6. 用例稳定后,再把 `automated` 改成 `true`
## 推荐工作流
1. 先用产品语言把场景写清楚。
2. 先决定它归哪个模块文档。
3. 判断它能不能归到已有的自动化 flow。
4. 只在确实需要的节点补 `data-testid`
5. 优先 mock `/api/chat` 的 SSE,保证稳定性。
6. 项目创建、路由、持久化、文件 API 尽量走真实链路。
## 适合放进来的范围
适合:
- 项目创建主流程
- 生成与 artifact 预览流程
- 会话生命周期流程
- 文件上传、mention、重新打开流程
- deep link 和刷新恢复流程
不建议优先放:
- 纯视觉、容易抖的检查
- 模型质量评估
- 强依赖真实外部 agent CLI 的测试
## 运行方式
```bash
pnpm run test:ui
```
也可以直接在独立测试包内运行:
```bash
pnpm --filter @open-design/e2e test:ui
```
运行完成后会自动生成:
- `e2e/reports/latest.md`
- `e2e/reports/ui-test-report.html`
- `e2e/reports/playwright-html-report/`
- `e2e/reports/results.json`
- `e2e/reports/junit.xml`
运行开始前会自动清理旧的 e2e 运行时数据和上一次报告,避免:
- `.od-data` 里累积空 project 目录
- `e2e/reports/test-results` 混入旧失败截图
- 报告内容和本次执行结果不一致
如果要带界面调试:
```bash
pnpm run test:ui:headed
```
+405
View File
@@ -0,0 +1,405 @@
import type { UICase } from './types';
export const uiCases: UICase[] = [
{
id: 'prototype-basic',
title: 'Prototype project creates and previews a generated artifact',
kind: 'prototype',
flow: 'standard',
automated: true,
description:
'Validates the primary happy path: create a prototype project, send one prompt, persist the generated HTML, and render it in the preview iframe.',
create: {
projectName: 'UI automation smoke',
tab: 'prototype',
},
prompt: 'Create a small test artifact',
mockArtifact: {
identifier: 'mock-artifact',
title: 'Mock Artifact',
fileName: 'mock-artifact.html',
heading: 'Mock Artifact',
html:
'<!doctype html><html><body><main><h1>Mock Artifact</h1><p>Generated by Playwright.</p></main></body></html>',
},
notes: [
'This is the seed smoke test and should stay fast.',
'It uses mocked SSE so the UI path stays deterministic.',
],
},
{
id: 'deck-basic',
title: 'Deck project renders a mocked slide artifact',
kind: 'deck',
flow: 'standard',
automated: true,
description:
'Covers the deck tab in project creation and verifies that a deck artifact lands in the workspace preview.',
create: {
projectName: 'Deck automation smoke',
tab: 'deck',
},
prompt: 'Create a short deck with two slides',
mockArtifact: {
identifier: 'mock-deck',
title: 'Mock Deck',
fileName: 'mock-deck.html',
heading: 'Mock Deck',
html:
'<!doctype html><html><body><section class="slide"><h1>Mock Deck</h1></section></body></html>',
},
notes: [
'Confirms the deck creation tab still routes into the same generation path.',
],
},
{
id: 'comment-attachment-flow',
title: 'Preview comments attach to chat and send as structured context',
kind: 'prototype',
flow: 'comment-attachment-flow',
automated: true,
description:
'Exercises V1 comment mode: save a latest element comment, attach/remove it from the composer, and send it as an empty visible prompt with structured comment context.',
create: {
projectName: 'Comment attachment flow',
tab: 'prototype',
},
prompt: 'Create a commentable preview artifact',
mockArtifact: {
identifier: 'commentable-artifact',
title: 'Commentable Artifact',
fileName: 'commentable-artifact.html',
heading: 'Prototype headline',
html:
'<!doctype html><html><body><main data-od-id="hero-section"><h1 data-od-id="hero-title" data-screen-label="Hero title">Prototype headline</h1><p data-od-id="hero-copy">Preview copy for comment mode.</p></main></body></html>',
},
notes: [
'The composer textarea stays empty; selected preview comments are sent through commentAttachments.',
],
},
{
id: 'design-system-selection',
title: 'Selecting a design system carries through project creation',
kind: 'prototype',
flow: 'design-system-selection',
automated: true,
description:
'Verifies that a chosen design system is selectable in the new-project panel and remains visible in project metadata after creation.',
create: {
projectName: 'Design system selection',
tab: 'prototype',
},
prompt: 'Create a small test artifact',
notes: [
'Uses a mocked design-system list so the picker stays deterministic across environments.',
'Focuses on creation and metadata persistence instead of generation output.',
],
},
{
id: 'example-use-prompt',
title: 'Using an example prompt creates a project with a seeded draft',
kind: 'prototype',
flow: 'example-use-prompt',
automated: true,
description:
'Verifies the Examples tab fast path: click Use this prompt, create a project immediately, and carry the example prompt into the chat composer.',
create: {
projectName: 'Example prompt project',
tab: 'prototype',
},
prompt: 'Draft a warm utility landing page for a productivity app',
notes: [
'Uses a mocked skills list so the examples gallery stays deterministic.',
'Targets the pendingPrompt fast-create path instead of the standard new-project form.',
],
},
{
id: 'conversation-persistence',
title: 'Conversation history survives refresh and switching',
kind: 'workspace',
flow: 'conversation-persistence',
automated: true,
description:
'Exercises conversation creation, persistence, refresh reload, and switching between threads in one project.',
create: {
projectName: 'Conversation persistence',
tab: 'prototype',
},
prompt: 'Create a small test artifact',
secondaryPrompt: 'Create another artifact in a fresh conversation',
mockArtifact: {
identifier: 'mock-artifact',
title: 'Mock Artifact',
fileName: 'mock-artifact.html',
heading: 'Mock Artifact',
html:
'<!doctype html><html><body><main><h1>Mock Artifact</h1><p>Generated by Playwright.</p></main></body></html>',
},
notes: [
'Should use the same mock SSE flow as the prototype smoke path.',
'Reload should keep the original conversation content available from the history menu.',
],
},
{
id: 'file-mention',
title: 'Uploaded files can be mentioned and sent back to the agent',
kind: 'workspace',
flow: 'file-mention',
automated: true,
description:
'Validates the upload, staged attachment, and @ mention flow inside the chat composer.',
create: {
projectName: 'File mention flow',
tab: 'prototype',
},
prompt: 'Review @reference.txt and use it as context',
notes: [
'Seeds a tiny text fixture through the project file API, then exercises the composer mention flow.',
],
},
{
id: 'deep-link-preview',
title: 'Deep-linking to a file route opens the expected preview tab',
kind: 'workspace',
flow: 'deep-link-preview',
automated: true,
description:
'Verifies that /projects/:id/files/:name restores the matching open tab and preview frame after navigation or refresh.',
create: {
projectName: 'Deep link preview',
tab: 'prototype',
},
prompt: 'Create a small test artifact',
mockArtifact: {
identifier: 'mock-artifact',
title: 'Mock Artifact',
fileName: 'mock-artifact.html',
heading: 'Mock Artifact',
html:
'<!doctype html><html><body><main><h1>Mock Artifact</h1><p>Generated by Playwright.</p></main></body></html>',
},
notes: [
'Can reuse the generated HTML from prototype-basic, then revisit with a routed URL.',
],
},
{
id: 'file-upload-send',
title: 'Composer file picker uploads a file and sends it with the prompt',
kind: 'workspace',
flow: 'file-upload-send',
automated: true,
description:
'Exercises the real attach button and hidden file input, then verifies the staged file is sent and shown back on the user message.',
create: {
projectName: 'File upload send flow',
tab: 'prototype',
},
prompt: 'Use the uploaded reference as context',
notes: [
'Uses Playwright setInputFiles on the hidden composer picker instead of seeding through the API.',
],
},
{
id: 'design-files-upload',
title: 'Design Files panel uploads an image and opens it in the workspace',
kind: 'workspace',
flow: 'design-files-upload',
automated: true,
description:
'Exercises the Design Files upload flow in the workspace, then verifies the uploaded image can be previewed and opened as a tab.',
create: {
projectName: 'Design files upload flow',
tab: 'prototype',
},
prompt: 'Upload an image through the design files browser',
notes: [
'Uses the FileWorkspace upload input rather than the chat composer upload path.',
],
},
{
id: 'design-files-delete',
title: 'Design Files panel deletes an uploaded file and clears its tab',
kind: 'workspace',
flow: 'design-files-delete',
automated: true,
description:
'Uploads a file through the Design Files panel, deletes it from the row menu, and verifies it disappears from both the list and open tabs.',
create: {
projectName: 'Design files delete flow',
tab: 'prototype',
},
prompt: 'Delete an uploaded image through the design files browser',
notes: [
'Builds on the same workspace file flow as design-files-upload, then verifies cleanup behavior.',
],
},
{
id: 'design-files-tab-persistence',
title: 'Open file tabs survive refresh with the correct active tab',
kind: 'workspace',
flow: 'design-files-tab-persistence',
automated: true,
description:
'Uploads multiple files through the Design Files flow, switches the active tab, reloads the page, and verifies both the tab set and selected tab are restored.',
create: {
projectName: 'Design files tab persistence',
tab: 'prototype',
},
prompt: 'Restore open file tabs after refresh',
notes: [
'Covers the persisted tabs state stored by ProjectView and restored by FileWorkspace.',
],
},
{
id: 'conversation-delete-recovery',
title: 'Deleting the active conversation falls back cleanly',
kind: 'workspace',
flow: 'conversation-delete-recovery',
automated: true,
description:
'Creates multiple conversations, deletes the active one, and verifies the UI falls back to the remaining thread instead of getting stuck.',
create: {
projectName: 'Conversation delete recovery',
tab: 'prototype',
},
prompt: 'Create a small test artifact',
secondaryPrompt: 'Create another artifact before deleting this thread',
mockArtifact: {
identifier: 'mock-artifact',
title: 'Mock Artifact',
fileName: 'mock-artifact.html',
heading: 'Mock Artifact',
html:
'<!doctype html><html><body><main><h1>Mock Artifact</h1><p>Generated by Playwright.</p></main></body></html>',
},
notes: [
'Confirms the project still has a live conversation after deleting the current thread.',
],
},
{
id: 'question-form-selection-limit',
title: 'Question form checkbox limits block selecting more than the allowed maximum',
kind: 'workspace',
flow: 'question-form-selection-limit',
automated: true,
description:
'Verifies that a discovery-style checkbox question with maxSelections=2 cannot be pushed past two selected options.',
create: {
projectName: 'Question form selection limit',
tab: 'prototype',
},
prompt: 'Help me plan a restaurant homepage',
notes: [
'Mocks a question-form response instead of an artifact so the test can exercise the inline clarifying UI.',
'Confirms both the interaction guard and the rendered checked state stay capped at two options.',
],
},
{
id: 'question-form-submit-persistence',
title: 'Question form answers persist into chat history and reload in a locked state',
kind: 'workspace',
flow: 'question-form-submit-persistence',
automated: true,
description:
'Verifies that answering a question form writes a user follow-up message, then rehydrates the form in an answered and locked state after reload.',
create: {
projectName: 'Question form submit persistence',
tab: 'prototype',
},
prompt: 'Plan a small restaurant homepage',
notes: [
'Mocks an inline question form on the first assistant turn and a plain acknowledgment on the follow-up turn.',
'Confirms the answered state survives a full page reload instead of relying only on local submit state.',
],
},
{
id: 'generation-does-not-create-extra-file',
title: 'Generated artifacts stay stable when no new prompt is sent',
kind: 'workspace',
flow: 'generation-does-not-create-extra-file',
automated: true,
description:
'Generates one HTML artifact, then verifies reload and idle time do not create any additional project files without a new user prompt.',
create: {
projectName: 'No extra generated file',
tab: 'prototype',
},
prompt: 'Create one landing page artifact',
mockArtifact: {
identifier: 'stable-artifact',
title: 'Stable Artifact',
fileName: 'stable-artifact.html',
heading: 'Stable Artifact',
html:
'<!doctype html><html><body><main><h1>Stable Artifact</h1><p>Only one file should exist.</p></main></body></html>',
},
notes: [
'Targets the trust-sensitive bug where a project can appear to generate a fresh file on its own.',
'Uses the files API after reload to assert the project file set is unchanged.',
],
},
{
id: 'deck-pagination-next-prev-correctness',
title: 'Deck preview previous and next controls move in the correct direction',
kind: 'deck',
flow: 'deck-pagination-next-prev-correctness',
automated: false,
description:
'Should verify that deck preview pagination moves to the actual previous and next slide instead of routing both actions to the same page.',
create: {
projectName: 'Deck pagination controls',
tab: 'deck',
},
prompt: 'Review pagination behavior in a multi-slide deck preview',
},
{
id: 'deck-pagination-per-file-isolated',
title: 'Each HTML deck tab preserves its own pagination state',
kind: 'deck',
flow: 'deck-pagination-per-file-isolated',
automated: false,
description:
'Should verify that switching between multiple deck HTML files does not leak page position across tabs or reset both files to page 1.',
create: {
projectName: 'Deck pagination isolation',
tab: 'deck',
},
prompt: 'Keep pagination state isolated per generated deck file',
},
{
id: 'uploaded-image-renders-in-preview',
title: 'Uploaded reference images render correctly in generated deck preview',
kind: 'workspace',
flow: 'uploaded-image-renders-in-preview',
automated: false,
description:
'Should verify that uploaded images resolve to loadable src paths inside generated HTML instead of rendering as broken images.',
create: {
projectName: 'Uploaded image preview render',
tab: 'prototype',
},
prompt: 'Use uploaded brand images inside a generated deck preview',
},
{
id: 'python-source-preview',
title: 'Python files should open with a readable inline source preview',
kind: 'workspace',
flow: 'python-source-preview',
automated: false,
description:
'Should verify that opening a .py file in the main workspace renders a readable source/code preview instead of an unsupported blank state.',
create: {
projectName: 'Python source preview',
tab: 'prototype',
},
prompt: 'Open a generated Python file and inspect its source inline',
notes: [
'Candidate follow-up to the Python preview gap in the file viewer.',
'Likely automation shape: seed a .py file through the project files API, open it, and assert the viewer renders code text.',
],
},
];
export function automatedCases(): UICase[] {
return uiCases.filter((entry) => entry.automated);
}
+67
View File
@@ -0,0 +1,67 @@
# 会话生命周期
这个模块聚焦项目内聊天会话的生命周期:
- 新建会话
- 切换会话
- 刷新恢复
- 删除会话
- 后续可扩展重命名等场景
## 当前用例
### `conversation-persistence`
- 状态:已自动化
- 对应 flow`conversation-persistence`
- 目标:覆盖会话创建、刷新恢复、历史切换
- 核心步骤:
1. 在第一个会话里发送 prompt
2. 新建第二个会话
3. 在第二个会话里发送新的 prompt
4. 刷新页面
5. 校验当前会话内容仍在
6. 打开历史菜单切回第一个会话
### `conversation-delete-recovery`
- 状态:已自动化
- 对应 flow`conversation-delete-recovery`
- 目标:覆盖删除当前活跃会话后的回退逻辑
- 核心步骤:
1. 创建两个会话
2. 删除当前活跃会话
3. 校验界面自动回退到剩余会话
4. 校验项目仍然保有可用会话
### `question-form-selection-limit`
- 状态:已自动化
- 对应 flow`question-form-selection-limit`
- 目标:覆盖快速确认里 checkbox 多选上限约束
- 核心步骤:
1. 创建项目并发送一条 prompt
2. mock 返回带 `maxSelections: 2` 的 question form
3. 连续点击三个视觉风格选项
4. 校验始终只有两个选项处于选中态
5. 校验第三个选项不会被错误选中
### `question-form-submit-persistence`
- 状态:已自动化
- 对应 flow`question-form-submit-persistence`
- 目标:覆盖 question form 提交后的用户回答落盘、锁定态与刷新回填
- 核心步骤:
1. mock 返回一个带必填项的 question form
2. 选择答案并点击提交
3. 校验会话里写入了用户回答消息
4. 校验原表单进入 answered / locked 状态
5. 刷新页面后再次确认锁定态和已选答案仍然正确
## 推荐后续补充
- 会话重命名
- 删除最后一个会话后的自动重建
- 历史菜单关闭/重新打开后的状态一致性
- 长会话列表滚动与选中态
- 多轮对话后的会话标题生成或更新策略
+121
View File
@@ -0,0 +1,121 @@
# 文件链路
这个模块聚焦项目文件相关的主链路:
- 文件上传
- 文件 mention
- staged attachment
- 文件路由打开
- 预览恢复
## 当前用例
### `file-mention`
- 状态:已自动化
- 对应 flow`file-mention`
- 目标:覆盖 `@` mention 选择文件并加入 staged attachment
- 核心步骤:
1. 通过项目文件 API 预置 `reference.txt`
2. 在聊天输入框中输入 `@ref`
3. 选择 mention popover 里的文件
4. 校验输入框中插入 `@reference.txt`
5. 校验 staged attachment 显示正确
### `file-upload-send`
- 状态:已自动化
- 对应 flow`file-upload-send`
- 目标:覆盖聊天区真实上传文件并发送
- 核心步骤:
1. 通过 composer 的隐藏 file input 上传文件
2. 校验 staged attachment 出现
3. 发送 prompt
4. 校验用户消息里带上上传文件
### `deep-link-preview`
- 状态:已自动化
- 对应 flow`deep-link-preview`
- 目标:覆盖文件路由直达和预览恢复
- 核心步骤:
1. 生成 artifact
2. 校验 URL 进入 `/projects/:id/files/:name`
3. 离开项目文件路由
4. 再次通过文件路由进入
5. 校验预览 iframe 正常恢复
### `design-files-upload`
- 状态:已自动化
- 对应 flow`design-files-upload`
- 目标:覆盖 Design Files 面板真实上传、预览与打开
- 核心步骤:
1. 通过 Design Files 面板的上传入口选择图片
2. 校验文件行出现在列表中
3. 校验右侧预览信息出现
4. 双击文件行
5. 校验文件以 tab 形式打开
### `design-files-delete`
- 状态:已自动化
- 对应 flow`design-files-delete`
- 目标:覆盖 Design Files 面板删除文件以及打开 tab 的清理
- 核心步骤:
1. 先上传一张图片
2. 回到 Design Files 面板
3. 打开文件行菜单并执行删除
4. 确认文件行从列表中消失
5. 确认对应文件 tab 也被清理
### `design-files-tab-persistence`
- 状态:已自动化
- 对应 flow`design-files-tab-persistence`
- 目标:覆盖多个打开文件 tab 在刷新后的恢复
- 核心步骤:
1. 先上传两张图片
2. 确认两张图片都打开为 tab
3. 切换当前 active tab
4. 刷新页面
5. 确认两个 tab 都被恢复
6. 确认刷新前的 active tab 仍然是 active
## 推荐后续补充
### `deck-pagination-per-file-isolated`
- 状态:待自动化
- 对应 flow`deck-pagination-per-file-isolated`
- 目标:覆盖多个 deck HTML 之间的分页状态隔离
- 核心步骤:
1. 打开两个多页 deck 文件
2. 分别停留在不同页码
3. 来回切换文件 tab
4. 校验每个文件维持自己的页码
### `uploaded-image-renders-in-preview`
- 状态:待自动化
- 对应 flow`uploaded-image-renders-in-preview`
- 目标:覆盖上传图片参与生成后,预览中的图片真实可加载
- 核心步骤:
1. 上传图片作为参考素材
2. 生成引用该图片的 HTML artifact
3. 进入预览 iframe
4. 校验对应 `img``src` 可解析且不是 broken image
### `python-source-preview`
- 状态:待自动化
- 对应 flow`python-source-preview`
- 目标:覆盖 `.py` 文件在主工作区中的源码预览能力
- 核心步骤:
1. 通过项目文件 API 预置一个 `.py` 文件
2. 在主工作区打开该文件
3. 校验文件查看器进入源码/文本预览模式
4. 校验能看到 Python 源码内容,而不是空白或不支持状态
- 图片文件上传与缩略图展示
- 刷新后 staged attachment 清理策略
@@ -0,0 +1,88 @@
# 项目创建与生成
这个模块聚焦主入口链路:
- 创建项目
- 进入工作区
- 发送 prompt
- 生成 artifact
- 打开预览
## 当前用例
### `prototype-basic`
- 状态:已自动化
- 对应 flow`standard`
- 目标:覆盖 prototype 项目的主 happy path
- 核心步骤:
1. 创建 `prototype` 项目
2. 输入 prompt
3. mock `/api/chat` SSE 返回 HTML artifact
4. 校验生成文件出现在工作区
5. 校验 iframe 预览正常
### `deck-basic`
- 状态:已自动化
- 对应 flow`standard`
- 目标:覆盖 deck 项目创建分支
- 核心步骤:
1. 切换到 `deck` 创建 tab
2. 创建项目
3. 发送 prompt
4. mock 返回 deck artifact
5. 校验预览正常
### `design-system-selection`
- 状态:已自动化
- 对应 flow`design-system-selection`
- 目标:覆盖设计系统选择后创建项目,并确认项目元信息保留了该选择
- 核心步骤:
1. mock 设计系统列表
2. 打开设计系统选择器
3. 搜索并选择指定设计系统
4. 创建项目
5. 校验项目页 meta 中出现设计系统名称
### `example-use-prompt`
- 状态:已自动化
- 对应 flow`example-use-prompt`
- 目标:覆盖 Examples 页的快捷创建链路
- 核心步骤:
1. mock skills 列表,提供一个示例卡片
2. 切到 Examples 页
3. 点击 `Use this prompt`
4. 校验项目被直接创建
5. 校验聊天输入框预填了 example prompt
### `generation-does-not-create-extra-file`
- 状态:已自动化
- 对应 flow`generation-does-not-create-extra-file`
- 目标:覆盖“没有新 prompt 却自己多生成一个 HTML 文件”的回归风险
- 核心步骤:
1. 生成一个 mocked artifact
2. 通过 files API 记录当前项目文件集合
3. 刷新页面但不发送新 prompt
4. 再次读取 files API
5. 校验文件集合没有变化,也没有新增 HTML 文件
## 推荐后续补充
### `deck-pagination-next-prev-correctness`
- 状态:待自动化
- 对应 flow`deck-pagination-next-prev-correctness`
- 目标:覆盖 deck 预览上一页 / 下一页按钮的方向正确性
- 核心步骤:
1. 打开多页 deck HTML
2. 进入中间页
3. 点击上一页并校验页码递减
4. 点击下一页并校验页码递增
- template 项目创建
- 创建项目后的刷新恢复
- 创建失败或必填校验
+134
View File
@@ -0,0 +1,134 @@
export interface ReportCaseMetadata {
module: string;
assertions: string[];
}
const caseMetadata: Record<string, ReportCaseMetadata> = {
'prototype-basic': {
module: '项目创建与生成',
assertions: [
'可以创建 prototype 项目并进入工作区',
'发送 prompt 后会收到 mocked artifact',
'生成文件会出现在工作区',
'预览 iframe 中能看到期望标题',
],
},
'deck-basic': {
module: '项目创建与生成',
assertions: [
'可以通过 deck tab 创建项目',
'发送 prompt 后会收到 deck artifact',
'deck 文件会出现在工作区',
'预览 iframe 中能看到期望标题',
],
},
'design-system-selection': {
module: '项目创建与生成',
assertions: [
'设计系统选择器可以搜索并选中目标设计系统',
'创建项目后项目 meta 会保留设计系统名称',
'项目成功进入工作区而不是停留在创建页',
],
},
'example-use-prompt': {
module: '项目创建与生成',
assertions: [
'Examples 页的 Use this prompt 可以直接创建项目',
'创建后的项目标题与 meta 会带上对应 skill 名称',
'聊天输入框会预填 example prompt',
],
},
'conversation-persistence': {
module: '会话生命周期',
assertions: [
'可以创建第二个会话并发送新的 prompt',
'刷新后当前会话消息仍然存在',
'历史菜单中可以切回旧会话',
'切回后旧会话内容仍然正确显示',
],
},
'conversation-delete-recovery': {
module: '会话生命周期',
assertions: [
'删除当前活跃会话后不会卡死在空状态',
'界面会回退到剩余会话',
'被删除会话的消息不会继续显示',
],
},
'question-form-selection-limit': {
module: '会话生命周期',
assertions: [
'question form 中声明 maxSelections=2 的 checkbox 题目最多只能选中两个选项',
'达到上限后新的未选项不会被选中',
'界面中的已选数量会保持在约束范围内',
],
},
'question-form-submit-persistence': {
module: '会话生命周期',
assertions: [
'提交 question form 后会写入一条用户回答消息',
'表单会立即进入 answered / locked 状态',
'刷新页面后表单仍会根据历史答案正确回填并保持锁定',
],
},
'generation-does-not-create-extra-file': {
module: '项目创建与生成',
assertions: [
'第一次生成后项目中只出现预期的 artifact 文件',
'在没有发送新 prompt 的情况下刷新页面不会新增文件',
'files API 返回的文件集合在前后两次检查中保持一致',
],
},
'file-mention': {
module: '文件链路',
assertions: [
'预置文件后 mention popover 可以搜索并选中文件',
'输入框会插入 @filename',
'staged attachment 会显示对应文件',
],
},
'file-upload-send': {
module: '文件链路',
assertions: [
'聊天区 file input 可以上传文件',
'上传后 staged attachment 会显示文件',
'发送消息后用户消息中会保留该附件',
],
},
'deep-link-preview': {
module: '文件链路',
assertions: [
'生成 artifact 后 URL 会进入文件路由',
'离开项目文件路由后可再次通过文件路由进入',
'重新进入后预览 iframe 仍能恢复到正确文件',
],
},
'design-files-upload': {
module: '文件链路',
assertions: [
'Design Files 面板可以真实上传图片',
'上传后文件行会出现在 Design Files 列表',
'右侧预览面板会显示文件信息',
'双击文件行会把文件打开成 tab',
],
},
'design-files-delete': {
module: '文件链路',
assertions: [
'Design Files 行级菜单可以触发删除',
'删除确认后文件行会从列表消失',
'如果文件已打开,对应 tab 也会被清理',
],
},
'design-files-tab-persistence': {
module: '文件链路',
assertions: [
'多个文件 tab 可以同时打开',
'切换 active tab 后状态会被持久化',
'刷新页面后 tab 集合会恢复',
'刷新前选中的 active tab 仍然保持选中',
],
},
} satisfies Record<string, ReportCaseMetadata>;
export default caseMetadata;
+45
View File
@@ -0,0 +1,45 @@
export type CaseKind = 'prototype' | 'deck' | 'template' | 'workspace';
export interface MockArtifactCase {
identifier: string;
title: string;
html: string;
fileName: string;
heading: string;
}
export interface UICase {
id: string;
title: string;
kind: CaseKind;
flow?:
| 'standard'
| 'design-system-selection'
| 'example-use-prompt'
| 'conversation-persistence'
| 'file-mention'
| 'deep-link-preview'
| 'file-upload-send'
| 'design-files-upload'
| 'design-files-delete'
| 'design-files-tab-persistence'
| 'conversation-delete-recovery'
| 'question-form-selection-limit'
| 'question-form-submit-persistence'
| 'generation-does-not-create-extra-file'
| 'comment-attachment-flow'
| 'deck-pagination-next-prev-correctness'
| 'deck-pagination-per-file-isolated'
| 'uploaded-image-renders-in-preview'
| 'python-source-preview';
automated: boolean;
description: string;
create: {
projectName: string;
tab?: 'prototype' | 'deck' | 'template' | 'other';
};
prompt: string;
secondaryPrompt?: string;
mockArtifact?: MockArtifactCase;
notes?: string[];
}