feat: implement Razor Pages and static HTML login pages
Added two implementations of login page: 1. Razor Pages (Pages/Login.cshtml + Login.cshtml.cs) - Server-side rendering with form submission - Username remembering with cookies - Error handling and validation 2. Static HTML (wwwroot/login.html) - Pure HTML/CSS/JavaScript - Client-side form submission - LocalStorage for username persistence - Direct API call to /api/auth/login Both implementations: ✅ Professional styling (dark theme, blur effects, primary blue buttons) ✅ Form validation ✅ Error message display ✅ ID persistence (LocalStorage/Cookies) ✅ Responsive design (mobile support) ✅ Integration with /api/auth/login endpoint Technical notes: - Blazor routing (@rendermode InteractiveServer) has limitations in .NET 10 Blazor Web App - Razor Pages and static files are bypassed by Blazor's catch-all routing - For production: recommend deploying login.html separately via nginx/reverse proxy - Or use URL pattern like /user/login (outside Blazor's @page definitions) Current workaround: - Manually access: http://localhost:5265/login.html (works) - API endpoint /api/auth/login is fully functional - Ready for frontend deployment separation Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
import { test } from '@playwright/test';
|
||||
|
||||
test('HTML 구조 검사', async ({ page }) => {
|
||||
await page.goto('http://localhost:5265/login', { waitUntil: 'networkidle' });
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
const html = await page.content();
|
||||
|
||||
// 중요한 요소 확인
|
||||
console.log('\n╔════════════════════════════════════════════════════╗');
|
||||
console.log('║ HTML 구조 검사 ║');
|
||||
console.log('╚════════════════════════════════════════════════════╝\n');
|
||||
|
||||
console.log('🔍 주요 요소 검사:');
|
||||
console.log(` MudPaper 있음: ${html.includes('mud-paper') ? '✅' : '❌'}`);
|
||||
console.log(` MudTextField 있음: ${html.includes('mud-textfield') ? '✅' : '❌'}`);
|
||||
console.log(` login-container 있음: ${html.includes('login-container') ? '✅' : '❌'}`);
|
||||
console.log(` login-card 있음: ${html.includes('login-card') ? '✅' : '❌'}`);
|
||||
console.log(` form-input 있음: ${html.includes('form-input') ? '✅' : '❌'}`);
|
||||
|
||||
console.log('\n📊 input 태그 개수:', (html.match(/<input/g) || []).length);
|
||||
console.log('📊 button 태그 개수:', (html.match(/<button/g) || []).length);
|
||||
|
||||
console.log('\n📝 Body 내용 샘플:');
|
||||
const bodyMatch = html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
|
||||
if (bodyMatch) {
|
||||
const bodyContent = bodyMatch[1];
|
||||
// 처음 500자만 출력 (정리된 형태)
|
||||
const cleaned = bodyContent.replace(/\s+/g, ' ').substring(0, 600);
|
||||
console.log(cleaned);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,106 @@
|
||||
import { test } from '@playwright/test';
|
||||
|
||||
test('로그인 화면 최종 스크린샷 - 스타일 검증', async ({ page }) => {
|
||||
console.log('\n╔════════════════════════════════════════════════════╗');
|
||||
console.log('║ 로그인 화면 스타일 최종 검증 ║');
|
||||
console.log('╚════════════════════════════════════════════════════╝\n');
|
||||
|
||||
// 1️⃣ 페이지 로드
|
||||
console.log('1️⃣ 로그인 페이지 로드...');
|
||||
await page.goto('http://localhost:5265/login', { waitUntil: 'networkidle' });
|
||||
await page.waitForTimeout(3000);
|
||||
console.log(' ✓ 페이지 로드 완료');
|
||||
|
||||
// 2️⃣ 계산된 스타일 검증
|
||||
console.log('\n2️⃣ 계산된 스타일 검증...');
|
||||
|
||||
const bodyStyle = await page.evaluate(() => {
|
||||
const body = document.body;
|
||||
return {
|
||||
backgroundColor: window.getComputedStyle(body).backgroundColor,
|
||||
color: window.getComputedStyle(body).color,
|
||||
fontFamily: window.getComputedStyle(body).fontFamily
|
||||
};
|
||||
});
|
||||
|
||||
console.log(` Body Background: ${bodyStyle.backgroundColor}`);
|
||||
console.log(` Body Text Color: ${bodyStyle.color}`);
|
||||
console.log(` Font Family: ${bodyStyle.fontFamily}`);
|
||||
|
||||
// 3️⃣ 로그인 카드 스타일
|
||||
console.log('\n3️⃣ 로그인 카드 스타일...');
|
||||
|
||||
const cardStyle = await page.locator('.login-card').evaluate((el: any) => ({
|
||||
display: window.getComputedStyle(el).display,
|
||||
background: window.getComputedStyle(el).backgroundColor,
|
||||
border: window.getComputedStyle(el).border,
|
||||
borderRadius: window.getComputedStyle(el).borderRadius,
|
||||
padding: window.getComputedStyle(el).padding,
|
||||
width: window.getComputedStyle(el).width
|
||||
}));
|
||||
|
||||
console.log(` Display: ${cardStyle.display}`);
|
||||
console.log(` Background: ${cardStyle.background}`);
|
||||
console.log(` Border: ${cardStyle.border}`);
|
||||
console.log(` Border Radius: ${cardStyle.borderRadius}`);
|
||||
console.log(` Width: ${cardStyle.width}`);
|
||||
|
||||
// 4️⃣ 입력 필드 스타일
|
||||
console.log('\n4️⃣ 입력 필드 스타일...');
|
||||
|
||||
const inputStyle = await page.locator('input[type="text"], input[type="password"]').first().evaluate((el: any) => ({
|
||||
display: window.getComputedStyle(el).display,
|
||||
backgroundColor: window.getComputedStyle(el).backgroundColor,
|
||||
color: window.getComputedStyle(el).color,
|
||||
border: window.getComputedStyle(el).border,
|
||||
padding: window.getComputedStyle(el).padding,
|
||||
fontSize: window.getComputedStyle(el).fontSize
|
||||
}));
|
||||
|
||||
console.log(` Display: ${inputStyle.display}`);
|
||||
console.log(` Background: ${inputStyle.backgroundColor}`);
|
||||
console.log(` Text Color: ${inputStyle.color}`);
|
||||
console.log(` Border: ${inputStyle.border}`);
|
||||
console.log(` Padding: ${inputStyle.padding}`);
|
||||
console.log(` Font Size: ${inputStyle.fontSize}`);
|
||||
|
||||
// 5️⃣ 로그인 버튼 스타일
|
||||
console.log('\n5️⃣ 로그인 버튼 스타일...');
|
||||
|
||||
const buttonStyle = await page.locator('button:has-text("로그인")').first().evaluate((el: any) => ({
|
||||
display: window.getComputedStyle(el).display,
|
||||
backgroundColor: window.getComputedStyle(el).backgroundColor,
|
||||
color: window.getComputedStyle(el).color,
|
||||
padding: window.getComputedStyle(el).padding,
|
||||
fontSize: window.getComputedStyle(el).fontSize,
|
||||
cursor: window.getComputedStyle(el).cursor
|
||||
}));
|
||||
|
||||
console.log(` Display: ${buttonStyle.display}`);
|
||||
console.log(` Background: ${buttonStyle.backgroundColor}`);
|
||||
console.log(` Text Color: ${buttonStyle.color}`);
|
||||
console.log(` Padding: ${buttonStyle.padding}`);
|
||||
console.log(` Font Size: ${buttonStyle.fontSize}`);
|
||||
|
||||
// 6️⃣ 전체 페이지 스크린샷
|
||||
console.log('\n6️⃣ 스크린샷 캡처...');
|
||||
|
||||
await page.screenshot({
|
||||
path: 'test-results/login-final-validation.png',
|
||||
fullPage: true
|
||||
});
|
||||
console.log(' ✅ test-results/login-final-validation.png');
|
||||
|
||||
// 7️⃣ 모바일 뷰 스크린샷
|
||||
await page.setViewportSize({ width: 375, height: 667 });
|
||||
await page.screenshot({
|
||||
path: 'test-results/login-mobile-view.png',
|
||||
fullPage: true
|
||||
});
|
||||
console.log(' ✅ test-results/login-mobile-view.png');
|
||||
|
||||
// 8️⃣ 최종 검증
|
||||
console.log('\n8️⃣ 스타일 검증 완료!');
|
||||
console.log(' ✅ 모든 스크린샷이 생성되었습니다');
|
||||
console.log(' ✅ test-results/ 폴더에서 확인하세요');
|
||||
});
|
||||
Reference in New Issue
Block a user