import { expect, test } from "@playwright/test"; type UserSummary = { id: string; email: string; name: string; phone?: string; role: string; status: string; companyCode?: string; department?: string; createdAt: string; updatedAt: string; }; type UserCreatePayload = { email: string; password?: string; name: string; phone?: string; role?: string; companyCode?: string; department?: string; }; test("user create and delete flow", async ({ page }) => { const nowInSeconds = Math.floor(Date.now() / 1000); await page.addInitScript((issuedAt) => { const mockOidcUser = { id_token: "playwright-id-token", session_state: "playwright-session", access_token: "playwright-access-token", refresh_token: "playwright-refresh-token", token_type: "Bearer", scope: "openid profile email", profile: { sub: "playwright-admin", email: "admin@example.com", name: "Playwright Admin", }, expires_at: issuedAt + 3600, }; window.localStorage.setItem("admin_session", mockOidcUser.access_token); window.localStorage.setItem( "oidc.user:http://localhost:5000/oidc:adminfront", JSON.stringify(mockOidcUser), ); window.localStorage.setItem( "oidc.user:http://localhost:5000/oidc/:adminfront", JSON.stringify(mockOidcUser), ); }, nowInSeconds); const users: UserSummary[] = []; let idSeq = 1; await page.route("**/api/v1/admin/tenants**", async (route) => { const request = route.request(); if (request.method() !== "GET") { await route.fulfill({ status: 404, contentType: "application/json", body: JSON.stringify({ error: "Not found" }), }); return; } await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ items: [ { id: "tenant-e2e", name: "E2E Tenant", slug: "e2e", description: "Playwright tenant", status: "active", createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, ], limit: 100, offset: 0, total: 1, }), }); }); await page.route("**/api/v1/admin/tenants/*", async (route) => { const request = route.request(); if (request.method() !== "GET") { await route.fulfill({ status: 404, contentType: "application/json", body: JSON.stringify({ error: "Not found" }), }); return; } await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ id: "tenant-e2e", name: "E2E Tenant", slug: "e2e", description: "Playwright tenant", status: "active", config: { userSchema: [] }, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }), }); }); await page.route("**/api/v1/admin/users**", async (route) => { const request = route.request(); const url = new URL(request.url()); const path = url.pathname; const isCollection = path.endsWith("/api/v1/admin/users"); const isItem = path.includes("/api/v1/admin/users/"); if (request.method() === "GET" && isCollection) { const search = url.searchParams.get("search")?.toLowerCase() ?? ""; const limit = Number(url.searchParams.get("limit") ?? "50"); const offset = Number(url.searchParams.get("offset") ?? "0"); const filtered = search ? users.filter( (user) => user.name.toLowerCase().includes(search) || user.email.toLowerCase().includes(search), ) : users; const items = filtered.slice(offset, offset + limit); await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ items, limit, offset, total: filtered.length, }), }); return; } if (request.method() === "POST" && isCollection) { const payload = request.postDataJSON() as UserCreatePayload; const now = new Date().toISOString(); const user: UserSummary = { id: `user-${idSeq++}`, email: payload.email, name: payload.name, phone: payload.phone, role: payload.role ?? "user", status: "active", companyCode: payload.companyCode, department: payload.department, createdAt: now, updatedAt: now, }; users.unshift(user); await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(user), }); return; } if (request.method() === "DELETE" && isItem) { const userId = path.split("/").pop(); const index = users.findIndex((user) => user.id === userId); if (index === -1) { await route.fulfill({ status: 404, contentType: "application/json", body: JSON.stringify({ error: "User not found" }), }); return; } users.splice(index, 1); await route.fulfill({ status: 204, body: "" }); return; } await route.fulfill({ status: 404, contentType: "application/json", body: JSON.stringify({ error: "Not found" }), }); }); await page.goto("/users"); await expect(page).toHaveURL(/\/users$/); await expect( page.getByRole("heading", { name: "사용자 관리" }), ).toBeVisible(); const addUserLink = page.getByRole("link", { name: "사용자 추가" }); await expect(addUserLink).toBeVisible(); await page.goto("/users/new"); await expect(page).toHaveURL(/\/users\/new$/); const uniqueEmail = `playwright-${Date.now()}@example.com`; await page.getByRole("checkbox", { name: "자동 생성" }).setChecked(false); await page.getByLabel("이메일").fill(uniqueEmail); await page.getByLabel("비밀번호").fill("Test1234!"); await page.getByLabel("이름").fill("Playwright User"); await page.getByLabel("전화번호").fill("010-0000-0000"); await page.getByLabel("부서").fill("QA"); await page.getByLabel("역할 (Role)").selectOption("admin"); await page.getByRole("button", { name: "사용자 생성" }).click(); await expect(page).toHaveURL(/\/users$/); const createdRow = page.locator("tbody tr").filter({ hasText: uniqueEmail }); await expect(createdRow).toBeVisible(); page.once("dialog", (dialog) => dialog.accept()); await createdRow.getByRole("button", { name: /사용자 삭제/ }).click(); await expect( page.locator("tbody tr").filter({ hasText: uniqueEmail }), ).toHaveCount(0); });