feat(08-04): 添加工作区主题颜色回归端到端测试
- 添加可复用的 setTheme 辅助函数,用于在端到端测试中切换亮色/暗色主题 - 添加 theme-colors 测试规范,覆盖线程根节点、提交按钮悬停、产物详情等场景
This commit is contained in:
parent
08b3864673
commit
cf36873d99
|
|
@ -100,6 +100,15 @@ export async function openChat(
|
|||
}
|
||||
}
|
||||
|
||||
export async function setTheme(page: Page, theme: "light" | "dark") {
|
||||
await page.evaluate((nextTheme) => {
|
||||
const root = document.documentElement;
|
||||
root.classList.remove("light", "dark");
|
||||
root.classList.add(nextTheme);
|
||||
root.style.colorScheme = nextTheme;
|
||||
}, theme);
|
||||
}
|
||||
|
||||
export async function expandComposer(page: Page) {
|
||||
const expander = page.locator("div.absolute.inset-0.z-1.cursor-text");
|
||||
if ((await expander.count()) > 0) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,170 @@
|
|||
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)");
|
||||
}
|
||||
|
||||
function parseRgb(color: string) {
|
||||
const match = color.match(
|
||||
/rgba?\(\s*([0-9.]+)\s*,\s*([0-9.]+)\s*,\s*([0-9.]+)(?:\s*,\s*([0-9.]+))?\s*\)/i,
|
||||
);
|
||||
if (!match) return null;
|
||||
return {
|
||||
r: Number(match[1]),
|
||||
g: Number(match[2]),
|
||||
b: Number(match[3]),
|
||||
a: match[4] == null ? 1 : Number(match[4]),
|
||||
};
|
||||
}
|
||||
|
||||
function luminance(color: string) {
|
||||
const rgb = parseRgb(color);
|
||||
if (!rgb) return null;
|
||||
return 0.2126 * rgb.r + 0.7152 * rgb.g + 0.0722 * rgb.b;
|
||||
}
|
||||
|
||||
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!));
|
||||
|
||||
const main = page.getByRole("main").first();
|
||||
await expect(main).toBeVisible();
|
||||
|
||||
await setTheme(page, "light");
|
||||
const lightBg = await main.evaluate(
|
||||
(element) => getComputedStyle(element).backgroundColor,
|
||||
);
|
||||
|
||||
await setTheme(page, "dark");
|
||||
const darkBg = await main.evaluate(
|
||||
(element) => getComputedStyle(element).backgroundColor,
|
||||
);
|
||||
|
||||
expect(isTransparent(lightBg)).toBe(false);
|
||||
expect(isTransparent(darkBg)).toBe(false);
|
||||
expect(darkBg).not.toBe(lightBg);
|
||||
});
|
||||
|
||||
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)).toBe(false);
|
||||
expect(isTransparent(after.color)).toBe(false);
|
||||
|
||||
const bgLum = luminance(after.background);
|
||||
const textLum = luminance(after.color);
|
||||
if (bgLum != null && textLum != null) {
|
||||
expect(Math.abs(bgLum - textLum)).toBeGreaterThan(15);
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue