Merge pull request #7 from JoshHeaps/tests/CreatePlaywrightTests

Add playwright tests with github actions
This commit is contained in:
Josh Heaps
2025-09-24 14:43:01 -06:00
committed by GitHub
14 changed files with 894 additions and 5 deletions
+9
View File
@@ -0,0 +1,9 @@
{
"permissions": {
"allow": [
"Bash(dotnet test:*)"
],
"deny": [],
"ask": []
}
}
+92
View File
@@ -0,0 +1,92 @@
name: Playwright Tests
on:
pull_request:
branches: [ master, main ]
push:
branches: [ master, main ]
jobs:
playwright-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build solution
run: dotnet build --no-restore
- name: Install Playwright browsers
run: |
cd JoshHeaps.Net.UiTests
dotnet build
# Install Chromium browser
pwsh bin/Debug/net8.0/playwright.ps1 install chromium
# Install system dependencies manually for Ubuntu 24.04 compatibility
sudo apt-get update
sudo apt-get install -y \
libasound2t64 \
libatk-bridge2.0-0 \
libdrm2 \
libxkbcommon0 \
libxcomposite1 \
libxdamage1 \
libxrandr2 \
libgbm1 \
libxss1 \
libasound2-data \
xvfb
- name: Start server in background
run: |
cd JoshHeaps.Net
dotnet run --urls "https://localhost:7118" &
echo $! > server.pid
# Wait for server to start - try health endpoint first, then chess page
echo "Waiting for server to start..."
timeout 60 bash -c 'until curl -k -s https://localhost:7118/health >/dev/null 2>&1 || curl -k -s https://localhost:7118/chess >/dev/null 2>&1; do echo "Waiting..."; sleep 2; done'
echo "Server appears to be running"
sleep 3
- name: Run Playwright tests
run: |
cd JoshHeaps.Net.UiTests
xvfb-run -a dotnet test --logger "trx;LogFileName=test-results.trx" --logger "console;verbosity=detailed"
env:
CI: true
GITHUB_ACTIONS: true
DISPLAY: :99
- name: Stop server
if: always()
run: |
if [ -f JoshHeaps.Net/server.pid ]; then
kill $(cat JoshHeaps.Net/server.pid) || true
fi
pkill -f "dotnet run" || true
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-test-results
path: |
JoshHeaps.Net.UiTests/TestResults/
JoshHeaps.Net.UiTests/test-results/
retention-days: 7
- name: Upload Playwright videos
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-videos
path: JoshHeaps.Net.UiTests/test-results/videos/
retention-days: 7
+119
View File
@@ -0,0 +1,119 @@
using Microsoft.Playwright;
using Microsoft.Playwright.NUnit;
using NUnit.Framework;
using System.Text.Json;
namespace JoshHeaps.Net.UiTests;
[TestFixture]
public class ApiTests : PageTest
{
private IAPIRequestContext? _apiContext;
private TestConfiguration Config => TestConfiguration.Instance;
[SetUp]
public async Task Setup()
{
_apiContext = await Playwright.APIRequest.NewContextAsync(new()
{
BaseURL = Config.Test.BaseUrl,
IgnoreHTTPSErrors = true
});
}
[TearDown]
public async Task TearDown()
{
if (_apiContext != null)
{
await _apiContext.DisposeAsync();
}
}
[Test]
public async Task JoinGame_Returns_Valid_Response()
{
var response = await _apiContext!.GetAsync("/api/chess/JoinGame");
Assert.That(response.Status, Is.EqualTo(200));
var jsonResponse = await response.JsonAsync();
var gameData = JsonSerializer.Deserialize<JsonElement>(jsonResponse.ToString()!);
Assert.That(gameData.TryGetProperty("id", out _), Is.True);
Assert.That(gameData.TryGetProperty("isWhite", out _), Is.True);
Assert.That(gameData.TryGetProperty("gameId", out _), Is.True);
}
[Test]
public async Task CreateCPUGame_Returns_Valid_Response()
{
var response = await _apiContext!.GetAsync("/api/chess/new/5");
Assert.That(response.Status, Is.EqualTo(200));
var jsonResponse = await response.JsonAsync();
var gameData = JsonSerializer.Deserialize<JsonElement>(jsonResponse.ToString()!);
Assert.That(gameData.TryGetProperty("id", out _), Is.True);
Assert.That(gameData.TryGetProperty("isWhite", out _), Is.True);
Assert.That(gameData.TryGetProperty("gameId", out _), Is.True);
}
[Test]
public async Task GetGameState_Returns_Valid_Game_Data()
{
// First create a game
var createResponse = await _apiContext!.GetAsync("/api/chess/JoinGame");
var createData = JsonSerializer.Deserialize<JsonElement>((await createResponse.JsonAsync()).ToString()!);
var gameId = createData.GetProperty("gameId").GetString();
// Then get the game state
var response = await _apiContext.GetAsync($"/api/chess/{gameId}");
Assert.That(response.Status, Is.EqualTo(200));
var jsonResponse = await response.JsonAsync();
var gameState = JsonSerializer.Deserialize<JsonElement>(jsonResponse.ToString()!);
Assert.That(gameState.TryGetProperty("gameId", out _), Is.True);
Assert.That(gameState.TryGetProperty("currentPlayer", out _), Is.True);
Assert.That(gameState.TryGetProperty("pieces", out var pieces), Is.True);
// Should have 32 pieces initially
Assert.That(pieces.GetArrayLength(), Is.EqualTo(32));
}
[Test]
public async Task GetLegalMoves_Returns_Valid_Moves()
{
// First create a game
var createResponse = await _apiContext!.GetAsync("/api/chess/JoinGame");
var createData = JsonSerializer.Deserialize<JsonElement>((await createResponse.JsonAsync()).ToString()!);
var gameId = createData.GetProperty("gameId").GetString();
// Get game state to find a piece
var gameStateResponse = await _apiContext.GetAsync($"/api/chess/{gameId}");
var gameState = JsonSerializer.Deserialize<JsonElement>((await gameStateResponse.JsonAsync()).ToString()!);
var pieces = gameState.GetProperty("pieces");
var firstPiece = pieces[0];
var pieceId = firstPiece.GetProperty("id").GetString();
// Get legal moves for the piece
var response = await _apiContext.GetAsync($"/api/chess/{gameId}/legalMoves/{pieceId}");
Assert.That(response.Status, Is.EqualTo(200));
var moves = await response.JsonAsync();
Assert.That(moves, Is.Not.Null);
}
[Test]
public async Task Invalid_GameId_Returns_NotFound()
{
var invalidGameId = Guid.NewGuid().ToString();
var response = await _apiContext!.GetAsync($"/api/chess/{invalidGameId}");
Assert.That(response.Status, Is.EqualTo(404));
}
}
+121
View File
@@ -0,0 +1,121 @@
using Microsoft.Playwright;
using Microsoft.Playwright.NUnit;
using NUnit.Framework;
namespace JoshHeaps.Net.UiTests;
[TestFixture]
public class ChessGameTests : PageTest
{
private TestConfiguration Config => TestConfiguration.Instance;
public override BrowserNewContextOptions ContextOptions()
{
return Config.GetBrowserContextOptions();
}
[OneTimeSetUp]
public async Task OneTimeSetUp()
{
// Set Playwright browser options for headless control
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);
}
protected async Task WaitDefault()
{
await Page.WaitForTimeoutAsync(Config.Test.WaitTimeout);
}
[Test]
public async Task Chess_Page_Loads_Successfully()
{
await Expect(Page.Locator("h1")).ToContainTextAsync("Chess");
await Expect(Page.Locator("#startGameBtn")).ToBeVisibleAsync();
await Expect(Page.Locator("#startCPUGame")).ToBeVisibleAsync();
await Expect(Page.Locator("#chessBoard")).ToBeVisibleAsync();
}
[Test]
public async Task Chess_Board_Has_64_Squares()
{
var squares = Page.Locator(".chessSquare");
await Expect(squares).ToHaveCountAsync(64);
}
[Test]
public async Task Start_New_Game_Button_Works()
{
await Page.ClickAsync("#startGameBtn");
// Wait for game to start (pieces should appear)
await Page.Locator(".chessPiece").First.WaitForAsync();
// Check that pieces have been rendered
var pieces = Page.Locator(".chessPiece");
await Expect(pieces).ToHaveCountAsync(32); // Standard chess has 32 pieces
}
[Test]
public async Task Start_CPU_Game_Shows_Difficulty_Modal()
{
await Page.ClickAsync("#startCPUGame");
// Should show difficulty modal
await Expect(Page.Locator("#difficultyModal")).ToBeVisibleAsync();
await Expect(Page.Locator("#difficultyModal p")).ToContainTextAsync("Set bot difficulty to:");
// Should have difficulty buttons 1-20
var difficultyButtons = Page.Locator("#difficultyButtonContainer button");
await Expect(difficultyButtons).ToHaveCountAsync(20);
}
[Test]
public async Task CPU_Game_Starts_After_Selecting_Difficulty()
{
await Page.ClickAsync("#startCPUGame");
// Select difficulty 5
await Page.ClickAsync("#difficultyButtonContainer button:nth-child(5)");
// Modal should close
await Expect(Page.Locator("#difficultyModal")).ToBeHiddenAsync();
// Wait for game to start
await Page.Locator(".chessPiece").First.WaitForAsync();
// Check that pieces have been rendered
var pieces = Page.Locator(".chessPiece");
await Expect(pieces).ToHaveCountAsync(32);
}
[Test]
public async Task Pieces_WhenClicked_HighlightLegalMoves()
{
// Start a new game first
await Page.ClickAsync("#startGameBtn");
// Wait for pieces to be rendered
await Page.Locator(".chessPiece").First.WaitForAsync();
// Find any white piece and click it
var pawn = Page.Locator("#chessBoard div:has(img[src*='WhitePawn'])").First;
await pawn.ClickAsync();
var legalMoves = Page.Locator(".legal");
await Expect(legalMoves).ToHaveCountAsync(2);
}
}
+190
View File
@@ -0,0 +1,190 @@
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);
}
}
@@ -0,0 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.9" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.9" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="Microsoft.Playwright.NUnit" Version="1.27.1" />
<PackageReference Include="NUnit" Version="3.14.0" />
<PackageReference Include="NUnit.Analyzers" Version="3.9.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
</ItemGroup>
<ItemGroup>
<Using Include="Microsoft.Playwright.NUnit" />
<Using Include="NUnit.Framework" />
<Using Include="System.Text.RegularExpressions" />
<Using Include="System.Threading.Tasks" />
</ItemGroup>
<ItemGroup>
<Content Include="appsettings.Integration.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="appsettings.CI.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>
+98
View File
@@ -0,0 +1,98 @@
using Microsoft.Playwright;
using Microsoft.Playwright.NUnit;
using NUnit.Framework;
namespace JoshHeaps.Net.UiTests;
[TestFixture]
public class MultiplayerTests : 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 Two_Players_Can_Join_Same_Game()
{
// This test requires running two browser contexts
await using var browser = await Playwright.Chromium.LaunchAsync();
await using var context1 = await browser.NewContextAsync(ContextOptions());
await using var context2 = await browser.NewContextAsync(ContextOptions());
var page1 = await context1.NewPageAsync();
var page2 = await context2.NewPageAsync();
await page1.GotoAsync($"{Config.Test.BaseUrl}/chess");
await page2.GotoAsync($"{Config.Test.BaseUrl}/chess");
// Player 1 starts a game
await page1.ClickAsync("#startGameBtn");
await page1.Locator(".chessPiece").First.WaitForAsync();
// Player 2 joins the same game
await page2.ClickAsync("#startGameBtn");
await page2.Locator(".chessPiece").First.WaitForAsync();
// Both players should see pieces
var pieces1 = page1.Locator(".chessPiece");
var pieces2 = page2.Locator(".chessPiece");
await Expect(pieces1).ToHaveCountAsync(32);
await Expect(pieces2).ToHaveCountAsync(32);
}
[Test]
public async Task SignalR_Connection_Established()
{
// Start a game to trigger SignalR connection
await Page.ClickAsync("#startGameBtn");
await Page.Locator(".chessPiece").First.WaitForAsync();
// Check browser console for SignalR connection message
var consoleLogs = new List<string>();
Page.Console += (_, e) => consoleLogs.Add(e.Text);
await Page.ReloadAsync();
await Page.WaitForTimeoutAsync(1000);
// Should see SignalR connected message in console
Assert.That(consoleLogs, Does.Contain("✅ SignalR connected").Or.Contain("SignalR connected"));
}
[Test]
public async Task Move_Updates_Are_Sent_Via_SignalR()
{
// This would require more complex setup with two players
// For now, just verify the SignalR methods exist in the JavaScript
var jsContent = await Page.EvaluateAsync<string>(@"
() => {
return document.querySelector('script[src*=""chessLogic.js""]') ? 'chessLogic.js loaded' : 'not found';
}
");
Assert.That(jsContent, Is.EqualTo("chessLogic.js loaded"));
}
}
+61
View File
@@ -0,0 +1,61 @@
# JoshHeaps.Net UI Tests
This project contains end-to-end tests for the JoshHeaps.Net website using Playwright.
## Setup
1. Install Playwright browsers:
```bash
# From the test project directory
dotnet build
# Then install browsers (requires PowerShell or appropriate command for your OS)
bin/Debug/net8.0/playwright.cmd install # Windows
```
2. Make sure the main application is running on `https://localhost:7065`
## Running Tests
```bash
# Run all tests
dotnet test
# Run specific test file
dotnet test --filter "FullyQualifiedName~ChessGameTests"
# Run with verbose output
dotnet test --logger "console;verbosity=detailed"
```
## Test Categories
### ChessGameTests
- Basic chess page functionality
- Game start/stop operations
- Piece interaction and move validation
- UI element verification
### MultiplayerTests
- Two-player game scenarios
- SignalR connection testing
- Real-time move synchronization
### ApiTests
- Chess API endpoint testing
- Game creation and state management
- Move validation at API level
- Error handling
## Configuration
The tests are configured to:
- Use headless browsers by default
- Take screenshots on failure
- Record videos on failure
- Automatically start the web server if needed (port 7065)
## Notes
- Tests expect the main application to be available at `https://localhost:7065`
- Some tests require browsers to be installed via Playwright CLI
- Browser installations may require PowerShell on Windows systems
@@ -0,0 +1,97 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Playwright;
namespace JoshHeaps.Net.UiTests;
public class TestConfiguration
{
private static TestConfiguration? _instance;
private static readonly object _lock = new object();
public PlaywrightSettings Playwright { get; }
public TestSettings Test { get; }
private TestConfiguration()
{
var isCI = Environment.GetEnvironmentVariable("CI") == "true" ||
Environment.GetEnvironmentVariable("GITHUB_ACTIONS") == "true";
var configFile = isCI ? "appsettings.CI.json" : "appsettings.Integration.json";
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile(configFile, optional: true, reloadOnChange: false)
.AddJsonFile("appsettings.Integration.json", optional: true, reloadOnChange: false) // fallback
.Build();
Playwright = configuration.GetSection("PlaywrightSettings").Get<PlaywrightSettings>() ?? new PlaywrightSettings();
Test = configuration.GetSection("TestSettings").Get<TestSettings>() ?? new TestSettings();
}
public static TestConfiguration Instance
{
get
{
if (_instance == null)
{
lock (_lock)
{
if (_instance == null)
{
_instance = new TestConfiguration();
}
}
}
return _instance;
}
}
public BrowserNewContextOptions GetBrowserContextOptions()
{
return new BrowserNewContextOptions
{
ViewportSize = new ViewportSize
{
Width = Playwright.Viewport.Width,
Height = Playwright.Viewport.Height
},
RecordVideoDir = Playwright.EnableVideoRecording ? Path.Combine(Directory.GetCurrentDirectory(), "test-results", "videos") : null,
IgnoreHTTPSErrors = Playwright.IgnoreHTTPSErrors
};
}
public BrowserTypeLaunchOptions GetLaunchOptions()
{
return new BrowserTypeLaunchOptions
{
Headless = Playwright.Headless,
SlowMo = Playwright.SlowMotion,
Timeout = Playwright.BrowserTimeout
};
}
}
public class PlaywrightSettings
{
public bool Headless { get; set; } = true;
public float SlowMotion { get; set; } = 0;
public ViewportSettings Viewport { get; set; } = new();
public float BrowserTimeout { get; set; } = 30000;
public float ActionTimeout { get; set; } = 10000;
public bool EnableVideoRecording { get; set; } = false;
public bool IgnoreHTTPSErrors { get; set; } = false;
}
public class ViewportSettings
{
public int Width { get; set; } = 1280;
public int Height { get; set; } = 720;
}
public class TestSettings
{
public string BaseUrl { get; set; } = "https://localhost:7118";
public int WaitTimeout { get; set; } = 3000;
public bool EnableScreenshots { get; set; } = true;
public bool EnableVideoRecording { get; set; } = false;
}
View File
+20
View File
@@ -0,0 +1,20 @@
{
"PlaywrightSettings": {
"Headless": true,
"SlowMotion": 0,
"Viewport": {
"Width": 1280,
"Height": 720
},
"BrowserTimeout": 60000,
"ActionTimeout": 30000,
"EnableVideoRecording": true,
"IgnoreHTTPSErrors": true
},
"TestSettings": {
"BaseUrl": "https://localhost:7118",
"WaitTimeout": 5000,
"EnableScreenshots": true,
"EnableVideoRecording": true
}
}
@@ -0,0 +1,19 @@
{
"PlaywrightSettings": {
"Headless": false,
"SlowMotion": 500,
"Viewport": {
"Width": 1280,
"Height": 720
},
"BrowserTimeout": 30000,
"ActionTimeout": 10000,
"IgnoreHTTPSErrors": true
},
"TestSettings": {
"BaseUrl": "https://localhost:7118",
"WaitTimeout": 3000,
"EnableScreenshots": true,
"EnableVideoRecording": true
}
}
@@ -0,0 +1,24 @@
{
"use": {
"headless": true,
"viewport": { "width": 1280, "height": 720 },
"ignoreHTTPSErrors": true,
"video": "retain-on-failure",
"screenshot": "only-on-failure"
},
"projects": [
{
"name": "chromium",
"use": { "browserName": "chromium" }
},
{
"name": "firefox",
"use": { "browserName": "firefox" }
}
],
"webServer": {
"command": "dotnet run --project ../JoshHeaps.Net",
"port": 7065,
"reuseExistingServer": true
}
}
+5 -5
View File
@@ -5,7 +5,7 @@ VisualStudioVersion = 17.9.34728.123
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JoshHeaps.Net", "JoshHeaps.Net\JoshHeaps.Net.csproj", "{9F0182CC-470F-4D1A-99F5-348D7921751E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{82206AD3-19DF-4DDA-9647-02B691B78CE8}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JoshHeaps.Net.UiTests", "JoshHeaps.Net.UiTests\JoshHeaps.Net.UiTests.csproj", "{360264F4-8292-4EB3-B67D-98376C13438B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -17,10 +17,10 @@ Global
{9F0182CC-470F-4D1A-99F5-348D7921751E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9F0182CC-470F-4D1A-99F5-348D7921751E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9F0182CC-470F-4D1A-99F5-348D7921751E}.Release|Any CPU.Build.0 = Release|Any CPU
{82206AD3-19DF-4DDA-9647-02B691B78CE8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{82206AD3-19DF-4DDA-9647-02B691B78CE8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{82206AD3-19DF-4DDA-9647-02B691B78CE8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{82206AD3-19DF-4DDA-9647-02B691B78CE8}.Release|Any CPU.Build.0 = Release|Any CPU
{360264F4-8292-4EB3-B67D-98376C13438B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{360264F4-8292-4EB3-B67D-98376C13438B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{360264F4-8292-4EB3-B67D-98376C13438B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{360264F4-8292-4EB3-B67D-98376C13438B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE