Back to "Multi-Browser E2E Testning med Playwright för .NET"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

E2E Testing Multi-Browser Playwright Testing xUnit

Multi-Browser E2E Testning med Playwright för .NET

Thursday, 27 November 2025

Playwright är Microsofts officiella lösning för modern webbläsarautomation, som erbjuder stöd för multi-browser (Chrome, Firefox, Safari) med ett enda API, inbyggd spår felsökning och mobil emulering. Denna guide täcker allt från installation till avancerade testmönster, jämföra det med PuppeterSharp för att hjälpa dig att välja rätt verktyg. Om du bara behöver Chrome och vill ha enklare inställning, överväga PuppeterSharp istället - men om du behöver omfattande kors-browser testning, Playwright är vägen framåt.

Inledning

Om du har läst min artikel om PuppeterSharp, du vet att jag är ett fan av moderna E2E-testverktyg som inte gör att du vill riva håret ut. PuppeterSharp är lysande för Chrome-endast testning, men tänk om du behöver testa över flera webbläsare? Det är där Uppspelningsrätt Jag kommer in.

Playwright är Microsofts svar på problemet med webbläsarautomation, och de har lärt sig av allt som kom tidigare. Det är som PuppeterSharp är mer ambitiösa syskon - det gör allt Puppeter gör, men lägger Firefox och Safari stöd, bättre auto-vänta, och en mängd felsökningsverktyg som gör att hitta frågor en absolut doddle.

I den här artikeln ska jag visa dig hur du använder Playwright för .NET att testa över Chrome, Firefox och Safari, med riktiga kod exempel och praktiska mönster som du kan använda idag.

Varför spela på PuppeterSharp?

Missförstå mig inte. PuppeterSharp är utmärkt om du bara behöver Chrome. Men här är när Playwright verkar vettigt:

Problemet med flera webbläsare

Alla användare använder inte Chrome. De använder:

  • Krom/ edge: ~65% av användarna (krombaserad)
  • Safari: ~20% av användarna (särskilt mobila)
  • Firefox Ordförande: ~5% av användarna (men ofta olika rendering)

En bugg som bara visas i Safari kan förlora dig 20% av dina potentiella användare. Playwright låter dig testa alla tre med samma API.

Bättre erfarenhet av utvecklare

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

Viktiga fördelar

  1. Stöd för flera browserName - Testa Chrome, Firefox, Safari med identisk kod
  2. Bättre automatiskt väntande - Mer pålitlig ur lådan, färre flagnande tester
  3. Spårvisare - Registrera fullständiga spår av testkörningar för felsökning
  4. Kod - Generera testkod genom att interagera med din webbplats
  5. Nätverksavlyssning - Mer kraftfull än PuppeterSharp
  6. Moderna webbfunktioner - Bättre stöd för Shadow DOM, iframes, etc.

Ställa in Playwright för .NET

Installera Microsoft.Playwright Ordförande NuGet-paket:

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

Installera sedan webbläsare (detta nedladdningar Tillverkning utgående från material enligt vilket HS-nummer som helst, utom material enligt samma HS-nummer som produkten, Firefox Ordförande, och Webbkit):

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

Eller på Linux/Mac:

playwright install

Anmärkning: Den första webbläsaren installera nedladdningar runt 400MB. Efterföljande uppdateringar är mycket mindre.

Här är min testprojektkonfiguration:

<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>

Jag använder xUnit, men Playwright fungerar lika bra med Nunit Ordförande eller MSTest Ordförande. Faktum är att Playwright har en hängiven Integrering av nätverk med ytterligare hjälpare.

Skapa en testklass i basen

Liknar PuppeterSharp, men med multi-browser stöd:

Klassstrukturen

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
}

Varför webbläsare sammanhang?

Lägg märke till att vi skapar en Context innan du skapar en Page. Detta är ett Playwright koncept som PuppeteerSharp inte har:

  • Bakgrund = Isolerad webbläsarsession (som inkognitoläge)
  • Huvudfrågor = En flik i det sammanhanget

Sammanhang låter dig:

  • Test med olika användarsessioner samtidigt
  • Ställ in olika cookies, behörigheter eller lokal per sammanhang
  • Isolera tester helt (även i samma webbläsare instans)

Metoder för hjälp

// 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 - Playwright hemliga vapen

Lägg märke till Page.Locator() Det här skiljer sig från PuppeterSharp's. QuerySelector. Sökare är:

  • Lazy Ordförande - De frågar inte DOM förrän du använder dem.
  • Försök igen - De väntar automatiskt och försöker igen.
  • Strikt - De fel om flera element matchar (förhindra flagnande tester)
// PuppeteerSharp way (manual waiting)
await Page.WaitForSelectorAsync("#button");
await Page.ClickAsync("#button");

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

Provning av flera webbläsare

Här lyser Playwright. Du kan köra samma test i alla webbläsare:

Användning av xUnit-teori för multi-Browser-tester

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}");
    }
}

Browser-specifika testklasser

Eller skapa separata testklasser för varje webbläsare:

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);
    }
}

Skriva tester - Uppspelningsrätt sätt

