|
|
@@ -0,0 +1,225 @@
|
|
|
+using Microsoft.Playwright;
|
|
|
+using Tests.E2e.Infra;
|
|
|
+using Tests.E2e.PageObjectModels;
|
|
|
+using Xunit.Abstractions;
|
|
|
+
|
|
|
+namespace Tests.E2e;
|
|
|
+
|
|
|
+public class OtherCardTests(
|
|
|
+ PlaywrightFixture fixture,
|
|
|
+ ITestOutputHelper output) : E2ETestBase(fixture, output) {
|
|
|
+ private readonly PlaywrightFixture _fixture = fixture;
|
|
|
+ private readonly ITestOutputHelper _output = output;
|
|
|
+
|
|
|
+ // =============================================================
|
|
|
+ // Rename + Clone + Delete Flow
|
|
|
+ // =============================================================
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public async Task User_Can_Rename_Clone_And_Delete_Other_From_Details_Page() {
|
|
|
+ (IBrowserContext context, IPage page) = await CreatePageAsync();
|
|
|
+
|
|
|
+ var originalName = $"e2e-oth-{Guid.NewGuid():N}"[..16];
|
|
|
+ var renamedName = $"e2e-oth-rn-{Guid.NewGuid():N}"[..16];
|
|
|
+ var cloneName = $"e2e-oth-cl-{Guid.NewGuid():N}"[..16];
|
|
|
+
|
|
|
+ try {
|
|
|
+ await page.GotoAsync($"{_fixture.BaseUrl}/other/list");
|
|
|
+
|
|
|
+ var list = new OtherListPom(page);
|
|
|
+ await list.AddOtherAsync(originalName);
|
|
|
+
|
|
|
+ if (!page.Url.Contains($"/resources/hardware/{originalName}",
|
|
|
+ StringComparison.OrdinalIgnoreCase))
|
|
|
+ await list.OpenOtherAsync(originalName);
|
|
|
+
|
|
|
+ var card = new OtherCardPom(page);
|
|
|
+ await card.AssertVisibleAsync(originalName);
|
|
|
+
|
|
|
+ // -------------------------
|
|
|
+ // Rename
|
|
|
+ // -------------------------
|
|
|
+ await card.RenameAsync(originalName, renamedName);
|
|
|
+ await card.AssertVisibleAsync(renamedName);
|
|
|
+
|
|
|
+ // -------------------------
|
|
|
+ // Clone
|
|
|
+ // -------------------------
|
|
|
+ await card.CloneAsync(renamedName, cloneName);
|
|
|
+ await card.AssertVisibleAsync(cloneName);
|
|
|
+
|
|
|
+ // -------------------------
|
|
|
+ // Delete clone
|
|
|
+ // -------------------------
|
|
|
+ await card.DeleteAsync(cloneName);
|
|
|
+ await page.WaitForURLAsync("**/hardware/tree");
|
|
|
+
|
|
|
+ // Navigate back and delete renamed
|
|
|
+ await page.GotoAsync($"{_fixture.BaseUrl}/resources/hardware/{renamedName}");
|
|
|
+ await card.AssertVisibleAsync(renamedName);
|
|
|
+
|
|
|
+ await card.DeleteAsync(renamedName);
|
|
|
+ await page.WaitForURLAsync("**/hardware/tree");
|
|
|
+ }
|
|
|
+ catch (Exception) {
|
|
|
+ _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();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // =============================================================
|
|
|
+ // Edit + Save Flow
|
|
|
+ // =============================================================
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public async Task User_Can_Edit_And_Save_Other() {
|
|
|
+ (IBrowserContext context, IPage page) = await CreatePageAsync();
|
|
|
+ var name = $"e2e-oth-edit-{Guid.NewGuid():N}"[..16];
|
|
|
+
|
|
|
+ try {
|
|
|
+ await page.GotoAsync($"{_fixture.BaseUrl}/other/list");
|
|
|
+
|
|
|
+ var list = new OtherListPom(page);
|
|
|
+ await list.AddOtherAsync(name);
|
|
|
+
|
|
|
+ if (!page.Url.Contains($"/resources/hardware/{name}",
|
|
|
+ StringComparison.OrdinalIgnoreCase))
|
|
|
+ await list.OpenOtherAsync(name);
|
|
|
+
|
|
|
+ var card = new OtherCardPom(page);
|
|
|
+ await card.AssertVisibleAsync(name);
|
|
|
+
|
|
|
+ await card.BeginEditAsync(name);
|
|
|
+
|
|
|
+ await card.ModelInput(name).FillAsync("Building Bridge XG");
|
|
|
+ await card.DescriptionInput(name).FillAsync("Microwave radio bridge");
|
|
|
+
|
|
|
+ await card.SaveAsync(name);
|
|
|
+
|
|
|
+ await Assertions.Expect(
|
|
|
+ card.ModelValue(name)
|
|
|
+ ).ToContainTextAsync("Building Bridge XG");
|
|
|
+
|
|
|
+ await Assertions.Expect(
|
|
|
+ card.DescriptionValue(name)
|
|
|
+ ).ToContainTextAsync("Microwave radio bridge");
|
|
|
+ }
|
|
|
+ finally {
|
|
|
+ await context.CloseAsync();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // =============================================================
|
|
|
+ // Cancel Edit Flow
|
|
|
+ // =============================================================
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public async Task User_Can_Cancel_Other_Edit_Without_Saving() {
|
|
|
+ (IBrowserContext context, IPage page) = await CreatePageAsync();
|
|
|
+ var name = $"e2e-oth-cancel-{Guid.NewGuid():N}"[..16];
|
|
|
+
|
|
|
+ try {
|
|
|
+ await page.GotoAsync($"{_fixture.BaseUrl}/other/list");
|
|
|
+
|
|
|
+ var list = new OtherListPom(page);
|
|
|
+ await list.AddOtherAsync(name);
|
|
|
+
|
|
|
+ if (!page.Url.Contains($"/resources/hardware/{name}",
|
|
|
+ StringComparison.OrdinalIgnoreCase))
|
|
|
+ await list.OpenOtherAsync(name);
|
|
|
+
|
|
|
+ var card = new OtherCardPom(page);
|
|
|
+ await card.AssertVisibleAsync(name);
|
|
|
+
|
|
|
+ await card.BeginEditAsync(name);
|
|
|
+
|
|
|
+ await card.ModelInput(name).FillAsync("ShouldNotPersist");
|
|
|
+ await card.DescriptionInput(name).FillAsync("ShouldNotPersist");
|
|
|
+
|
|
|
+ await card.CancelAsync(name);
|
|
|
+
|
|
|
+ // Verify edit mode exited
|
|
|
+ await Assertions.Expect(
|
|
|
+ card.EditButton(name)
|
|
|
+ ).ToBeVisibleAsync();
|
|
|
+ }
|
|
|
+ finally {
|
|
|
+ await context.CloseAsync();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public async Task User_Can_Add_And_Remove_Tags_From_Other_Card() {
|
|
|
+ (IBrowserContext context, IPage page) = await CreatePageAsync();
|
|
|
+ var name = $"e2e-oth-{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.GotoOtherListAsync();
|
|
|
+
|
|
|
+ var list = new OtherListPom(page);
|
|
|
+ await list.AssertLoadedAsync();
|
|
|
+
|
|
|
+ await list.AddOtherAsync(name);
|
|
|
+ await page.WaitForURLAsync($"**/resources/hardware/{name}");
|
|
|
+
|
|
|
+ var card = new OtherCardPom(page);
|
|
|
+ await card.AssertVisibleAsync(name);
|
|
|
+
|
|
|
+ TagsPom tags = card.Tags;
|
|
|
+
|
|
|
+ // -------------------------------------------------
|
|
|
+ // Add multiple tags in one modal interaction
|
|
|
+ // -------------------------------------------------
|
|
|
+
|
|
|
+ await tags.AddTagsAsync("other", "Foo", "Bar", "Baz");
|
|
|
+
|
|
|
+ await tags.AssertTagVisibleAsync("other", "Foo");
|
|
|
+ await tags.AssertTagVisibleAsync("other", "Bar");
|
|
|
+ await tags.AssertTagVisibleAsync("other", "Baz");
|
|
|
+
|
|
|
+ // -------------------------------------------------
|
|
|
+ // Remove a single tag
|
|
|
+ // -------------------------------------------------
|
|
|
+
|
|
|
+ await tags.RemoveTagAsync("other", "Bar");
|
|
|
+
|
|
|
+ await tags.AssertTagNotVisibleAsync("other", "Bar");
|
|
|
+ await tags.AssertTagVisibleAsync("other", "Foo");
|
|
|
+ await tags.AssertTagVisibleAsync("other", "Baz");
|
|
|
+
|
|
|
+ // -------------------------------------------------
|
|
|
+ // Reload to verify persistence
|
|
|
+ // -------------------------------------------------
|
|
|
+
|
|
|
+ await page.ReloadAsync();
|
|
|
+
|
|
|
+ await tags.AssertTagVisibleAsync("other", "Foo");
|
|
|
+ await tags.AssertTagVisibleAsync("other", "Baz");
|
|
|
+ await tags.AssertTagNotVisibleAsync("other", "Bar");
|
|
|
+
|
|
|
+ await context.CloseAsync();
|
|
|
+ }
|
|
|
+ finally {
|
|
|
+ await context.CloseAsync();
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|