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
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:
@@ -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
|
||||
```
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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. 刷新页面后再次确认锁定态和已选答案仍然正确
|
||||
|
||||
## 推荐后续补充
|
||||
|
||||
- 会话重命名
|
||||
- 删除最后一个会话后的自动重建
|
||||
- 历史菜单关闭/重新打开后的状态一致性
|
||||
- 长会话列表滚动与选中态
|
||||
- 多轮对话后的会话标题生成或更新策略
|
||||
@@ -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 项目创建
|
||||
- 创建项目后的刷新恢复
|
||||
- 创建失败或必填校验
|
||||
@@ -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;
|
||||
@@ -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[];
|
||||
}
|
||||
Reference in New Issue
Block a user