import AxeBuilder from "@axe-core/playwright"; import { expect, test } from "@playwright/test"; import type { Page } from "@playwright/test"; async function loginThroughMockMode(page: Page): Promise { await page.goto("/login"); await page.getByLabel("이메일").fill("admin@test.com"); await page.getByLabel("비밀번호").fill("password"); await page.getByRole("button", { name: "로그인" }).click(); await expect(page).toHaveURL(/\/ai-characters$/); } async function expectNoBlockingAxeViolations(page: Page): Promise { const results = await new AxeBuilder({ page }).analyze(); const blockingViolations = results.violations.filter((violation) => violation.impact === "critical" || violation.impact === "serious"); expect(blockingViolations).toEqual([]); } async function storageDump(page: Page): Promise { return page.evaluate(() => JSON.stringify({ cookies: document.cookie, local: { ...localStorage }, session: { ...sessionStorage } })); } test("active mock routes have no critical or serious axe violations", async ({ page }) => { // Given await loginThroughMockMode(page); for (const route of ["/ai-characters", "/ai-characters/101", "/ai-characters/101/audio-contents/9001", "/ai-characters/101/community-posts", "/ai-characters/101/fan-talks"] as const) { // When await page.goto(route); // Then await expectNoBlockingAxeViolations(page); } }); test("keyboard skip link and representative sheet focus remain usable with reduced motion", async ({ browserName, isMobile, page }) => { test.skip(browserName !== "chromium" || isMobile, "Keyboard focus traversal is covered with desktop Chromium evidence."); // Given await page.emulateMedia({ reducedMotion: "reduce" }); await loginThroughMockMode(page); // When / Then await page.getByRole("link", { name: "본문으로 건너뛰기" }).focus(); await expect(page.getByRole("link", { name: "본문으로 건너뛰기" })).toBeFocused(); await page.keyboard.press("Enter"); await expect(page.getByRole("main", { name: "AI 캐릭터 관리" })).toBeFocused(); await page.goto("/ai-characters/101/community-posts"); const trigger = page.getByRole("button", { name: "오늘의 상담 기록입니다. 게시글 열기" }); await trigger.click(); await expect(page.getByRole("dialog", { name: "커뮤니티 게시글" })).toBeVisible(); await page.keyboard.press("Escape"); await expect(page.getByRole("dialog", { name: "커뮤니티 게시글" })).toBeHidden(); await expect(trigger).toBeFocused(); }); test("mock preview keeps bright theme and avoids sensitive persistence", async ({ page }) => { // Given const consoleMessages: string[] = []; page.on("console", (message) => consoleMessages.push(message.text())); await page.emulateMedia({ colorScheme: "dark" }); // When await loginThroughMockMode(page); await page.goto("/ai-characters/101/fan-talks"); await page.getByRole("button", { name: "첫 번째 응원입니다. 답변하기" }).click(); const dialog = page.getByRole("dialog", { name: "FanTalk 답변" }); await dialog.getByLabel("답변 내용").fill("보안 smoke 답변입니다."); await dialog.getByRole("button", { name: "답변 등록" }).click(); await expect(dialog.getByRole("status", { name: "답변 저장 성공" })).toBeVisible(); // Then const dump = await storageDump(page); const visibleText = await page.locator("body").innerText(); expect(await page.evaluate(() => document.documentElement.classList.contains("dark"))).toBe(false); await expect(page.getByRole("button", { name: /dark|theme|테마|다크/i })).toHaveCount(0); expect(dump).not.toContain("password"); expect(dump).not.toContain("data:audio"); expect(dump).not.toContain("data:image"); expect(visibleText).not.toContain("password"); expect(consoleMessages.join("\n")).not.toContain("password"); });