Test Automation — Selenium, Playwright, API and BDD with C#
In short: Many fast unit tests at the bottom, fewer API/integration tests in the middle, and a small number of slow UI/E2E tests at the top.
- It gives fast feedback and stable suites: most logic is tested cheaply below the UI, while a few E2E tests confirm critical user journeys.
- The anti-pattern is the "ice-cream cone" (mostly UI tests) — slow and flaky.
In short: Automate stable, repetitive, high-value checks: regression, smoke, data-driven cases, API contracts, cross-browser critical paths.
- Don't automate exploratory and usability testing, one-off checks, features that change daily, or anything where the maintenance costs more than running it manually.
In short: Prefer stable, unique attributes: data-testid/data-qa → id → name → CSS selectors → relative XPath (text, attributes, axes) → link text.
- Avoid absolute XPath and generated class names.
- If the app lacks good attributes, ask developers to add test ids — it's the most effective fix for brittle tests.
In short: Implicit: a global timeout applied to every element lookup.
- Explicit:
WebDriverWaitwaiting for a specific condition (visible, clickable, text present) up to a timeout — recommended. - Fluent: an explicit wait with configurable polling and ignored exceptions.
- Never use
Thread.Sleep, and don't mix implicit and explicit waits (timeouts combine unpredictably).
In short: A design pattern where each page or component is a class that owns its locators and exposes user actions (LoginAs, AddToCart).
- Tests read like user steps, locators live in one place (one fix when the UI changes), and code is reused.
- Variants: Page Factory (Java), component objects, Screenplay pattern.
In short: Playwright for new web projects: auto-waiting and retrying assertions (less flakiness), fast isolated browser contexts, built-in tracing, network mocking, API testing, parallel runs and codegen.
- Selenium when you need the W3C standard, the widest language/browser support (including real Safari and legacy browsers), Appium for mobile, an existing large Selenium framework, or vendor grids.
- Both work well with C#.
In short: It means the element you hold was removed or re-rendered in the DOM.
- Re-locate the element right before using it (page-object properties that find each time, not cached fields), wait for the page update to finish (explicit wait on a condition), and include it in ignored exceptions of a fluent wait for dynamic grids.
In short: Language and runner matching the team (C# + NUnit/xUnit), Selenium or Playwright for UI and HttpClient/RestSharp for API, layered structure (tests → pages/components → core: driver factory, config, waits, logging → data builders → API helpers), configuration per environment, test data created via API, parallel execution, screenshots/traces and reports on failure, CI integration with categories (smoke/regression), and coding standards with code review.
- Optionally Reqnroll for BDD.
In short: Using HttpClient/RestSharp/Postman: send requests with correct and incorrect data, check status codes (200/201/204/400/401/403/404/409), response body and JSON schema, headers, error format, authentication and authorisation per role, boundary values, pagination and sorting, idempotency of PUT/DELETE, and response time.
- Chain requests (create → get → update → delete) and clean up data.
In short: Running the same test logic with multiple data sets: NUnit [TestCase]/[TestCaseSource], xUnit [Theory] + [InlineData]/[MemberData], Reqnroll Scenario Outline + Examples, or data from JSON/CSV/Excel/DB.
- It increases coverage (boundaries, partitions) without duplicating code.
In short: Frames: driver.SwitchTo().Frame(idOrElement) then SwitchTo().DefaultContent().
- Windows/tabs: store
CurrentWindowHandle, loopWindowHandles,SwitchTo().Window(handle). - Alerts:
SwitchTo().Alert().Accept()/Dismiss()/Text. - Dropdowns:
new SelectElement(element).SelectByText("India")for<select>; custom dropdowns are clicked like normal elements.
In short: Behaviour-Driven Development describes behaviour in Gherkin (Given/When/Then) agreed with business and QA, then automates those scenarios with step definitions (Reqnroll in .NET).
- Worth it when business stakeholders read and help write scenarios — it becomes living documentation.
- If only engineers read them, the Gherkin layer is overhead; plain readable tests are enough.
In short: One driver/browser context per test (thread-local or per-test instance), no static shared state, unique test data per test (GUIDs, separate users), tests independent of order, config for worker count, and infrastructure that can handle the load (Selenium Grid, cloud grid, Playwright workers).
- NUnit:
[Parallelizable]with instance-per-test-case; xUnit: collections control parallelism.
In short: Tests live in the same repo or a sibling repo, run via dotnet test in Azure DevOps/GitHub Actions/Jenkins: unit and API smoke on every PR (quality gate), deployment smoke tests after each environment deploy, full regression nightly.
- Headless browsers or a grid, results published (TRX/JUnit), artefacts on failure (screenshots, videos, traces), notifications, and failing tests block the pipeline.
In short: Pass rate and flaky rate, execution time, coverage of critical flows/requirements (not just code coverage), defects found by automation vs escaped defects, time to fix broken tests, and automation ROI (manual hours saved).
- Use them to decide what to fix, speed up or delete.
In short: Measure first: list slowest and flakiest tests.
- Move logic checks down the pyramid (API/unit tests); keep UI tests for critical journeys.
- Replace sleeps with condition waits or Playwright auto-wait; use stable locators; create test data via API instead of UI steps; reuse login state.
- Run in parallel on a grid with independent data.
- Quarantine flaky tests and fix root causes.
- Target: smoke < 10 min per PR, full regression < 1 hour, flake rate < 1–2%.
In short: Compare environments: headless vs headed (viewport/screen size differs), browser/driver versions, timing on slower agents, environment URLs and config, test data present/missing, parallel runs colliding, time zone/culture, network/proxy.
- Collect evidence: screenshots, video, Playwright trace, browser console and network logs, CI logs.
- Reproduce with the same settings locally (headless, same browser version, same data).
Agree with developers to add stable data-testid attributes (part of the definition of done), use role/text-based locators (Playwright GetByRole, GetByLabel) that follow user-visible behaviour, centralise locators in page/component objects, review UI changes with QA early, and add a quick smoke run on every PR so breaks are found immediately — not at the end of the sprint.
In short: Valid login;
- invalid password;
- unknown user;
- empty fields;
- field validation (email format, length);
- password masked;
- case sensitivity;
- account lockout after N attempts;
- "remember me";
- forgot password flow;
- SQL injection/XSS strings;
- session timeout and logout;
- back button after logout;
- HTTPS;
- multiple browsers/devices;
- accessibility (labels, keyboard);
- performance under load;
- error messages don't reveal whether the user exists.