전 저장소 리팩터링과 SSOT 정비

This commit is contained in:
Yun Chan 2026-07-15 21:31:30 +09:00
parent 14ecbd4e7d
commit 3dfddcac6f
173 changed files with 19679 additions and 6952 deletions

View file

@ -0,0 +1,117 @@
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,
});
}
}
});