fix: 產出格式修正

This commit is contained in:
skytek_xinliang
2026-06-08 11:53:46 +08:00
parent 23fc852321
commit 75a12f3760
7 changed files with 182 additions and 415 deletions
-190
View File
@@ -1,190 +0,0 @@
# Product Requirement Document
## HTML Transform
| 欄位 | 內容 |
|------|------|
| 版本 | v0.3 |
| 日期 | 2026-05-04 |
| 狀態 | MVP |
---
## 1. 背景與目標
HTML Transform 是 prototype evidence 工具。它的目標不是完整自動化前端生成,而是把 HTML prototype 轉成可 review、可追溯、可交給 coding agent 實作的前置證據。
目前 MVP 只保留有實際價值的功能:
- 檢查 `scan` 前置條件。
- 用真實瀏覽器擷取 prototype evidence。
- 推論跨頁面的 App Structure Map。
- 產生頁面級 UI Contract。
- 驗證 UI Contract 與 DOM evidence 是否明顯衝突。
不保留未完成的 pipeline 骨架。`plan``run``diff``verify``go``status` 已不屬於目前產品面。
---
## 2. CLI Scope
| 指令 | 說明 |
|------|------|
| `ht doctor` | 檢查 `scan` 所需環境 |
| `ht scan` | 產生 browser evidence、page contract、validation report |
沒有 Codex slash command。根目錄輸入 `/scan` 不會自動執行本工具。
---
## 3. 設定檔
MVP 只使用 `prototype`
```js
export default {
prototype: './prototype'
}
```
本 repo 的根目錄設定檔可以保留其他欄位給外部工作流參考,但 `packages/html-transform` 不會執行 frontend/backend/output 相關流程。
---
## 4. Stage 1Capture
目的:取得 prototype 的瀏覽器真相,避免只看 HTML source 或讓 agent 腦補畫面。
需求:
| ID | 說明 |
|----|------|
| F1-1 | 對每個 `.html` 檔案截取 desktop `1440 x 900` full-page screenshot |
| F1-2 | 使用本地 Vite static server,不使用 `file://` |
| F1-3 | 截圖前等待 `networkidle`,再等待 500ms |
| F1-4 | 以 HTML SHA-256 hash 作為 cache key,未變更時跳過重新截圖 |
| F1-5 | 截圖寫入 `.ht/cache/prototype/{page}/desktop-default.png` |
| F1-6 | 產出 `dom-summary.json``accessibility-tree.json` |
| F1-7 | 產出 `capture-metadata.json`,記錄 resource failure 與 console error/warning |
| F1-8 | 產出 `.ht/app-map.json`,推論頁面角色與 layout 策略 |
產出:
```text
.ht/cache/prototype/{page}/desktop-default.png
.ht/cache/prototype/{page}/dom-summary.json
.ht/cache/prototype/{page}/accessibility-tree.json
.ht/cache/prototype/{page}/capture-metadata.json
.ht/app-map.json
```
---
## 5. Stage 2Decompose
MVP 不執行。
不做的項目:
- 不呼叫 vision agent 切主要區域。
- 不產生 `decomposition.json`
- 不用 Playwright `clip` 截取 table/form/main 等局部圖。
- 不建立 region-first extraction pipeline。
---
## 6. Stage 3-litePage Contract
目的:從 Stage 1 evidence 與 HTML source 產生頁面級 UI Contract。
需求:
| ID | 說明 |
|----|------|
| F3-1 | 每個 HTML 產出一份 `pageContract` |
| F3-2 | `pageContract` 以整頁為單位,不以 region screenshot 為單位 |
| F3-3 | 記錄 source、screenshot、title、sections、forms、tables、actions、textSamples |
| F3-4 | 建議 Vuetify components 必須來自 MVP 白名單 |
| F3-5 | forms 記錄 labels、fields、required 狀態與 actions |
| F3-6 | tables 記錄 headers 與少量 sample rows |
| F3-7 | 不確定或 evidence 對不上時保留 warnings |
產出:
```text
.ht/spec/{page}.spec.json
.ht/spec/{page}.ui-contract.md
```
`.spec.json` 目前仍保留 `regions` 欄位作為相容層,但它不是 MVP 的主要設計中心。
---
## 6.1 App Structure Map
目的:讓通用 prompt 不必靠人工說明,就能知道每個 prototype 應如何放進前端 app。
產出:
```text
.ht/app-map.json
```
每個 route 應包含:
| 欄位 | 說明 |
|------|------|
| `prototype` | prototype 相對路徑 |
| `kind` | `auth``auth-support``legacy-shell-reference``prototype-navigation``feature-page``reference` |
| `module` | 由資料夾推論的功能模組 |
| `targetRole` | 對前端實作的角色提示 |
| `layout` | `template-auth``template-app``ignore` |
| `usePrototypeStyle` | MVP 固定為 `false` |
| `usePrototypeContent` | 是否採用 prototype 的內容資訊架構 |
| `routeHint` | 可用的路由建議,無正式頁面時為 `null` |
推論規則:
- password input 或登入/忘記密碼文字:`auth`
- `portal/app-layout.html``portal/menu-pane.html``portal/index.html` 或含登出/隱藏選單:`legacy-shell-reference`
- 非 portal 且有 table/form/action`feature-page`
- module index 若像 prototype 導覽頁:`prototype-navigation`
- layout 與 login style 來源固定為 `preparation/skt-vuetify-templates`
---
## 7. Stage 4-liteValidate Contract
目的:確認 UI Contract 與 browser evidence 沒有明顯衝突。
需求:
| ID | 說明 |
|----|------|
| F4-1 | 驗證 `textSamples` 是否可在 DOM summary 找到 |
| F4-2 | 驗證 actions 是否可在 DOM summary 找到 |
| F4-3 | 驗證 form labels 是否可在 DOM summary 找到 |
| F4-4 | 驗證建議 Vuetify components 是否在白名單內 |
| F4-5 | 產出 `ValidationReport`,包含 warnings、nullFieldCount、nullFieldRatio、requiresHumanReview |
產出:
```text
.ht/spec/{page}.validation.json
```
---
## 8. 非目標
MVP 不追求:
- tablet/mobile 多 viewport capture。
- region decomposition。
- 每個 region 獨立 vision extraction。
- TaskList 產生。
- 自動寫入 frontend/output。
- visual similarity scoring。
- flow smoke test。
- 全自動 coding agent invocation。
這些能力只有在實際需求明確出現後才新增。
+119 -2
View File
@@ -82,9 +82,23 @@ pnpm --filter html-transform exec playwright-cli --version
若環境還沒有 Chromium
```bash
HOME=/tmp \
XDG_CACHE_HOME=/tmp \
PLAYWRIGHT_DAEMON_SESSION_DIR=/tmp/ms-playwright/daemon \
pnpm --filter html-transform exec playwright-cli install-browser chromium
```
若是在受限制環境中執行(例如 home directory 唯讀、sandbox 不允許寫入 `~/.cache`),建議同時指定:
```bash
HOME=/tmp
XDG_CACHE_HOME=/tmp
PLAYWRIGHT_DAEMON_SESSION_DIR=/tmp/ms-playwright/daemon
PLAYWRIGHT_BROWSERS_PATH=/tmp/ms-playwright
```
這樣 Playwright 的 browser、cache 與 daemon session 都會寫到可寫目錄。
## 設定檔
設定檔不是工具執行的絕對必要條件;沒有設定檔時,工具會使用內建預設並讀取:
@@ -138,6 +152,99 @@ export default {
`frontend``backend``output``plan` 等欄位不會被 `doctor``scan` 使用,不需要為本 MVP 加入設定檔。
## 本 repo 實際執行
這個 monorepo 目前要能穩定跑 `scan`,至少需要處理三件事:
1. 根目錄 `ht.config.mjs` 將 prototype 指到 `./prototype`
2. Playwright Chromium 已安裝
3. 若根目錄 `.playwright/cli.config.json` 有指定 `channel`,需避免 `scan` 誤走系統 Chrome
目前本 repo 的最小設定是:
```js
export default {
prototype: "./prototype",
};
```
若根目錄 `.playwright/cli.config.json` 類似:
```json
{
"browser": {
"browserName": "chromium",
"launchOptions": {
"channel": "chromium"
}
}
}
```
`scan` 可能會失敗,並出現類似:
```text
Chromium distribution 'chrome' is not found at /opt/google/chrome/chrome
```
原因是 `html-transform` 會在 `.ht/cache/playwright-cli` 底下呼叫 `playwright-cli`。在這種情況下,可在該工作目錄放一份最小覆寫設定:
```text
.ht/cache/playwright-cli/.playwright/cli.config.json
```
內容:
```json
{
"browser": {
"browserName": "chromium"
}
}
```
在這個 repo 內,實際可重現的流程如下。
1. 確認設定與 CLI
```bash
pnpm --filter html-transform exec playwright-cli --version
HOME=/tmp \
XDG_CACHE_HOME=/tmp \
PLAYWRIGHT_DAEMON_SESSION_DIR=/tmp/ms-playwright/daemon \
PLAYWRIGHT_BROWSERS_PATH=/tmp/ms-playwright \
node packages/html-transform/src/cli.js doctor
```
2. 安裝 Chromium
```bash
HOME=/tmp \
XDG_CACHE_HOME=/tmp \
PLAYWRIGHT_DAEMON_SESSION_DIR=/tmp/ms-playwright/daemon \
PLAYWRIGHT_BROWSERS_PATH=/tmp/ms-playwright \
pnpm --filter html-transform exec playwright-cli install-browser chromium
```
3. 準備 Playwright CLI 的本地覆寫設定(只有在根目錄 `.playwright/cli.config.json``channel` 時需要):
```bash
mkdir -p .ht/cache/playwright-cli/.playwright
printf '%s\n' '{' ' "browser": {' ' "browserName": "chromium"' ' }' '}' > .ht/cache/playwright-cli/.playwright/cli.config.json
```
4. 執行 scan
```bash
HOME=/tmp \
XDG_CACHE_HOME=/tmp \
PLAYWRIGHT_DAEMON_SESSION_DIR=/tmp/ms-playwright/daemon \
PLAYWRIGHT_BROWSERS_PATH=/tmp/ms-playwright \
node packages/html-transform/src/cli.js scan
```
在受限制 sandbox 中,`scan` 可能因為 Vite 無法綁定本機 port、或 Chromium 啟動時的系統呼叫受限而失敗。這種情況下,需要在不受 sandbox 限制的 shell 執行上面的 `scan` 指令。
## Doctor
```bash
@@ -157,6 +264,16 @@ node packages/html-transform/src/cli.js doctor
node packages/html-transform/src/cli.js scan
```
在本 repo 的受限制環境中,建議實際使用:
```bash
HOME=/tmp \
XDG_CACHE_HOME=/tmp \
PLAYWRIGHT_DAEMON_SESSION_DIR=/tmp/ms-playwright/daemon \
PLAYWRIGHT_BROWSERS_PATH=/tmp/ms-playwright \
node packages/html-transform/src/cli.js scan
```
`scan` 會自動:
- 用 Vite static server 提供 prototype。
@@ -297,6 +414,6 @@ node packages/html-transform/src/cli.js scan
```bash
pnpm --filter html-transform typecheck
pnpm --filter html-transform test
node packages/html-transform/src/cli.js doctor
node packages/html-transform/src/cli.js scan
HOME=/tmp XDG_CACHE_HOME=/tmp PLAYWRIGHT_DAEMON_SESSION_DIR=/tmp/ms-playwright/daemon PLAYWRIGHT_BROWSERS_PATH=/tmp/ms-playwright node packages/html-transform/src/cli.js doctor
HOME=/tmp XDG_CACHE_HOME=/tmp PLAYWRIGHT_DAEMON_SESSION_DIR=/tmp/ms-playwright/daemon PLAYWRIGHT_BROWSERS_PATH=/tmp/ms-playwright node packages/html-transform/src/cli.js scan
```
-173
View File
@@ -1,173 +0,0 @@
# TODO
## 目前狀態
`html-transform scan` 已從單純 prototype evidence,推進到可輸出 Page Implementation Contract 的第一版。
已完成:
- 強化 prototype form/table/action 萃取。
- 支援 legacy table-form 的 `<th>/<td>` 欄位 label 推論。
- 欄位輸出包含 `label``name``type``required``readonly``maxLength``options``defaultValue``sourceTable``sourceRow`
- action 會輸出 `actionType``scope`,例如 `search``save``edit``delete``print``back``formAction``rowAction`
- table 會輸出 `role`,例如 `searchTable``resultTable``detailTable``printTable``layoutTable`
- 解析 `prototype/*.md` guide。
- 保留 `legacyJsp``legacyPb``targetView``description`
- 解析各 prototype 段落 checklist,寫入 route guide 與 page spec。
- 解析 backend API 文件。
-`apps/backend/API.md` 解析 endpoint index。
-`apps/backend/API_Manual.md` 解析 method/path、request/response JSON、field rules、notes、ProblemDetails examples。
- 輸出 `.ht/api-catalog.json`
- 建立 prototype-to-api matching。
- 依 prototype path/module、guide、title/text/actions 與 endpoint path/description tokens 做通用匹配。
- 不針對 Venue 寫死規則;Venue 只是目前驗證案例。
- 產出維護頁 `maintenanceContract`
- 輸出 `pageKind``capabilities``recommendedTemplate``confidence``reasons``warnings``dataModel`
- 可初步分辨 `maintenance``query``application``print``chooser``layout-reference`
- 更新 `.ui-contract.md`
- 加入 page kind、recommended template、capabilities、primary entity、target view、legacy source、prototype checklist、API endpoints。
- 補測試。
- 使用 generic `orders` domain 測試 API catalog、API matching 與 maintenance template 推論,避免只驗證 Venue。
已驗證:
```bash
pnpm --filter html-transform test
pnpm --filter html-transform run typecheck
node packages/html-transform/src/cli.js scan
```
目前 Venue prototype 重新 scan 後的分類:
```text
applications-list.html maintenance single-record
apply-choose.html chooser none
apply-facility.html application master-detail-c
apply-room-print.html print none
apply-room.html application master-detail-c
query-choose.html chooser none
query-facility.html query none
query-room.html query none
```
## 目標
`html-transform scan` 產出的 `.ht/` artifact 不只描述 prototype 畫面,也能結合 backend API 文件與維護頁範本規則,形成可穩定交給 LLM 生成 Vue/Vuetify 頁面的實作契約。
核心輸出:
- prototype 提供畫面結構、欄位文案、表格、按鈕與舊流程線索。
- backend API docs 提供 endpoint、request/response DTO、欄位規則、狀態碼與錯誤格式。
- template guide 或 maint README 提供專案層的頁面範本選擇規則。
- `.ht/app-map.json``.ht/api-catalog.json``.ht/spec/{page}.spec.json``.ht/spec/{page}.ui-contract.md` 說明頁面型態、資料模型、API 對應、建議範本與信心理由。
## 待辦
### 1. Layout Evidence
目前 `scan` evidence 可以找出頁面結構、labels、inputs、buttons、tables、screenshots 與 app-map hints。`GEN-FE-PROMPT.md` 的 UI 實作規則要求 feature page 使用現代 Vuetify 承載 prototype 的功能與資訊架構,不逐像素複刻舊 HTML/JSP,但仍要保留資訊密度、區塊順序、欄位行列對齊與主要操作流。
待補強:
- 擷取重要可見元素 bounding boxes
- headings
- labels
- inputs/selects/textareas
- buttons/links
- tables
- section containers
- 用 bounding boxes 將表單欄位分群成可能的 rows / columns。
- 擷取 first-viewport density
- visible section count
- visible form field count
- visible table/header count
- approximate occupied content area
-`.ht/spec/{page}.spec.json` 輸出:
- `layoutEvidence.sections`
- `layoutEvidence.formRows`
- `layoutEvidence.tables`
- `layoutEvidence.primaryActions`
- `layoutEvidence.firstViewport`
-`.ht/spec/{page}.ui-contract.md` 呈現 layout hints。
Non-goals
- 不要求 pixel-perfect HTML/JSP reproduction。
- 不複製 legacy CSS。
- 除非是功能辨識必要,不把精確 colors/fonts 編成硬限制。
### 2. API Matching 品質
目前 API matching 是通用 token scoring,已可用,但仍偏粗。
待補強:
- 降低同 module 但不同 page 的誤配風險。
- 把 guide 的 `targetView`、JSP/PB method、prototype path 與 endpoint path 做更細的權重拆分。
- 將 matched endpoints 分成用途:
- `lookup`
- `search`
- `detail`
- `create`
- `update`
- `delete`
- `print`
- `customAction`
-`apiContract` 裡輸出 rejected candidates 與 rejection reason,方便檢查錯配。
### 3. Maintenance Contract 精準度
目前 `maintenanceContract` 已能初步推薦範本,但仍是 heuristic。
待補強:
- 加入 row action rule
- 從 prototype checklist、button disabled、API notes 推論 `enabledWhen`
- 例如 `aprvYn === 'Z'` 才能 edit/delete。
- 更精準拆出:
- `searchFields`
- `formFields`
- `tableColumns`
- `detailCollections`
- `rowActions`
-`recommendedTemplate` 與特定前端專案範本解耦。
- 通用輸出保留 `single-record``master-detail-c` 等抽象 template id。
- 專案若有自己的 README 或 guide,再由 prompt 對應到實際檔案。
- 加 validation warnings
- `maintenance` 頁缺少 search/table/action。
-`edit/delete` action 但沒有對應 API endpoint。
- request schema 有欄位但 prototype 找不到對應欄位。
- prototype 有欄位但 API schema 找不到對應欄位。
- row action 有狀態限制但未產出 `enabledWhen`
### 4. Backend Docs 通用性
目前 backend docs 預設讀 `./apps/backend`,且 parser 針對 markdown heading、table、code fence 做通用解析。
待補強:
- 允許 config 指定多個 backend docs 來源。
- 支援 OpenAPI JSON/YAML 作為輸入來源。
- 支援沒有 `API.md` index、只有 manual 的情境。
- 支援不同語言的章節標籤,例如 `Request body``Response body``Validation`
### 5. UI Contract Markdown
目前 `.ui-contract.md` 已加入主要 contract 摘要。
待補強:
- 輸出 field rules 與 schema 摘要。
- 輸出 row action rules。
- 輸出 layout evidence。
-`recommendedTemplate: none` 的頁面明確標示原因,例如 chooser/query/print/layout-reference。
### 6. 文件與範例
待補強:
- 加一個最小 fixture,示範非 Venue domain 如何產出 api catalog 與 maintenance contract。
- 在 README 補充 config 範例:
- 只指定 `prototype`
- 同時指定 `prototype``backendDocs`
- 沒有 backend docs 時的輸出行為
+14 -40
View File
@@ -229,29 +229,10 @@ export function buildAppMapWithGuides(layoutSpecs, prototypeDir, prototypeGuides
tableCount: spec.pageContract.tables.length
}
}
})
}).filter((route) => ['auth', 'auth-support', 'feature-page'].includes(route.kind))
return {
version: 1,
generatedAt: new Date().toISOString(),
rules: {
layoutSource: 'preparation/skt-vuetify-templates',
loginStyleSource: 'preparation/skt-vuetify-templates',
prototypeStylePolicy: 'content-only',
prototypeOuterFramePolicy: 'ignore'
},
guideSources: prototypeGuides.map((guide) => ({
source: guide.source,
title: guide.title,
entryCount: guide.entries.length,
legacyFlowCount: guide.legacyFlows.length
})),
legacyFlows: prototypeGuides.flatMap((guide) => guide.legacyFlows.map((flow) => ({
source: guide.source,
title: flow.title,
nodeCount: flow.nodes.length,
tasks: unique(flow.nodes.map((node) => node.task).filter(Boolean))
}))),
modules: buildModules(routes),
routes
}
}
@@ -433,7 +414,9 @@ function buildPrototypeGuideIndex(prototypeGuides) {
const index = new Map()
for (const guide of prototypeGuides) {
for (const entry of guide.entries) {
index.set(entry.prototype, entry)
const normalized = normalizePrototypeGuidePath(entry.prototype)
index.set(normalized, entry)
index.set(`module/${normalized}`, entry)
}
}
return index
@@ -469,7 +452,7 @@ function findGuideValue(values, needles) {
function inferRoute(spec, relativeSource) {
const path = relativeSource.replace(/\\/g, '/')
const segments = path.split('/')
const module = segments.length > 1 ? segments[0] : 'root'
const module = inferModuleName(segments)
const basename = segments.at(-1) ?? spec.page
const text = [
spec.pageContract.title,
@@ -548,24 +531,15 @@ function inferRoute(spec, relativeSource) {
}
}
function buildModules(routes) {
const modules = new Map()
for (const route of routes) {
const current = modules.get(route.module) ?? {
name: route.module,
featurePageCount: 0,
authPageCount: 0,
referencePageCount: 0
}
if (route.kind === 'feature-page') current.featurePageCount += 1
else if (route.kind === 'auth' || route.kind === 'auth-support') current.authPageCount += 1
else current.referencePageCount += 1
modules.set(route.module, current)
}
return [...modules.values()].map((module) => ({
...module,
kind: module.featurePageCount > 0 ? 'feature-module' : module.authPageCount > 0 ? 'entry-module' : 'reference-module'
}))
function normalizePrototypeGuidePath(path) {
return String(path ?? '').replace(/^\.?\/*/, '').replace(/^module\//, '')
}
function inferModuleName(segments) {
if (segments.length === 1) return 'root'
if (segments[0] === 'module' && segments[1]) return segments[1]
if (segments[0] === 'guide' && segments[1]) return segments[1]
return segments[0] ?? 'root'
}
function hasPasswordField(spec) {
+41 -2
View File
@@ -1,5 +1,5 @@
import { spawn } from 'node:child_process'
import { readFile } from 'node:fs/promises'
import { readFile, rm, writeFile } from 'node:fs/promises'
import { basename, join, relative } from 'node:path'
import { loadConfig } from '../lib/config.js'
import { artifactPath, ensureDir, exists, listFiles, readJson, sha256File, writeJson } from '../lib/files.js'
@@ -19,11 +19,12 @@ const viewports = [
*/
export async function scan() {
const config = await loadConfig()
const htmlFiles = await listFiles(config.prototypeDir, ['.html'])
const htmlFiles = await listScanHtmlFiles(config)
if (htmlFiles.length === 0) throw new Error(`找不到 HTML prototype${config.prototypeDir}`)
const prototypeGuides = await readPrototypeGuides(config)
const apiCatalog = await readApiCatalog(config)
await ensureDir(config.htDir)
if (isModuleScopedScan(config, htmlFiles)) await resetModuleScopedArtifacts(config)
const plans = []
for (const file of htmlFiles) {
plans.push(await prepareScanFile(config, file))
@@ -51,6 +52,22 @@ export async function scan() {
console.log(`scan 完成:${htmlFiles.length} 個 HTML 檔案`)
}
async function listScanHtmlFiles(config) {
const moduleDir = join(config.prototypeDir, 'module')
if (await exists(moduleDir)) return listFiles(moduleDir, ['.html'])
return listFiles(config.prototypeDir, ['.html'])
}
function isModuleScopedScan(config, htmlFiles) {
const moduleDir = join(config.prototypeDir, 'module')
return htmlFiles.length > 0 && htmlFiles.every((file) => file.startsWith(moduleDir))
}
async function resetModuleScopedArtifacts(config) {
await rm(join(config.htDir, 'spec'), { recursive: true, force: true })
await rm(join(config.htDir, 'cache/prototype'), { recursive: true, force: true })
}
/**
* 讀取 prototype 旁的人工導覽文件。
* 這些 guide 是 legacy JSP/PB、checklist 與 flow refs 的來源,後續 BDD 與 maintenance contract 都會使用。
@@ -213,6 +230,7 @@ async function captureRenderedPrototype(config, plan, serverUrl) {
const playwrightCwd = join(config.htDir, 'cache/playwright-cli')
await ensureDir(playwrightCwd)
await ensurePlaywrightCliConfig(playwrightCwd, config.cwd)
const commandCwd = resolved.source === 'npx-local' ? config.cwd : playwrightCwd
const session = `ht-${process.pid}-${plan.hash.slice(0, 8)}`
const url = new URL(`${encodePath(plan.name)}.html`, serverUrl).href
@@ -261,6 +279,27 @@ async function captureRenderedPrototype(config, plan, serverUrl) {
}
}
async function ensurePlaywrightCliConfig(playwrightCwd, repoCwd) {
const sourcePath = join(repoCwd, '.playwright/cli.config.json')
let browserName = 'chromium'
if (await exists(sourcePath)) {
try {
const parsed = JSON.parse(await readFile(sourcePath, 'utf8'))
browserName = parsed?.browser?.browserName ?? browserName
} catch {
browserName = 'chromium'
}
}
const override = {
browser: {
browserName
}
}
const targetDir = join(playwrightCwd, '.playwright')
await ensureDir(targetDir)
await writeFile(join(targetDir, 'cli.config.json'), `${JSON.stringify(override, null, 2)}\n`)
}
/**
* 建立 Playwright CLI 執行的頁面擷取程式。
* 這段字串刻意集中處理 rendered DOM evidence,讓 source HTML 與實際瀏覽器狀態的差異能被 capture artifacts 保留下來。
+6 -1
View File
@@ -30,6 +30,12 @@ test('CLI runs doctor and scan against one prototype', async () => {
| --- | --- | --- |
| [\`index.html\`](index.html) | \`legacy/index.jsp\` | Customer portal entry |
`)
await mkdir(join(cwd, '.playwright'), { recursive: true })
await writeFile(join(cwd, '.playwright/cli.config.json'), JSON.stringify({
browser: {
browserName: 'chromium'
}
}, null, 2))
const doctor = await exec('node', [cli, 'doctor'], { cwd })
await exec('node', [cli, 'scan'], { cwd })
@@ -59,7 +65,6 @@ test('CLI runs doctor and scan against one prototype', async () => {
assert.equal(appMap.routes[0].evidence.recommendedTemplate, undefined)
assert.equal(appMap.routes[0].guide.legacyJsp, 'legacy/index.jsp')
assert.equal(appMap.routes[0].guide.description, 'Customer portal entry')
assert.equal(appMap.guideSources[0].source, 'portal.md')
})
function pick(object, keys) {
+2 -7
View File
@@ -131,7 +131,7 @@ test('validatePageContract reports evidence mismatches', () => {
assert.match(report.warnings.join('\n'), /Save/)
})
test('buildAppMap classifies auth, shell references, and feature pages', () => {
test('buildAppMap only keeps AI-implementable auth and feature pages', () => {
const prototypeDir = '/repo/prototype'
const specs = [
buildSpec('/repo/prototype/portal/login.html', '<main><input type="password" name="pwd"><button>登入</button></main>'),
@@ -146,11 +146,9 @@ test('buildAppMap classifies auth, shell references, and feature pages', () => {
assert.equal(buildAppMap([
buildSpec('/repo/prototype/portal/forget-password.html', '<main><h1>忘記密碼</h1><input name="idno"><button>發送至信箱</button></main>')
], prototypeDir).routes[0].targetRole, 'forgot-password')
assert.equal(appMap.routes.find((route) => route.prototype === 'portal/app-layout.html').kind, 'legacy-shell-reference')
assert.equal(appMap.routes.find((route) => route.prototype === 'portal/app-layout.html').usePrototypeContent, false)
assert.equal(appMap.routes.find((route) => route.prototype === 'portal/app-layout.html'), undefined)
assert.equal(appMap.routes.find((route) => route.prototype === 'venue/applications-list.html').kind, 'feature-page')
assert.equal(appMap.routes.find((route) => route.prototype === 'venue/applications-list.html').layout, 'template-app')
assert.equal(appMap.modules.find((module) => module.name === 'venue').kind, 'feature-module')
})
test('buildAppMap enriches routes with prototype markdown guide entries', () => {
@@ -177,13 +175,10 @@ test('buildAppMap enriches routes with prototype markdown guide entries', () =>
], prototypeDir, [guide])
const route = appMap.routes[0]
assert.equal(appMap.guideSources[0].source, 'venue.md')
assert.equal(route.evidence.prototypeGuide, 'venue.md')
assert.equal(route.guide.legacyJsp, 'zte_pro/zte451_02.jsp + zte451_02_1.jsp')
assert.equal(route.guide.legacyPb, 'n_zte451.of_zte451_02 / of_zte451_02_1')
assert.equal(route.guide.targetView, 'RoomQueryView.vue')
assert.equal(appMap.guideSources[0].legacyFlowCount, 1)
assert.equal(appMap.legacyFlows[0].tasks[0], '場地查詢')
assert.equal(route.guide.flowRefs[0].tasks[0], '場地查詢')
assert.deepEqual(route.guide.flowRefs[0].matchedKeys, ['zte451_02.jsp', 'of_zte451_02', 'zte451_02_1.jsp', 'of_zte451_02_1'])
assert.equal(route.guide.flowRefs[0].nodes[0].nodeType, 'query')