فهرست منبع

Added e2e tests

Tim Jones 1 ماه پیش
والد
کامیت
a0f203747b

+ 61 - 0
Tests.E2e/DesktopTests.cs

@@ -0,0 +1,61 @@
+using Tests.E2e.Infra;
+using Tests.E2e.PageObjectModels;
+using Xunit.Abstractions;
+
+namespace Tests.E2e;
+
+public class DesktopTests(
+    PlaywrightFixture fixture,
+    ITestOutputHelper output) : E2ETestBase(fixture, output)
+{
+    private readonly ITestOutputHelper _output = output;
+
+    [Fact]
+    public async Task User_Can_Add_And_Delete_Desktop()
+    {
+        var (context, page) = await CreatePageAsync();
+        var resourceName = $"e2e-ap-{Guid.NewGuid():N}"[..16];
+
+        try
+        {
+            // Go home
+            await page.GotoAsync(fixture.BaseUrl);
+
+            _output.WriteLine($"URL after Goto: {page.Url}");
+
+            var layout = new MainLayoutPom(page);
+            await layout.AssertLoadedAsync();
+            await layout.GotoHardwareAsync();
+
+            var hardwarePage = new HardwareTreePom(page);
+            await hardwarePage.AssertLoadedAsync();
+            await hardwarePage.GotoDesktopsListAsync();
+
+            var listPage = new DesktopsListPom(page);
+            await listPage.AssertLoadedAsync();
+            await listPage.AddDesktopAsync(resourceName);
+            await listPage.AssertDesktopExists(resourceName);
+            await listPage.DeleteDesktopAsync(resourceName);
+            await listPage.AssertDesktopDoesNotExist(resourceName);
+
+            await context.CloseAsync();
+        }
+        catch (Exception ex)
+        {
+            _output.WriteLine("TEST FAILED — Capturing diagnostics");
+
+            _output.WriteLine($"Current URL: {page.Url}");
+
+            var html = await page.ContentAsync();
+            _output.WriteLine("==== DOM SNAPSHOT START ====");
+            _output.WriteLine(html);
+            _output.WriteLine("==== DOM SNAPSHOT END ====");
+
+            throw;
+        }
+        finally
+        {
+            await context.CloseAsync();
+        }
+    }
+}

+ 61 - 0
Tests.E2e/FirewallTests.cs

@@ -0,0 +1,61 @@
+using Tests.E2e.Infra;
+using Tests.E2e.PageObjectModels;
+using Xunit.Abstractions;
+
+namespace Tests.E2e;
+
+public class FirewallTests(
+    PlaywrightFixture fixture,
+    ITestOutputHelper output) : E2ETestBase(fixture, output)
+{
+    private readonly ITestOutputHelper _output = output;
+
+    [Fact]
+    public async Task User_Can_Add_And_Delete_Firewall()
+    {
+        var (context, page) = await CreatePageAsync();
+        var resourceName = $"e2e-ap-{Guid.NewGuid():N}"[..16];
+
+        try
+        {
+            // Go home
+            await page.GotoAsync(fixture.BaseUrl);
+
+            _output.WriteLine($"URL after Goto: {page.Url}");
+
+            var layout = new MainLayoutPom(page);
+            await layout.AssertLoadedAsync();
+            await layout.GotoHardwareAsync();
+
+            var hardwarePage = new HardwareTreePom(page);
+            await hardwarePage.AssertLoadedAsync();
+            await hardwarePage.GotoFirewallsListAsync();
+
+            var listPage = new FirewallsListPom(page);
+            await listPage.AssertLoadedAsync();
+            await listPage.AddFirewallAsync(resourceName);
+            await listPage.AssertFirewallExists(resourceName);
+            await listPage.DeleteFirewallAsync(resourceName);
+            await listPage.AssertFirewallDoesNotExist(resourceName);
+
+            await context.CloseAsync();
+        }
+        catch (Exception ex)
+        {
+            _output.WriteLine("TEST FAILED — Capturing diagnostics");
+
+            _output.WriteLine($"Current URL: {page.Url}");
+
+            var html = await page.ContentAsync();
+            _output.WriteLine("==== DOM SNAPSHOT START ====");
+            _output.WriteLine(html);
+            _output.WriteLine("==== DOM SNAPSHOT END ====");
+
+            throw;
+        }
+        finally
+        {
+            await context.CloseAsync();
+        }
+    }
+}

+ 61 - 0
Tests.E2e/LaptopTests.cs

