From 8f78dbf68cbd719e31b35f8a3a76dbaf810424bd Mon Sep 17 00:00:00 2001 From: chan Date: Fri, 27 Mar 2026 19:06:51 +0900 Subject: [PATCH 1/8] test(adminfront): add E2E tests for login ID validation and conflict handling --- adminfront/tests/users.spec.ts | 245 +++++++++++++++++++++++++++++++++ 1 file changed, 245 insertions(+) create mode 100644 adminfront/tests/users.spec.ts diff --git a/adminfront/tests/users.spec.ts b/adminfront/tests/users.spec.ts new file mode 100644 index 00000000..5676b00d --- /dev/null +++ b/adminfront/tests/users.spec.ts @@ -0,0 +1,245 @@ +import { expect, test } from "@playwright/test"; + +test.describe("User Management", () => { + test.beforeEach(async ({ page }) => { + await page.addInitScript(() => { + const authority = "http://localhost:5000/oidc"; + const client_id = "adminfront"; + const key = `oidc.user:${authority}:${client_id}`; + const authData = { + id_token: "fake-id-token", + access_token: "fake-token", + token_type: "Bearer", + scope: "openid profile email", + profile: { + sub: "admin-user", + name: "Admin", + email: "admin@test.com", + role: "super_admin", + }, + expires_at: Math.floor(Date.now() / 1000) + 36000, + }; + window.localStorage.setItem(key, JSON.stringify(authData)); + window.localStorage.setItem("admin_session", "fake-token"); + window.localStorage.setItem("locale", "ko"); + window.localStorage.setItem("oidc.state", "dummy"); + + ( + window as Window & typeof globalThis & { _IS_TEST_MODE?: boolean } + )._IS_TEST_MODE = true; + }); + + await page.route("**/oidc/**", async (route) => { + if (route.request().url().includes("/.well-known/openid-configuration")) { + return route.fulfill({ + json: { + issuer: "http://localhost:5000/oidc", + authorization_endpoint: "http://localhost:5000/oidc/auth", + token_endpoint: "http://localhost:5000/oidc/token", + userinfo_endpoint: "http://localhost:5000/oidc/userinfo", + jwks_uri: "http://localhost:5000/oidc/jwks", + }, + }); + } + await route.fulfill({ json: { issuer: "http://localhost:5000/oidc" } }); + }); + + await page.route(/.*\/api\/v1\/.*/, async (route) => { + const url = route.request().url(); + const method = route.request().method(); + + if (url.includes("/user/me")) { + return route.fulfill({ + json: { + id: "admin-user", + name: "Admin", + email: "admin@test.com", + role: "super_admin", + manageableTenants: [], + }, + }); + } + + if (url.includes("/admin/tenants") && method === "GET") { + return route.fulfill({ + json: { + items: [ + { + id: "t-1", + slug: "test-tenant", + name: "Test Tenant", + config: { + userSchema: [], + }, + }, + ], + total: 1, + limit: 100, + offset: 0, + }, + }); + } + + if (url.includes("/admin/users/u-1") && method === "GET") { + return route.fulfill({ + json: { + id: "u-1", + name: "John Doe", + email: "john@test.com", + loginId: "johndoe", + tenantSlug: "test-tenant", + role: "user", + status: "active", + }, + }); + } + + if (url.includes("/admin/users") && method === "POST") { + // Parse request payload to simulate validation checks + const postData = route.request().postDataJSON(); + if (postData && postData.loginId === "existing_user") { + // Simulate a backend conflict error (409) for an existing loginId + return route.fulfill({ + status: 409, + json: { + error: "이미 존재하는 로그인 ID 입니다.", + }, + }); + } + // Mock successful user creation + return route.fulfill({ + json: { + id: "new-user-id", + name: "New User", + email: "newuser@test.com", + loginId: postData?.loginId || "newuser123", + }, + }); + } + + if (url.includes("/admin/users/u-1") && method === "PUT") { + // Parse request payload + const postData = route.request().postDataJSON(); + if (postData && postData.loginId === "existing_user") { + // Simulate a backend conflict error (409) for an existing loginId + return route.fulfill({ + status: 409, + json: { + error: "이미 존재하는 로그인 ID 입니다.", + }, + }); + } + + // Mock successful user update + return route.fulfill({ + json: { + id: "u-1", + name: "John Doe Updated", + email: "john@test.com", + loginId: postData?.loginId || "johndoe_updated", + }, + }); + } + + if (url.includes("/admin/users") && method === "GET") { + return route.fulfill({ + json: { + items: [ + { + id: "u-1", + name: "John Doe", + email: "john@test.com", + loginId: "johndoe", + role: "user", + status: "active", + } + ], + total: 1, + limit: 50, + offset: 0, + }, + }); + } + + return route.fulfill({ json: { items: [], total: 0 } }); + }); + }); + + test("should successfully edit a user's Login ID", async ({ page }) => { + await page.goto("/users/u-1"); + + // Wait for the form to load with the existing login ID + const loginIdInput = page.locator('input[id="loginId"]'); + await expect(loginIdInput).toBeVisible(); + await expect(loginIdInput).toHaveValue("johndoe"); + + // Change the Login ID + await loginIdInput.fill("johndoe_updated"); + + // Submit the form + const saveButton = page.getByRole("button", { name: /변경사항 저장|Save/i }); + await saveButton.click(); + + // Check for success message + await expect(page.getByText(/사용자 정보가 수정되었습니다/i).first()).toBeVisible(); + }); + + test("should show conflict error when updating to an existing Login ID", async ({ page }) => { + await page.goto("/users/u-1"); + + const loginIdInput = page.locator('input[id="loginId"]'); + await expect(loginIdInput).toBeVisible(); + + // Enter a login ID that triggers our mock conflict error + await loginIdInput.fill("existing_user"); + + const saveButton = page.getByRole("button", { name: /변경사항 저장|Save/i }); + await saveButton.click(); + + // Check for the specific conflict error message from the backend mock + await expect(page.getByText(/이미 존재하는 로그인 ID 입니다/i)).toBeVisible(); + }); + + test("should successfully create a new user with a Login ID", async ({ page }) => { + await page.goto("/users/new"); + + // Ensure the page title is loaded + await expect(page.getByText(/사용자 추가/i).first()).toBeVisible(); + + // Fill required fields + await page.locator('input[name="name"]').fill("New User"); + await page.locator('input[name="email"]').fill("newuser@test.com"); + + // Fill Login ID + const loginIdInput = page.locator('input[name="loginId"]'); + await loginIdInput.fill("newuser123"); + + // Submit the form + const createButton = page.getByRole("button", { name: /생성/i }); + await createButton.click(); + + // Assuming successful creation redirects back to the user list + await expect(page).toHaveURL(/.*\/users$/, { timeout: 10000 }); + }); + + test("should show conflict error when creating with an existing Login ID", async ({ page }) => { + await page.goto("/users/new"); + + await expect(page.getByText(/사용자 추가/i).first()).toBeVisible(); + + // Fill required fields + await page.locator('input[name="name"]').fill("New User"); + await page.locator('input[name="email"]').fill("newuser@test.com"); + + // Fill Login ID that triggers the mock conflict error + const loginIdInput = page.locator('input[name="loginId"]'); + await loginIdInput.fill("existing_user"); + + // Submit the form + const createButton = page.getByRole("button", { name: /생성/i }); + await createButton.click(); + + // Check for the specific conflict error message from the backend mock + await expect(page.getByText(/이미 존재하는 로그인 ID 입니다/i)).toBeVisible(); + }); +}); From 987b4797bb9768d604a237def775135765bf66c7 Mon Sep 17 00:00:00 2001 From: chan Date: Fri, 27 Mar 2026 19:21:33 +0900 Subject: [PATCH 2/8] style(userfront): add missing curly braces for flow control structures --- userfront/lib/features/auth/presentation/signup_screen.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/userfront/lib/features/auth/presentation/signup_screen.dart b/userfront/lib/features/auth/presentation/signup_screen.dart index e6fffa74..830c14fe 100644 --- a/userfront/lib/features/auth/presentation/signup_screen.dart +++ b/userfront/lib/features/auth/presentation/signup_screen.dart @@ -1488,8 +1488,9 @@ class _SignupScreenState extends State { .replaceAll('Exception: ', ''), ); } finally { - if (mounted) + if (mounted) { setState(() => _isLoading = false); + } } }, child: const Text('중복 확인'), From 603b9e003245263d8a375530d87f8facf16b9c32 Mon Sep 17 00:00:00 2001 From: chan Date: Fri, 27 Mar 2026 20:03:50 +0900 Subject: [PATCH 3/8] fix(backend): resolve signup issues by fixing tenant slug case-sensitivity and exposing Kratos errors --- backend/internal/handler/auth_handler.go | 5 ++++- backend/internal/repository/tenant_repository.go | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/backend/internal/handler/auth_handler.go b/backend/internal/handler/auth_handler.go index 85792667..7f38a41c 100644 --- a/backend/internal/handler/auth_handler.go +++ b/backend/internal/handler/auth_handler.go @@ -473,6 +473,8 @@ func (h *AuthHandler) Signup(c *fiber.Ctx) error { normalizedPhone = "+" + normalizedPhone } + slog.Info("[Signup] Phone normalization", "raw", req.Phone, "normalized", normalizedPhone) + // IDP에 전달할 BrokerUser 스키마 구성 attributes := map[string]interface{}{ "department": req.Department, @@ -517,7 +519,8 @@ func (h *AuthHandler) Signup(c *fiber.Ctx) error { if strings.Contains(err.Error(), "already exists") { return errorJSON(c, fiber.StatusConflict, "User already exists") } - return errorJSON(c, fiber.StatusInternalServerError, "Failed to create user") + // Include the actual error message in the response for debugging + return errorJSON(c, fiber.StatusInternalServerError, fmt.Sprintf("Failed to create user: %v", err)) } // 4. Cleanup Redis diff --git a/backend/internal/repository/tenant_repository.go b/backend/internal/repository/tenant_repository.go index 9a18c4fe..6eed4e73 100644 --- a/backend/internal/repository/tenant_repository.go +++ b/backend/internal/repository/tenant_repository.go @@ -3,6 +3,7 @@ package repository import ( "baron-sso-backend/internal/domain" "context" + "strings" "gorm.io/gorm" ) @@ -45,7 +46,7 @@ func (r *tenantRepository) FindByID(ctx context.Context, id string) (*domain.Ten func (r *tenantRepository) FindBySlug(ctx context.Context, slug string) (*domain.Tenant, error) { var tenant domain.Tenant - if err := r.db.WithContext(ctx).Preload("Domains").Where("slug = ?", slug).First(&tenant).Error; err != nil { + if err := r.db.WithContext(ctx).Preload("Domains").Where("slug = ?", strings.ToLower(slug)).First(&tenant).Error; err != nil { return nil, err } return &tenant, nil From 13469b14fb324c1b6e954f0bef2eea6c34b3fafb Mon Sep 17 00:00:00 2001 From: chan Date: Fri, 27 Mar 2026 20:39:49 +0900 Subject: [PATCH 4/8] fix: refine error messages for signup failure and company code --- backend/internal/handler/auth_handler.go | 2 +- .../lib/features/auth/presentation/signup_screen.dart | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/internal/handler/auth_handler.go b/backend/internal/handler/auth_handler.go index 7f38a41c..401fd4a0 100644 --- a/backend/internal/handler/auth_handler.go +++ b/backend/internal/handler/auth_handler.go @@ -460,7 +460,7 @@ func (h *AuthHandler) Signup(c *fiber.Ctx) error { } else { // If companyCode provided but not found, we should probably reject if we want strictness, // or just treat as GENERAL user. Given the risk "존재하지 않는 테넌트도 저장됨", we should reject. - return errorJSON(c, fiber.StatusBadRequest, "Invalid company code.") + return errorJSON(c, fiber.StatusBadRequest, "해당하는 가족사(테넌트)를 찾을 수 없습니다.") } } diff --git a/userfront/lib/features/auth/presentation/signup_screen.dart b/userfront/lib/features/auth/presentation/signup_screen.dart index 830c14fe..d32673ca 100644 --- a/userfront/lib/features/auth/presentation/signup_screen.dart +++ b/userfront/lib/features/auth/presentation/signup_screen.dart @@ -328,19 +328,19 @@ class _SignupScreenState extends State { } catch (e) { String eStr = e.toString().toLowerCase(); setState(() { - if (eStr.contains('uppercase')) { + if (eStr.contains('password') && eStr.contains('uppercase')) { _passwordError = tr( 'msg.userfront.signup.password.uppercase_required', ); - } else if (eStr.contains('lowercase')) { + } else if (eStr.contains('password') && eStr.contains('lowercase')) { _passwordError = tr( 'msg.userfront.signup.password.lowercase_required', ); - } else if (eStr.contains('digit') || eStr.contains('number')) { + } else if (eStr.contains('password') && (eStr.contains('digit') || eStr.contains('number'))) { _passwordError = tr('msg.userfront.signup.password.number_required'); - } else if (eStr.contains('symbol') || eStr.contains('special')) { + } else if (eStr.contains('password') && (eStr.contains('symbol') || eStr.contains('special'))) { _passwordError = tr('msg.userfront.signup.password.symbol_required'); - } else if (eStr.contains('length') || eStr.contains('12 characters')) { + } else if (eStr.contains('password') && (eStr.contains('length') || eStr.contains('12 characters'))) { _passwordError = tr('msg.userfront.signup.password.length_required'); } else { _passwordError = tr( From 543607069e777c3b340727a5cdaee67088481a91 Mon Sep 17 00:00:00 2001 From: chan Date: Fri, 27 Mar 2026 20:52:40 +0900 Subject: [PATCH 5/8] fix(userfront): correctly display general signup errors in a dialog instead of password field --- .../auth/presentation/signup_screen.dart | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/userfront/lib/features/auth/presentation/signup_screen.dart b/userfront/lib/features/auth/presentation/signup_screen.dart index d32673ca..20929ec4 100644 --- a/userfront/lib/features/auth/presentation/signup_screen.dart +++ b/userfront/lib/features/auth/presentation/signup_screen.dart @@ -6,6 +6,7 @@ import 'package:go_router/go_router.dart'; import 'package:userfront/i18n.dart'; import '../../../core/i18n/locale_utils.dart'; import '../../../core/services/auth_proxy_service.dart'; +import '../../../core/ui/toast_service.dart'; class SignupScreen extends StatefulWidget { const SignupScreen({super.key}); @@ -336,16 +337,21 @@ class _SignupScreenState extends State { _passwordError = tr( 'msg.userfront.signup.password.lowercase_required', ); - } else if (eStr.contains('password') && (eStr.contains('digit') || eStr.contains('number'))) { + } else if (eStr.contains('password') && + (eStr.contains('digit') || eStr.contains('number'))) { _passwordError = tr('msg.userfront.signup.password.number_required'); - } else if (eStr.contains('password') && (eStr.contains('symbol') || eStr.contains('special'))) { + } else if (eStr.contains('password') && + (eStr.contains('symbol') || eStr.contains('special'))) { _passwordError = tr('msg.userfront.signup.password.symbol_required'); - } else if (eStr.contains('password') && (eStr.contains('length') || eStr.contains('12 characters'))) { + } else if (eStr.contains('password') && + (eStr.contains('length') || eStr.contains('12 characters'))) { _passwordError = tr('msg.userfront.signup.password.length_required'); } else { - _passwordError = tr( - 'msg.userfront.signup.failed', - params: {'error': e.toString()}, + _showError( + tr( + 'msg.userfront.signup.failed', + params: {'error': e.toString().replaceFirst('Exception: ', '')}, + ), ); } }); @@ -354,6 +360,11 @@ class _SignupScreenState extends State { } } + void _showError(String message) { + if (!mounted) return; + ToastService.error(message); + } + void _showSuccessDialog() { showDialog( context: context, From 2e14c9d6fe93d4ab6501e90bb76723e77c98b2c6 Mon Sep 17 00:00:00 2001 From: chan Date: Fri, 27 Mar 2026 21:18:51 +0900 Subject: [PATCH 6/8] test(backend): update expected error message for invalid company code to match korean translation --- backend/internal/handler/auth_handler_signup_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/internal/handler/auth_handler_signup_test.go b/backend/internal/handler/auth_handler_signup_test.go index bbd3367a..c7de8253 100644 --- a/backend/internal/handler/auth_handler_signup_test.go +++ b/backend/internal/handler/auth_handler_signup_test.go @@ -119,7 +119,7 @@ func TestSignup_CompanyCodeValidation(t *testing.T) { assert.Equal(t, http.StatusBadRequest, resp.StatusCode) var res map[string]interface{} json.NewDecoder(resp.Body).Decode(&res) - assert.Equal(t, "Invalid company code.", res["error"]) + assert.Equal(t, "해당하는 가족사(테넌트)를 찾을 수 없습니다.", res["error"]) }) t.Run("Active Company Code", func(t *testing.T) { From 809ece6a68c00ffe46504fdc55fab2cb1e1f6e9f Mon Sep 17 00:00:00 2001 From: chan Date: Fri, 27 Mar 2026 21:27:03 +0900 Subject: [PATCH 7/8] chore: ignore playwright artifacts in biome and fix minor devfront imports --- adminfront/biome.json | 8 +++++++- devfront/biome.json | 8 +++++++- devfront/src/features/audit/AuditLogsPage.tsx | 2 +- devfront/src/features/clients/ClientsPage.tsx | 2 +- devfront/src/features/profile/ProfilePage.tsx | 2 +- 5 files changed, 17 insertions(+), 5 deletions(-) diff --git a/adminfront/biome.json b/adminfront/biome.json index 29399f2f..74913169 100644 --- a/adminfront/biome.json +++ b/adminfront/biome.json @@ -19,6 +19,12 @@ "enabled": true }, "files": { - "ignore": ["dist", "node_modules", "tsconfig*.json"] + "ignore": [ + "dist", + "node_modules", + "tsconfig*.json", + "test-results", + "playwright-report" + ] } } diff --git a/devfront/biome.json b/devfront/biome.json index 04f99bc8..44d528eb 100644 --- a/devfront/biome.json +++ b/devfront/biome.json @@ -18,6 +18,12 @@ "enabled": true }, "files": { - "ignore": ["dist", "node_modules", "tsconfig*.json"] + "ignore": [ + "dist", + "node_modules", + "tsconfig*.json", + "test-results", + "playwright-report" + ] } } diff --git a/devfront/src/features/audit/AuditLogsPage.tsx b/devfront/src/features/audit/AuditLogsPage.tsx index 1330b979..1f20529a 100644 --- a/devfront/src/features/audit/AuditLogsPage.tsx +++ b/devfront/src/features/audit/AuditLogsPage.tsx @@ -9,9 +9,9 @@ import { Search, } from "lucide-react"; import * as React from "react"; +import { ForbiddenMessage } from "../../components/common/ForbiddenMessage"; import { Badge } from "../../components/ui/badge"; import { Button } from "../../components/ui/button"; -import { ForbiddenMessage } from "../../components/common/ForbiddenMessage"; import { Card, CardContent, diff --git a/devfront/src/features/clients/ClientsPage.tsx b/devfront/src/features/clients/ClientsPage.tsx index bba994ff..ae7019bc 100644 --- a/devfront/src/features/clients/ClientsPage.tsx +++ b/devfront/src/features/clients/ClientsPage.tsx @@ -11,6 +11,7 @@ import { import { useState } from "react"; import { useAuth } from "react-oidc-context"; import { Link, useNavigate } from "react-router-dom"; +import { ForbiddenMessage } from "../../components/common/ForbiddenMessage"; import { Avatar, AvatarFallback, @@ -18,7 +19,6 @@ import { } from "../../components/ui/avatar"; import { Badge } from "../../components/ui/badge"; import { Button } from "../../components/ui/button"; -import { ForbiddenMessage } from "../../components/common/ForbiddenMessage"; import { Card, CardContent, diff --git a/devfront/src/features/profile/ProfilePage.tsx b/devfront/src/features/profile/ProfilePage.tsx index 424c8ee3..e19164bc 100644 --- a/devfront/src/features/profile/ProfilePage.tsx +++ b/devfront/src/features/profile/ProfilePage.tsx @@ -17,8 +17,8 @@ import { CardTitle, } from "../../components/ui/card"; import { t } from "../../lib/i18n"; -import ProfileTenantSwitcher from "./ProfileTenantSwitcher"; import { fetchMe } from "../auth/authApi"; +import ProfileTenantSwitcher from "./ProfileTenantSwitcher"; function ProfilePage() { const auth = useAuth(); From dcc5708d176cf2e3251a1135c393f7cf025b0d6a Mon Sep 17 00:00:00 2001 From: chan Date: Fri, 27 Mar 2026 21:37:40 +0900 Subject: [PATCH 8/8] =?UTF-8?q?=EB=A6=B0=ED=8A=B8=20=EC=A0=81=EC=9A=A9?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- adminfront/tests/users.spec.ts | 78 ++++++++++++++++++++-------------- 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/adminfront/tests/users.spec.ts b/adminfront/tests/users.spec.ts index 5676b00d..386fd464 100644 --- a/adminfront/tests/users.spec.ts +++ b/adminfront/tests/users.spec.ts @@ -98,13 +98,13 @@ test.describe("User Management", () => { // Parse request payload to simulate validation checks const postData = route.request().postDataJSON(); if (postData && postData.loginId === "existing_user") { - // Simulate a backend conflict error (409) for an existing loginId - return route.fulfill({ - status: 409, - json: { - error: "이미 존재하는 로그인 ID 입니다.", - }, - }); + // Simulate a backend conflict error (409) for an existing loginId + return route.fulfill({ + status: 409, + json: { + error: "이미 존재하는 로그인 ID 입니다.", + }, + }); } // Mock successful user creation return route.fulfill({ @@ -121,13 +121,13 @@ test.describe("User Management", () => { // Parse request payload const postData = route.request().postDataJSON(); if (postData && postData.loginId === "existing_user") { - // Simulate a backend conflict error (409) for an existing loginId - return route.fulfill({ - status: 409, - json: { - error: "이미 존재하는 로그인 ID 입니다.", - }, - }); + // Simulate a backend conflict error (409) for an existing loginId + return route.fulfill({ + status: 409, + json: { + error: "이미 존재하는 로그인 ID 입니다.", + }, + }); } // Mock successful user update @@ -142,17 +142,17 @@ test.describe("User Management", () => { } if (url.includes("/admin/users") && method === "GET") { - return route.fulfill({ + return route.fulfill({ json: { items: [ - { - id: "u-1", - name: "John Doe", - email: "john@test.com", - loginId: "johndoe", - role: "user", - status: "active", - } + { + id: "u-1", + name: "John Doe", + email: "john@test.com", + loginId: "johndoe", + role: "user", + status: "active", + }, ], total: 1, limit: 50, @@ -177,14 +177,20 @@ test.describe("User Management", () => { await loginIdInput.fill("johndoe_updated"); // Submit the form - const saveButton = page.getByRole("button", { name: /변경사항 저장|Save/i }); + const saveButton = page.getByRole("button", { + name: /변경사항 저장|Save/i, + }); await saveButton.click(); // Check for success message - await expect(page.getByText(/사용자 정보가 수정되었습니다/i).first()).toBeVisible(); + await expect( + page.getByText(/사용자 정보가 수정되었습니다/i).first(), + ).toBeVisible(); }); - test("should show conflict error when updating to an existing Login ID", async ({ page }) => { + test("should show conflict error when updating to an existing Login ID", async ({ + page, + }) => { await page.goto("/users/u-1"); const loginIdInput = page.locator('input[id="loginId"]'); @@ -193,14 +199,20 @@ test.describe("User Management", () => { // Enter a login ID that triggers our mock conflict error await loginIdInput.fill("existing_user"); - const saveButton = page.getByRole("button", { name: /변경사항 저장|Save/i }); + const saveButton = page.getByRole("button", { + name: /변경사항 저장|Save/i, + }); await saveButton.click(); // Check for the specific conflict error message from the backend mock - await expect(page.getByText(/이미 존재하는 로그인 ID 입니다/i)).toBeVisible(); + await expect( + page.getByText(/이미 존재하는 로그인 ID 입니다/i), + ).toBeVisible(); }); - test("should successfully create a new user with a Login ID", async ({ page }) => { + test("should successfully create a new user with a Login ID", async ({ + page, + }) => { await page.goto("/users/new"); // Ensure the page title is loaded @@ -222,7 +234,9 @@ test.describe("User Management", () => { await expect(page).toHaveURL(/.*\/users$/, { timeout: 10000 }); }); - test("should show conflict error when creating with an existing Login ID", async ({ page }) => { + test("should show conflict error when creating with an existing Login ID", async ({ + page, + }) => { await page.goto("/users/new"); await expect(page.getByText(/사용자 추가/i).first()).toBeVisible(); @@ -240,6 +254,8 @@ test.describe("User Management", () => { await createButton.click(); // Check for the specific conflict error message from the backend mock - await expect(page.getByText(/이미 존재하는 로그인 ID 입니다/i)).toBeVisible(); + await expect( + page.getByText(/이미 존재하는 로그인 ID 입니다/i), + ).toBeVisible(); }); });