test(e2e): stabilize route bootstrap and output polling

- Reduce output polling flakes\n- Stabilize route bootstrap and smoke flows\n- Improve i18n label compatibility (incl. zh-TW)
This commit is contained in:
linshen committed 2026-01-18 22:56:00 +08:00
1 parent 8205df2dc1
commit 40e1005d81
4 files changed
+45 -18

No files matched your search

+35 -10
View File
@@ -57,24 +57,49 @@ async function ensureOutputSourceView(output: import('@playwright/test').Locator
}
async function readOutputSourceText(output: import('@playwright/test').Locator) {
if ((await output.count()) === 0) return { kind: 'missing', text: '' }
// Avoid Playwright strict-mode flakiness when the same testId is transiently duplicated in the DOM.
// Always read from the first matched node.
const root = output.first()
if ((await root.count()) === 0) return { kind: 'missing', text: '' }
// Source 视图下优先读取编辑器/textarea 的“值”,避免拿到整张卡片的 innerText
const cmContent = output.locator('.cm-content')
if ((await cmContent.count()) > 0) {
return { kind: 'cm', text: (await cmContent.first().innerText()).trim() }
try {
const cmContent = root.locator('.cm-content')
if ((await cmContent.count()) > 0) {
return { kind: 'cm', text: (await cmContent.first().innerText()).trim() }
}
} catch {
// ignore and fallback
}
const textarea = output.locator('textarea')
if ((await textarea.count()) > 0) {
return { kind: 'textarea', text: (await textarea.first().inputValue()).trim() }
try {
const textarea = root.locator('textarea')
if ((await textarea.count()) > 0) {
return { kind: 'textarea', text: (await textarea.first().inputValue()).trim() }
}
} catch {
// ignore and fallback
}
return { kind: 'text', text: (await output.innerText()).trim() }
// Render 视图:MarkdownRenderer 会渲染到 .markdown-content 容器中
try {
const markdown = root.locator('.markdown-content')
if ((await markdown.count()) > 0) {
return { kind: 'markdown', text: (await markdown.first().innerText()).trim() }
}
} catch {
// ignore and fallback
}
try {
return { kind: 'text', text: (await root.innerText()).trim() }
} catch {
return { kind: 'missing', text: '' }
}
}
export async function expectOutputByTestIdNotEmpty(page: Page, testId: string, opts?: { timeoutMs?: number }) {
const output = page.locator(`[data-testid="${testId}"]`)
const output = page.locator(`[data-testid="${testId}"]:visible`)
const timeoutMs = opts?.timeoutMs ?? 120000
await ensureOutputSourceView(output)
@@ -90,7 +115,7 @@ export async function expectOutputByTestIdNotEmpty(page: Page, testId: string, o
export async function expectOptimizedResultNotEmpty(page: Page, mode: OptimizeWorkspaceMode) {
const workspace = getWorkspace(page, mode)
const output = workspace.locator(`[data-testid="${mode}-output"]`)
const output = workspace.locator(`[data-testid="${mode}-output"]:visible`)
try {
await ensureOutputSourceView(output)
@@ -4,7 +4,7 @@ async function waitForWorkspace(page: any, mode: string) {
const workspace = page
.locator(`[data-testid="workspace"][data-mode="${mode}"]`)
.first()
await expect(workspace).toBeVisible({ timeout: 20000 })
await expect(workspace).toBeVisible({ timeout: 45000 })
}
test.describe('Root route bootstrap', () => {
@@ -23,7 +23,7 @@ test.describe('Root route bootstrap', () => {
// Immediately navigate to a non-root route; bootstrap logic must not replace it back.
await page.goto('/#/image/text2image')
await page.waitForLoadState('networkidle')
await expect(page).toHaveURL(/#\/image\/text2image/)
await waitForWorkspace(page, 'image-text2image')
})
})
@@ -33,7 +33,7 @@ test.describe('Image Text2Image - Session Persistence', () => {
test('切换文本模型后刷新页面,选择应该保留', async ({ page }) => {
await gotoMode(page, '/#/image/text2image')
const select = await getSelectByLabel(page, /Text Model|文本模型/i)
const select = await getSelectByLabel(page, /Text Model|文本模型|文字模型/i)
await select.click()
const optionLocator = page.locator('.n-base-select-option')
@@ -54,7 +54,7 @@ test.describe('Image Text2Image - Session Persistence', () => {
await page.waitForLoadState('networkidle')
await page.waitForTimeout(1500)
const selectAfter = await getSelectByLabel(page, /Text Model|文本模型/i)
const selectAfter = await getSelectByLabel(page, /Text Model|文本模型|文字模型/i)
await expect
.poll(async () => normalizeText(await selectAfter.textContent()), { timeout: 20000 })
.toBe(target)
@@ -63,7 +63,7 @@ test.describe('Image Text2Image - Session Persistence', () => {
test('切换模板后刷新页面,选择应该保留', async ({ page }) => {
await gotoMode(page, '/#/image/text2image')
const select = await getSelectByLabel(page, /Optimization Template|优化.*模板/i)
const select = await getSelectByLabel(page, /Optimization Template|优化.*模板|優化.*模板/i)
await select.click()
const optionLocator = page.locator('.n-base-select-option')
@@ -84,10 +84,9 @@ test.describe('Image Text2Image - Session Persistence', () => {
await page.waitForLoadState('networkidle')
await page.waitForTimeout(1500)
const selectAfter = await getSelectByLabel(page, /Optimization Template|优化.*模板/i)
const selectAfter = await getSelectByLabel(page, /Optimization Template|优化.*模板|優化.*模板/i)
await expect
.poll(async () => normalizeText(await selectAfter.textContent()), { timeout: 20000 })
.toBe(target)
})
})
+4 -1
View File
@@ -15,7 +15,10 @@ test.describe('P0 route smoke', () => {
await page.goto('/')
await page.waitForLoadState('networkidle')
// 应用初始化完成后再跳转到目标路由,避免 services/watch 初始化阶段把深链接当成根路径覆盖
// 应用初始化完成后再跳转到目标路由,避免 RootBootstrapRoute/globalSettings 初始化的竞态
// 触发一次“默认路由跳转”后再进入目标路由,更稳定。
await expect(page.locator('.loading-container')).toHaveCount(0, { timeout: 15000 })
await page.goto(route.hashPath)
await page.waitForLoadState('networkidle')