@@ -0,0 +1,61 @@
+using Tests.E2e.Infra;
+using Tests.E2e.PageObjectModels;
+using Xunit.Abstractions;
+
+namespace Tests.E2e;
+
+public class LaptopTests(
+    PlaywrightFixture fixture,
+    ITestOutputHelper output) : E2ETestBase(fixture, output)
+{
+    private readonly ITestOutputHelper _output = output;
+
+    [Fact]
+    public async Task User_Can_Add_And_Delete_Laptop()
+    {
+        var (context, page) = await CreatePageAsync();
+        var resourceName = $"e2e-ap-{Guid.NewGuid():N}"[..16];
+
+        try
+        {
+            // Go home
+            await page.GotoAsync(fixture.BaseUrl);
+
+            _output.WriteLine($"URL after Goto: {page.Url}");
+
+            var layout = new MainLayoutPom(page);
+            await layout.AssertLoadedAsync();
+            await layout.GotoHardwareAsync();
+
+            var hardwarePage = new HardwareTreePom(page);
+            await hardwarePage.AssertLoadedAsync();
+            await hardwarePage.GotoLaptopsListAsync();
+
+            var listPage = new LaptopListPom(page);
+            await listPage.AssertLoadedAsync();
+            await listPage.AddLaptopAsync(resourceName);
+            await listPage.AssertLaptopExists(resourceName);
+            await listPage.DeleteLaptopAsync(resourceName);
+            await listPage.AssertLaptopDoesNotExist(resourceName);
+
+            await context.CloseAsync();
+        }
+        catch (Exception ex)
+        {
+            _output.WriteLine("TEST FAILED — Capturing diagnostics");
+
+            _output.WriteLine($"Current URL: {page.Url}");
+
+            var html = await page.ContentAsync();
+            _output.WriteLine("==== DOM SNAPSHOT START ====");
+            _output.WriteLine(html);
+            _output.WriteLine("==== DOM SNAPSHOT END ====");
+
+            throw;
+        }
+        finally
+        {
+            await context.CloseAsync();
+        }
+    }
+}

+ 124 - 0
Tests.E2e/PageObjectModels/DesktopListPom.cs

@@ -0,0 +1,124 @@
+using Microsoft.Playwright;
+
+namespace Tests.E2e.PageObjectModels;
+
+public class DesktopsListPom(IPage page)
+{
+    public AddResourceComponent AddDesktop => new(page, "desktop");
+
+    public ILocator PageRoot => page.GetByTestId("desktops-page-root");
+    public ILocator PageTitle => page.GetByTestId("desktops-page-title");
+
+    public ILocator Loading => page.GetByTestId("desktops-loading");
+    public ILocator EmptyState => page.GetByTestId("desktops-empty");
+    public ILocator DesktopsList => page.GetByTestId("desktops-list");
+
+    public ILocator AddSection => page.GetByTestId("desktops-add-section");
+
+    // Must match AddResourceComponent test IDs
+    public ILocator AddInput => page.GetByTestId("add-desktop-input");
+    public ILocator AddButton => page.GetByTestId("add-desktop-button");
+
+    // -------------------------------------------------
+    // Dynamic Desktop Items
+    // -------------------------------------------------
+
+    public ILocator DesktopItem(string name)
+    {
+        return page.GetByTestId($"desktop-item-{Sanitize(name)}");
+    }
+
+    public ILocator OpenLink(string name)
+    {
+        return DesktopItem(name)
+            .GetByTestId("open-desktop-link");
+    }
+
+    public ILocator DeleteButton(string name)
+    {
+        return DesktopItem(name)
+            .GetByTestId("delete-desktop-button");
+    }
+
+    public ILocator RenameButton(string name)
+    {
+        return DesktopItem(name)
+            .GetByTestId("rename-desktop-button");
+    }
+
+    public ILocator CloneButton(string name)
+    {
+        return DesktopItem(name)
+            .GetByTestId("clone-desktop-button");
+    }
+
+    public ILocator ModelBadge(string name)
+    {
+        return DesktopItem(name)
+            .GetByTestId("desktop-model-badge");
+    }
+
+    // -------------------------------------------------
+    // Navigation
+    // -------------------------------------------------
+
+    public async Task GotoAsync(string baseUrl)
+    {
+        await page.GotoAsync($"{baseUrl}/desktops/list");
+        await AssertLoadedAsync();
+    }
+
+    public async Task AssertLoadedAsync()
+    {
+        await Assertions.Expect(PageRoot).ToBeVisibleAsync();
+        await Assertions.Expect(PageTitle).ToBeVisibleAsync();
+    }
+
+    public async Task WaitForListAsync()
+    {
+        await Assertions.Expect(DesktopsList).ToBeVisibleAsync();
+    }
+
+    // -------------------------------------------------
+    // Actions
+    // -------------------------------------------------
+
+    public async Task AddDesktopAsync(string name)
+    {
+        await AddDesktop.AddAsync(name);
+        await Assertions.Expect(DesktopItem(name))
+            .ToBeVisibleAsync();
+    }
+
+    public async Task DeleteDesktopAsync(string name)
+    {
+        await DeleteButton(name).ClickAsync();
+        await page.GetByTestId("Desktop-confirm-modal-confirm").ClickAsync();
+
+        await Assertions.Expect(DesktopItem(name))
+            .Not.ToBeVisibleAsync();
+    }
+
+    public async Task OpenDesktopAsync(string name)
+    {
+        await OpenLink(name).ClickAsync();
+        await page.WaitForURLAsync($"**/resources/hardware/{name}");
+    }
+
+    public async Task AssertDesktopExists(string name)
+    {
+        await Assertions.Expect(DesktopItem(name))
+            .ToBeVisibleAsync();
+    }
+
+    public async Task AssertDesktopDoesNotExist(string name)
+    {
+        await Assertions.Expect(DesktopItem(name))
+            .Not.ToBeVisibleAsync();
+    }
+
+    private static string Sanitize(string value)
+    {
+        return value.Replace(" ", "-");
+    }
+}

+ 136 - 0
Tests.E2e/PageObjectModels/FirewallListPom.cs

@@ -0,0 +1,136 @@
+using Microsoft.Playwright;
+
+namespace Tests.E2e.PageObjectModels;
+
+public class FirewallsListPom(IPage page)
+{
+    public AddResourceComponent AddFirewall => new(page, "firewall");
+
+    public ILocator PageRoot => page.GetByTestId("firewalls-page-root");
+    public ILocator PageTitle => page.GetByTestId("firewalls-page-title");
+
+    public ILocator Loading => page.GetByTestId("firewalls-loading");
+    public ILocator EmptyState => page.GetByTestId("firewalls-empty");
+    public ILocator FirewallsList => page.GetByTestId("firewalls-list");
+
+    public ILocator AddSection => page.GetByTestId("firewalls-add-section");
+
+    // Must match AddResourceComponent test IDs
+    public ILocator AddInput => page.GetByTestId("add-firewall-input");
+    public ILocator AddButton => page.GetByTestId("add-firewall-button");
+
+    // -------------------------------------------------
+    // Dynamic Firewall Items
+    // -------------------------------------------------
+
+    public ILocator FirewallItem(string name)
+    {
+        return page.GetByTestId($"firewall-item-{Sanitize(name)}");
+    }
+
+    public ILocator OpenLink(string name)
+    {
+        return FirewallItem(name)
+            .GetByTestId("open-firewall-link");
+    }
+
+    public ILocator EditButton(string name)
+    {
+        return FirewallItem(name)
+            .GetByTestId("edit-firewall-button");
+    }
+
+    public ILocator SaveButton(string name)
+    {
+        return FirewallItem(name)
+            .GetByTestId("save-firewall-button");
+    }
+
+    public ILocator CancelButton(string name)
+    {
+        return FirewallItem(name)
+            .GetByTestId("cancel-firewall-button");
+    }
+
+    public ILocator RenameButton(string name)
+    {
+        return FirewallItem(name)
+            .GetByTestId("rename-firewall-button");
+    }
+
+    public ILocator CloneButton(string name)
+    {
+        return FirewallItem(name)
+            .GetByTestId("clone-firewall-button");
+    }
+
+    public ILocator DeleteButton(string name)
+    {
+        return FirewallItem(name)
+            .GetByTestId("delete-firewall-button");
+    }
+
+    // -------------------------------------------------
+    // Navigation
+    // -------------------------------------------------
+
+    public async Task GotoAsync(string baseUrl)
+    {
+        await page.GotoAsync($"{baseUrl}/firewalls/list");
+        await AssertLoadedAsync();
+    }
+
+    public async Task AssertLoadedAsync()
+    {
+        await Assertions.Expect(PageRoot).ToBeVisibleAsync();
+        await Assertions.Expect(PageTitle).ToBeVisibleAsync();
+    }
+
+    public async Task WaitForListAsync()
+    {
+        await Assertions.Expect(FirewallsList).ToBeVisibleAsync();
+    }
+
+    // -------------------------------------------------
+    // Actions
+    // -------------------------------------------------
+
+    public async Task AddFirewallAsync(string name)
+    {
+        await AddFirewall.AddAsync(name);
+        await Assertions.Expect(FirewallItem(name))
+            .ToBeVisibleAsync();
+    }
+
+    public async Task DeleteFirewallAsync(string name)
+    {
+        await DeleteButton(name).ClickAsync();
+        await page.GetByTestId("Firewall-confirm-modal-confirm").ClickAsync();
+
+        await Assertions.Expect(FirewallItem(name))
+            .Not.ToBeVisibleAsync();
+    }
+
+    public async Task OpenFirewallAsync(string name)
+    {
+        await OpenLink(name).ClickAsync();
+        await page.WaitForURLAsync($"**/resources/hardware/{name}");
+    }
+
+    public async Task AssertFirewallExists(string name)
+    {
+        await Assertions.Expect(FirewallItem(name))
+            .ToBeVisibleAsync();
+    }
+
+    public async Task AssertFirewallDoesNotExist(string name)
+    {
+        await Assertions.Expect(FirewallItem(name))
+            .Not.ToBeVisibleAsync();
+    }
+
+    private static string Sanitize(string value)
+    {
+        return value.Replace(" ", "-");
+    }
+}

