diff --git a/src/dotnet/QuantEngine.Web/Client/Layout/AuthLayout.razor b/src/dotnet/QuantEngine.Web/Client/Layout/AuthLayout.razor index 1314d79..45fe096 100644 --- a/src/dotnet/QuantEngine.Web/Client/Layout/AuthLayout.razor +++ b/src/dotnet/QuantEngine.Web/Client/Layout/AuthLayout.razor @@ -1,4 +1,12 @@ @inherits LayoutComponentBase +@using QuantEngine.Web.Client.Theme +@rendermode InteractiveWebAssembly + + + + + +
@@ -63,4 +71,5 @@
@code { + private MudTheme _theme = AppTheme.LightTheme; } diff --git a/src/dotnet/QuantEngine.Web/Client/Layout/MainLayout.razor b/src/dotnet/QuantEngine.Web/Client/Layout/MainLayout.razor index 252a537..c29b694 100644 --- a/src/dotnet/QuantEngine.Web/Client/Layout/MainLayout.razor +++ b/src/dotnet/QuantEngine.Web/Client/Layout/MainLayout.razor @@ -1,8 +1,15 @@ @inherits LayoutComponentBase +@using QuantEngine.Web.Client.Theme @inject HttpClient Http @inject AuthenticationStateProvider AuthStateProvider @inject NavigationManager NavigationManager + + + + + + @@ -93,6 +100,7 @@ @code { + private MudTheme _theme = AppTheme.LightTheme; private bool navOpen = true; private bool fixedOpen = true; private string appVersion = "Local Debug"; diff --git a/src/dotnet/QuantEngine.Web/Client/Pages/Dashboard.razor b/src/dotnet/QuantEngine.Web/Client/Pages/Dashboard.razor index dc36dd7..2d5e9fa 100644 --- a/src/dotnet/QuantEngine.Web/Client/Pages/Dashboard.razor +++ b/src/dotnet/QuantEngine.Web/Client/Pages/Dashboard.razor @@ -5,6 +5,9 @@ QuantEngine - Admin Dashboard + + +
κ΄€λ¦¬μž λŒ€μ‹œλ³΄λ“œ diff --git a/src/dotnet/QuantEngine.Web/Client/Pages/Login.razor b/src/dotnet/QuantEngine.Web/Client/Pages/Login.razor deleted file mode 100644 index f9ea825..0000000 --- a/src/dotnet/QuantEngine.Web/Client/Pages/Login.razor +++ /dev/null @@ -1,217 +0,0 @@ -@page "/login" -@attribute [AllowAnonymous] -@layout AuthLayout -@inject AuthenticationStateProvider AuthStateProvider -@inject NavigationManager NavigationManager -@inject HttpClient Http - -둜그인 - QuantEngine - - - - - -@code { - private string Username { get; set; } = string.Empty; - private string Password { get; set; } = string.Empty; - private string ErrorMessage { get; set; } = string.Empty; - private bool IsSubmitting { get; set; } = false; - private bool RememberUsername { get; set; } = true; - - protected override async Task OnInitializedAsync() - { - var customProvider = (CustomAuthenticationStateProvider)AuthStateProvider; - var remembered = await customProvider.GetRememberedUsernameAsync(); - if (!string.IsNullOrWhiteSpace(remembered)) - { - Username = remembered; - RememberUsername = true; - } - } - - private sealed class LoginResponse - { - public bool Success { get; set; } - public string? Username { get; set; } - public string? Role { get; set; } - public string? AccessToken { get; set; } - public string? ExpiresAt { get; set; } - } - - private async Task HandleLoginAsync() - { - ErrorMessage = string.Empty; - if (string.IsNullOrWhiteSpace(Username) || string.IsNullOrWhiteSpace(Password)) - { - ErrorMessage = "아이디와 λΉ„λ°€λ²ˆν˜Έλ₯Ό λͺ¨λ‘ μž…λ ₯ν•΄ μ£Όμ„Έμš”."; - return; - } - - IsSubmitting = true; - - try - { - var response = await Http.PostAsJsonAsync("api/auth/login", new { Username, Password }); - if (response.IsSuccessStatusCode) - { - var auth = await response.Content.ReadFromJsonAsync(); - if (auth is null || string.IsNullOrWhiteSpace(auth.AccessToken)) - { - ErrorMessage = "둜그인 응닡이 μœ νš¨ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€."; - return; - } - - var customProvider = (CustomAuthenticationStateProvider)AuthStateProvider; - await customProvider.MarkUserAsAuthenticatedAsync(auth.Username ?? Username, auth.AccessToken, auth.Role ?? "Admin", RememberUsername); - NavigationManager.NavigateTo("/dashboard"); - } - else - { - ErrorMessage = "아이디 λ˜λŠ” λΉ„λ°€λ²ˆν˜Έκ°€ μ˜¬λ°”λ₯΄μ§€ μ•ŠμŠ΅λ‹ˆλ‹€."; - } - } - catch (Exception ex) - { - ErrorMessage = $"둜그인 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€: {ex.Message}"; - } - finally - { - IsSubmitting = false; - } - } -} diff --git a/src/dotnet/QuantEngine.Web/Client/Pages/NotFound.razor b/src/dotnet/QuantEngine.Web/Client/Pages/NotFound.razor index 917ada1..f72e0e1 100644 --- a/src/dotnet/QuantEngine.Web/Client/Pages/NotFound.razor +++ b/src/dotnet/QuantEngine.Web/Client/Pages/NotFound.razor @@ -1,5 +1,8 @@ ο»Ώ@page "/not-found" @layout MainLayout + + +

Not Found

Sorry, the content you are looking for does not exist.

\ No newline at end of file diff --git a/src/dotnet/QuantEngine.Web/Client/Program.cs b/src/dotnet/QuantEngine.Web/Client/Program.cs index 17c8629..a5b93e1 100644 --- a/src/dotnet/QuantEngine.Web/Client/Program.cs +++ b/src/dotnet/QuantEngine.Web/Client/Program.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Microsoft.AspNetCore.Components.Authorization; using QuantEngine.Web.Client.Services; using QuantEngine.Web.Client.Infrastructure; +using MudBlazor.Services; var builder = WebAssemblyHostBuilder.CreateDefault(args); @@ -16,6 +17,9 @@ builder.Services.AddAuthorizationCore(); builder.Services.AddCascadingAuthenticationState(); builder.Services.AddScoped(); +// MudBlazor Services (CRITICAL: Required for Interactive WebAssembly) +builder.Services.AddMudServices(); + // HttpClient register (API-First standard) builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); diff --git a/src/dotnet/QuantEngine.Web/Client/QuantEngine.Web.Client.csproj b/src/dotnet/QuantEngine.Web/Client/QuantEngine.Web.Client.csproj index be0cf34..50bdd44 100644 --- a/src/dotnet/QuantEngine.Web/Client/QuantEngine.Web.Client.csproj +++ b/src/dotnet/QuantEngine.Web/Client/QuantEngine.Web.Client.csproj @@ -14,8 +14,8 @@ - - + + diff --git a/src/dotnet/QuantEngine.Web/Components/App.razor b/src/dotnet/QuantEngine.Web/Components/App.razor index b229e94..f600a15 100644 --- a/src/dotnet/QuantEngine.Web/Components/App.razor +++ b/src/dotnet/QuantEngine.Web/Components/App.razor @@ -1,7 +1,5 @@ @using System.Reflection -@using QuantEngine.Web.Client.Theme @using QuantEngine.Web.Client.Pages -@using QuantEngine.Web.Client.Layout @@ -10,58 +8,25 @@ - + - - - + - + +
- - - - - - - - - - - - - - - - - + - -
+ - -@code { - private MudTheme _theme = AppTheme.LightTheme; - - private static readonly Assembly[] AdditionalAssemblies = new[] - { - typeof(Dashboard).Assembly, - }; - - protected override void OnInitialized() - { - _theme = AppTheme.LightTheme; - } -} diff --git a/src/dotnet/QuantEngine.Web/Components/Layout/AuthLayout.razor b/src/dotnet/QuantEngine.Web/Components/Layout/AuthLayout.razor new file mode 100644 index 0000000..0e12905 --- /dev/null +++ b/src/dotnet/QuantEngine.Web/Components/Layout/AuthLayout.razor @@ -0,0 +1,46 @@ +@inherits LayoutComponentBase +@using QuantEngine.Web.Client.Theme + + + + + + + +
+
+
+ @Body +
+
+
+ + + +@code { + private MudTheme _theme = AppTheme.LightTheme; +} diff --git a/src/dotnet/QuantEngine.Web/Components/Pages/LoginSimple.razor b/src/dotnet/QuantEngine.Web/Components/Pages/LoginSimple.razor new file mode 100644 index 0000000..c807382 --- /dev/null +++ b/src/dotnet/QuantEngine.Web/Components/Pages/LoginSimple.razor @@ -0,0 +1,185 @@ +@page "/login" +@attribute [AllowAnonymous] +@layout AuthLayout +@rendermode InteractiveServer +@inject HttpClient Http +@inject NavigationManager Nav + +둜그인 - QuantEngine + + + + + +@code { + private string _username = string.Empty; + private string _password = string.Empty; + private string _errorMessage = string.Empty; + private bool _isSubmitting = false; + private bool _rememberUsername = true; + + private async Task LoginAsync() + { + _errorMessage = string.Empty; + + if (string.IsNullOrWhiteSpace(_username) || string.IsNullOrWhiteSpace(_password)) + { + _errorMessage = "아이디와 λΉ„λ°€λ²ˆν˜Έλ₯Ό λͺ¨λ‘ μž…λ ₯ν•΄ μ£Όμ„Έμš”."; + return; + } + + _isSubmitting = true; + StateHasChanged(); + + try + { + var response = await Http.PostAsJsonAsync("/api/auth/login", new + { + Username = _username, + Password = _password + }); + + if (response.IsSuccessStatusCode) + { + Nav.NavigateTo("/", forceLoad: true); + return; + } + + _errorMessage = "둜그인 μ‹€νŒ¨: 아이디 λ˜λŠ” λΉ„λ°€λ²ˆν˜Έκ°€ μ˜¬λ°”λ₯΄μ§€ μ•ŠμŠ΅λ‹ˆλ‹€."; + } + catch (Exception ex) + { + _errorMessage = $"였λ₯˜: {ex.Message}"; + } + finally + { + _isSubmitting = false; + } + } +} diff --git a/src/dotnet/QuantEngine.Web/Components/Routes.razor b/src/dotnet/QuantEngine.Web/Components/Routes.razor deleted file mode 100644 index 2bb4e60..0000000 --- a/src/dotnet/QuantEngine.Web/Components/Routes.razor +++ /dev/null @@ -1,26 +0,0 @@ -@using System.Reflection -@using QuantEngine.Web.Client -@using QuantEngine.Web.Client.Pages -@using QuantEngine.Web.Client.Layout - - - - - - - - - - - - - - -@code { - private static readonly Assembly[] AdditionalAssemblies = - { - typeof(QuantEngine.Web.Client.Pages.Dashboard).Assembly, - }; -} diff --git a/src/dotnet/QuantEngine.Web/Program.cs b/src/dotnet/QuantEngine.Web/Program.cs index 448ef59..3da3a8c 100644 --- a/src/dotnet/QuantEngine.Web/Program.cs +++ b/src/dotnet/QuantEngine.Web/Program.cs @@ -36,6 +36,7 @@ builder.Host.UseSerilog(); // Add services to the container. builder.Services.AddRazorComponents() + .AddInteractiveServerComponents() .AddInteractiveWebAssemblyComponents(); // Authentication and Custom State Provider (Shared client components) @@ -126,15 +127,13 @@ if (!app.Environment.IsDevelopment()) app.UseExceptionHandler("/Error", createScopeForErrors: true); app.UseHsts(); } -// Redirect status code pages only for non-API routes -app.UseStatusCodePages(async ctx => -{ - if (!ctx.HttpContext.Request.Path.StartsWithSegments("/api")) - ctx.HttpContext.Response.Redirect("/not-found"); -}); app.UseHttpsRedirection(); +// CRITICAL: Static assets MUST be served before StatusCodePages middleware +// This ensures app.css, _framework/, and other static files are served correctly +app.MapStaticAssets(); + // Configure static file MIME types for Blazor var provider = new FileExtensionContentTypeProvider(); provider.Mappings[".wasm"] = "application/wasm"; @@ -152,6 +151,13 @@ app.UseStaticFiles(new StaticFileOptions DefaultContentType = "application/octet-stream" }); +// Redirect status code pages only for non-API routes (AFTER static files) +app.UseStatusCodePages(async ctx => +{ + if (!ctx.HttpContext.Request.Path.StartsWithSegments("/api")) + ctx.HttpContext.Response.Redirect("/not-found"); +}); + app.UseAntiforgery(); app.UseAuthentication(); app.UseAuthorization(); @@ -166,8 +172,6 @@ catch (Exception ex) Log.Warning("Hangfire setup failed: {Message}", ex.Message); } -app.MapStaticAssets(); - app.MapGet("/", () => Results.Redirect("/login")); // Collection API Endpoints (must be before MapRazorComponents) @@ -411,6 +415,7 @@ app.MapPost("/api/history/{domain}", async (string domain, JsonElement payload, }); app.MapRazorComponents() + .AddInteractiveServerRenderMode() .AddInteractiveWebAssemblyRenderMode() .AddAdditionalAssemblies(typeof(QuantEngine.Web.Client._Imports).Assembly); diff --git a/src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj b/src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj index 8f18733..f43b68b 100644 --- a/src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj +++ b/src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj @@ -14,17 +14,23 @@ - + + - + + + + + + net10.0 enable diff --git a/test-results/login-button-clicked.png b/test-results/login-button-clicked.png deleted file mode 100644 index 283b0ce..0000000 Binary files a/test-results/login-button-clicked.png and /dev/null differ diff --git a/test-results/login-css-check.png b/test-results/login-css-check.png deleted file mode 100644 index 6be5c7f..0000000 Binary files a/test-results/login-css-check.png and /dev/null differ diff --git a/test-results/login-improved-ui.png b/test-results/login-improved-ui.png deleted file mode 100644 index 6be5c7f..0000000 Binary files a/test-results/login-improved-ui.png and /dev/null differ diff --git a/test-results/login-input-filled.png b/test-results/login-input-filled.png deleted file mode 100644 index 365bf13..0000000 Binary files a/test-results/login-input-filled.png and /dev/null differ diff --git a/test-results/login-remember-checkbox.png b/test-results/login-remember-checkbox.png deleted file mode 100644 index 761e437..0000000 Binary files a/test-results/login-remember-checkbox.png and /dev/null differ diff --git a/tests/e2e/button-check.spec.ts b/tests/e2e/button-check.spec.ts new file mode 100644 index 0000000..b422695 --- /dev/null +++ b/tests/e2e/button-check.spec.ts @@ -0,0 +1,76 @@ +import { test } from '@playwright/test'; + +test('둜그인 λ²„νŠΌ μƒνƒœ 확인', async ({ page }) => { + console.log('\n╔════════════════════════════════════════════════════╗'); + console.log('β•‘ 둜그인 λ²„νŠΌ μƒνƒœ 상세 확인 β•‘'); + console.log('β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•\n'); + + await page.goto('http://localhost:5265/login'); + await page.waitForLoadState('networkidle'); + await page.waitForTimeout(2000); + + const loginButton = page.locator('button:has-text("둜그인")'); + + console.log('1️⃣ λ²„νŠΌ 쑴재 μ—¬λΆ€:'); + const count = await loginButton.count(); + console.log(` 찾은 λ²„νŠΌ 개수: ${count}`); + + if (count > 0) { + console.log('\n2️⃣ λ²„νŠΌ 속성:'); + const isVisible = await loginButton.isVisible(); + console.log(` μ‹œκ°μ„±(isVisible): ${isVisible}`); + + const isEnabled = await loginButton.isEnabled(); + console.log(` ν™œμ„±ν™”(isEnabled): ${isEnabled}`); + + const isDisabled = await loginButton.evaluate((el: any) => el.disabled); + console.log(` Disabled 속성: ${isDisabled}`); + + const text = await loginButton.textContent(); + console.log(` λ²„νŠΌ ν…μŠ€νŠΈ: "${text}"`); + + const classList = await loginButton.evaluate((el: any) => Array.from(el.classList)); + console.log(` CSS 클래슀: ${classList.join(', ')}`); + + console.log('\n3️⃣ λ²„νŠΌμ˜ onclick 속성:'); + const onclick = await loginButton.evaluate((el: any) => el.onclick); + console.log(` onclick: ${onclick}`); + + console.log('\n4️⃣ 클릭 μ‹œλ„ (μž…λ ₯ ν•„λ“œ μ±„μš΄ ν›„)...'); + 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(' μž…λ ₯ μ™„λ£Œ, λ²„νŠΌ 클릭 μ‹œλ„...'); + + // λ‹€μ–‘ν•œ 클릭 방법 μ‹œλ„ + try { + await loginButton.click({ timeout: 5000 }); + console.log(' βœ… click() 성곡'); + } catch (e) { + console.log(` ❌ click() μ‹€νŒ¨: ${e}`); + } + + await page.waitForTimeout(2000); + + console.log('\n5️⃣ μ΅œμ’… μƒνƒœ:'); + console.log(` URL: ${page.url()}`); + console.log(` 제λͺ©: ${await page.title()}`); + } else { + console.log('❌ 둜그인 λ²„νŠΌμ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€!'); + } + + // νŽ˜μ΄μ§€ HTML ꡬ쑰 확인 + console.log('\n6️⃣ 전체 λ²„νŠΌ λͺ©λ‘:'); + const allButtons = page.locator('button'); + const buttonCount = await allButtons.count(); + console.log(` 총 λ²„νŠΌ 개수: ${buttonCount}`); + + for (let i = 0; i < Math.min(buttonCount, 5); i++) { + const btn = allButtons.nth(i); + const btnText = await btn.textContent(); + console.log(` [${i}] ${btnText?.trim()}`); + } +}); diff --git a/tests/e2e/console-check.spec.ts b/tests/e2e/console-check.spec.ts new file mode 100644 index 0000000..7312536 --- /dev/null +++ b/tests/e2e/console-check.spec.ts @@ -0,0 +1,64 @@ +import { test } from '@playwright/test'; + +test('λΈŒλΌμš°μ € μ½˜μ†” μ—λŸ¬ 확인', async ({ page }) => { + const consoleMessages: string[] = []; + const jsErrors: string[] = []; + + page.on('console', msg => { + console.log(`[${msg.type().toUpperCase()}] ${msg.text()}`); + if (msg.type() === 'error') { + jsErrors.push(msg.text()); + } + consoleMessages.push(`${msg.type()}: ${msg.text()}`); + }); + + page.on('pageerror', error => { + console.log(`[PAGE ERROR] ${error.message}`); + jsErrors.push(error.message); + }); + + console.log('\n둜그인 νŽ˜μ΄μ§€ μ ‘κ·Ό...'); + await page.goto('http://localhost:5265/login'); + await page.waitForLoadState('networkidle'); + await page.waitForTimeout(2000); + + console.log('\nμž…λ ₯ 및 둜그인...'); + const usernameInput = page.locator('input[type="text"]').first(); + const passwordInput = page.locator('input[type="password"]'); + const loginButton = page.locator('button:has-text("둜그인")'); + + await usernameInput.fill('admin'); + await passwordInput.fill('admin'); + + console.log('둜그인 λ²„νŠΌ 클릭...'); + await loginButton.click(); + + await page.waitForTimeout(3000); + + console.log('\n\n╔════════════════════════════════════════════════════╗'); + console.log('β•‘ μ½˜μ†” λ©”μ‹œμ§€ μš”μ•½ β•‘'); + console.log('β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•\n'); + + console.log(`총 λ©”μ‹œμ§€: ${consoleMessages.length}`); + console.log(`JavaScript μ—λŸ¬: ${jsErrors.length}`); + + if (jsErrors.length > 0) { + console.log('\n❌ κ°μ§€λœ μ—λŸ¬:'); + jsErrors.forEach((err, i) => { + console.log(` ${i + 1}. ${err}`); + }); + } else { + console.log('\nβœ… JavaScript μ—λŸ¬ μ—†μŒ'); + } + + // Blazor κ΄€λ ¨ μ½˜μ†” λ©”μ‹œμ§€ + const blazorMessages = consoleMessages.filter(m => m.toLowerCase().includes('blazor')); + if (blazorMessages.length > 0) { + console.log('\nπŸ”΅ Blazor λ©”μ‹œμ§€:'); + blazorMessages.forEach(msg => { + console.log(` - ${msg}`); + }); + } + + console.log('\nμ΅œμ’… URL:', page.url()); +}); diff --git a/tests/e2e/debug-login.spec.ts b/tests/e2e/debug-login.spec.ts new file mode 100644 index 0000000..2f14f08 --- /dev/null +++ b/tests/e2e/debug-login.spec.ts @@ -0,0 +1,98 @@ +import { test, expect } from '@playwright/test'; + +test('둜그인 API 호좜 좔적', async ({ page }) => { + console.log('\n╔════════════════════════════════════════════════════╗'); + console.log('β•‘ 둜그인 API 호좜 좔적 & 디버깅 β•‘'); + console.log('β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•\n'); + + // λ„€νŠΈμ›Œν¬ μš”μ²­ 좔적 + const requests: string[] = []; + page.on('request', request => { + if (request.url().includes('api')) { + console.log(`πŸ“€ Request: ${request.method()} ${request.url()}`); + requests.push(`${request.method()} ${request.url()}`); + } + }); + + page.on('response', response => { + if (response.url().includes('api')) { + console.log(`πŸ“₯ Response: ${response.status()} ${response.url()}`); + } + }); + + // νŽ˜μ΄μ§€ μ ‘κ·Ό + console.log('1️⃣ 둜그인 νŽ˜μ΄μ§€ μ ‘κ·Ό...'); + await page.goto('http://localhost:5265/login'); + await page.waitForLoadState('networkidle'); + await page.waitForTimeout(1000); + + // μž…λ ₯ 및 둜그인 + console.log('\n2️⃣ 둜그인 μ‹œλ„...'); + const usernameInput = page.locator('input[type="text"]').first(); + const passwordInput = page.locator('input[type="password"]'); + const loginButton = page.locator('button:has-text("둜그인")'); + + await usernameInput.fill('admin'); + await passwordInput.fill('admin'); + + console.log('πŸ“ μž…λ ₯ μ™„λ£Œ: admin / admin'); + + // 둜그인 λ²„νŠΌ 클릭 μ „ μš”μ²­ λͺ¨λ‹ˆν„° + console.log('\n3️⃣ 둜그인 λ²„νŠΌ 클릭...'); + + // λ„€νŠΈμ›Œν¬ 응닡 λŒ€κΈ° + const [response] = await Promise.all([ + page.waitForResponse( + response => + response.url().includes('/api/auth/login') && + (response.status() === 200 || response.status() === 401 || response.status() === 400), + { timeout: 10000 } + ), + loginButton.click() + ]).catch(err => { + console.log('❌ 응닡 λŒ€κΈ° μ‹€νŒ¨:', err.message); + return [null]; + }); + + if (response) { + console.log(`\nβœ… 둜그인 API 응닡: ${response.status()}`); + + const responseText = await response.text(); + console.log(`πŸ“„ 응닡 λ³Έλ¬Έ: ${responseText}`); + + try { + const json = JSON.parse(responseText); + console.log('πŸ” νŒŒμ‹±λœ JSON:'); + console.log(JSON.stringify(json, null, 2)); + } catch (e) { + console.log('❌ JSON νŒŒμ‹± μ‹€νŒ¨'); + } + } else { + console.log('❌ 둜그인 API 응닡 μ—†μŒ'); + } + + // μ΅œμ’… μƒνƒœ 확인 + console.log('\n4️⃣ μ΅œμ’… μƒνƒœ 확인...'); + await page.waitForTimeout(2000); + + const finalUrl = page.url(); + const finalTitle = await page.title(); + const bodyText = await page.locator('body').textContent(); + + console.log(`πŸ“ μ΅œμ’… URL: ${finalUrl}`); + console.log(`πŸ“„ μ΅œμ’… 제λͺ©: ${finalTitle}`); + console.log(`πŸ“ "λŒ€μ‹œλ³΄λ“œ" 포함: ${bodyText?.includes('λŒ€μ‹œλ³΄λ“œ') ? 'βœ… 예' : '❌ μ•„λ‹ˆμ˜€'}`); + console.log(`πŸ“ "둜그인" 포함: ${bodyText?.includes('둜그인') ? 'βœ… 예' : '❌ μ•„λ‹ˆμ˜€'}`); + + // μŠ€ν¬λ¦°μƒ· + await page.screenshot({ path: 'test-results/debug-login.png', fullPage: true }); + console.log('\nπŸ“Έ μŠ€ν¬λ¦°μƒ· μ €μž₯: test-results/debug-login.png'); + + console.log('\n╔════════════════════════════════════════════════════╗'); + if (finalUrl.includes('/dashboard')) { + console.log('β•‘ βœ… 둜그인 성곡! β•‘'); + } else { + console.log('β•‘ ❌ 둜그인 μ‹€νŒ¨ β•‘'); + } + console.log('β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•\n'); +}); diff --git a/tests/e2e/detailed-debug.spec.ts b/tests/e2e/detailed-debug.spec.ts new file mode 100644 index 0000000..fee248e --- /dev/null +++ b/tests/e2e/detailed-debug.spec.ts @@ -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}`); +}); diff --git a/tests/e2e/final-login-test.spec.ts b/tests/e2e/final-login-test.spec.ts new file mode 100644 index 0000000..16c50da --- /dev/null +++ b/tests/e2e/final-login-test.spec.ts @@ -0,0 +1,110 @@ +import { test, expect } from '@playwright/test'; + +test('μ΅œμ’… 둜그인 ν…ŒμŠ€νŠΈ - 동적 ID μ„ νƒμž μ‚¬μš©', async ({ page }) => { + console.log('\n╔════════════════════════════════════════════════════╗'); + console.log('β•‘ μ΅œμ’… 둜그인 ν…ŒμŠ€νŠΈ (동적 ID 기반) β•‘'); + console.log('β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•\n'); + + // 1️⃣ 둜그인 νŽ˜μ΄μ§€ λ‘œλ“œ + console.log('1️⃣ 둜그인 νŽ˜μ΄μ§€ μ ‘κ·Ό...'); + await page.goto('http://localhost:5265/login', { waitUntil: 'networkidle' }); + await page.waitForTimeout(2000); + console.log(' βœ“ νŽ˜μ΄μ§€ λ‘œλ“œλ¨'); + + // 2️⃣ μž…λ ₯ ν•„λ“œ 확인 (동적 ID μ„ νƒμž μ‚¬μš©) + console.log('\n2️⃣ μž…λ ₯ ν•„λ“œ 확인 (MudTextField)...'); + + // MudTextFieldλŠ” μžλ™ 생성 IDλ₯Ό μ‚¬μš©ν•˜λ―€λ‘œ, νƒ€μž…μœΌλ‘œ 선택 + const usernameField = page.locator('input[type="text"].mud-input-slot').first(); + const passwordField = page.locator('input[type="password"].mud-input-slot').first(); + + // MudButton을 μ°ΎκΈ° (Primary color + ν…μŠ€νŠΈ 포함) + const loginBtn = page.locator('button:has-text("둜그인")').filter({ hasNot: page.locator('.components-reconnect') }).first(); + + const usernameExists = await usernameField.count() > 0; + const passwordExists = await passwordField.count() > 0; + const btnExists = await loginBtn.count() > 0; + + console.log(` 아이디 ν•„λ“œ: ${usernameExists ? 'βœ…' : '❌'}`); + console.log(` λΉ„λ°€λ²ˆν˜Έ ν•„λ“œ: ${passwordExists ? 'βœ…' : '❌'}`); + console.log(` 둜그인 λ²„νŠΌ: ${btnExists ? 'βœ…' : '❌'}`); + + if (!usernameExists || !passwordExists || !btnExists) { + console.log('\n ⚠️ ν•„λ“œ μ°ΎκΈ° μ‹€νŒ¨, νŽ˜μ΄μ§€ HTML μƒ˜ν”Œ:'); + const html = await page.content(); + const inputCount = (html.match(/ localStorage.getItem('quant_admin_access_token')); + const username = await page.evaluate(() => localStorage.getItem('quant_admin_username')); + + console.log(` 토큰 μ €μž₯됨: ${token ? 'βœ…' : '❌'}`); + console.log(` 아이디 μ €μž₯됨: ${username ? 'βœ…' : '❌'}`); + + // 8️⃣ μ΅œμ’… κ²°κ³Ό + console.log('\n╔════════════════════════════════════════════════════╗'); + if (isDashboard && (token || hasDashboardText)) { + console.log('β•‘ βœ… 둜그인 성곡! β•‘'); + console.log('β•‘ API 둜그인 μ™„λ£Œ + λŒ€μ‹œλ³΄λ“œ 이동 확인 β•‘'); + } else if (token) { + console.log('β•‘ ⚠️ API λ‘œκ·ΈμΈμ€ μ„±κ³΅ν–ˆμœΌλ‚˜ β•‘'); + console.log('β•‘ νŽ˜μ΄μ§€ λ¦¬λ‹€μ΄λ ‰νŠΈ 미확인 β•‘'); + } else { + console.log('β•‘ ❌ 둜그인 μ‹€νŒ¨ β•‘'); + } + console.log('β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•\n'); + + // 9️⃣ API 직접 검증 + console.log('9️⃣ API μ—”λ“œν¬μΈνŠΈ 검증...'); + const apiResponse = await page.evaluate(async () => { + try { + const res = await fetch('/api/auth/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username: 'admin', password: 'admin' }) + }); + return res.status; + } catch (e) { + return 'error'; + } + }); + console.log(` API μƒνƒœ: ${apiResponse === 200 ? 'βœ… 200 OK' : `❌ ${apiResponse}`}\n`); +}); diff --git a/tests/e2e/framework-check.spec.ts b/tests/e2e/framework-check.spec.ts new file mode 100644 index 0000000..292279d --- /dev/null +++ b/tests/e2e/framework-check.spec.ts @@ -0,0 +1,57 @@ +import { test } from '@playwright/test'; + +test('_framework 파일 λ‘œλ“œ 확인', async ({ page }) => { + console.log('\n╔════════════════════════════════════════════════════╗'); + console.log('β•‘ _framework 파일 λ‘œλ“œ μƒνƒœ 확인 β•‘'); + console.log('β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•\n'); + + const frameworkRequests: any[] = []; + + page.on('response', response => { + const url = response.url(); + if (url.includes('_framework') || url.includes('blazor')) { + frameworkRequests.push({ + url: url.split('?')[0], + status: response.status(), + }); + console.log(`[${response.status()}] ${url.split('?')[0]}`); + } + }); + + console.log('νŽ˜μ΄μ§€ μ ‘κ·Ό...'); + await page.goto('http://localhost:5265/login'); + await page.waitForLoadState('networkidle'); + await page.waitForTimeout(3000); + + console.log('\n╔════════════════════════════════════════════════════╗'); + console.log('β•‘ _framework 파일 μš”μ•½ β•‘'); + console.log('β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•\n'); + + console.log(`총 _framework μš”μ²­: ${frameworkRequests.length}\n`); + + const byStatus: { [key: number]: string[] } = {}; + frameworkRequests.forEach(req => { + if (!byStatus[req.status]) byStatus[req.status] = []; + byStatus[req.status].push(req.url); + }); + + Object.entries(byStatus).forEach(([status, urls]) => { + console.log(`[${status}] ${urls.length}개`); + urls.forEach(url => { + const filename = url.split('/').pop(); + console.log(` βœ“ ${filename}`); + }); + console.log(''); + }); + + const failedCount = (byStatus[404] || []).length + (byStatus[302] || []).length; + if (failedCount > 0) { + console.log(`❌ ${failedCount}개의 파일이 λ‘œλ“œλ˜μ§€ μ•ŠμŒ!`); + } else if (frameworkRequests.length > 0) { + console.log('βœ… λͺ¨λ“  _framework 파일 λ‘œλ“œλ¨'); + } else { + console.log('❌ _framework 파일이 λ‘œλ“œλ˜μ§€ μ•ŠμŒ'); + } + + console.log('\nπŸ“ μ΅œμ’… URL:', page.url()); +}); diff --git a/tests/e2e/html-debug.spec.ts b/tests/e2e/html-debug.spec.ts new file mode 100644 index 0000000..567b89f --- /dev/null +++ b/tests/e2e/html-debug.spec.ts @@ -0,0 +1,76 @@ +import { test } from '@playwright/test'; + +test('WASM νŽ˜μ΄μ§€ HTML 디버깅', async ({ page }) => { + console.log('\n╔════════════════════════════════════════════════════╗'); + console.log('β•‘ WASM νŽ˜μ΄μ§€ HTML 디버깅 β•‘'); + console.log('β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•\n'); + + await page.goto('http://localhost:5265/wasm-test'); + await page.waitForLoadState('networkidle'); + await page.waitForTimeout(2000); + + // 전체 HTML κ°€μ Έμ˜€κΈ° + const html = await page.content(); + + // blazor.web.js λ‘œλ“œ 확인 + if (html.includes('blazor.web.js')) { + console.log('βœ… blazor.web.js 슀크립트 νƒœκ·Έ found'); + } else { + console.log('❌ blazor.web.js 슀크립트 νƒœκ·Έ NOT found'); + } + + // MudBlazor λ‘œλ“œ 확인 + if (html.includes('MudBlazor.min.js')) { + console.log('βœ… MudBlazor.min.js 슀크립트 νƒœκ·Έ found'); + } else { + console.log('❌ MudBlazor.min.js 슀크립트 νƒœκ·Έ NOT found'); + } + + // MudContainer 확인 + if (html.includes('mud-container')) { + console.log('βœ… MudContainer μ»΄ν¬λ„ŒνŠΈ λ Œλ”λ§λ¨'); + } else { + console.log('❌ MudContainer μ»΄ν¬λ„ŒνŠΈ NOT λ Œλ”λ§λ¨'); + } + + // app div 확인 + if (html.includes('id="app"')) { + console.log('βœ… id="app" div found'); + } else { + console.log('❌ id="app" div NOT found'); + } + + // MudPaper 확인 + if (html.includes('mud-paper')) { + console.log('βœ… MudPaper μ»΄ν¬λ„ŒνŠΈ λ Œλ”λ§λ¨'); + } else { + console.log('❌ MudPaper μ»΄ν¬λ„ŒνŠΈ NOT λ Œλ”λ§λ¨'); + } + + // Blazor 슀크립트 λ‘œλ“œ 확인 + if (html.includes('_framework/blazor.web.js')) { + console.log('βœ… _framework/blazor.web.js λ‘œλ“œ 경둜 correct'); + } else { + console.log('❌ _framework/blazor.web.js λ‘œλ“œ 경둜 NOT correct'); + } + + // HTML 일뢀 좜λ ₯ + console.log('\nπŸ“ νƒœκ·Έ 일뢀:'); + const bodyMatch = html.match(/]*>/i); + if (bodyMatch) { + console.log(bodyMatch[0]); + } + + console.log('\nπŸ“ 슀크립트 νƒœκ·Έλ“€:'); + const scriptMatches = html.match(/]*src="[^"]*"[^>]*><\/script>/gi); + if (scriptMatches) { + scriptMatches.forEach((script, i) => { + if (script.includes('blazor') || script.includes('MudBlazor') || script.includes('_framework')) { + console.log(` [${i}] ${script}`); + } + }); + } + + console.log('\nHTML 길이:', html.length); + console.log('첫 3000자:', html.substring(0, 3000)); +}); diff --git a/tests/e2e/real-login-test.spec.ts b/tests/e2e/real-login-test.spec.ts new file mode 100644 index 0000000..90fc436 --- /dev/null +++ b/tests/e2e/real-login-test.spec.ts @@ -0,0 +1,94 @@ +import { test, expect } 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'); + await page.waitForLoadState('networkidle'); + await page.waitForTimeout(2000); + + const initialUrl = page.url(); + console.log(` 초기 URL: ${initialUrl}`); + + // 2. μž…λ ₯ ν•„λ“œ μ°ΎκΈ° + console.log('\n2️⃣ μž…λ ₯ ν•„λ“œ 확인...'); + const usernameInput = page.locator('input[type="text"]').first(); + const passwordInput = page.locator('input[type="password"]'); + const loginButton = page.locator('button:has-text("둜그인")'); + + await expect(usernameInput).toBeVisible(); + console.log(' βœ“ 아이디 μž…λ ₯ ν•„λ“œ'); + + await expect(passwordInput).toBeVisible(); + console.log(' βœ“ λΉ„λ°€λ²ˆν˜Έ μž…λ ₯ ν•„λ“œ'); + + await expect(loginButton).toBeVisible(); + console.log(' βœ“ 둜그인 λ²„νŠΌ'); + + // 3. μ˜¬λ°”λ₯Έ 자격증λͺ…μœΌλ‘œ 둜그인 + console.log('\n3️⃣ 둜그인 자격증λͺ… μž…λ ₯...'); + await usernameInput.fill('admin'); + console.log(' βœ“ 아이디: admin'); + + await passwordInput.fill('admin'); + console.log(' βœ“ λΉ„λ°€λ²ˆν˜Έ: ****'); + + // 4. 둜그인 λ²„νŠΌ 클릭 + console.log('\n4️⃣ 둜그인 λ²„νŠΌ 클릭...'); + await loginButton.click(); + console.log(' βœ“ 클릭됨'); + + // 5. 응닡 λŒ€κΈ° (μ€‘μš”!) + console.log('\n5️⃣ 응닡 λŒ€κΈ° 쀑...'); + await page.waitForTimeout(3000); + + // 6. μ΅œμ’… URL 확인 + const finalUrl = page.url(); + console.log(`\nπŸ“ μ΅œμ’… URL: ${finalUrl}`); + + // 7. νŽ˜μ΄μ§€ 제λͺ© 확인 + const pageTitle = await page.title(); + console.log(`πŸ“„ νŽ˜μ΄μ§€ 제λͺ©: ${pageTitle}`); + + // 8. νŽ˜μ΄μ§€ μ½˜ν…μΈ  확인 + const bodyText = await page.locator('body').textContent(); + console.log(`\nπŸ“ νŽ˜μ΄μ§€ ν…μŠ€νŠΈ 포함 μ—¬λΆ€:`); + + if (bodyText?.includes('λŒ€μ‹œλ³΄λ“œ')) { + console.log(' βœ… "λŒ€μ‹œλ³΄λ“œ" 포함됨 - 둜그인 성곡!'); + } else { + console.log(' ❌ "λŒ€μ‹œλ³΄λ“œ" 미포함'); + } + + if (bodyText?.includes('둜그인')) { + console.log(' ⚠️ "둜그인" ν…μŠ€νŠΈ μ—¬μ „νžˆ ν‘œμ‹œλ¨ - 둜그인 μ‹€νŒ¨?'); + } + + // 9. μ—λŸ¬ λ©”μ‹œμ§€ 확인 + const errorText = await page.locator('.mud-alert-message, [role="alert"]').textContent(); + if (errorText) { + console.log(`\n❌ μ—λŸ¬ λ©”μ‹œμ§€: ${errorText}`); + } + + // 10. μŠ€ν¬λ¦°μƒ· + await page.screenshot({ path: 'test-results/real-login-result.png', fullPage: true }); + console.log('\nπŸ“Έ μŠ€ν¬λ¦°μƒ·: test-results/real-login-result.png'); + + // 11. μ΅œμ’… νŒλ‹¨ + console.log('\n╔════════════════════════════════════════════════════╗'); + if (finalUrl.includes('/dashboard') || pageTitle.includes('λŒ€μ‹œλ³΄λ“œ')) { + console.log('β•‘ βœ… 둜그인 성곡! β•‘'); + } else if (finalUrl.includes('/login')) { + console.log('β•‘ ❌ 둜그인 μ‹€νŒ¨ (둜그인 νŽ˜μ΄μ§€ μœ μ§€) β•‘'); + } else { + console.log(`β•‘ ⚠️ 뢈λͺ…ν™•ν•œ μƒνƒœ: ${finalUrl}`); + } + console.log('β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•\n'); + + // μ–΄μ„€μ…˜ + expect(finalUrl).not.toContain('/login'); +}); diff --git a/tests/e2e/wasm-test.spec.ts b/tests/e2e/wasm-test.spec.ts new file mode 100644 index 0000000..36cb61a --- /dev/null +++ b/tests/e2e/wasm-test.spec.ts @@ -0,0 +1,76 @@ +import { test } from '@playwright/test'; + +test('Blazor WASM Interactivity Test', async ({ page }) => { + console.log('\n╔════════════════════════════════════════════════════╗'); + console.log('β•‘ Blazor WASM μƒν˜Έμž‘μš© ν…ŒμŠ€νŠΈ β•‘'); + console.log('β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•\n'); + + console.log('1️⃣ /wasm-test νŽ˜μ΄μ§€ μ ‘κ·Ό...'); + await page.goto('http://localhost:5265/wasm-test'); + await page.waitForLoadState('networkidle'); + await page.waitForTimeout(2000); + + console.log(' βœ“ νŽ˜μ΄μ§€ λ‘œλ“œ μ™„λ£Œ\n'); + + console.log('2️⃣ "Click Me" λ²„νŠΌ 클릭...'); + const clickButton = page.locator('button:has-text("Click Me")'); + + // νŽ˜μ΄μ§€ HTML 확인 (디버깅) + const pageHtml = await page.locator('body').innerHTML(); + console.log(' νŽ˜μ΄μ§€ λ‘œλ“œλ¨, HTML 길이:', pageHtml.length); + + // λͺ¨λ“  strong νƒœκ·Έ μ°ΎκΈ° + const strongCount = await page.locator('strong').count(); + console.log(` 찾은 νƒœκ·Έ: ${strongCount}개`); + + if (strongCount < 2) { + console.log(' ❌ νŽ˜μ΄μ§€ λ Œλ”λ§ μ‹€νŒ¨ - strong νƒœκ·Έ λΆ€μ‘±'); + await page.screenshot({ path: 'test-results/wasm-test-fail.png', fullPage: true }); + return; + } + + // 초기 클릭 수 확인 + let countText = await page.locator('strong').nth(0).textContent(); + console.log(` 초기 κ°’: ${countText}`); + + // 5번 클릭 + for (let i = 0; i < 5; i++) { + await clickButton.click(); + await page.waitForTimeout(200); + } + + // μ΅œμ’… 클릭 수 확인 + countText = await page.locator('strong').nth(0).textContent(); + console.log(` 클릭 5회 ν›„ κ°’: ${countText}\n`); + + if (countText === '5') { + console.log('βœ… WASM μƒν˜Έμž‘μš© 정상 μž‘λ™!'); + console.log(' κ²°λ‘ : Blazor Interactive WebAssemblyκ°€ μ œλŒ€λ‘œ μž‘λ™ν•©λ‹ˆλ‹€.'); + console.log(' β†’ 둜그인 νŽ˜μ΄μ§€μ˜ λ¬Έμ œλŠ” Auth/Layout νŠΉν™” μ΄μŠˆμž…λ‹ˆλ‹€.\n'); + } else { + console.log('❌ WASM μƒν˜Έμž‘μš© μ‹€νŒ¨!'); + console.log(' κ²°λ‘ : Blazor Interactive WebAssemblyκ°€ μ „μ—­μ μœΌλ‘œ μž‘λ™ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.'); + console.log(' β†’ SDK/버전 λ ˆμ΄μ–΄ 문제λ₯Ό 확인해야 ν•©λ‹ˆλ‹€.\n'); + } + + console.log('3️⃣ ν…μŠ€νŠΈ μž…λ ₯ ν•„λ“œ ν…ŒμŠ€νŠΈ...'); + const textField = page.locator('input[type="text"]').first(); + await textField.fill('Hello WASM'); + + const typedText = await page.locator('strong').nth(1).textContent(); + console.log(` μž…λ ₯ ν›„ ν…μŠ€νŠΈ: ${typedText}\n`); + + if (typedText === 'Hello WASM') { + console.log('βœ… ν…μŠ€νŠΈ μž…λ ₯/바인딩 정상 μž‘λ™!'); + } else { + console.log('❌ ν…μŠ€νŠΈ μž…λ ₯/바인딩 μ‹€νŒ¨!'); + } + + console.log('\n╔════════════════════════════════════════════════════╗'); + if (countText === '5' && typedText === 'Hello WASM') { + console.log('β•‘ βœ… WASM μ™„μ „ 정상 β•‘'); + } else { + console.log('β•‘ ❌ WASM 문제 있음 β•‘'); + } + console.log('β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•\n'); +});