frontend: FE-034 UI改版回归验收,真实TFS走查12项通过并归档关键截图

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
fenghp
2026-06-11 17:51:50 +08:00
co-authored by Cursor
parent 67ecb24ffc
commit e951a24889
11 changed files with 279 additions and 9 deletions
+127
View File
@@ -0,0 +1,127 @@
// FE-034 补充回归:目录已被既有 Workspace 映射(含用户真实挂起更改),
// 因此跳过创建/取消映射,只验证 目录对比、签出→Diff→撤销(仅 ai-smoke 文件)、操作日志。
import { _electron as electron } from "playwright-core"
import { mkdirSync } from "node:fs"
import path from "node:path"
const ROOT = process.cwd()
const SHOT_DIR = path.join(ROOT, ".e2e-shots")
mkdirSync(SHOT_DIR, { recursive: true })
const TEST_TREE_CHAIN = ["北京排水集团人力系统", "5SRC", "MicroFront", "subapp_pm", "mactfs"]
const SMOKE_FILE = "ai-smoke-20260606051038.txt"
const results = []
/** 记录一步结果并打印。 */
function record(step, ok, detail = "") {
results.push({ step, ok, detail })
console.log(`${ok ? "PASS" : "FAIL"} | ${step}${detail ? " | " + detail : ""}`)
}
/** 截图保存。 */
async function shot(win, name) {
await win.screenshot({ path: path.join(SHOT_DIR, `${name}.png`) })
}
const app = await electron.launch({
args: ["electron/main.cjs"],
cwd: ROOT,
env: { ...process.env, NODE_ENV: "development" },
})
try {
const win = await app.firstWindow()
win.setDefaultTimeout(90000)
// 进入工作台并导航到测试目录
await win.getByRole("button", { name: "连接 TFS" }).waitFor({ timeout: 90000 })
await win.getByRole("button", { name: "连接 TFS" }).click()
await win.getByText("选择 Collection").waitFor({ timeout: 120000 })
await win.locator("ul button", { hasText: "PKUSEHR" }).first().click()
await win.getByRole("button", { name: "进入工作台" }).click()
await win.getByText("源码目录").waitFor({ timeout: 120000 })
const tree = win.locator("aside").first()
for (const name of TEST_TREE_CHAIN) {
const node = tree.getByText(name, { exact: true }).first()
await node.waitFor({ timeout: 120000 })
await node.click()
await win.waitForTimeout(2500)
}
record("0 进入工作台并导航到测试目录", true)
// A. 目录对比(目录已映射 → 菜单应可用;忽略仅本地/仅服务器项保持默认)
await tree.getByText("mactfs", { exact: true }).first().click({ button: "right" })
const compareItem = win.getByRole("menuitem", { name: "目录对比", exact: true })
await compareItem.waitFor({ timeout: 10000 })
await compareItem.click()
await win.locator("[data-slot=dialog-title]", { hasText: "目录对比" }).waitFor()
await shot(win, "12-compare-options")
// 新交互:先确认选项再执行
const startButton = win.getByRole("button", { name: /开始对比|执行对比|对比/ }).first()
if ((await startButton.count()) > 0) {
await startButton.click()
}
await win.waitForTimeout(10000)
const compareText = await win.locator("[data-slot=dialog-content]").innerText()
record("A 目录对比弹窗", !compareText.includes("目录对比失败"), compareText.slice(0, 100).replace(/\n/g, " "))
await shot(win, "12-compare")
await win.keyboard.press("Escape")
await win.locator("[data-slot=dialog-content]").waitFor({ state: "detached", timeout: 10000 }).catch(() => {})
await win.waitForTimeout(800)
// B. 签出 ai-smoke 文件 → 挂起更改出现 → 与最新版本比较 → 撤销(只动这一个文件)
const fileRow = win.locator("section table tbody tr").filter({ hasText: SMOKE_FILE }).first()
await fileRow.click({ button: "right" })
const checkoutItem = win.getByRole("menuitem", { name: "签出编辑", exact: true })
await checkoutItem.waitFor({ timeout: 10000 })
await checkoutItem.click()
await win.waitForTimeout(15000)
const pendingPanel = await win.locator("aside").nth(1).innerText()
const hasPending = pendingPanel.includes(SMOKE_FILE)
record("B1 签出文件产生挂起更改", hasPending, "")
await shot(win, "13-pending")
if (hasPending) {
const row = win.locator("aside").nth(1).getByText(SMOKE_FILE).first()
await row.click({ button: "right" })
const diffItem = win.getByRole("menuitem", { name: "与最新版本比较", exact: true })
if ((await diffItem.count()) > 0) {
await diffItem.click()
await win.waitForTimeout(8000)
const diffText = await win.locator("[data-slot=dialog-content]").innerText()
record("B2 本地 vs latest Diff(Monaco)", diffText.includes("差异") || diffText.includes("一致"), diffText.slice(0, 80).replace(/\n/g, " "))
await shot(win, "14-diff")
await win.keyboard.press("Escape")
await win.locator("[data-slot=dialog-content]").waitFor({ state: "detached", timeout: 10000 }).catch(() => {})
await win.waitForTimeout(800)
}
// 仅撤销 ai-smoke 这一个文件,避免影响用户已有挂起更改
await row.click({ button: "right" })
await win.getByRole("menuitem", { name: "撤销更改", exact: true }).click()
await win.waitForTimeout(12000)
const afterUndo = await win.locator("aside").nth(1).innerText()
record("B3 撤销该文件挂起更改", !afterUndo.includes(SMOKE_FILE), "")
await shot(win, "15-undone")
}
// C. 操作日志面板有真实记录
const consoleText = await win.locator("footer").innerText()
record("C 操作日志记录", /checkout|undo|connect|serverTree|folderDiff|compare/i.test(consoleText), consoleText.split("\n").slice(0, 5).join(" | "))
await shot(win, "16-console")
} catch (error) {
record("EXCEPTION", false, String(error).slice(0, 300))
try {
const win = await app.firstWindow()
await shot(win, "98-exception")
} catch {}
} finally {
await app.close().catch(() => {})
}
console.log("\n==== RESULT SUMMARY ====")
for (const item of results) {
console.log(`${item.ok ? "✅" : "❌"} ${item.step}${item.detail ? " — " + item.detail : ""}`)
}
const failed = results.filter((item) => !item.ok).length
console.log(`\n${results.length - failed}/${results.length} passed`)
process.exit(failed > 0 ? 1 : 0)
+109
View File
@@ -0,0 +1,109 @@
// FE-034 清理与诊断:撤销 part2 遗留的 ai-smoke 签出;再从文件列表打开「与最新版本比较」
// 复现 Diff 弹窗导致窗口关闭的问题,捕获 renderer 崩溃与主进程输出。
import { _electron as electron } from "playwright-core"
import { mkdirSync } from "node:fs"
import path from "node:path"
const ROOT = process.cwd()
const SHOT_DIR = path.join(ROOT, ".e2e-shots")
mkdirSync(SHOT_DIR, { recursive: true })
const TEST_TREE_CHAIN = ["北京排水集团人力系统", "5SRC", "MicroFront", "subapp_pm", "mactfs"]
const SMOKE_FILE = "ai-smoke-20260606051038.txt"
const results = []
function record(step, ok, detail = "") {
results.push({ step, ok, detail })
console.log(`${ok ? "PASS" : "FAIL"} | ${step}${detail ? " | " + detail : ""}`)
}
async function shot(win, name) {
await win.screenshot({ path: path.join(SHOT_DIR, `${name}.png`) }).catch(() => {})
}
const app = await electron.launch({
args: ["electron/main.cjs"],
cwd: ROOT,
env: { ...process.env, NODE_ENV: "development", ELECTRON_ENABLE_LOGGING: "1" },
})
app.process().stderr?.on("data", (chunk) => {
const text = String(chunk)
if (/error|crash|fatal/i.test(text)) {
console.log("[main-stderr]", text.slice(0, 400))
}
})
try {
const win = await app.firstWindow()
win.setDefaultTimeout(90000)
win.on("crash", () => console.log("[renderer] CRASHED"))
win.on("close", () => console.log("[renderer] window closed"))
win.on("console", (msg) => {
if (msg.type() === "error") {
console.log("[console.error]", msg.text().slice(0, 300))
}
})
win.on("pageerror", (err) => console.log("[pageerror]", String(err).slice(0, 300)))
await win.getByRole("button", { name: "连接 TFS" }).waitFor({ timeout: 90000 })
await win.getByRole("button", { name: "连接 TFS" }).click()
await win.getByText("选择 Collection").waitFor({ timeout: 120000 })
await win.locator("ul button", { hasText: "PKUSEHR" }).first().click()
await win.getByRole("button", { name: "进入工作台" }).click()
await win.getByText("源码目录").waitFor({ timeout: 120000 })
await win.waitForTimeout(5000)
// 1. 清理:撤销 ai-smoke 的遗留签出(只动这一个文件)
const changes = win.locator("aside").nth(1)
const smokeRow = changes.getByText(SMOKE_FILE).first()
if ((await smokeRow.count()) > 0) {
await smokeRow.click({ button: "right" })
await win.getByRole("menuitem", { name: "撤销更改", exact: true }).click()
await win.waitForTimeout(12000)
const after = await changes.innerText()
record("1 清理遗留签出", !after.includes(SMOKE_FILE), "")
await shot(win, "17-cleanup")
} else {
record("1 清理遗留签出", true, "无遗留挂起更改")
}
// 2. 诊断:从文件列表打开 与最新版本比较(Monaco Diff)
const tree = win.locator("aside").first()
for (const name of TEST_TREE_CHAIN) {
const node = tree.getByText(name, { exact: true }).first()
await node.waitFor({ timeout: 120000 })
await node.click()
await win.waitForTimeout(2000)
}
const fileRow = win.locator("section table tbody tr").filter({ hasText: SMOKE_FILE }).first()
await fileRow.click({ button: "right" })
const diffItem = win.getByRole("menuitem", { name: "与最新版本比较", exact: true })
await diffItem.waitFor({ timeout: 10000 })
await diffItem.click()
await win.waitForTimeout(3000)
await shot(win, "18-diff-opening")
await win.waitForTimeout(7000)
const dialogCount = await win.locator("[data-slot=dialog-content]").count()
if (dialogCount > 0) {
const diffText = await win.locator("[data-slot=dialog-content]").innerText()
record("2 Diff 弹窗(Monaco)", diffText.includes("差异") || diffText.includes("一致"), diffText.slice(0, 80).replace(/\n/g, " "))
await shot(win, "18-diff")
await win.keyboard.press("Escape")
} else {
record("2 Diff 弹窗(Monaco)", false, "弹窗未出现")
}
} catch (error) {
record("EXCEPTION", false, String(error).slice(0, 300))
try {
const win = await app.firstWindow()
await shot(win, "97-exception")
} catch {}
} finally {
await app.close().catch(() => {})
}
console.log("\n==== RESULT SUMMARY ====")
for (const item of results) {
console.log(`${item.ok ? "✅" : "❌"} ${item.step}${item.detail ? " — " + item.detail : ""}`)
}
process.exit(results.some((item) => !item.ok) ? 1 : 0)
+12
View File
@@ -57,6 +57,18 @@ try {
record("3 进入工作台(固定上下文)", topbar.includes("PKUSEHR"), topbar.replace(/\n/g, " "))
await shot(win, "03-workspace")
// 3.5 明暗主题三态切换(FE-033 回归):跟随系统 → 亮 → 暗 → 跟随系统
const themeButton = win.getByRole("button", { name: /主题:/ })
await themeButton.click()
await win.waitForTimeout(400)
await themeButton.click()
await win.waitForTimeout(600)
const darkApplied = await win.evaluate(() => document.documentElement.classList.contains("dark"))
record("3.5 主题切换到暗色", darkApplied, "")
await shot(win, "03b-dark-workspace")
await themeButton.click()
await win.waitForTimeout(600)
// 4. 逐级展开目录树到指定测试目录(懒加载真实数据)
const tree = win.locator("aside").first()
for (const name of TEST_TREE_CHAIN) {