+ 124 - 0
Tests.E2e/PageObjectModels/LaptopListPom.cs

@@ -0,0 +1,124 @@
+using Microsoft.Playwright;
+
+namespace Tests.E2e.PageObjectModels;
+
+public class LaptopListPom(IPage page)
+{
+    public AddResourceComponent Addlaptop => new(page, "laptop");
+
+    public ILocator PageRoot => page.GetByTestId("laptops-page-root");
+    public ILocator PageTitle => page.GetByTestId("laptops-page-title");
+
+    public ILocator Loading => page.GetByTestId("laptops-loading");
+    public ILocator EmptyState => page.GetByTestId("laptops-empty");
+    public ILocator LaptopsList => page.GetByTestId("laptops-list");
+
+    public ILocator AddSection => page.GetByTestId("laptops-add-section");
+
+    // Must match AddResourceComponent test IDs
+    public ILocator AddInput => page.GetByTestId("add-laptop-input");
+    public ILocator AddButton => page.GetByTestId("add-laptop-button");
+
+    // -------------------------------------------------
+    // Dynamic laptop Items
+    // -------------------------------------------------
+
+    public ILocator LaptopItem(string name)
+    {
+        return page.GetByTestId($"laptop-item-{Sanitize(name)}");
+    }
+
+    public ILocator OpenLink(string name)
+    {
+        return LaptopItem(name)
+            .GetByTestId("open-laptop-link");
+    }
+
+    public ILocator DeleteButton(string name)
+    {
+        return LaptopItem(name)
+            .GetByTestId("delete-laptop-button");
+    }
+
+    public ILocator RenameButton(string name)
+    {
+        return LaptopItem(name)
+            .GetByTestId("rename-laptop-button");
+    }
+
+    public ILocator CloneButton(string name)
+    {
+        return LaptopItem(name)
+            .GetByTestId("clone-laptop-button");
+    }
+
+    public ILocator ModelBadge(string name)
+    {
+        return LaptopItem(name)
+            .GetByTestId("laptop-model-badge");
+    }
+
+    // -------------------------------------------------
+    // Navigation
+    // -------------------------------------------------
+
+    public async Task GotoAsync(string baseUrl)
+    {
+        await page.GotoAsync($"{baseUrl}/laptops/list");
+        await AssertLoadedAsync();
+    }
+
+    public async Task AssertLoadedAsync()
+    {
+        await Assertions.Expect(PageRoot).ToBeVisibleAsync();
+        await Assertions.Expect(PageTitle).ToBeVisibleAsync();
+    }
+
+    public async Task WaitForListAsync()
+    {
+        await Assertions.Expect(LaptopsList).ToBeVisibleAsync();
+    }
+
+    // -------------------------------------------------
+    // Actions
+    // -------------------------------------------------
+
+    public async Task AddLaptopAsync(string name)
+    {
+        await Addlaptop.AddAsync(name);
+        await Assertions.Expect(LaptopItem(name))
+            .ToBeVisibleAsync();
+    }
+
+    public async Task DeleteLaptopAsync(string name)
+    {
+        await DeleteButton(name).ClickAsync();
+        await page.GetByTestId("Laptop-confirm-modal-confirm").ClickAsync();
+
+        await Assertions.Expect(LaptopItem(name))
+            .Not.ToBeVisibleAsync();
+    }
+
+    public async Task OpenLaptopAsync(string name)
+    {
+        await OpenLink(name).ClickAsync();
+        await page.WaitForURLAsync($"**/resources/hardware/{name}");
+    }
+
+    public async Task AssertLaptopExists(string name)
+    {
+        await Assertions.Expect(LaptopItem(name))
+            .ToBeVisibleAsync();
+    }
+
+    public async Task AssertLaptopDoesNotExist(string name)
+    {
+        await Assertions.Expect(LaptopItem(name))
+            .Not.ToBeVisibleAsync();
+    }
+
+    private static string Sanitize(string value)
+    {
+        return value.Replace(" ", "-");
+    }
+}

