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:
1 parent
8205df2dc1
commit
40e1005d81
4 files changed
+45
-18
No files matched your search
@@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
Reference in new issue
Block a user