diff --git a/tests/e2e/helpers/optimize.ts b/tests/e2e/helpers/optimize.ts index 4c3c231b..db70a080 100644 --- a/tests/e2e/helpers/optimize.ts +++ b/tests/e2e/helpers/optimize.ts @@ -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) diff --git a/tests/e2e/regression/root-route-bootstrap.spec.ts b/tests/e2e/regression/root-route-bootstrap.spec.ts index 545e9b7d..a513edc4 100644 --- a/tests/e2e/regression/root-route-bootstrap.spec.ts +++ b/tests/e2e/regression/root-route-bootstrap.spec.ts @@ -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') }) }) - diff --git a/tests/e2e/session-persistence/image-text2image-persistence.spec.ts b/tests/e2e/session-persistence/image-text2image-persistence.spec.ts index 5d48e834..caed7172 100644 --- a/tests/e2e/session-persistence/image-text2image-persistence.spec.ts +++ b/tests/e2e/session-persistence/image-text2image-persistence.spec.ts @@ -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) }) }) - diff --git a/tests/e2e/workflows/p0-route-smoke.spec.ts b/tests/e2e/workflows/p0-route-smoke.spec.ts index 2ba79c58..06c8e862 100644 --- a/tests/e2e/workflows/p0-route-smoke.spec.ts +++ b/tests/e2e/workflows/p0-route-smoke.spec.ts @@ -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')