+ 136 - 0
Tests.E2e/PageObjectModels/RouterListPom.cs

@@ -0,0 +1,136 @@
+using Microsoft.Playwright;
+
+namespace Tests.E2e.PageObjectModels;
+
+public class RouterListPom(IPage page)
+{
+    public AddResourceComponent AddRouter => new(page, "router");
+
+    public ILocator PageRoot => page.GetByTestId("routers-page-root");
+    public ILocator PageTitle => page.GetByTestId("routers-page-title");
+
+    public ILocator Loading => page.GetByTestId("routers-loading");
+    public ILocator EmptyState => page.GetByTestId("routers-empty");
+    public ILocator RoutersList => page.GetByTestId("routers-list");
+
+    public ILocator AddSection => page.GetByTestId("routers-add-section");
+
+    // Must match AddResourceComponent test IDs
+    public ILocator AddInput => page.GetByTestId("add-router-input");
+    public ILocator AddButton => page.GetByTestId("add-router-button");
+
+    // -------------------------------------------------
+    // Dynamic Router Items
+    // -------------------------------------------------
+
+    public ILocator RouterItem(string name)
+    {
+        return page.GetByTestId($"router-item-{Sanitize(name)}");
+    }
+
+    public ILocator OpenLink(string name)
+    {
+        return RouterItem(name)
+            .GetByTestId("open-router-link");
+    }
+
+    public ILocator EditButton(string name)
+    {
+        return RouterItem(name)
+            .GetByTestId("edit-router-button");
+    }
+
+    public ILocator SaveButton(string name)
+    {
+        return RouterItem(name)
+            .GetByTestId("save-router-button");
+    }
+
+    public ILocator CancelButton(string name)
+    {
+        return RouterItem(name)
+            .GetByTestId("cancel-router-button");
+    }
+
+    public ILocator RenameButton(string name)
+    {
+        return RouterItem(name)
+            .GetByTestId("rename-router-button");
+    }
+
+    public ILocator CloneButton(string name)
+    {
+        return RouterItem(name)
+            .GetByTestId("clone-router-button");
+    }
+
+    public ILocator DeleteButton(string name)
+    {
+        return RouterItem(name)
+            .GetByTestId("delete-router-button");
+    }
+
+    // -------------------------------------------------
+    // Navigation
+    // -------------------------------------------------
+
+    public async Task GotoAsync(string baseUrl)
+    {
+        await page.GotoAsync($"{baseUrl}/routers/list");
+        await AssertLoadedAsync();
+    }
+
+    public async Task AssertLoadedAsync()
+    {
+        await Assertions.Expect(PageRoot).ToBeVisibleAsync();
+        await Assertions.Expect(PageTitle).ToBeVisibleAsync();
+    }
+
+    public async Task WaitForListAsync()
+    {
+        await Assertions.Expect(RoutersList).ToBeVisibleAsync();
+    }
+
+    // -------------------------------------------------
+    // Actions
+    // -------------------------------------------------
+
+    public async Task AddRouterAsync(string name)
+    {
+        await AddRouter.AddAsync(name);
+        await Assertions.Expect(RouterItem(name))
+            .ToBeVisibleAsync();
+    }
+
+    public async Task DeleteRouterAsync(string name)
+    {
+        await DeleteButton(name).ClickAsync();
+        await page.GetByTestId("Router-confirm-modal-confirm").ClickAsync();
+
+        await Assertions.Expect(RouterItem(name))
+            .Not.ToBeVisibleAsync();
+    }
+
+    public async Task OpenRouterAsync(string name)
+    {
+        await OpenLink(name).ClickAsync();
+        await page.WaitForURLAsync($"**/resources/hardware/{name}");
+    }
+
+    public async Task AssertRouterExists(string name)
+    {
+        await Assertions.Expect(RouterItem(name))
+            .ToBeVisibleAsync();
+    }
+
+    public async Task AssertRouterDoesNotExist(string name)
+    {
+        await Assertions.Expect(RouterItem(name))
+            .Not.ToBeVisibleAsync();
+    }
+
+    private static string Sanitize(string value)
+    {
+        return value.Replace(" ", "-");
+    }
+}

+ 136 - 0
Tests.E2e/PageObjectModels/SwitchListPom.cs