Låt oss titta på verkliga exempel som visar Playwrights fördelar:

Testning av HTMX med automatisk väntan

[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);
}

Pröva Alpine.js interaktioner

Spelrättshandtag Alpina.js Reaktivitet och animeringar sömlöst:

[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);
}

Testformulär med validering

[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("[email protected]");
    await Page.Locator("#comment-content").FillAsync("Great article!");

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

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

Avancerade funktioner för spelrätt

Nätverksinterception - mer kraftfull än PuppeterSharp

[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);
}

Spårvisare - Felsökning misslyckades tester

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();
}

Visa sedan spåren:

playwright show-trace traces/TestName.zip

Detta öppnar ett användargränssnitt som visar:

  • Varje åtgärd ditt test tog
  • Skärmbilder vid varje steg
  • Nätverksförfrågningar
  • Konsolloggar
  • DOM-ögonblicksbilder

Det är helt lysande för felsökning.

Skärmdumpar vid fel

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();
}

Mobil emulering

[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();
}

Playwright följer med Enhetsdeskriptorer i stället för:

  • iPhone 13, 13 Pro, 12, 11, SE
  • Andra, med en bredd av mindre än 600 mm
  • Samsung Galaxy, Pixel Ordförande
  • Och många fler

Test av mörkt läge

[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 PuppeterSharp - sida vid sida

Här är samma test i båda biblioteken för att visa skillnaderna:

Version av PuppeteerSharp

[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);
}

Version av Playwright

[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=.*"));
}

Meddelande:

  • Ingen manuell WaitForSelectorAsync behövlig
  • Ej tillämpligt Task.Delay behövlig
  • Renare påståenden med Expect
  • Mer läsbar med has-text välj

PDF-generering med Playwright

Liksom PuppeterSharp kan Playwright generera PDF-filer. API:et är nästan identiskt:

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"
        }
    });
}

Samma PDF fickchas från PuppeteerSharp artikeln gäller även här.

När du ska välja Playwright

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

Välj Playwright när:

  • Du behöver flerbrowser-testning
  • Du vill ha bättre felsökningsverktyg (trace viewer)
  • Du testar komplexa moderna webbappar
  • Du behöver mobil emulering
  • Du vill ha mer tillförlitliga tester ur lådan

Välj PuppeterSharp när:

  • Endast Chrome/Edge är bra
  • Du vill ha enklare inställning
  • Du känner redan till Puppeter.
  • Du behöver något mindre resurs overhead

Körning i CI/CD

Exempel på GitHub- åtgärder

Playwright fungerar sömlöst i CI/CD rörledningar. Här är en komplett GitHub-åtgärder arbetsflöde:

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

Stöd för DockerName

Köra Playwright tester i Docka kräver att systemberoenden installeras:

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"]

Resultatjämförelse

Från mitt test på den här bloggen:

på funktionen "PuppeteerSharp" |---------|---------------|------------| | Kromtesthastighet till ~2.5s på ~2.8s | Flerradig browser (tillägg ~1s per webbläsare) | Automatiskt väntande tillförlitlighet till Bra (manuell) på Utmärkt (automatisk) | Minne per webbläsare Omvandling till ett värde av mer än 150 Mbit/m2 | Konfigurering av komplexitet "Enkla " moderata " | Avlusningsverktyg på Chrome DevTools på Trace Viewer + DevTools | Mobil emulering och basic på ett utmärkt sätt

Playwright är något långsammare och använder lite mer minne, men tillförlitligheten och felsökningsfördelarna överväger vanligtvis detta.

Vanliga gotchor

1. Strikt läge

Playwrights lokalisering är strikt som standard:

// 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. Installation av webbläsare

Glöm inte att springa. playwright install Efter att ha lagt till paketet. Webbläsaren ingår inte i NuGet-paketet.

3. Sammanhang mot sida

Kom ihåg sammanhang isolera tester:

// 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. Webbkit Skillnader

Safari (WebKit) kan bete sig annorlunda:

// 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();

Slutsatser

Playwright representerar utvecklingen av webbläsarautomation för .NET-utvecklare. Om du behöver stöd för multi-browser, är det det tydliga valet. Ja, det är något mer komplicerat än PuppeterSharp, men fördelarna är betydande:

  • Testa alla större webbläsare med identisk kod
  • Bättre tillförlitlighet med automatisk väntan
  • Utmärkt felsökning med spårvisning
  • Mobil och tablettemulering ut ur lådan
  • Mer motståndskraftig mot timingfrågor

Min rekommendation:

  • Nya projekt som behöver kors-browser-testas: Börja med Playwright
  • Endast Chrome-projektPuppeterSharp är enklare
  • Befintliga PuppeterSharp-projekt: Migrera endast om du behöver multi-browser
  • Komplexa moderna webbappar: Playwright verktyg är värt overhead

Spårvisningen ensam har sparat mig timmar av felsökning. Att kunna spela upp ett misslyckat test och se exakt vad webbläsaren såg vid varje steg är ovärderligt.

Ge Playwright en chans på ditt nästa projekt - du kommer att bli glatt förvånad över hur mycket lättare det gör kors-browser tester.

Ytterligare läsning

logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.