using Microsoft.Playwright; using Tests.E2e.Infra; using Tests.E2e.PageObjectModels; using Xunit.Abstractions; namespace Tests.E2e; public class DesktopCardTests( PlaywrightFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, output) { private readonly PlaywrightFixture _fixture = fixture; private readonly ITestOutputHelper _output = output; [Fact] public async Task User_Can_Add_And_Delete_Desktop() { (IBrowserContext context, IPage page) = await CreatePageAsync(); var desktopName = $"e2e-dt-{Guid.NewGuid():N}"[..16]; try { await page.GotoAsync(_fixture.BaseUrl); 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(desktopName); // creation should navigate to details page await page.WaitForURLAsync($"**/resources/hardware/{desktopName}"); // delete from details page (card) var card = new DesktopCardPom(page); await Assertions.Expect(card.DesktopItem(desktopName)).ToBeVisibleAsync(); await card.DeleteDesktopAsync(desktopName); // after deletion you redirect (your page does Nav.NavigateTo("/hardware/tree")) await page.WaitForURLAsync("**/hardware/tree"); } catch (Exception) { _output.WriteLine("TEST FAILED — Capturing diagnostics"); _output.WriteLine($"Current URL: {page.Url}"); _output.WriteLine("==== DOM SNAPSHOT START ===="); _output.WriteLine(await page.ContentAsync()); _output.WriteLine("==== DOM SNAPSHOT END ===="); throw; } finally { await context.CloseAsync(); } } [Fact] public async Task User_Can_Rename_Desktop_From_Details_Page() { (IBrowserContext context, IPage page) = await CreatePageAsync(); var originalName = $"e2e-dt-{Guid.NewGuid():N}"[..16]; var renamedName = $"e2e-dt-{Guid.NewGuid():N}"[..16]; try { await page.GotoAsync(_fixture.BaseUrl); 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(originalName); await page.WaitForURLAsync($"**/resources/hardware/{originalName}"); var card = new DesktopCardPom(page); await Assertions.Expect(card.DesktopItem(originalName)).ToBeVisibleAsync(); await card.RenameDesktopAsync(originalName, renamedName); await Assertions.Expect(card.DesktopItem(renamedName)).ToBeVisibleAsync(); // cleanup await card.DeleteDesktopAsync(renamedName); await page.WaitForURLAsync("**/hardware/tree"); } catch (Exception) { _output.WriteLine("TEST FAILED — Capturing diagnostics"); _output.WriteLine($"Current URL: {page.Url}"); _output.WriteLine("==== DOM SNAPSHOT START ===="); _output.WriteLine(await page.ContentAsync()); _output.WriteLine("==== DOM SNAPSHOT END ===="); throw; } finally { await context.CloseAsync(); } } [Fact] public async Task User_Can_Clone_Desktop_From_Details_Page() { (IBrowserContext context, IPage page) = await CreatePageAsync(); var originalName = $"e2e-dt-{Guid.NewGuid():N}"[..16]; var cloneName = $"e2e-dt-{Guid.NewGuid():N}"[..16]; try { await page.GotoAsync(_fixture.BaseUrl); 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(originalName); await page.WaitForURLAsync($"**/resources/hardware/{originalName}"); var card = new DesktopCardPom(page); await Assertions.Expect(card.DesktopItem(originalName)).ToBeVisibleAsync(); await card.CloneDesktopAsync(originalName, cloneName); await Assertions.Expect(card.DesktopItem(cloneName)).ToBeVisibleAsync(); // cleanup: delete clone then original await card.DeleteDesktopAsync(cloneName); await page.WaitForURLAsync("**/hardware/tree"); // go back to original and delete it too await page.GotoAsync($"{_fixture.BaseUrl}/resources/hardware/{originalName}"); await Assertions.Expect(card.DesktopItem(originalName)).ToBeVisibleAsync(); await card.DeleteDesktopAsync(originalName); await page.WaitForURLAsync("**/hardware/tree"); } catch (Exception) { _output.WriteLine("TEST FAILED — Capturing diagnostics"); _output.WriteLine($"Current URL: {page.Url}"); _output.WriteLine("==== DOM SNAPSHOT START ===="); _output.WriteLine(await page.ContentAsync()); _output.WriteLine("==== DOM SNAPSHOT END ===="); throw; } finally { await context.CloseAsync(); } } [Fact] public async Task User_Can_Edit_Desktop_Notes_And_Save() { (IBrowserContext context, IPage page) = await CreatePageAsync(); var desktopName = $"e2e-dt-{Guid.NewGuid():N}"[..16]; var notes = $"notes-{Guid.NewGuid():N}"; try { await page.GotoAsync(_fixture.BaseUrl); 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(desktopName); await page.WaitForURLAsync($"**/resources/hardware/{desktopName}"); var card = new DesktopCardPom(page); await Assertions.Expect(card.DesktopItem(desktopName)).ToBeVisibleAsync(); // start editing notes via MarkdownViewer edit button await card.NotesViewerEditButton(desktopName).ClickAsync(); // ensure editor visible then fill + save await Assertions.Expect(card.NotesEditorRoot(desktopName)).ToBeVisibleAsync(); await card.NotesEditorTextarea(desktopName).FillAsync(notes); await card.NotesEditorSave(desktopName).ClickAsync(); // viewer back, and content should contain the notes await Assertions.Expect(card.NotesViewerRoot(desktopName)).ToBeVisibleAsync(); await Assertions.Expect(card.NotesViewerRoot(desktopName)).ToContainTextAsync(notes); // cleanup await card.DeleteDesktopAsync(desktopName); await page.WaitForURLAsync("**/hardware/tree"); } catch (Exception) { _output.WriteLine("TEST FAILED — Capturing diagnostics"); _output.WriteLine($"Current URL: {page.Url}"); _output.WriteLine("==== DOM SNAPSHOT START ===="); _output.WriteLine(await page.ContentAsync()); _output.WriteLine("==== DOM SNAPSHOT END ===="); throw; } finally { await context.CloseAsync(); } } [Fact] public async Task User_Can_Edit_Desktop_Notes_And_Cancel() { (IBrowserContext context, IPage page) = await CreatePageAsync(); var desktopName = $"e2e-dt-{Guid.NewGuid():N}"[..16]; var notes = $"notes-{Guid.NewGuid():N}"; try { await page.GotoAsync(_fixture.BaseUrl); 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(desktopName); await page.WaitForURLAsync($"**/resources/hardware/{desktopName}"); var card = new DesktopCardPom(page); await Assertions.Expect(card.DesktopItem(desktopName)).ToBeVisibleAsync(); await card.NotesViewerEditButton(desktopName).ClickAsync(); await Assertions.Expect(card.NotesEditorRoot(desktopName)).ToBeVisibleAsync(); await card.NotesEditorTextarea(desktopName).FillAsync(notes); await card.NotesEditorCancel(desktopName).ClickAsync(); // viewer should be back, and should NOT show the cancelled notes await Assertions.Expect(card.NotesViewerRoot(desktopName)).ToBeVisibleAsync(); await Assertions.Expect(card.NotesViewerRoot(desktopName)).Not.ToContainTextAsync(notes); // cleanup await card.DeleteDesktopAsync(desktopName); await page.WaitForURLAsync("**/hardware/tree"); } catch (Exception) { _output.WriteLine("TEST FAILED — Capturing diagnostics"); _output.WriteLine($"Current URL: {page.Url}"); _output.WriteLine("==== DOM SNAPSHOT START ===="); _output.WriteLine(await page.ContentAsync()); _output.WriteLine("==== DOM SNAPSHOT END ===="); throw; } finally { await context.CloseAsync(); } } [Fact] public async Task User_Can_Add_And_Remove_Tags_From_Desktop_Card() { (IBrowserContext context, IPage page) = await CreatePageAsync(); var name = $"e2e-ap-{Guid.NewGuid():N}"[..16]; try { await page.GotoAsync(_fixture.BaseUrl); var layout = new MainLayoutPom(page); await layout.AssertLoadedAsync(); await layout.GotoHardwareAsync(); var hardwareTree = new HardwareTreePom(page); await hardwareTree.AssertLoadedAsync(); await hardwareTree.GotoDesktopsListAsync(); var list = new DesktopsListPom(page); await list.AssertLoadedAsync(); await list.AddDesktopAsync(name); await page.WaitForURLAsync($"**/resources/hardware/{name}"); var card = new DesktopCardPom(page); await Assertions.Expect(card.DesktopItem(name)).ToBeVisibleAsync(); TagsPom tags = card.Tags; // ------------------------------------------------- // Add multiple tags in one modal interaction // ------------------------------------------------- await tags.AddTagsAsync("desktop", "Foo", "Bar", "Baz"); await tags.AssertTagVisibleAsync("desktop", "Foo"); await tags.AssertTagVisibleAsync("desktop", "Bar"); await tags.AssertTagVisibleAsync("desktop", "Baz"); // ------------------------------------------------- // Remove a single tag // ------------------------------------------------- await tags.RemoveTagAsync("desktop", "Bar"); await tags.AssertTagNotVisibleAsync("desktop", "Bar"); await tags.AssertTagVisibleAsync("desktop", "Foo"); await tags.AssertTagVisibleAsync("desktop", "Baz"); // ------------------------------------------------- // Reload to verify persistence // ------------------------------------------------- await page.ReloadAsync(); await tags.AssertTagVisibleAsync("desktop", "Foo"); await tags.AssertTagVisibleAsync("desktop", "Baz"); await tags.AssertTagNotVisibleAsync("desktop", "Bar"); await context.CloseAsync(); } finally { await context.CloseAsync(); } } }