feat(ai-character): 관리자 인증 셸 구현
This commit is contained in:
77
tests/e2e/accessibility-shell.spec.ts
Normal file
77
tests/e2e/accessibility-shell.spec.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import AxeBuilder from "@axe-core/playwright";
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
const apiBaseUrl = "https://test-character-admin.sodalive.net";
|
||||
const sessionStorageKey = "ai-character-admin-auth-session";
|
||||
|
||||
async function openShell(page: import("@playwright/test").Page): Promise<void> {
|
||||
await page.route(`${apiBaseUrl}/api/v2/admin/ai-characters?page=0&size=20`, async (route) => {
|
||||
await route.fulfill({
|
||||
contentType: "application/json",
|
||||
json: { success: true, message: null, data: null, errorProperty: null },
|
||||
});
|
||||
});
|
||||
|
||||
await page.addInitScript(
|
||||
([key, value]) => {
|
||||
window.sessionStorage.setItem(key, value);
|
||||
},
|
||||
[sessionStorageKey, JSON.stringify({ token: "admin-token", role: "ADMIN" })],
|
||||
);
|
||||
|
||||
await page.goto("/ai-characters");
|
||||
await expect(page.getByRole("heading", { name: "AI 캐릭터", exact: true })).toBeVisible();
|
||||
}
|
||||
|
||||
test("keeps shell controls visible without horizontal overflow at 320px and 200 percent zoom", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 320, height: 640 });
|
||||
await openShell(page);
|
||||
await page.evaluate(() => {
|
||||
document.documentElement.style.zoom = "2";
|
||||
});
|
||||
|
||||
const overflow = await page.evaluate(() => document.documentElement.scrollWidth > document.documentElement.clientWidth);
|
||||
await expect(page.getByRole("button", { name: "모바일 메뉴 열기" })).toBeInViewport();
|
||||
await expect(page.getByRole("button", { name: "로그아웃" })).toBeInViewport();
|
||||
expect(overflow).toBe(false);
|
||||
});
|
||||
|
||||
test("keeps the open mobile menu visible and keyboard-contained at 320px and 200 percent zoom", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 320, height: 640 });
|
||||
await openShell(page);
|
||||
await page.evaluate(() => {
|
||||
document.documentElement.style.zoom = "2";
|
||||
});
|
||||
|
||||
await page.getByRole("button", { name: "모바일 메뉴 열기" }).focus();
|
||||
await page.keyboard.press("Enter");
|
||||
|
||||
const closeButton = page.getByRole("button", { name: "모바일 메뉴 닫기" });
|
||||
const navLink = page.getByRole("navigation", { name: "모바일 주 메뉴" }).getByRole("link", { name: "AI 캐릭터" });
|
||||
await expect(closeButton).toBeFocused();
|
||||
await expect(closeButton).toBeInViewport();
|
||||
await expect(navLink).toBeInViewport();
|
||||
|
||||
await page.keyboard.press("Shift+Tab");
|
||||
await expect(navLink).toBeFocused();
|
||||
await page.keyboard.press("Tab");
|
||||
await expect(closeButton).toBeFocused();
|
||||
|
||||
const overflow = await page.evaluate(() => document.documentElement.scrollWidth > document.documentElement.clientWidth);
|
||||
expect(overflow).toBe(false);
|
||||
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(page.getByRole("navigation", { name: "모바일 주 메뉴" })).toBeHidden();
|
||||
await expect(page.getByRole("button", { name: "모바일 메뉴 열기" })).toBeFocused();
|
||||
});
|
||||
|
||||
test("has no critical or serious axe violations on the shell route", async ({ page }) => {
|
||||
await openShell(page);
|
||||
|
||||
const results = await new AxeBuilder({ page }).analyze();
|
||||
const blockingViolations = results.violations.filter(
|
||||
(violation) => violation.impact === "critical" || violation.impact === "serious",
|
||||
);
|
||||
|
||||
expect(blockingViolations).toEqual([]);
|
||||
});
|
||||
43
tests/e2e/auth.spec.ts
Normal file
43
tests/e2e/auth.spec.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
const apiBaseUrl = "https://test-character-admin.sodalive.net";
|
||||
|
||||
test("completes login, navigation, and logout using only the keyboard", async ({ page }) => {
|
||||
await page.route(`${apiBaseUrl}/admin/member/login`, async (route) => {
|
||||
await route.fulfill({
|
||||
contentType: "application/json",
|
||||
json: { success: true, message: null, data: { token: "admin-token", role: "ADMIN" }, errorProperty: null },
|
||||
});
|
||||
});
|
||||
await page.route(`${apiBaseUrl}/api/v2/admin/ai-characters?page=0&size=20`, async (route) => {
|
||||
await route.fulfill({
|
||||
contentType: "application/json",
|
||||
json: { success: true, message: null, data: null, errorProperty: null },
|
||||
});
|
||||
});
|
||||
await page.route(`${apiBaseUrl}/member/logout`, async (route) => {
|
||||
await route.fulfill({
|
||||
contentType: "application/json",
|
||||
json: { success: true, message: null, data: {}, errorProperty: null },
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto("/login");
|
||||
await page.getByLabel("이메일").focus();
|
||||
await page.keyboard.type("admin@test.com");
|
||||
await page.getByLabel("비밀번호").focus();
|
||||
await page.keyboard.type("password");
|
||||
await page.getByRole("button", { name: "로그인" }).focus();
|
||||
await page.keyboard.press("Enter");
|
||||
|
||||
await expect(page).toHaveURL(/\/ai-characters$/);
|
||||
await expect(page.getByRole("heading", { name: "AI 캐릭터", exact: true })).toBeVisible();
|
||||
|
||||
const logoutButton = page.getByRole("button", { name: "로그아웃" });
|
||||
await logoutButton.focus();
|
||||
await expect(logoutButton).toBeFocused();
|
||||
await page.keyboard.press("Enter");
|
||||
|
||||
await expect(page).toHaveURL(/\/login$/);
|
||||
await expect(page.getByRole("heading", { name: "관리자 로그인" })).toBeVisible();
|
||||
});
|
||||
@@ -4,5 +4,5 @@ test("opens the root shell", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
|
||||
await expect(page.locator("html")).toHaveAttribute("lang", "ko");
|
||||
await expect(page.getByRole("main")).toContainText("AI 캐릭터 관리자");
|
||||
await expect(page.getByRole("main")).toContainText("관리자 로그인");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user