using Microsoft.Playwright; using Microsoft.Playwright.NUnit; using NUnit.Framework; namespace JoshHeaps.Net.UiTests; [TestFixture] public class GameplayTests : PageTest { private TestConfiguration Config => TestConfiguration.Instance; public override BrowserNewContextOptions ContextOptions() { return Config.GetBrowserContextOptions(); } [OneTimeSetUp] public async Task OneTimeSetUp() { if (!Config.Playwright.Headless) { Environment.SetEnvironmentVariable("HEADED", "1"); } if (Config.Playwright.SlowMotion > 0) { Environment.SetEnvironmentVariable("PWSLOWMO", Config.Playwright.SlowMotion.ToString()); } } [SetUp] public async Task Setup() { await Page.GotoAsync($"{Config.Test.BaseUrl}/chess"); await Page.WaitForLoadStateAsync(LoadState.NetworkIdle); } [Test] public async Task Scholars_Mate_Results_In_Checkmate() { // Start a multiplayer game await Page.ClickAsync("#startGameBtn"); await Page.WaitForSelectorAsync(".chessPiece"); if (await Page.Locator("#square-55:has(img[src='/images/Chess Images/WhitePawn.svg'])").CountAsync() == 0) { await Page.ClickAsync("#startGameBtn"); await Page.WaitForSelectorAsync(".chessPiece"); } // Create a second browser context for the second player await using var browser = await Playwright.Chromium.LaunchAsync(); await using var context2 = await browser.NewContextAsync(ContextOptions()); var page2 = await context2.NewPageAsync(); await page2.GotoAsync($"{Config.Test.BaseUrl}/chess"); await page2.ClickAsync("#startGameBtn"); await page2.WaitForSelectorAsync(".chessPiece"); /* * Scholar's Mate sequence: * 1. e4 e5 * 2. Bc4 Nc6 * 3. Qh5 Nf6?? * 4. Qxf7# (checkmate) * * In source square to destination square, accounting for black ui swapping: * 1. 52-36 51-35 * 2. 61-34 58-37 * 3. 59-45 62-45 * 4. 45-13 */ // Move 1: White plays e4 (pawn e2-e4) await MakeMove(Page, 52, 36); // e2 to e4 // Move 1: Black plays e5 (pawn e7-e5) await MakeMove(page2, 51, 35); // e7 to e5 // Move 2: White plays Bc4 (bishop f1-c4) await MakeMove(Page, 61, 34); // f1 to c4 // Move 2: Black plays Nc6 (knight b8-c6) await MakeMove(page2, 58, 37); // b8 to c6 // Move 3: White plays Qh5 (queen d1-h5) await MakeMove(Page, 59, 45); // d1 to h5 // Move 3: Black plays Nf6 (knight g8-f6) - the blunder await MakeMove(page2, 62, 45); // g8 to f6 string? alertMessage = null; Page.Dialog += async (_, dialog) => { alertMessage = dialog.Message; await dialog.AcceptAsync(); }; // Move 4: White plays Qxf7# (queen h5-f7, checkmate) await MakeMove(Page, 45, 13); // h5 to f7 // The test passes if we can execute all moves without errors // Specific checkmate verification depends on how your UI handles game end Assert.That(alertMessage, Is.Not.Null); Assert.That(alertMessage, Contains.Substring("Checkmate")); } [Test] public async Task Pawn_Promotion_Shows_Modal() { // Start a multiplayer game await Page.ClickAsync("#startGameBtn"); await Page.WaitForSelectorAsync(".chessPiece"); if (await Page.Locator("#square-55:has(img[src='/images/Chess Images/WhitePawn.svg'])").CountAsync() == 0) { await Page.ClickAsync("#startGameBtn"); await Page.WaitForSelectorAsync(".chessPiece"); } // Create a second browser context for the second player (black) await using var browser = await Playwright.Chromium.LaunchAsync(); await using var context2 = await browser.NewContextAsync(ContextOptions()); var page2 = await context2.NewPageAsync(); await page2.GotoAsync($"{Config.Test.BaseUrl}/chess"); await page2.ClickAsync("#startGameBtn"); await page2.WaitForSelectorAsync(".chessPiece"); // Exact sequence: 1. h4 g5 2. hxg5 h6 3. gxh6 a6 4. h7 a5 5. hxg8=Q // in square numbers: 55-39 49-33 39-30 48-40 30-23 55-47 23-15 47-39 15-6 // Move 1: White h4 (h2-h4, square 55 to 39) await MakeMove(Page, 55, 39); // h2 to h4 // Move 1: Black g5 (g7-g5, square 14 to 30) await MakeMove(page2, 49, 33); // g7 to g5 // Move 2: White hxg5 (h4 captures g5, square 39 to 30) await MakeMove(Page, 39, 30); // h4 captures g5 // Move 2: Black h6 (h7-h6, square 15 to 23) await MakeMove(page2, 48, 40); // h7 to h6 // Move 3: White gxh6 (g5 captures h6, square 30 to 23) await MakeMove(Page, 30, 23); // g5 captures h6 // Move 3: Black a6 (a7-a6, square 8 to 16) await MakeMove(page2, 55, 47); // a7 to a6 // Move 4: White h7 (h6-h7, square 23 to 15) await MakeMove(Page, 23, 15); // h6 to h7 // Move 4: Black a5 (a6-a5, square 16 to 24) await MakeMove(page2, 47, 39); // a6 to a5 // Move 5: White hxg8=Q (h7 captures g8 and promotes, square 15 to 6) await MakeMove(Page, 15, 6); // h7 captures g8 (promotion) // Check if promotion modal appears var promotionModal = Page.Locator("#promotionModal"); var queenOption = Page.Locator("button:has(img[alt='Queen'])"); var promotedQueenSquare = Page.Locator("#square-6:has(img[src='/images/Chess Images/WhiteQueen.svg'])"); var isModalVisible = await promotionModal.IsVisibleAsync(); await Expect(promotionModal).ToBeVisibleAsync(); await Expect(queenOption).ToBeVisibleAsync(); await queenOption.ClickAsync(); await promotionModal.WaitForAsync(new() { State = WaitForSelectorState.Hidden }); await Expect(promotedQueenSquare).ToBeVisibleAsync(); } private async Task MakeMove(IPage page, int fromSquare, int toSquare) { // Click on the source square/piece var fromSquareElement = page.Locator($"#square-{fromSquare}"); var piece = fromSquareElement.Locator(".chessPiece"); if (await piece.CountAsync() > 0) { await piece.ClickAsync(); await page.WaitForTimeoutAsync(500); } // Click on the destination square var toSquareElement = page.Locator($"#square-{toSquare}"); await toSquareElement.ClickAsync(); await page.WaitForTimeoutAsync(1000); } }