feat: separate create/edit form pages, Playwright test infrastructure, /auth/token endpoint
Move all CRUD create/edit forms from inline on list pages to dedicated form pages with back-button navigation and post-save redirect. Add Playwright Docker container (browser server on port 3000) with 25 passing E2E tests covering login, navigation, and site CRUD workflows. Add POST /auth/token endpoint for clean JWT retrieval.
This commit is contained in:
115
tests/ScadaLink.CentralUI.PlaywrightTests/LoginTests.cs
Normal file
115
tests/ScadaLink.CentralUI.PlaywrightTests/LoginTests.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
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<int>(@"
|
||||
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<JsonElement>(@"
|
||||
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<int>(@"
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user