Skip to content

Repository files navigation

playwright-dotnet-framework

CI

A small but production-shaped UI test-automation framework in C# / .NET 10 with Playwright and MSTest. It shows how I structure a maintainable suite — page objects, component objects, isolated fixtures, layered configuration, and proper secret handling — rather than a flat pile of tests.

System under test: SauceDemo, a stable public demo shop chosen deliberately so the suite stays reliable and CI-green.

Why this exists

Most of my automation work lives in private company repositories. This is a sanitized, self-contained showcase of the patterns I use day to day — the kind of framework a team extends, not a script someone runs once.

Tech stack

  • .NET 10 / C#
  • Playwright for browser automation
  • MSTest (classic VSTest runner) with Microsoft.Playwright.MSTest fixtures
  • Microsoft.Extensions.Configuration for layered configuration
  • Chromium, auto-installed on first run

Quickstart

Prerequisite: the .NET 10 SDK.

git clone https://github.com/SnoxSoft/playwright-dotnet-framework.git
cd playwright-dotnet-framework

# Provide the (public) SauceDemo password locally — this file is git-ignored:
cp PlaywrightFramework.Tests/appsettings.local.json.example PlaywrightFramework.Tests/appsettings.local.json
# then set  "Password": "secret_sauce"  in that file

dotnet test

Browser binaries install themselves on the first run (see Fixtures/GlobalSetup.cs), so there is no separate playwright install step.

Project structure

PlaywrightFramework.Tests/
├─ Config/         # layered configuration bound to a strongly-typed TestSettings
├─ Fixtures/       # BaseTest (isolated browser context) + GlobalSetup (browser install)
├─ Pages/          # page objects — one class per screen (LoginPage, InventoryPage)
├─ Components/     # component objects — reusable UI pieces (InventoryItem)
├─ Tests/          # the scenarios (LoginTests, CartTests)
├─ appsettings.json                # non-secret config (committed)
└─ appsettings.local.json.example  # template for the git-ignored secret file

Architecture & decisions

Page objects vs. component objects. A page object models a whole screen and exposes intent (LoginPage.LoginAsync(user, pass)). A component object models a reusable slice of UI scoped to a root locator (InventoryItem), so one class works for any product card. Reach for a component when a widget repeats or spans pages — but don't wrap every single element in one; that trades clarity for ceremony.

Isolated, parallel tests. Every test class extends BaseTest : PageTest, which hands each test its own browser context. No shared state means tests run in parallel ([assembly: Parallelize]) without bleeding into each other.

Configuration & secrets. TestConfig builds a layered configuration and binds it to TestSettings, in increasing precedence:

appsettings.json → appsettings.{TEST_ENV}.json → appsettings.local.json → User Secrets → environment variables

Non-secret defaults are committed; the password lives only in the git-ignored appsettings.local.json, in User Secrets, or in a CI env var (TestSettings__Password). SauceDemo's credentials are public, so they're used openly here — but the mechanism is the real one. A missing value fails fast with an actionable message (TestSettings.Validate()).

Locator strategy. Prefer stable, test-dedicated hooks ([data-test=...]) and user-facing locators (roles, text) over brittle CSS/XPath, so tests survive styling changes.

Tooling choices.

  • MSTest 3.x (classic VSTest runner). The .NET 10 template defaults to the new Microsoft.Testing.Platform runner, which dotnet test and IDE Test Explorers don't discover; pinning 3.x restores the standard path and matches common enterprise setups.
  • Self-installing browsers. GlobalSetup runs playwright install in-process, so the suite is self-sufficient in any runner or CI — no separate setup step.
  • Stable demo target. A flaky target undermines a green badge, so the system under test is intentionally boring and reliable.

Running tests

dotnet test                                     # all tests, headless
dotnet test --filter "FullyQualifiedName~Cart"  # only the cart tests

Set PWDEBUG=1 to run headed and open the Playwright Inspector for step-through debugging. In Rider, open the solution and use the Unit Tests window.

Honest limits

  • The target is a public demo site, so the scenarios are illustrative — the patterns are the point, not exhaustive coverage of a real product.
  • Cross-browser (Firefox/WebKit) isn't wired up yet; the design supports it (add browsers in GlobalSetup and a .runsettings), it's simply out of scope for this showcase.

About

C#/.NET 10 + Playwright test automation framework (MSTest): page objects, component objects, isolated fixtures, layered config and secret handling.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages