171 lines
5.1 KiB
TypeScript
171 lines
5.1 KiB
TypeScript
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);
|
|
});
|
|
});
|