117 lines
4.6 KiB
TypeScript
117 lines
4.6 KiB
TypeScript
import { promises as fs } from "node:fs";
|
|
import path from "node:path";
|
|
import { expect, test, type Page } from "@playwright/test";
|
|
import { expectNoHorizontalOverflow } from "./support";
|
|
|
|
const SHOT_DIR = path.join(process.cwd(), "node_modules", ".tmp", "auth-visual");
|
|
const THEMES = ["light", "dark"] as const;
|
|
const VIEWPORTS = [
|
|
{ width: 390, height: 844, label: "mobile" },
|
|
{ width: 1280, height: 800, label: "desktop" },
|
|
] as const;
|
|
|
|
async function setTheme(page: Page, theme: (typeof THEMES)[number], pathName: string) {
|
|
await page.goto(pathName);
|
|
await page.evaluate((nextTheme) => {
|
|
localStorage.setItem("vignette.theme", nextTheme);
|
|
}, theme);
|
|
await page.reload();
|
|
}
|
|
|
|
async function expectGlassSurface(page: Page, selector: string) {
|
|
const surface = await page.locator(selector).evaluate((element) => {
|
|
const style = getComputedStyle(element);
|
|
return {
|
|
backgroundImage: style.backgroundImage,
|
|
backdropFilter: style.backdropFilter || style.webkitBackdropFilter,
|
|
borderColor: style.borderColor,
|
|
boxShadow: style.boxShadow,
|
|
};
|
|
});
|
|
expect(surface.backgroundImage.match(/linear-gradient/g)?.length ?? 0).toBeGreaterThanOrEqual(2);
|
|
expect(surface.backdropFilter).toContain("blur(");
|
|
expect(surface.borderColor).not.toBe("rgba(0, 0, 0, 0)");
|
|
expect(surface.boxShadow).not.toBe("none");
|
|
}
|
|
|
|
test("@single-run 인증 화면이 공통 테마와 전체 viewport를 유지한다", async ({ page }) => {
|
|
await fs.mkdir(SHOT_DIR, { recursive: true });
|
|
const loginRoomBackgrounds = new Map<(typeof THEMES)[number], string>();
|
|
|
|
for (const theme of THEMES) {
|
|
for (const viewport of VIEWPORTS) {
|
|
await page.setViewportSize(viewport);
|
|
await setTheme(page, theme, "/login");
|
|
await expect(page.locator(".lg-panel")).toBeVisible();
|
|
await expect(page.locator("html")).toHaveAttribute("data-theme", theme);
|
|
await expectNoHorizontalOverflow(page);
|
|
await expectGlassSurface(page, ".lg-panel");
|
|
|
|
const themeColors = await page.locator(".lg-root").evaluate((root) => {
|
|
const brand = root.querySelector<HTMLElement>(".lg-brand");
|
|
const panel = root.querySelector<HTMLElement>(".lg-panel");
|
|
const shellStyle = getComputedStyle(root);
|
|
if (!brand || !panel) throw new Error("login theme surfaces are missing");
|
|
const luma = (color: string) => {
|
|
const [red = 0, green = 0, blue = 0] = color.match(/\d+(?:\.\d+)?/g)?.map(Number) ?? [];
|
|
return red * 0.2126 + green * 0.7152 + blue * 0.0722;
|
|
};
|
|
return {
|
|
brandLuma: luma(getComputedStyle(brand).color),
|
|
panelLuma: luma(getComputedStyle(panel).color),
|
|
roomBackground: shellStyle.backgroundImage,
|
|
};
|
|
});
|
|
if (theme === "light") {
|
|
expect(themeColors.brandLuma).toBeLessThan(96);
|
|
expect(themeColors.panelLuma).toBeLessThan(96);
|
|
} else {
|
|
expect(themeColors.brandLuma).toBeGreaterThan(180);
|
|
expect(themeColors.panelLuma).toBeGreaterThan(180);
|
|
}
|
|
loginRoomBackgrounds.set(theme, themeColors.roomBackground);
|
|
|
|
const canvas = await page.locator(".vg-auth-shell").evaluate((element) => {
|
|
const rect = element.getBoundingClientRect();
|
|
return {
|
|
width: Math.round(rect.width),
|
|
minHeight: Math.round(rect.height),
|
|
viewportWidth: document.documentElement.clientWidth,
|
|
viewportHeight: window.innerHeight,
|
|
};
|
|
});
|
|
expect(canvas.width).toBe(canvas.viewportWidth);
|
|
expect(canvas.minHeight).toBeGreaterThanOrEqual(canvas.viewportHeight);
|
|
|
|
await page.screenshot({
|
|
path: path.join(SHOT_DIR, `login-${theme}-${viewport.label}.png`),
|
|
fullPage: true,
|
|
});
|
|
}
|
|
}
|
|
expect(loginRoomBackgrounds.get("light")).not.toBe(loginRoomBackgrounds.get("dark"));
|
|
|
|
const login = await page.request.post("/api/auth/dev-login", {
|
|
data: {
|
|
email: `auth-visual.${Date.now()}@hs.ac.kr`,
|
|
role: "learner",
|
|
display_name: "인증 화면 검증 학습자",
|
|
},
|
|
});
|
|
expect(login.ok(), await login.text()).toBeTruthy();
|
|
|
|
for (const theme of THEMES) {
|
|
for (const viewport of VIEWPORTS) {
|
|
await page.setViewportSize(viewport);
|
|
await setTheme(page, theme, "/onboarding");
|
|
await expect(page.getByRole("heading", { name: "가입 정보를 입력합니다." })).toBeVisible();
|
|
await expect(page.locator("html")).toHaveAttribute("data-theme", theme);
|
|
await expectNoHorizontalOverflow(page);
|
|
await expectGlassSurface(page, ".ob-shell");
|
|
await page.screenshot({
|
|
path: path.join(SHOT_DIR, `onboarding-${theme}-${viewport.label}.png`),
|
|
fullPage: true,
|
|
});
|
|
}
|
|
}
|
|
});
|