๐Ÿงช

Playwright Dotnet Guide

Testing Intermediate 1 min read 100 words

Playwright for .NET - E2E Testing Guide

Playwright is a modern browser automation framework for reliable end-to-end testing.

Getting Started

Installation

dotnet add package Microsoft.Playwright
dotnet add package Microsoft.Playwright.NUnit

Initialize Playwright

pwsh bin/Debug/net8.0/playwright.ps1 install

Basic Test Structure

using Microsoft.Playwright.NUnit;

[TestFixture]
public class ExampleTests : PageTest
{
    [Test]
    public async Task BasicNavigationTest()
    {
        await Page.GotoAsync("https://example.com");
        await Expect(Page).ToHaveTitleAsync("Example Domain");
    }
    
    [Test]
    public async Task LoginTest()
    {
        await Page.GotoAsync("https://myapp.com/login");
        
        await Page.FillAsync("#email", "user@example.com");
        await Page.FillAsync("#password", "password123");
        await Page.ClickAsync("button[type='submit']");
        
        await Expect(Page).ToHaveURLAsync("https://myapp.com/dashboard");
    }
}

Selectors

Common Strategies

// By Role (Recommended)
await Page.GetByRole(AriaRole.Button, new() { Name = "Submit" }).ClickAsync();

// By Text
await Page.GetByText("Welcome").ClickAsync();

// By Label
await Page.GetByLabel("Email").FillAsync("user@test.com");

// By CSS
await Page.Locator(".submit-button").ClickAsync();

// By XPath
await Page.Locator("//button[@type='submit']").ClickAsync();

Assertions

// Element visibility
await Expect(Page.GetByText("Success")).ToBeVisibleAsync();

// Element count
await Expect(Page.Locator(".item")).ToHaveCountAsync(5);

// Text content
await Expect(Page.Locator("h1")).ToHaveTextAsync("Dashboard");

// URL
await Expect(Page).ToHaveURLAsync("/dashboard");

// Attribute
await Expect(Page.Locator("#username")).ToHaveAttributeAsync("disabled", "");

Page Object Pattern

public class LoginPage
{
    private readonly IPage _page;
    
    public LoginPage(IPage page) => _page = page;
    
    private ILocator EmailInput => _page.GetByLabel("Email");
    private ILocator PasswordInput => _page.GetByLabel("Password");
    private ILocator SubmitButton => _page.GetByRole(AriaRole.Button, new() { Name = "Login" });
    
    public async Task LoginAsync(string email, string password)
    {
        await EmailInput.FillAsync(email);
        await PasswordInput.FillAsync(password);
        await SubmitButton.ClickAsync();
    }
}

// Usage
[Test]
public async Task LoginWithPageObject()
{
    var loginPage = new LoginPage(Page);
    await Page.GotoAsync("/login");
    await loginPage.LoginAsync("user@test.com", "password");
    
    await Expect(Page).ToHaveURLAsync("/dashboard");
}

API Testing

[Test]
public async Task ApiTest()
{
    var context = await Browser.NewContextAsync();
    var request = await context.Request.GetAsync("https://api.example.com/users");
    
    Assert.That(request.Status, Is.EqualTo(200));
    
    var json = await request.JsonAsync();
    Assert.That(json?.users?.length, Is.GreaterThan(0));
}

CI/CD Integration

GitHub Actions

name: Playwright Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Setup .NET
        uses: actions/setup-dotnet@v4
      - name: Install dependencies
        run: dotnet restore
      - name: Install Playwright
        run: pwsh src/bin/Debug/net8.0/playwright.ps1 install --with-deps
      - name: Run tests
        run: dotnet test

Best Practices

  1. Use role-based selectors - More resilient to changes
  2. Implement Page Objects - Better maintainability
  3. Use auto-waiting - Playwright waits automatically
  4. Run in parallel - Faster test execution
  5. Take screenshots on failure - Easier debugging
[TearDown]
public async Task TearDown()
{
    if (TestContext.CurrentContext.Result.Outcome.Status == TestStatus.Failed)
    {
        await Page.ScreenshotAsync(new() { Path = $"screenshot-{TestContext.CurrentContext.Test.Name}.png" });
    }
}

๐Ÿ“š Related Articles