# Moninpelin E2E-testi Playwrightilla .NET-verkolle

<datetime class="hidden">2025-11-27T14:00</datetime>

<!--category-- Playwright, E2E Testing, xUnit, Testing, Multi-Browser -->
Playwright on Microsoftin virallinen ratkaisu moderniin selainautomaatioon, joka tarjoaa usean selaimen tukea (Chrome, Firefox, Safari) yhdellä API:llä, sisäänrakennetulla jäljitysvianjäljityksellä ja mobiilijäljityksellä. Tämä opas kattaa kaiken setupista kehittyneisiin testausmalleihin ja vertaa sitä PuppeteerSharpiin, jotta voit valita oikean työkalun. Jos tarvitset vain Chromea ja haluat yksinkertaisemman asennuksen, harkitse. [PuppeteerSharp](/blog/puppeteersharp-e2e-testing) sen sijaan - mutta jos tarvitaan kokonaisvaltaista ristiselkätestiä, Playwright on tie eteenpäin.

## Johdanto

Jos olet lukenut artikkelini aiheesta [PuppeteerSharp](/blog/puppeteersharp-e2e-testing)PuppeteerSharp on loistava vain Chrome-testaukseen, mutta entä jos sinun pitää testata useita selaimia? [Näytelmäkirjailija](https://playwright.dev/) Tule sisään.

Playwright on Microsoftin vastaus selaimen automaatio-ongelmaan, ja he ovat oppineet kaikesta aiemmasta. Se on kuin PuppeteerSharpin kunnianhimoisempi sisarus - se tekee kaiken, mitä Puppeteer tekee, mutta lisää Firefox- ja Safari-tukea, parempaa automaattista odotusta ja joukon vianetsintätyökaluja, jotka tekevät ongelmien löytämisestä ehdottoman doddlen.

Tässä artikkelissa näytän, miten Playwrightia käytetään .NET-verkossa Chrome-, Firefox- ja Safari-testeissä, joissa on todellisia koodiesimerkkejä ja käytännön malleja, joita voit käyttää tänään.

[TOC]

## Miksi PuppeteerSharpin näytelmäkirjailija?

Älä ymmärrä väärin. [PuppeteerSharp](/blog/puppeteersharp-e2e-testing) Se on erinomainen, jos tarvitset vain Chromea, mutta nyt Playwrightissa on järkeä:

### Moninpeliongelma

Kaikki käyttäjät eivät käytä Chromea. He käyttävät:

- **Chrome/Edge**: ~65 % käyttäjistä (kromipohjainen)
- **Safari**: ~20 % käyttäjistä (etenkin mobiili)
- **Firefox**: ~5 % käyttäjistä (mutta usein erilainen renderointi)

Vain Safarissa esiintyvä bugi voi menettää 20 prosenttia potentiaalisista käyttäjistään. Playwrightin avulla voit testata kaikkia kolmea samalla API-rajapinnalla.

### Parempaa kehityskokemusta

```mermaid
graph TB
    subgraph "PuppeteerSharp"
        PS[Chrome Only]
        PS --> PS1[Fast setup]
        PS --> PS2[Simple API]
        PS --> PS3[Chrome DevTools]
    end

    subgraph "Playwright"
        PW[Multi-Browser]
        PW --> PW1[Chrome, Firefox, Safari]
        PW --> PW2[Auto-waiting built-in]
        PW --> PW3[Trace viewer]
        PW --> PW4[Codegen tool]
        PW --> PW5[Better error messages]
    end

    style PW stroke:#00aa00,stroke-width:3px
    style PW1 stroke:#0066cc,stroke-width:2px
    style PW3 stroke:#0066cc,stroke-width:2px
    style PW4 stroke:#0066cc,stroke-width:2px
```

### Tärkeimmät edut

1. **Useiden selausten tuki** - Testaa Chrome, Firefox, Safari identtisellä koodilla
2. **Parempaa automaattista odotusta** - Luotettavampi ulos laatikosta, vähemmän hilseileviä testejä
3. **Jäljitä katsoja** - Nauhoita täydet tulokset vianetsintään
4. **Codegen** - Luo testikoodi olemalla vuorovaikutuksessa sivustosi kanssa
5. **Verkkokuuntelu** - Voimakkaampi kuin PuppeteerSharp
6. **Nykyaikaiset verkko-ominaisuudet** - Parempi tuki Shadow DOMille, iframeille jne.

## Näyttämön asetukset .NETille

Asenna [Microsoft.Playwright](https://www.nuget.org/packages/Microsoft.Playwright) NuGet-paketti:

```bash
dotnet add package Microsoft.Playwright
dotnet add package Microsoft.Playwright.NUnit  # Or use xUnit
```

Asenna sitten selaimet (tämä lataus [Kromi](https://www.chromium.org/), [Firefox](https://www.mozilla.org/firefox/), ja [WebKit](https://webkit.org/)):

```bash
pwsh bin/Debug/net9.0/playwright.ps1 install
```

Tai Linuxissa/Macissa:

```bash
playwright install
```

**Huomaa:** Ensimmäinen selain asentaa latauksia noin 400MB. Seuraavat päivitykset ovat paljon pienempiä.

Tässä on testiprojektin kokoonpanoni:

```xml
<PackageReference Include="Microsoft.Playwright" Version="1.48.0" />
<PackageReference Include="Microsoft.Playwright.NUnit" Version="1.48.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
```

Käytän [xUnit](https://xunit.net/), mutta Playwright toimii yhtä hyvin [NUNIT](https://nunit.org/) tai [MSTest](https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-with-mstest)Itse asiassa Playwright on omistautunut [NUnit-integraatio](https://playwright.dev/dotnet/docs/test-runners#nunit) lisäauttajilla.

## Peruskoeluokan luominen

Samanlainen kuin [PuppeteerSharp](/blog/puppeteersharp-e2e-testing#creating-a-base-test-class), mutta moniselitteisellä tuella:

### Luokan rakenne

```csharp
using Microsoft.Playwright;
using Xunit.Abstractions;

namespace Mostlylucid.Test.E2E;

public abstract class PlaywrightTestBase : IAsyncLifetime
{
    protected IPlaywright Playwright = null!;
    protected IBrowser Browser = null!;
    protected IBrowserContext Context = null!;
    protected IPage Page = null!;

    protected readonly ITestOutputHelper Output;
    protected const string BaseUrl = "http://localhost:8080";

    // Override this in derived classes to test different browsers
    protected virtual BrowserType BrowserType => BrowserType.Chromium;

    protected PlaywrightTestBase(ITestOutputHelper output)
    {
        Output = output;
    }

    public async Task InitializeAsync()
    {
        // Create Playwright instance
        Playwright = await Microsoft.Playwright.Playwright.CreateAsync();

        // Launch the specified browser
        Browser = await LaunchBrowserAsync();

        // Create a new context (like an incognito window)
        Context = await Browser.NewContextAsync(new BrowserNewContextOptions
        {
            ViewportSize = new ViewportSize { Width = 1400, Height = 900 },
            IgnoreHTTPSErrors = true
        });

        // Create a new page
        Page = await Context.NewPageAsync();

        // Set default timeout
        Page.SetDefaultTimeout(30000);
    }

    private async Task<IBrowser> LaunchBrowserAsync()
    {
        var options = new BrowserTypeLaunchOptions
        {
            Headless = true, // Set to false for debugging
        };

        return BrowserType switch
        {
            BrowserType.Chromium => await Playwright.Chromium.LaunchAsync(options),
            BrowserType.Firefox => await Playwright.Firefox.LaunchAsync(options),
            BrowserType.Webkit => await Playwright.Webkit.LaunchAsync(options),
            _ => throw new ArgumentException($"Unknown browser type: {BrowserType}")
        };
    }

    public async Task DisposeAsync()
    {
        await Page?.CloseAsync()!;
        await Context?.CloseAsync()!;
        await Browser?.CloseAsync()!;
        Playwright?.Dispose();
    }
}

public enum BrowserType
{
    Chromium,
    Firefox,
    Webkit
}
```

### Miksi selainkontekstit?

Huomaa, että luomme `Context` ennen kuin luodaan `Page`Tämä on näytelmäkirjailijan konsepti, jota PuppeteerSharpilla ei ole:

- **Konteksti** = Erillinen selainsessio (kuten incognito-tila)
- **Sivu** = Tässä yhteydessä välilehti

Kontekstit antavat sinulle mahdollisuuden:

- Testaa eri käyttäjätunnuksilla samanaikaisesti
- Aseta eri evästeet, käyttöoikeudet tai paikka kontekstia kohti
- Eristystestit kokonaan (jopa samassa selaimessa)

### Auttajan menetelmät

```csharp
// Navigation with better waiting
protected async Task NavigateAsync(string path)
{
    var url = path.StartsWith("http") ? path : $"{BaseUrl}{path}";
    await Page.GotoAsync(url, new PageGotoOptions
    {
        WaitUntil = WaitUntilState.NetworkIdle
    });
}

// Playwright has better built-in waiting, but these are still useful
protected async Task<IElementHandle?> WaitForSelectorAsync(string selector)
{
    try
    {
        return await Page.WaitForSelectorAsync(selector, new PageWaitForSelectorOptions
        {
            State = WaitForSelectorState.Visible
        });
    }
    catch (TimeoutException)
    {
        return null;
    }
}

// Improved element operations with auto-waiting
protected async Task<bool> ElementExistsAsync(string selector)
{
    return await Page.Locator(selector).CountAsync() > 0;
}

protected async Task<string?> GetTextContentAsync(string selector)
{
    var locator = Page.Locator(selector);
    return await locator.TextContentAsync();
}

protected async Task TypeAsync(string selector, string text)
{
    await Page.Locator(selector).FillAsync(text);
}

protected async Task ClickAsync(string selector)
{
    await Page.Locator(selector).ClickAsync();
}
```

### Locators - näytelmäkirjailijan salainen ase

Huomaa, että `Page.Locator()` Tämä on erilaista kuin PuppeteerSharpin `QuerySelector`. Paikallistajat ovat:

- **Laiska** - He eivät kysy DOM: lta ennen kuin käytät niitä.
- **Uudelleenyrittäminen** - He odottavat ja yrittävät uudelleen operaatioita
- **Tiukka** - He erehtyvät, jos useat elementit täsmäävät (hiutaletestien estäminen)

```csharp
// PuppeteerSharp way (manual waiting)
await Page.WaitForSelectorAsync("#button");
await Page.ClickAsync("#button");

// Playwright way (auto-waiting)
await Page.Locator("#button").ClickAsync(); // Waits automatically!
```

## Moninpelin testaus

Tässä Playwright loistaa. Samaa testiä voi tehdä kaikilla selaimilla:

### XUnit-teorian käyttö usean pelaajan testeihin

```csharp
public class CrossBrowserTests : PlaywrightTestBase
{
    public CrossBrowserTests(ITestOutputHelper output) : base(output) { }

    [Theory]
    [InlineData(BrowserType.Chromium)]
    [InlineData(BrowserType.Firefox)]
    [InlineData(BrowserType.Webkit)]
    public async Task HomePage_LoadsCorrectly_InAllBrowsers(BrowserType browserType)
    {
        // Override the browser type for this test
        BrowserType = browserType;
        await InitializeAsync();

        // Arrange & Act
        await NavigateAsync("/");

        // Assert
        var title = await Page.TitleAsync();
        Assert.Contains("Mostlylucid", title);

        Output.WriteLine($"✓ Test passed in {browserType}");
    }
}
```

### Selainkohtaiset testiluokat

Tai luo jokaiselle selaimelle erilliset testiluokat:

```csharp
public class ChromiumTests : PlaywrightTestBase
{
    protected override BrowserType BrowserType => BrowserType.Chromium;

    public ChromiumTests(ITestOutputHelper output) : base(output) { }

    [Fact]
    public async Task FilterBar_WorksInChrome()
    {
        await NavigateAsync("/blog");

        await Page.Locator("#LanguageDropDown button").ClickAsync();
        await Page.Locator("#LanguageDropDown li:has-text('Spanish')").ClickAsync();

        var url = Page.Url;
        Assert.Contains("language=es", url);
    }
}

public class FirefoxTests : PlaywrightTestBase
{
    protected override BrowserType BrowserType => BrowserType.Firefox;

    public FirefoxTests(ITestOutputHelper output) : base(output) { }

    [Fact]
    public async Task FilterBar_WorksInFirefox()
    {
        // Same test, different browser!
        await NavigateAsync("/blog");

        await Page.Locator("#LanguageDropDown button").ClickAsync();
        await Page.Locator("#LanguageDropDown li:has-text('Spanish')").ClickAsync();

        var url = Page.Url;
        Assert.Contains("language=es", url);
    }
}

public class SafariTests : PlaywrightTestBase
{
    protected override BrowserType BrowserType => BrowserType.Webkit;

    public SafariTests(ITestOutputHelper output) : base(output) { }

    [Fact]
    public async Task FilterBar_WorksInSafari()
    {
        await NavigateAsync("/blog");

        await Page.Locator("#LanguageDropDown button").ClickAsync();
        await Page.Locator("#LanguageDropDown li:has-text('Spanish')").ClickAsync();

        var url = Page.Url;
        Assert.Contains("language=es", url);
    }
}
```

## Kirjoitustestit - The Playwright Way

Tarkastellaanpa todellisia esimerkkejä, joissa näytetään Playwrightin edut:

### HTMX:n testaaminen automaattisella odottelulla

```csharp
[Fact]
public async Task SortingDropdown_UpdatesContentViaHTMX()
{
    // Arrange
    await NavigateAsync("/blog");

    // Get first post title before sorting
    var firstPostBefore = await Page.Locator("article h2 a").First.TextContentAsync();
    Output.WriteLine($"First post before: {firstPostBefore}");

    // Act - Change sort order
    await Page.Locator("#orderSelect").SelectOptionAsync("date_asc");

    // Playwright automatically waits for HTMX to update the DOM!
    await Page.WaitForLoadStateAsync(LoadState.NetworkIdle);

    // Assert - Content should have changed
    var firstPostAfter = await Page.Locator("article h2 a").First.TextContentAsync();
    Output.WriteLine($"First post after: {firstPostAfter}");

    var selectValue = await Page.Locator("#orderSelect").InputValueAsync();
    Assert.Equal("date_asc", selectValue);
}
```

### Alppien vuorovaikutusten testaaminen

Näytelmänohjaimella varustetut kahvat [Alppi.js](https://alpinejs.dev/) reaktiivisuus ja animaatiot saumattomasti:

```csharp
[Fact]
public async Task AlpineDropdown_OpensAndCloses()
{
    await NavigateAsync("/blog");

    // Click to open dropdown (Alpine.js controlled)
    await Page.Locator("#LanguageDropDown button").ClickAsync();

    // Wait for Alpine.js animation
    await Page.WaitForSelectorAsync("#LanguageDropDown div[x-show]", new()
    {
        State = WaitForSelectorState.Visible
    });

    // Check dropdown is visible
    var isVisible = await Page.Locator("#LanguageDropDown div[x-show]").IsVisibleAsync();
    Assert.True(isVisible);

    // Click outside to close
    await Page.Locator("body").ClickAsync(new LocatorClickOptions
    {
        Position = new Position { X = 0, Y = 0 }
    });

    // Verify closed
    await Page.WaitForSelectorAsync("#LanguageDropDown div[x-show]", new()
    {
        State = WaitForSelectorState.Hidden
    });

    isVisible = await Page.Locator("#LanguageDropDown div[x-show]").IsVisibleAsync();
    Assert.False(isVisible);
}
```

### Testauslomakkeet ja varmennus

```csharp
[Fact]
public async Task CommentForm_ValidatesRequiredFields()
{
    await NavigateAsync("/blog/some-post");

    // Try to submit without filling fields
    await Page.Locator("#comment-submit").ClickAsync();

    // Check HTML5 validation messages
    var nameInput = Page.Locator("#comment-name");
    var validationMessage = await nameInput.EvaluateAsync<string>("el => el.validationMessage");

    Assert.NotEmpty(validationMessage);
    Output.WriteLine($"Validation message: {validationMessage}");

    // Fill form properly
    await nameInput.FillAsync("Test User");
    await Page.Locator("#comment-email").FillAsync("test@example.com");
    await Page.Locator("#comment-content").FillAsync("Great article!");

    // Submit
    await Page.Locator("#comment-submit").ClickAsync();

    // Wait for success
    await Page.Locator(".comment-success").WaitForAsync();
}
```

## Kehittyneet näytelmänpiirteet

### Verkkohäirintä - PuppeteerSharp tehokkaampi kuin PuppeteerSharp

```csharp
[Fact]
public async Task PageLoadsWithoutImages_ToTestPerformance()
{
    // Block all image requests
    await Page.RouteAsync("**/*.{png,jpg,jpeg,gif,webp}", route => route.AbortAsync());

    await NavigateAsync("/blog");

    // Page should still function without images
    var title = await Page.Locator("h1").TextContentAsync();
    Assert.NotEmpty(title);
}

[Fact]
public async Task MockAPIResponse_ForTesting()
{
    // Intercept API calls and return mock data
    await Page.RouteAsync("**/api/posts", route => route.FulfillAsync(new()
    {
        Status = 200,
        ContentType = "application/json",
        Body = System.Text.Json.JsonSerializer.Serialize(new
        {
            posts = new[]
            {
                new { id = 1, title = "Mock Post 1" },
                new { id = 2, title = "Mock Post 2" }
            }
        })
    }));

    await NavigateAsync("/blog");

    // Should show mock data
    var posts = await Page.Locator(".post-title").CountAsync();
    Assert.Equal(2, posts);
}
```

### Trace Viewer - vianetsintätestit epäonnistuivat

```csharp
public async Task InitializeAsync()
{
    Playwright = await Microsoft.Playwright.Playwright.CreateAsync();
    Browser = await LaunchBrowserAsync();

    // Start tracing
    Context = await Browser.NewContextAsync();
    await Context.Tracing.StartAsync(new()
    {
        Screenshots = true,
        Snapshots = true,
        Sources = true
    });

    Page = await Context.NewPageAsync();
}

public async Task DisposeAsync()
{
    // Save trace on test completion
    var tracePath = Path.Combine("traces", $"{TestContext.TestName}.zip");
    await Context.Tracing.StopAsync(new()
    {
        Path = tracePath
    });

    // View trace with: playwright show-trace traces/TestName.zip

    await Page?.CloseAsync()!;
    await Context?.CloseAsync()!;
    await Browser?.CloseAsync()!;
    Playwright?.Dispose();
}
```

Katso sitten jälkiä:

```bash
playwright show-trace traces/TestName.zip
```

Tämä avaa käyttöliittymän, jossa näytetään:

- Jokainen teko, jonka testisi teki
- Kuvakaappauksia jokaisessa vaiheessa
- Verkkopyynnöt
- Konsolidoidut lokit
- DOM-kuvakaappaukset

Se on loistava vianetsintään.

### Kuvakaappauksia epäonnistumisesta

```csharp
public async Task DisposeAsync()
{
    // Take screenshot if test failed
    if (TestContext.CurrentTestOutcome != TestOutcome.Passed)
    {
        var screenshot = await Page.ScreenshotAsync();
        File.WriteAllBytes($"failure-{TestContext.TestName}.png", screenshot);
        Output.WriteLine($"Screenshot saved: failure-{TestContext.TestName}.png");
    }

    await Page?.CloseAsync()!;
    await Context?.CloseAsync()!;
    await Browser?.CloseAsync()!;
    Playwright?.Dispose();
}
```

### Liikkuva emulointi

```csharp
[Fact]
public async Task BlogPage_WorksOnMobile()
{
    // Create context with mobile emulation
    var iPhone = Playwright.Devices["iPhone 13"];
    await using var context = await Browser.NewContextAsync(iPhone);
    await using var page = await context.NewPageAsync();

    await page.GotoAsync($"{BaseUrl}/blog");

    // Check mobile menu is visible
    var mobileMenu = page.Locator(".mobile-menu");
    await Expect(mobileMenu).ToBeVisibleAsync();

    // Desktop menu should be hidden
    var desktopMenu = page.Locator(".desktop-menu");
    await Expect(desktopMenu).Not.ToBeVisibleAsync();
}
```

Näytelmäkirjailija tulee mukaan [Laitteen kuvaukset](https://playwright.dev/dotnet/docs/emulation#devices) korvataan:

- iPhone 13, 13 Pro, 12, 11, SE
- iPad, iPad Pro
- Samsung Galaxy, Pixel
- Ja paljon muuta

### Tumman tilan testaus

```csharp
[Fact]
public async Task DarkMode_TogglesCorrectly()
{
    // Start with dark color scheme
    await using var context = await Browser.NewContextAsync(new()
    {
        ColorScheme = ColorScheme.Dark
    });
    await using var page = await context.NewPageAsync();

    await page.GotoAsync($"{BaseUrl}");

    // Check dark mode is active
    var isDark = await page.EvaluateAsync<bool>(
        "() => window.matchMedia('(prefers-color-scheme: dark)').matches"
    );
    Assert.True(isDark);

    // Check background color reflects dark mode
    var bgColor = await page.Locator("body").EvaluateAsync<string>(
        "el => getComputedStyle(el).backgroundColor"
    );
    Assert.Contains("rgb(0, 0, 0)", bgColor); // Dark background
}
```

## Playwright vs PuppeteerSharp - sivuttain

Tässä sama testi molemmissa kirjastoissa näyttää erot:

### PuppeteerSharp-versio

```csharp
[Fact]
public async Task FilterTest_PuppeteerSharp()
{
    await Page.GoToAsync("http://localhost:8080/blog");

    // Manual waiting required
    await Page.WaitForSelectorAsync("#LanguageDropDown button");
    await Page.ClickAsync("#LanguageDropDown button");

    // Wait for dropdown animation
    await Task.Delay(300);

    // Click Spanish option
    await Page.WaitForSelectorAsync("#LanguageDropDown li:nth-child(2) a");
    await Page.ClickAsync("#LanguageDropDown li:nth-child(2) a");

    // Wait for navigation
    await Task.Delay(500);

    // Check URL
    var url = Page.Url;
    Assert.Contains("language=", url);
}
```

### Playwright-versio

```csharp
[Fact]
public async Task FilterTest_Playwright()
{
    await Page.GotoAsync("http://localhost:8080/blog");

    // Auto-waiting built in
    await Page.Locator("#LanguageDropDown button").ClickAsync();

    // Click Spanish option (waits automatically for visibility)
    await Page.Locator("#LanguageDropDown li:has-text('Spanish')").ClickAsync();

    // Check URL (waits automatically for navigation)
    await Expect(Page).ToHaveURLAsync(new Regex(".*language=.*"));
}
```

Huomautus:

- Ei ohjekirjaa `WaitForSelectorAsync` tarvitaan
- Ei tarvitse. `Task.Delay` tarvitaan
- Cleaner väittää, että `Expect`
- Lisää luettavissa `has-text` valitsin

## PDF-sukupolvi Playwrightilla

Kuten PuppeteerSharp, Playwright voi luoda pdf-tiedostoja. API on lähes identtinen:

```csharp
public async Task<byte[]> GeneratePdfAsync(string url)
{
    await using var browser = await Playwright.Chromium.LaunchAsync();
    await using var page = await browser.NewPageAsync();
    await page.GotoAsync(url);

    return await page.PdfAsync(new()
    {
        Format = "A4",
        PrintBackground = true,
        Margin = new()
        {
            Top = "20mm",
            Right = "20mm",
            Bottom = "20mm",
            Left = "20mm"
        }
    });
}
```

Samaa [PuppeteerSharpin artikkelista löytyi pdf-julkaisuja](/blog/puppeteersharp-e2e-testing#pdf-generation-gotchas) Hae myös täältä.

## Milloin näytelmäkirjailija valitaan

```mermaid
graph TD
    A[Need E2E Testing?] --> B{Multi-browser required?}
    B -->|Yes| C[Playwright]
    B -->|No| D{Chrome only?}
    D -->|Yes| E[PuppeteerSharp or Playwright]
    D -->|No| C

    C --> F[Benefits]
    F --> F1[Test all major browsers]
    F --> F2[Better debugging tools]
    F --> F3[More reliable waiting]
    F --> F4[Mobile emulation]

    E --> G[PuppeteerSharp Benefits]
    G --> G1[Simpler setup]
    G --> G2[Smaller overhead]
    G --> G3[Chromium focused]

    style C stroke:#00aa00,stroke-width:3px
    style E stroke:#0066cc,stroke-width:2px
```

**Valitse Playwright, kun:**

- Tarvitset usean selaimen testausta
- Haluatko parempia vianetsintätyökaluja (jäljitä katsojaa)
- Testaat monimutkaisia nykyaikaisia verkkosovelluksia
- Tarvitset liikkuvan emuloinnin
- Haluat luotettavampia testejä ulos laatikosta

**Valitse PuppeteerSharp, kun:**

- Vain Chrome/Edge on kunnossa
- Haluat yksinkertaisemman asetelman
- Olet jo tutustunut Puppeteeriin
- Tarvitset hieman vähemmän resursseja.

## Suoritetaan CI/CD:ssä

### GitHub-toiminnot esimerkki

Playwright toimii saumattomasti CI/CD-putkistoissa. [GitHub-toimet](https://github.com/features/actions) työnkulku:

```yaml
name: Playwright Tests

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3

    - name: Setup .NET
      uses: actions/setup-dotnet@v3
      with:
        dotnet-version: '9.0.x'

    - name: Install dependencies
      run: dotnet restore

    - name: Build
      run: dotnet build --no-restore

    - name: Install Playwright browsers
      run: pwsh Mostlylucid.Test/bin/Debug/net9.0/playwright.ps1 install --with-deps

    - name: Start application
      run: |
        dotnet run --project Mostlylucid/Mostlylucid.csproj &
        echo $! > app.pid

    - name: Wait for application
      run: |
        timeout 60 bash -c 'until curl -f http://localhost:8080/health; do sleep 2; done'

    - name: Run Playwright tests
      run: |
        dotnet test Mostlylucid.Test/Mostlylucid.Test.csproj \
          --filter "Category=E2E" \
          --logger "console;verbosity=detailed"

    - name: Upload test results
      if: always()
      uses: actions/upload-artifact@v3
      with:
        name: playwright-results
        path: |
          **/test-results/
          **/traces/

    - name: Stop application
      if: always()
      run: kill $(cat app.pid) || true
```

### Dockerin tuki

Playwright-testien suorittaminen [Docker](https://www.docker.com/) vaatii järjestelmän riippuvuussuhteiden asentamista:

```dockerfile
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build

# Install Playwright dependencies
RUN apt-get update && apt-get install -y \
    libnss3 \
    libnspr4 \
    libatk1.0-0 \
    libatk-bridge2.0-0 \
    libcups2 \
    libdrm2 \
    libxkbcommon0 \
    libxcomposite1 \
    libxdamage1 \
    libxfixes3 \
    libxrandr2 \
    libgbm1 \
    libasound2

WORKDIR /app
COPY . .

RUN dotnet restore
RUN dotnet build
RUN pwsh Mostlylucid.Test/bin/Debug/net9.0/playwright.ps1 install

CMD ["dotnet", "test"]
```

## Tulosvertailu

Testauksestani tässä blogissa:

Esitys PuppeteerSharp Näytelmäkirjailija
|---------|---------------|------------|
| **Chrome-testinopeus** : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :
| **Moniselitteinen** (lisätään ~1s/selain)
| **Luotettavuus odottamassa automaattisesti** Hyvä (käsikäyttöinen) Erinomainen (automaattinen)
| **Muistia selainta kohden** 150 MB 180 MB
| **Asettelun monimutkaisuus** Yksinkertaista ja maltillista
| **Vianetsintätyökalut** Chrome DevTools Trace Viewer + DevTools
| **Liikkuva emulointi** "Esimerkillinen"

Playwright on hieman hitaampi ja käyttää hieman enemmän muistia, mutta luotettavuus- ja vianetsintäedut ovat yleensä tätä suuremmat.

## Common Gotchas

### 1. Strict Mode

Playwrightin paikantimet ovat oletuksena tiukat:

```csharp
// This errors if multiple buttons exist
await Page.Locator("button").ClickAsync();

// Be specific
await Page.Locator("button#submit").ClickAsync();

// Or use .First if you really want the first match
await Page.Locator("button").First.ClickAsync();
```

### 2. Selainasennus

Älä unohda juosta `playwright install` Kun paketti on lisätty. Selaimet eivät sisälly NuGet-pakettiin.

### 3. Konteksti vs. sivu

Muista, että kontekstit eristävät testejä:

```csharp
// Bad - state leaks between tests
await Page.GotoAsync("/login");
// Do stuff...
await Page.GotoAsync("/dashboard");
// Previous login state might still exist

// Good - fresh context per test
await using var context = await Browser.NewContextAsync();
await using var page = await context.NewPageAsync();
// Completely isolated
```

### 4. Webkit-eroja

Safari (WebKit) voi käyttäytyä toisin:

```csharp
// May work in Chrome but fail in WebKit
await Page.Locator(".dropdown").Hover();
await Page.Locator(".dropdown-item").ClickAsync();

// More reliable across browsers
await Page.Locator(".dropdown").ClickAsync();
await Page.WaitForSelectorAsync(".dropdown-item");
await Page.Locator(".dropdown-item").ClickAsync();
```

## Päätelmät

Playwright edustaa .NET-kehittäjien selainautomaation kehitystä. Jos tarvitset usean selaimen tukea, se on selkeä valinta. Kyllä, se on hieman monimutkaisempi kuin [PuppeteerSharp](/blog/puppeteersharp-e2e-testing), mutta hyödyt ovat huomattavat:

- Testaa kaikilla pääselaimilla, joilla on sama koodi
- Luotettavuuden parantaminen automaattisella odottelulla
- Erinomainen vianetsintä jäljitysnäytöllä
- Mobiili- ja tablettiemulointi ulos laatikosta
- Kestävämpi ajoitusongelmille

**Suositukseni:**

- **Uudet projektit, joihin tarvitaan ristiselauksen testausta**: Aloita Playwrightista
- **Vain Chrome-projektit**PuppeteerSharp on yksinkertaisempi
- **Olemassa olevat PuppeteerSharp-projektit**: Muuttoliike vain, jos tarvitset useita selaimia
- **Monimutkaiset nykyaikaiset verkkosovellukset**: Playwrightin työkalut ovat kulun arvoisia

Pelkästään jäljitin on säästänyt minulta tunteja vianetsintää. Se, että pystyn toistamaan epäonnistuneen testin ja katsomaan tarkasti, mitä selain näki jokaisessa vaiheessa, on korvaamaton.

Anna Playwrightille mahdollisuus seuraavaan projektiin - yllätyt iloisesti siitä, kuinka paljon helpompaa se on ristiselauksen testaamisessa.

## Lisää luettavaa

- [.NET-dokumentaation näytelmäkirjailija](https://playwright.dev/dotnet/)
- [Playwrightin API-viite](https://playwright.dev/dotnet/docs/api/class-playwright)
- [PuppeteerSharp vs. Playwright](/blog/puppeteersharp-e2e-testing)
- [Jäljitä katsojaopas](https://playwright.dev/dotnet/docs/trace-viewer)
- [Selainkontekstit selitetty](https://playwright.dev/dotnet/docs/browser-contexts)