refactor: rename ScadaLink → ZB.MOM.WW.ScadaBridge (code + projects + namespaces)
Solution + 23 src projects + 26 test projects renamed; folders, csproj, namespaces, and ScadaLinkDbContext/ScadaBridgeDbContext class updated. ActorSystem "scadalink" → "scadabridge", Akka seed-node URLs migrated. SQL roles/logins, LDAP domains, CLI command name, and CLI config dir (~/.scadalink → ~/.scadabridge) also renamed. Build green; 5 Host.Tests fail awaiting SQL login rename in next commit. Pre-existing StaleTagMonitor timing flakes unchanged. Rename script committed at tools/rename-to-scadabridge.sh.
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
using Microsoft.Playwright;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests;
|
||||
|
||||
/// <summary>
|
||||
/// Shared fixture that manages the Playwright browser connection.
|
||||
/// Creates a single browser connection per test collection, reused across all tests.
|
||||
/// Requires the Playwright Docker container running at ws://localhost:3000.
|
||||
/// </summary>
|
||||
public class PlaywrightFixture : IAsyncLifetime
|
||||
{
|
||||
/// <summary>
|
||||
/// Playwright Server WebSocket endpoint (Docker container on host port 3000).
|
||||
/// </summary>
|
||||
private const string PlaywrightWsEndpoint = "ws://localhost:3000";
|
||||
|
||||
/// <summary>
|
||||
/// Central UI base URL as seen from inside the Docker network.
|
||||
/// The browser runs in the Playwright container, so it uses the Docker hostname.
|
||||
/// </summary>
|
||||
public const string BaseUrl = "http://scadabridge-traefik";
|
||||
|
||||
/// <summary>Test LDAP credentials (multi-role user with Admin + Design + Deployment).</summary>
|
||||
public const string TestUsername = "multi-role";
|
||||
public const string TestPassword = "password";
|
||||
|
||||
public IPlaywright Playwright { get; private set; } = null!;
|
||||
public IBrowser Browser { get; private set; } = null!;
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
Playwright = await Microsoft.Playwright.Playwright.CreateAsync();
|
||||
Browser = await Playwright.Chromium.ConnectAsync(PlaywrightWsEndpoint);
|
||||
}
|
||||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
await Browser.CloseAsync();
|
||||
Playwright.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new browser context and page. Each test gets an isolated session.
|
||||
/// </summary>
|
||||
public async Task<IPage> NewPageAsync()
|
||||
{
|
||||
var context = await Browser.NewContextAsync();
|
||||
return await context.NewPageAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new page and log in with the default multi-role test user.
|
||||
/// </summary>
|
||||
public Task<IPage> NewAuthenticatedPageAsync() =>
|
||||
NewAuthenticatedPageAsync(TestUsername, TestPassword);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new page and log in with specific credentials.
|
||||
/// Uses JavaScript fetch() to POST to /auth/login from within the browser,
|
||||
/// which sets the auth cookie in the browser context. Then navigates to the dashboard.
|
||||
/// </summary>
|
||||
public async Task<IPage> NewAuthenticatedPageAsync(string username, string password)
|
||||
{
|
||||
var page = await NewPageAsync();
|
||||
|
||||
// Navigate to the login page first to establish the origin
|
||||
await page.GotoAsync($"{BaseUrl}/login");
|
||||
await page.WaitForLoadStateAsync(LoadState.DOMContentLoaded);
|
||||
|
||||
// POST to /auth/login via fetch() inside the browser.
|
||||
// This sets the auth cookie in the browser context automatically.
|
||||
var finalUrl = await page.EvaluateAsync<string>(@"
|
||||
async ([u, p]) => {
|
||||
const resp = await fetch('/auth/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: 'username=' + encodeURIComponent(u) + '&password=' + encodeURIComponent(p),
|
||||
redirect: 'follow'
|
||||
});
|
||||
return resp.url;
|
||||
}
|
||||
", new object[] { username, password });
|
||||
|
||||
if (finalUrl.Contains("/login"))
|
||||
{
|
||||
throw new InvalidOperationException($"Login failed for '{username}' — redirected back to login: {finalUrl}");
|
||||
}
|
||||
|
||||
// Navigate to the dashboard — cookie authenticates us
|
||||
await page.GotoAsync(BaseUrl);
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait for Blazor enhanced navigation to update the URL path.
|
||||
/// Blazor Server uses SignalR for client-side navigation (no full page reload),
|
||||
/// so standard WaitForURLAsync times out. This polls window.location instead.
|
||||
/// </summary>
|
||||
public static async Task WaitForPathAsync(IPage page, string path, string? excludePath = null, int timeoutMs = 10000)
|
||||
{
|
||||
var js = excludePath != null
|
||||
? $"() => window.location.pathname.includes('{path}') && !window.location.pathname.includes('{excludePath}')"
|
||||
: $"() => window.location.pathname.includes('{path}')";
|
||||
await page.WaitForFunctionAsync(js, null, new() { Timeout = timeoutMs });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Expand every collapsed sidebar nav section. Nav sections are collapsed by
|
||||
/// default, so a section's links are not in the DOM until it is expanded.
|
||||
/// Call this after authenticating, before interacting with sectioned nav links.
|
||||
/// </summary>
|
||||
public static async Task ExpandAllNavSectionsAsync(IPage page)
|
||||
{
|
||||
var toggles = page.Locator("button.nav-section-toggle");
|
||||
int count = await toggles.CountAsync();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var toggle = toggles.Nth(i);
|
||||
if (await toggle.GetAttributeAsync("aria-expanded") == "false")
|
||||
{
|
||||
await toggle.ClickAsync();
|
||||
// Wait for the toggle's own state to flip so the Blazor
|
||||
// re-render has landed before moving to the next section.
|
||||
await Assertions.Expect(toggle).ToHaveAttributeAsync("aria-expanded", "true");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[CollectionDefinition("Playwright")]
|
||||
public class PlaywrightCollection : ICollectionFixture<PlaywrightFixture>;
|
||||
Reference in New Issue
Block a user