@@ -0,0 +1,136 @@
+using Microsoft.Playwright;
+
+namespace Tests.E2e.PageObjectModels;
+
+public class SwitchListPom(IPage page)
+{
+    public AddResourceComponent AddSwitch => new(page, "switch");
+
+    public ILocator PageRoot => page.GetByTestId("switches-page-root");
+    public ILocator PageTitle => page.GetByTestId("switches-page-title");
+
+    public ILocator Loading => page.GetByTestId("switches-loading");
+    public ILocator EmptyState => page.GetByTestId("switches-empty");
+    public ILocator SwitchsList => page.GetByTestId("switches-list");
+
+    public ILocator AddSection => page.GetByTestId("switches-add-section");
+
+    // Must match AddResourceComponent test IDs
+    public ILocator AddInput => page.GetByTestId("add-switch-input");
+    public ILocator AddButton => page.GetByTestId("add-switch-button");
+
+    // -------------------------------------------------
+    // Dynamic Switch Items
+    // -------------------------------------------------
+
+    public ILocator SwitchItem(string name)
+    {
+        return page.GetByTestId($"switch-item-{Sanitize(name)}");
+    }
+
+    public ILocator OpenLink(string name)
+    {
+        return SwitchItem(name)
+            .GetByTestId("open-switch-link");
+    }
+
+    public ILocator EditButton(string name)
+    {
+        return SwitchItem(name)
+            .GetByTestId("edit-switch-button");
+    }
+
+    public ILocator SaveButton(string name)
+    {
+        return SwitchItem(name)
+            .GetByTestId("save-switch-button");
+    }
+
+    public ILocator CancelButton(string name)
+    {
+        return SwitchItem(name)
+            .GetByTestId("cancel-switch-button");
+    }
+
+    public ILocator RenameButton(string name)
+    {
+        return SwitchItem(name)
+            .GetByTestId("rename-switch-button");
+    }
+
+    public ILocator CloneButton(string name)
+    {
+        return SwitchItem(name)
+            .GetByTestId("clone-switch-button");
+    }
+
+    public ILocator DeleteButton(string name)
+    {
+        return SwitchItem(name)
+            .GetByTestId("delete-switch-button");
+    }
+
+    // -------------------------------------------------
+    // Navigation
+    // -------------------------------------------------
+
+    public async Task GotoAsync(string baseUrl)
+    {
+        await page.GotoAsync($"{baseUrl}/switches/list");
+        await AssertLoadedAsync();
+    }
+
+    public async Task AssertLoadedAsync()
+    {
+        await Assertions.Expect(PageRoot).ToBeVisibleAsync();
+        await Assertions.Expect(PageTitle).ToBeVisibleAsync();
+    }
+
+    public async Task WaitForListAsync()
+    {
+        await Assertions.Expect(SwitchsList).ToBeVisibleAsync();
+    }
+
+    // -------------------------------------------------
+    // Actions
+    // -------------------------------------------------
+
+    public async Task AddSwitchAsync(string name)
+    {
+        await AddSwitch.AddAsync(name);
+        await Assertions.Expect(SwitchItem(name))
+            .ToBeVisibleAsync();
+    }
+
+    public async Task DeleteSwitchAsync(string name)
+    {
+        await DeleteButton(name).ClickAsync();
+        await page.GetByTestId("Switch-confirm-modal-confirm").ClickAsync();
+
+        await Assertions.Expect(SwitchItem(name))
+            .Not.ToBeVisibleAsync();
+    }
+
+    public async Task OpenSwitchAsync(string name)
+    {
+        await OpenLink(name).ClickAsync();
+        await page.WaitForURLAsync($"**/resources/hardware/{name}");
+    }
+
+    public async Task AssertSwitchExists(string name)
+    {
+        await Assertions.Expect(SwitchItem(name))
+            .ToBeVisibleAsync();
+    }
+
+    public async Task AssertSwitchDoesNotExist(string name)
+    {
+        await Assertions.Expect(SwitchItem(name))
+            .Not.ToBeVisibleAsync();
+    }
+
+    private static string Sanitize(string value)
+    {
+        return value.Replace(" ", "-");
+    }
+}

+ 136 - 0
Tests.E2e/PageObjectModels/UpsListPom.cs

