import { expect, test } from "@playwright/test"; import { THREAD_WITH_ARTIFACTS, THREAD_WITH_HISTORY, openChat, reuseThreadChatEntry, setTheme, skipIfMissingThread, } from "./support/chat-helpers"; function isTransparent(color: string) { const normalized = color.replace(/\s+/g, "").toLowerCase(); return normalized === "transparent" || normalized.endsWith(",0)"); } test.describe("聊天工作台 / 主题颜色回归", () => { test("DF-THEME-001 thread 页面在 light/dark 根容器颜色不同且非透明", async ({ page, }, testInfo) => { skipIfMissingThread( testInfo, THREAD_WITH_HISTORY, "FRONTEND_E2E_THREAD_ID", ); await openChat(page, reuseThreadChatEntry(THREAD_WITH_HISTORY!)); await setTheme(page, "light"); const lightState = await page.evaluate(() => { const probe = document.createElement("div"); probe.className = "bg-background"; probe.style.position = "fixed"; probe.style.left = "-9999px"; probe.style.top = "-9999px"; document.body.appendChild(probe); const bg = getComputedStyle(probe).backgroundColor; probe.remove(); return { bg, rootBackground: getComputedStyle(document.documentElement) .getPropertyValue("--background") .trim(), }; }); await setTheme(page, "dark"); const darkState = await page.evaluate(() => { const probe = document.createElement("div"); probe.className = "bg-background"; probe.style.position = "fixed"; probe.style.left = "-9999px"; probe.style.top = "-9999px"; document.body.appendChild(probe); const bg = getComputedStyle(probe).backgroundColor; probe.remove(); return { bg, rootBackground: getComputedStyle(document.documentElement) .getPropertyValue("--background") .trim(), }; }); expect(isTransparent(lightState.bg)).toBe(false); expect(isTransparent(darkState.bg)).toBe(false); expect(darkState.rootBackground).not.toBe(lightState.rootBackground); }); test("DF-THEME-002 dark 模式下发送按钮 hover 前后颜色变化存在且可见", async ({ page, }, testInfo) => { skipIfMissingThread( testInfo, THREAD_WITH_HISTORY, "FRONTEND_E2E_THREAD_ID", ); await openChat(page, reuseThreadChatEntry(THREAD_WITH_HISTORY!)); await setTheme(page, "dark"); const textarea = page.locator("textarea[name='message']"); const submit = page.locator("button[aria-label='Submit']"); await textarea.fill("theme hover regression"); await expect(submit).toBeEnabled(); const before = await submit.evaluate((element) => { const style = getComputedStyle(element); return { background: style.backgroundColor, color: style.color, border: style.borderTopColor, }; }); await submit.hover(); const after = await submit.evaluate((element) => { const style = getComputedStyle(element); return { background: style.backgroundColor, color: style.color, border: style.borderTopColor, }; }); const changed = before.background !== after.background || before.color !== after.color || before.border !== after.border; expect(changed).toBe(true); expect(isTransparent(after.background) && isTransparent(after.border)).toBe( false, ); expect(isTransparent(after.color)).toBe(false); }); test("DF-THEME-003 artifact detail 面板在 light/dark 渲染 token 颜色", async ({ page, }, testInfo) => { skipIfMissingThread( testInfo, THREAD_WITH_ARTIFACTS, "FRONTEND_E2E_ARTIFACTS_THREAD_ID", ); await openChat(page, reuseThreadChatEntry(THREAD_WITH_ARTIFACTS!)); const openArtifacts = page.getByTestId("artifacts-open-button"); testInfo.skip( (await openArtifacts.count()) === 0, "当前线程未展示 artifacts 入口。", ); await openArtifacts.click(); const firstCard = page.getByTestId("artifact-file-card").first(); testInfo.skip((await firstCard.count()) === 0, "当前线程没有 artifact 文件。"); await firstCard.click(); const detailRoot = page .locator("div.bg-background.relative.h-full.overflow-hidden.rounded-2xl") .first(); await expect(detailRoot).toBeVisible(); await setTheme(page, "light"); const light = await detailRoot.evaluate((element) => { const style = getComputedStyle(element); const header = element.querySelector("header"); const headerStyle = header ? getComputedStyle(header) : null; return { panelBg: style.backgroundColor, headerBorder: headerStyle?.borderBottomColor ?? "", }; }); await setTheme(page, "dark"); const dark = await detailRoot.evaluate((element) => { const style = getComputedStyle(element); const header = element.querySelector("header"); const headerStyle = header ? getComputedStyle(header) : null; return { panelBg: style.backgroundColor, headerBorder: headerStyle?.borderBottomColor ?? "", }; }); expect(isTransparent(light.panelBg)).toBe(false); expect(isTransparent(dark.panelBg)).toBe(false); expect(light.panelBg).not.toBe(dark.panelBg); expect(light.headerBorder).not.toBe(dark.headerBorder); }); });