using System.Text.RegularExpressions; using Microsoft.Playwright; using Microsoft.Playwright.NUnit; using Tests.PageObjects; namespace Tests; [Parallelizable(ParallelScope.Self)] [TestFixture] public class DeckBuilderTests : PageTest { private DeckBuilderPage _builderPage = null!; private const string BaseUrl = "http://localhost:5256"; private Guid _savedDeckId = Guid.Empty; [SetUp] public void Setup() { _builderPage = new DeckBuilderPage(Page); } [TearDown] public async Task Cleanup() { if (_savedDeckId == Guid.Empty) return; using var client = new HttpClient(); await client.DeleteAsync($"{BaseUrl}/api/decks/{_savedDeckId}"); } [Test] public async Task DeckBuilder_AddFortyCards_TwoDivers_SavesToDB() { await _builderPage.GotoAsync(); await _builderPage.WaitForInteractiveAsync(); var deckName = $"Test Deck {DateTime.UtcNow:yyyyMMdd-HHmmss}"; // Add 40 cards first — this proves the Blazor SignalR circuit is active. // Click the first 13 browser cards 3 times each (39), then the 14th once (40th). for (var i = 0; i < 13; i++) { for (var copy = 0; copy < 3; copy++) { await _builderPage.BrowserCards.Nth(i).ClickAsync(); } } await _builderPage.BrowserCards.Nth(13).ClickAsync(); // Verify the deck has exactly 40 cards await Expect(_builderPage.CardCountLabel).ToContainTextAsync("40", new() { Timeout = 15_000 }); // Circuit is confirmed active — now fill the deck name so @bind:event=oninput fires correctly await _builderPage.NameInput.ClickAsync(); await _builderPage.NameInput.FillAsync(deckName); await _builderPage.NameInput.PressAsync("Tab"); // Add first diver await _builderPage.DiverSlots.Nth(0).ClickAsync(); await Expect(_builderPage.DiverPickerCards.First).ToBeVisibleAsync(new() { Timeout = 5_000 }); await _builderPage.DiverPickerCards.Nth(0).ClickAsync(); // Add second diver await _builderPage.DiverSlots.Nth(1).ClickAsync(); await Expect(_builderPage.DiverPickerCards.First).ToBeVisibleAsync(new() { Timeout = 5_000 }); await _builderPage.DiverPickerCards.Nth(0).ClickAsync(); // Save the deck await _builderPage.SaveButton.ClickAsync(); // Confirm save succeeded before waiting for redirect await Expect(_builderPage.SaveToast).ToContainTextAsync("Saved!", new() { Timeout = 10_000 }); // After first save the page redirects to /deck-builder/{guid} await Page.WaitForURLAsync( new Regex(@"/deck-builder/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"), new() { Timeout = 10_000 } ); // Extract saved deck ID from URL for cleanup var url = Page.Url; var match = Regex.Match(url, @"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"); if (match.Success) _savedDeckId = Guid.Parse(match.Value); // Verify save toast await Expect(_builderPage.SaveToast).ToContainTextAsync("Saved!", new() { Timeout = 5_000 }); // Navigate to My Decks and confirm the deck is listed with correct name await Page.GotoAsync($"{BaseUrl}/my-decks"); await Page.WaitForLoadStateAsync(LoadState.NetworkIdle); await Page.WaitForSelectorAsync(".deck-card", new() { Timeout = 10_000 }); await Expect(Page.Locator(".deck-name").First).ToContainTextAsync(deckName[..10]); } }