Bromine.Playwright.Extensions
1.0.4
dotnet add package Bromine.Playwright.Extensions --version 1.0.4
NuGet\Install-Package Bromine.Playwright.Extensions -Version 1.0.4
<PackageReference Include="Bromine.Playwright.Extensions" Version="1.0.4" />
<PackageVersion Include="Bromine.Playwright.Extensions" Version="1.0.4" />
<PackageReference Include="Bromine.Playwright.Extensions" />
paket add Bromine.Playwright.Extensions --version 1.0.4
#r "nuget: Bromine.Playwright.Extensions, 1.0.4"
#:package Bromine.Playwright.Extensions@1.0.4
#addin nuget:?package=Bromine.Playwright.Extensions&version=1.0.4
#tool nuget:?package=Bromine.Playwright.Extensions&version=1.0.4
Bromine.Playwright.Extensions
A Playwright extensions library for .NET providing fluent assertions (.Should()), browser/context builder patterns, and enhanced Page/Locator extension methods for E2E test automation.
Installation
dotnet add package Bromine.Playwright.Extensions
Or add to your .csproj:
<PackageReference Include="Bromine.Playwright.Extensions" Version="1.0.0" />
Features
1. Fluent Assertions — .Should()
Chain-able, readable assertions for ILocator, IPage, and IAPIResponse.
using Bromine.Playwright.Extensions.Assertions;
// Locator assertions
await page.Locator("#submit-btn").Should()
.BeVisibleAsync()
.HaveCountAsync(1)
.HaveTextAsync("Submit");
// Negated assertions
await page.Locator(".spinner").Should().Not.BeVisibleAsync();
await page.Locator("#old-element").Should().Not.HaveTextAsync("deprecated");
// Chaining multiple assertions
await page.Locator("#submit-btn").Should()
.BeVisibleAsync()
.Not.BeEnabledAsync();
// "because" messages for better failure diagnostics
await page.Locator("#submit-btn").Should()
.BeVisibleAsync(because: "the form should be loaded")
.BeEnabledAsync(because: "the user has filled all required fields");
// Page assertions
await page.Should().HaveTitleAsync("Dashboard");
await page.Should().HaveURLAsync("https://example.com/dashboard");
// Response assertions — fully chainable, same pattern as locator assertions
var response = await request.GetAsync("/api/users");
await response.Should()
.BeOKAsync()
.HaveStatusAsync(200)
.HaveHeaderValueAsync("Content-Type", "application/json")
.BodyContainsAsync("Alice");
// Negated
await response.Should().Not.BeOKAsync();
// With because messages
await response.Should()
.BeOKAsync(because: "the users endpoint should be healthy")
.BodyContainsAsync("Alice", because: "Alice should be in the user list");
2. Browser Builder Pattern
Create Playwright browser instances with a fluent API — no more remembering option class hierarchies.
using Bromine.Playwright.Extensions.Builders;
// Simple headless Chromium
var (playwright, browser) = await PlaywrightBrowserBuilder.Create()
.WithChromium()
.Headless()
.BuildAsync();
// Headed Firefox with slow motion for debugging
var result = await PlaywrightBrowserBuilder.Create()
.WithFirefox()
.Headed()
.WithSlowMotion(100)
.WithTimeout(15_000)
.BuildAsync();
// Chrome channel with proxy
var result = await PlaywrightBrowserBuilder.Create()
.WithChromium()
.WithChannel("chrome")
.Headless()
.WithProxy("http://proxy.company.com:8080")
.BuildAsync();
// Clean disposal
await using var browserSession = await PlaywrightBrowserBuilder.Create()
.WithChromium()
.Headless()
.BuildAsync();
// browserSession.Browser and browserSession.Playwright are available
// both get cleaned up on DisposeAsync
3. Browser Context Builder
Build IBrowserContext instances with all options via fluent API.
using Bromine.Playwright.Extensions.Builders;
// Desktop context with full options
var context = await BrowserContextBuilder.For(browser)
.WithViewport(1920, 1080)
.BypassCSP()
.WithPermissions("clipboard-read", "clipboard-write")
.WithHttpCredentials("admin", "password123")
.WithLocale("en-US")
.WithTimezone("Europe/Athens")
.AcceptDownloads()
.WithDefaultTimeout(30_000)
.WithDefaultNavigationTimeout(30_000)
.BuildAsync();
// Mobile emulation
var mobileContext = await BrowserContextBuilder.For(browser)
.WithDevice(playwright.Devices["iPhone 13"])
.AsMobile()
.WithPermissions("geolocation")
.WithGeolocation(37.9838f, 23.7275f) // Athens
.BuildAsync();
// With HAR + Video recording + Tracing
var debugContext = await BrowserContextBuilder.For(browser)
.WithViewport(1920, 1080)
.WithHarRecording("./traces/network.har", omitContent: true)
.WithVideoRecording("./traces/videos", width: 1920, height: 1080)
.WithTracing(screenshots: true, snapshots: true, sources: true)
.BuildAsync();
// With offline mode & storage state
var offlineContext = await BrowserContextBuilder.For(browser)
.Offline()
.WithStorageState("./auth-state.json")
.BuildAsync();
4. Page Extensions
Enhanced IPage extension methods for common operations.
using Bromine.Playwright.Extensions.Extensions;
// Navigation
await page.NavigateAndWaitAsync("https://example.com");
await page.NavigateAndWaitForDomAsync("https://example.com");
await page.ReloadAndWaitAsync();
await page.WaitForUrlContainingAsync("/dashboard");
await page.WaitForStableStateAsync();
// Cookies
var cookie = await page.GetCookieByNameAsync("session_id");
await page.SetCookieAsync("my_cookie", "value123", domain: ".example.com");
await page.ClearCookiesAsync();
// Screenshots
var bytes = await page.FullPageScreenshotAsync();
await page.FullPageScreenshotAsync("/path/to/screenshot.png");
var base64 = await page.ScreenshotToBase64Async();
// Safe interactions
bool clicked = await page.TryClickAsync(".optional-banner-dismiss");
string? text = await page.GetVisibleTextAsync(".user-greeting");
// Scrolling
await page.ScrollToBottomAsync();
await page.ScrollToTopAsync();
// Downloads
string filePath = await page.ClickAndDownloadAsync("#export-btn", "./downloads");
5. Global Configuration
Set default timeouts once at test startup.
using Bromine.Playwright.Extensions.Configuration;
// In your [BeforeTestRun] or setup
PlaywrightDefaults.AssertionTimeout = 10_000; // 10s for assertions (default: 5s)
PlaywrightDefaults.NavigationTimeout = 60_000; // 60s for navigation (default: 30s)
PlaywrightDefaults.ActionTimeout = 15_000; // 15s for actions (default: 15s)
PlaywrightDefaults.DefaultRetryCount = 3; // 3 retry attempts (default: 3)
PlaywrightDefaults.RetryDelayMs = 500; // 500ms between retries (default: 500ms)
// Reset to defaults
PlaywrightDefaults.Reset();
Static Analysis — Unawaited Assertion Detection
This package ships with a built-in Roslyn analyzer (BROE101) that produces a compile error if you forget to await a fluent assertion chain.
// ❌ BROE101: This fluent assertion chain is not awaited and will never execute.
locator.Should().BeVisibleAsync();
// ✅ Correct
await locator.Should().BeVisibleAsync();
No configuration needed — the analyzer is bundled in the NuGet package and activates automatically.
Running Tests
# Build
dotnet build -c Release
# Install Playwright browsers
pwsh tests/bin/Release/net8.0/playwright.ps1 install chromium --with-deps
# Run tests
dotnet test tests/Bromine.Playwright.Extensions.Tests.csproj -c Release --no-build
Tests run headed locally and headless in CI (detected via the
CIenvironment variable).
CI / CD
Pipelines
| Workflow | Trigger | Description |
|---|---|---|
| CI | Push / PR to main |
Build → Install browsers → Run tests |
| Publish | Tag push (v*) |
Run tests → Pack → Push to nuget.org → GitHub Release |
| Update Playwright | Weekly (Monday 8 AM UTC) | Check for new Playwright version → Update → Test → Open PR |
Publishing a New Version
git tag v1.0.1
git push origin v1.0.1
The tag version (1.0.1) becomes the NuGet package version automatically. Tests must pass before the package is published.
Setup
Add NUGET_API_KEY as a repository secret (Settings → Secrets → Actions). Get your key from nuget.org/account/apikeys.
Building & Packing Locally
Build
dotnet build -c Release
Pack
dotnet pack src/Bromine.Playwright.Extensions.csproj -c Release -o ./nupkgs
The .nupkg includes the library DLL, XML docs, and the bundled Roslyn analyzer.
Install Locally in Another Project
# Add the local folder as a NuGet source (once)
dotnet nuget add source /path/to/nupkgs --name LocalBromine
# Install
dotnet add package Bromine.Playwright.Extensions --version 1.0.0
Requirements
- .NET 8.0+
- Microsoft.Playwright 1.56.0+
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Microsoft.Playwright (>= 1.56.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.