using System.Text.Json; using Microsoft.Playwright; namespace ScadaLink.CentralUI.PlaywrightTests; [Collection("Playwright")] public class LoginTests { private readonly PlaywrightFixture _fixture; public LoginTests(PlaywrightFixture fixture) { _fixture = fixture; } [Fact] public async Task UnauthenticatedUser_RedirectsToLogin() { var page = await _fixture.NewPageAsync(); await page.GotoAsync(PlaywrightFixture.BaseUrl); Assert.Contains("/login", page.Url); await Expect(page.Locator("h4")).ToHaveTextAsync("ScadaLink"); await Expect(page.Locator("#username")).ToBeVisibleAsync(); await Expect(page.Locator("#password")).ToBeVisibleAsync(); await Expect(page.Locator("button[type='submit']")).ToHaveTextAsync("Sign In"); } [Fact] public async Task ValidCredentials_AuthenticatesSuccessfully() { var page = await _fixture.NewAuthenticatedPageAsync(); // Should be on the dashboard, not the login page Assert.DoesNotContain("/login", page.Url); } [Fact] public async Task InvalidCredentials_ShowsError() { var page = await _fixture.NewPageAsync(); await page.GotoAsync($"{PlaywrightFixture.BaseUrl}/login"); await page.WaitForLoadStateAsync(LoadState.DOMContentLoaded); // POST invalid credentials via fetch var status = await page.EvaluateAsync(@" async () => { const resp = await fetch('/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: 'username=baduser&password=badpass', redirect: 'follow' }); return resp.status; } "); // The login endpoint redirects to /login?error=... on failure. // Reload the page to see the error state. await page.ReloadAsync(); Assert.Equal(200, status); // redirect followed to login page } [Fact] public async Task TokenEndpoint_ReturnsJwt() { var page = await _fixture.NewPageAsync(); await page.GotoAsync($"{PlaywrightFixture.BaseUrl}/login"); await page.WaitForLoadStateAsync(LoadState.DOMContentLoaded); var result = await page.EvaluateAsync(@" async () => { const resp = await fetch('/auth/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: 'username=multi-role&password=password' }); const json = await resp.json(); return { status: resp.status, hasToken: !!json.access_token, username: json.username || '' }; } "); Assert.Equal(200, result.GetProperty("status").GetInt32()); Assert.True(result.GetProperty("hasToken").GetBoolean()); Assert.Equal("multi-role", result.GetProperty("username").GetString()); } [Fact] public async Task TokenEndpoint_InvalidCredentials_Returns401() { var page = await _fixture.NewPageAsync(); await page.GotoAsync($"{PlaywrightFixture.BaseUrl}/login"); await page.WaitForLoadStateAsync(LoadState.DOMContentLoaded); var status = await page.EvaluateAsync(@" async () => { const resp = await fetch('/auth/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: 'username=baduser&password=badpass' }); return resp.status; } "); Assert.Equal(401, status); } private static ILocatorAssertions Expect(ILocator locator) => Assertions.Expect(locator); }