feat(auth): implement option 3 architecture - server InteractiveServer login + client WASM dashboard

- Add Server.AddInteractiveServerComponents() + AddInteractiveServerRenderMode()
- Create Server AuthLayout for login form (MudBlazor)
- Implement LoginSimple.razor with @rendermode InteractiveServer in Server project
- Update App.razor: CascadingAuthenticationState + Router with AppAssembly=Server
- Fix Client MudBlazor Providers in MainLayout + AuthLayout
- Update Playwright tests: use dynamic selectors for MudTextField (auto-generated IDs)
- Build: 0 errors, 0 warnings
- API: /api/auth/login fully operational (200 OK confirmed)
- Playwright E2E test: 1 passed

Architecture:
/login    → Server InteractiveServer (MudBlazor form)
/         → Client WebAssembly (Dashboard)
/api/*    → Server Endpoints

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-07-05 23:10:54 +09:00
parent 98501c0d2f
commit 53db2f63e3
27 changed files with 1012 additions and 296 deletions
+74
View File
@@ -0,0 +1,74 @@
import { test } from '@playwright/test';
test('상세 디버깅 - onclick 확인', async ({ page }) => {
console.log('\n╔════════════════════════════════════════════════════╗');
console.log('║ 상세 디버깅 - Blazor 상호작용 확인 ║');
console.log('╚════════════════════════════════════════════════════╝\n');
// 네트워크 추적
page.on('request', request => {
console.log(`📤 [REQUEST] ${request.method()} ${request.url()}`);
});
page.on('response', response => {
console.log(`📥 [RESPONSE] ${response.status()} ${response.url()}`);
});
// 콘솔 추적
page.on('console', msg => {
if (msg.type() === 'log') {
console.log(`🖨️ [LOG] ${msg.text()}`);
} else if (msg.type() === 'error') {
console.log(`❌ [ERROR] ${msg.text()}`);
}
});
console.log('1️⃣ 페이지 접근...');
await page.goto('http://localhost:5265/login');
await page.waitForLoadState('networkidle');
await page.waitForTimeout(2000);
console.log('\n2️⃣ 버튼 onclick 핸들러 확인...');
const loginButton = page.locator('button:has-text("로그인")');
const onclickCheck = await loginButton.evaluate((el: any) => {
console.log('Button element:', el);
console.log('onclick:', el.onclick);
console.log('data attributes:', Array.from(el.attributes).map((a: any) => `${a.name}=${a.value}`));
return {
onclick: el.onclick,
onclickString: el.onclick?.toString() || 'null',
attributes: Array.from(el.attributes).map((a: any) => `${a.name}=${a.value}`),
};
});
console.log(' onclick 확인 결과:');
console.log(` - onclick: ${onclickCheck.onclick}`);
console.log(` - 속성 목록:`);
onclickCheck.attributes.forEach((attr: string) => {
console.log(`${attr}`);
});
console.log('\n3️⃣ 입력 필드 확인...');
const usernameInput = page.locator('input[type="text"]').first();
const passwordInput = page.locator('input[type="password"]');
await usernameInput.fill('admin');
await passwordInput.fill('admin');
console.log(' 입력 완료');
console.log('\n4️⃣ 버튼 클릭...');
await loginButton.click();
console.log(' 클릭됨');
await page.waitForTimeout(3000);
console.log('\n5️⃣ 최종 상태...');
const finalUrl = page.url();
console.log(` URL: ${finalUrl}`);
// 버튼 상태 재확인
const isVisible = await loginButton.isVisible();
const isEnabled = await loginButton.isEnabled();
console.log(` 버튼 시각성: ${isVisible}, 활성화: ${isEnabled}`);
});