@@ -0,0 +1,136 @@
+using Microsoft.Playwright;
+
+namespace Tests.E2e.PageObjectModels;
+
+public class UpsListPom(IPage page)
+{
+    public AddResourceComponent AddUps => new(page, "ups");
+
+    public ILocator PageRoot => page.GetByTestId("ups-page-root");
+    public ILocator PageTitle => page.GetByTestId("ups-page-title");
+
+    public ILocator Loading => page.GetByTestId("ups-loading");
+    public ILocator EmptyState => page.GetByTestId("ups-empty");
+    public ILocator UpsList => page.GetByTestId("ups-list");
+
+    public ILocator AddSection => page.GetByTestId("ups-add-section");
+
+    // Must match AddResourceComponent test IDs
+    public ILocator AddInput => page.GetByTestId("add-ups-input");
+    public ILocator AddButton => page.GetByTestId("add-ups-button");
+
+    // -------------------------------------------------
+    // Dynamic Ups Items
+    // -------------------------------------------------
+
+    public ILocator UpsItem(string name)
+    {
+        return page.GetByTestId($"ups-item-{Sanitize(name)}");
+    }
+
+    public ILocator OpenLink(string name)
+    {
+        return UpsItem(name)
+            .GetByTestId("open-ups-link");
+    }
+
+    public ILocator EditButton(string name)
+    {
+        return UpsItem(name)
+            .GetByTestId("edit-ups-button");
+    }
+
+    public ILocator SaveButton(string name)
+    {
+        return UpsItem(name)
+            .GetByTestId("save-ups-button");
+    }
+
+    public ILocator CancelButton(string name)
+    {
+        return UpsItem(name)
+            .GetByTestId("cancel-ups-button");
+    }
+
+    public ILocator RenameButton(string name)
+    {
+        return UpsItem(name)
+            .GetByTestId("rename-ups-button");
+    }
+
+    public ILocator CloneButton(string name)
+    {
+        return UpsItem(name)
+            .GetByTestId("clone-ups-button");
+    }
+
+    public ILocator DeleteButton(string name)
+    {
+        return UpsItem(name)
+            .GetByTestId("delete-ups-button");
+    }
+
+    // -------------------------------------------------
+    // Navigation
+    // -------------------------------------------------
+
+    public async Task GotoAsync(string baseUrl)
+    {
+        await page.GotoAsync($"{baseUrl}/ups/list");
+        await AssertLoadedAsync();
+    }
+
+    public async Task AssertLoadedAsync()
+    {
+        await Assertions.Expect(PageRoot).ToBeVisibleAsync();
+        await Assertions.Expect(PageTitle).ToBeVisibleAsync();
+    }
+
+    public async Task WaitForListAsync()
+    {
+        await Assertions.Expect(UpsList).ToBeVisibleAsync();
+    }
+
+    // -------------------------------------------------
+    // Actions
+    // -------------------------------------------------
+
+    public async Task AddUpsAsync(string name)
+    {
+        await AddUps.AddAsync(name);
+        await Assertions.Expect(UpsItem(name))
+            .ToBeVisibleAsync();
+    }
+
+    public async Task DeleteUpsAsync(string name)
+    {
+        await DeleteButton(name).ClickAsync();
+        await page.GetByTestId("Ups-confirm-modal-confirm").ClickAsync();
+
+        await Assertions.Expect(UpsItem(name))
+            .Not.ToBeVisibleAsync();
+    }
+
+    public async Task OpenUpsAsync(string name)
+    {
+        await OpenLink(name).ClickAsync();
+        await page.WaitForURLAsync($"**/resources/hardware/{name}");
+    }
+
+    public async Task AssertUpsExists(string name)
+    {
+        await Assertions.Expect(UpsItem(name))
+            .ToBeVisibleAsync();
+    }
+
+    public async Task AssertUpsDoesNotExist(string name)
+    {
+        await Assertions.Expect(UpsItem(name))
+            .Not.ToBeVisibleAsync();
+    }
+
+    private static string Sanitize(string value)
+    {
+        return value.Replace(" ", "-");
+    }
+}

+ 61 - 0
Tests.E2e/RouterTests.cs

@@ -0,0 +1,61 @@
+using Tests.E2e.Infra;
+using Tests.E2e.PageObjectModels;
+using Xunit.Abstractions;
+
+namespace Tests.E2e;
+
+public class RouterTests(
+    PlaywrightFixture fixture,
+    ITestOutputHelper output) : E2ETestBase(fixture, output)
+{
+    private readonly ITestOutputHelper _output = output;
+
+    [Fact]
+    public async Task User_Can_Add_And_Delete_Router()
+    {
+        var (context, page) = await CreatePageAsync();
+        var resourceName = $"e2e-ap-{Guid.NewGuid():N}"[..16];
+
+        try
+        {
+            // Go home
+            await page.GotoAsync(fixture.BaseUrl);
+
+            _output.WriteLine($"URL after Goto: {page.Url}");
+
+            var layout = new MainLayoutPom(page);
+            await layout.AssertLoadedAsync();
+            await layout.GotoHardwareAsync();
+
+            var hardwarePage = new HardwareTreePom(page);
+            await hardwarePage.AssertLoadedAsync();
+            await hardwarePage.GotoRoutersListAsync();
+
+            var listPage = new RouterListPom(page);
+            await listPage.AssertLoadedAsync();
+            await listPage.AddRouterAsync(resourceName);
+            await listPage.AssertRouterExists(resourceName);
+            await listPage.DeleteRouterAsync(resourceName);
+            await listPage.AssertRouterDoesNotExist(resourceName);
+
+            await context.CloseAsync();
+        }
+        catch (Exception ex)
+        {
+            _output.WriteLine("TEST FAILED — Capturing diagnostics");
+
+            _output.WriteLine($"Current URL: {page.Url}");
+
+            var html = await page.ContentAsync();
+            _output.WriteLine("==== DOM SNAPSHOT START ====");
+            _output.WriteLine(html);
+            _output.WriteLine("==== DOM SNAPSHOT END ====");
+
+            throw;
+        }
+        finally
+        {
+            await context.CloseAsync();
+        }
+    }
+}

+ 61 - 0
Tests.E2e/SwitchTests.cs

@@ -0,0 +1,61 @@
+using Tests.E2e.Infra;
+using Tests.E2e.PageObjectModels;
+using Xunit.Abstractions;
+
+namespace Tests.E2e;
+
+public class SwitchTests(
+    PlaywrightFixture fixture,
+    ITestOutputHelper output) : E2ETestBase(fixture, output)
+{
+    private readonly ITestOutputHelper _output = output;
+
+    [Fact]
+    public async Task User_Can_Add_And_Delete_Switch()
+    {
+        var (context, page) = await CreatePageAsync();
+        var resourceName = $"e2e-ap-{Guid.NewGuid():N}"[..16];
+
+        try
+        {
+            // Go home
+            await page.GotoAsync(fixture.BaseUrl);
+
+            _output.WriteLine($"URL after Goto: {page.Url}");
+
+            var layout = new MainLayoutPom(page);
+            await layout.AssertLoadedAsync();
+            await layout.GotoHardwareAsync();
+
+            var hardwarePage = new HardwareTreePom(page);
+            await hardwarePage.AssertLoadedAsync();
+            await hardwarePage.GotoSwitchesListAsync();
+
+            var listPage = new SwitchListPom(page);
+            await listPage.AssertLoadedAsync();
+            await listPage.AddSwitchAsync(resourceName);
+            await listPage.AssertSwitchExists(resourceName);
+            await listPage.DeleteSwitchAsync(resourceName);
+            await listPage.AssertSwitchDoesNotExist(resourceName);
+
+            await context.CloseAsync();
+        }
+        catch (Exception ex)
+        {
+            _output.WriteLine("TEST FAILED — Capturing diagnostics");
+
+            _output.WriteLine($"Current URL: {page.Url}");
+
+            var html = await page.ContentAsync();
+            _output.WriteLine("==== DOM SNAPSHOT START ====");
+            _output.WriteLine(html);
+            _output.WriteLine("==== DOM SNAPSHOT END ====");
+
+            throw;
+        }
+        finally
+        {
+            await context.CloseAsync();
+        }
+    }
+}

+ 61 - 0
Tests.E2e/UpsTests.cs

@@ -0,0 +1,61 @@
+using Tests.E2e.Infra;
+using Tests.E2e.PageObjectModels;
+using Xunit.Abstractions;
+
+namespace Tests.E2e;
+
+public class UpsTests(
+    PlaywrightFixture fixture,
+    ITestOutputHelper output) : E2ETestBase(fixture, output)
+{
+    private readonly ITestOutputHelper _output = output;
+
+    [Fact]
+    public async Task User_Can_Add_And_Delete_Ups()
+    {
+        var (context, page) = await CreatePageAsync();
+        var resourceName = $"e2e-ap-{Guid.NewGuid():N}"[..16];
+
+        try
+        {
+            // Go home
+            await page.GotoAsync(fixture.BaseUrl);
+
+            _output.WriteLine($"URL after Goto: {page.Url}");
+
+            var layout = new MainLayoutPom(page);
+            await layout.AssertLoadedAsync();
+            await layout.GotoHardwareAsync();
+
+            var hardwarePage = new HardwareTreePom(page);
+            await hardwarePage.AssertLoadedAsync();
+            await hardwarePage.GotoUpsListAsync();
+
+            var listPage = new UpsListPom(page);
+            await listPage.AssertLoadedAsync();
+            await listPage.AddUpsAsync(resourceName);
+            await listPage.AssertUpsExists(resourceName);
+            await listPage.DeleteUpsAsync(resourceName);
+            await listPage.AssertUpsDoesNotExist(resourceName);
+
+            await context.CloseAsync();
+        }
+        catch (Exception ex)
+        {
+            _output.WriteLine("TEST FAILED — Capturing diagnostics");
+
+            _output.WriteLine($"Current URL: {page.Url}");
+
+            var html = await page.ContentAsync();
+            _output.WriteLine("==== DOM SNAPSHOT START ====");
+            _output.WriteLine(html);
+            _output.WriteLine("==== DOM SNAPSHOT END ====");
+
+            throw;
+        }
+        finally
+        {
+            await context.CloseAsync();
+        }
+    }
+}