Back to "Multi-Browser E2E Testing με Playwright για .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 Testing με Playwright για .NET

Thursday, 27 November 2025

Το Playwright είναι η επίσημη λύση της Microsoft για τη σύγχρονη αυτοματοποίηση του προγράμματος περιήγησης, προσφέροντας υποστήριξη πολλαπλών προγραμμάτων περιήγησης (Chrome, Firefox, Safari) με ένα ενιαίο API, ενσωματωμένο ίχνος αποσφαλμάτωσης, και κινητή εξομοίωση. Αυτός ο οδηγός καλύπτει τα πάντα από τη ρύθμιση έως προηγμένα πρότυπα δοκιμών, συγκρίνοντας το με PuppeteerSharp για να σας βοηθήσει να επιλέξετε το σωστό εργαλείο. PuppeteerSharpCity name (optional, probably does not need a translation) αντί - αλλά αν χρειάζεστε ολοκληρωμένη δοκιμή cross-browser, Playwright είναι ο δρόμος προς τα εμπρός.

Εισαγωγή

Αν έχεις διαβάσει το άρθρο μου PuppeteerSharpCity name (optional, probably does not need a translation)Θα ξέρετε ότι είμαι οπαδός των σύγχρονων εργαλείων δοκιμής E2E που δεν σας κάνει να θέλετε να σχίσει τα μαλλιά σας έξω. PuppeteerSharp είναι λαμπρή για δοκιμές μόνο Chrome, αλλά τι γίνεται αν πρέπει να δοκιμάσετε σε πολλούς browsers; Εκεί είναι που Playwright Έλα μέσα.

Playwright είναι η απάντηση της Microsoft στο πρόβλημα αυτοματισμού του προγράμματος περιήγησης, και έχουν μάθει από όλα όσα ήρθε πριν. Είναι σαν PuppeteerSharp πιο φιλόδοξο αδερφό - κάνει ό, τι Puppeteer κάνει, αλλά προσθέτει υποστήριξη Firefox και Safari, καλύτερη αυτόματη αναμονή, και μια σειρά από εργαλεία αποσφαλμάτωσης που κάνουν την εύρεση θεμάτων ένα απόλυτο doddle.

Σε αυτό το άρθρο, θα σας δείξω πώς να χρησιμοποιήσετε Playwright για .NET για να δοκιμάσετε σε όλο Chrome, Firefox, και Safari, με πραγματικά παραδείγματα κώδικα και πρακτικά πρότυπα που μπορείτε να χρησιμοποιήσετε σήμερα.

Γιατί το Playwright Over PuppeteerSharp;

Μη με παρεξηγείς PuppeteerSharpCity name (optional, probably does not need a translation) Αλλά εδώ είναι όταν Playwright βγάζει νόημα:

Το πρόβλημα Multi-Browser

Οι χρήστες σας δεν χρησιμοποιούν όλοι Chrome.

  • Chrome/Edge~65% των χρηστών (με βάση το χρώμιο)
  • ΣαφάριCity name (optional, probably does not need a translation)~20% των χρηστών (ιδιαίτερα των κινητών)
  • Firefox~5% των χρηστών (αλλά συχνά διαφορετική απόδοση)

Ένα σφάλμα που εμφανίζεται μόνο στο Safari μπορεί να σας χάσει το 20% των πιθανών χρηστών σας. Playwright σας επιτρέπει να δοκιμάσετε και τα τρία με το ίδιο API.

Καλύτερη Εμπειρία Προγραμματιστή

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

Βασικά Πλεονεκτήματα

  1. Υποστήριξη πολλαπλών περιηγητών - Δοκιμή Chrome, Firefox, Safari με πανομοιότυπο κωδικό
  2. Καλύτερη αυτόματη αναμονή - Πιο αξιόπιστος από το κουτί, λιγότερες δοκιμές.
  3. Προβολέας ιχνοστοιχείωνName - Καταγράψτε τα πλήρη ίχνη των δοκιμών για αποσφαλμάτωση
  4. Codegen - Δημιουργία κώδικα δοκιμής με την αλληλεπίδραση με το site σας
  5. Παρακολούθηση δικτύου - Πιο ισχυρό από το PuppeteerSharp
  6. Σύγχρονα web features - Καλύτερη υποστήριξη για Shadow DOM, iframes, κλπ.

Ρύθμιση Playwright για .NET

Εγκαταστήστε το Microsoft.Playwright NuGet πακέτο:

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

Στη συνέχεια εγκαταστήστε τα προγράμματα περιήγησης (αυτό κατεβάζει Χρώμιο, Firefox, και WebKitCity name (optional, probably does not need a translation)):

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

Ή στο Linux/Mac:

playwright install

Σημείωση: Το πρώτο πρόγραμμα περιήγησης εγκαθιστά λήψεις περίπου 400MB. Οι επόμενες ενημερώσεις είναι πολύ μικρότερες.

Ορίστε η διαμόρφωση του δοκιμαστικού έργου μου:

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

Χρησιμοποιώ. xUnit, αλλά Playwright λειτουργεί εξίσου καλά με NUnitCity name (optional, probably does not need a translation) ή MSTestCity name (optional, probably does not need a translation)Στην πραγματικότητα, το Playwright έχει μια αφοσιωμένη Ενσωμάτωση NUnitName με επιπλέον βοηθούς.

Δημιουργία μιας βασικής κατηγορίας δοκιμών

Παρόμοια με PuppeteerSharpCity name (optional, probably does not need a translation), αλλά με υποστήριξη πολλαπλών browser:

Η δομή της κλάσης

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
}

Γιατί Πλαίσιο Περιήγησης;

Παρατηρήστε ότι δημιουργούμε ένα Context πριν από τη δημιουργία ενός Page. Αυτό είναι μια ιδέα Playwright ότι PuppeteerSharp δεν έχει:

  • Πλαίσιο = Απομονωμένη συνεδρία προγράμματος περιήγησης (όπως λειτουργία incognito)
  • Σελίδα = Μια καρτέλα στο πλαίσιο αυτό

Τα πλαίσια σάς επιτρέπουν:

  • Δοκιμή με διαφορετικές συνεδρίες χρήστη ταυτόχρονα
  • Ορισμός διαφορετικών cookies, αδειών, ή τοποθεσία ανά πλαίσιο
  • Απομόνωση των δοκιμών πλήρως (ακόμη και εντός της ίδιας περίπτωσης του προγράμματος περιήγησης)

Μέθοδοι Βοηθού

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

Εντοπιστές - Μυστικό Όπλο του Playwright

Παρατηρήστε το Page.Locator() Αυτό είναι διαφορετικό από το PuppeteerSharp's QuerySelectorΟι εντοπιστές είναι:

  • Τεμπέλης - Δεν ρωτούν το DOM μέχρι να τα χρησιμοποιήσεις.
  • Επαναπροσπάθεια - Περιμένουν αυτόματα και ξαναπροσπαθούν.
  • Αυστηρά - Σφάλμα αν πολλαπλά στοιχεία ταιριάζουν (προληπτικές δοκιμές)
// PuppeteerSharp way (manual waiting)
await Page.WaitForSelectorAsync("#button");
await Page.ClickAsync("#button");

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

Δοκιμή πολλαπλών περιηγητών

Εδώ είναι όπου Playwright λάμπει. Μπορείτε να εκτελέσετε το ίδιο τεστ σε όλους τους browser:

Χρησιμοποιώντας τη θεωρία xUnit για δοκιμές πολλαπλών περιηγητών

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

Ειδικές κατηγορίες δοκιμών για το πρόγραμμα περιήγησης

Ή δημιουργήστε ξεχωριστές κατηγορίες δοκιμών για κάθε πρόγραμμα περιήγησης:

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

Γράφοντας Δοκιμασίες - Ο Τρόπος των Παιχνιδιών

Ας δούμε πραγματικά παραδείγματα που δείχνουν τα πλεονεκτήματα του Playwright:

Δοκιμή HTMX με αυτόματη αναμονή

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

Δοκιμή αλπικών αλληλεπιδράσεων

Λαβές Playwright Alpine.js αντιδραστικότητα και κινούμενες εικόνες απρόσκοπτα:

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

Έντυπα δοκιμής με επικύρωση

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

Προχωρημένα χαρακτηριστικά του Playwright

Network Interception - Πιο ισχυρό από το PuppeteerSharp

[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 - Αποτυχημένες δοκιμές αποσφαλμάτωσης

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

Στη συνέχεια, δείτε το ίχνος:

playwright show-trace traces/TestName.zip

Αυτό ανοίγει ένα UI δείχνει:

  • Κάθε πράξη που έκανε το τεστ σου
  • Στιγμιότυπα οθόνης σε κάθε βήμα
  • Αιτήματα δικτύου
  • Καταχωρήσεις κονσόλας
  • Φωτογραφίες DOM

Είναι πανέξυπνο για αποσφαλμάτωση.

Στιγμιότυπα οθόνης για την αποτυχία

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

Κινητή εξομοίωση

[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 έρχεται με περιγραφές συσκευών αντί:

  • iPhone 13, 13 Pro, 12, 11, SE
  • iPad, iPad Pro
  • Samsung Galaxy, Pixel
  • Και πολλά άλλα.

Δοκιμή σκοτεινής λειτουργίας

[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 - Side by Side

Εδώ είναι το ίδιο τεστ και στις δύο βιβλιοθήκες για να δείξει τις διαφορές:

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

Έκδοση συγγραφέα

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

Ειδοποίηση:

  • Χωρίς εγχειρίδιο WaitForSelectorAsync που απαιτείται
  • Όχι. Task.Delay που απαιτείται
  • Καθαρότεροι ισχυρισμοί με Expect
  • Πιο ευανάγνωστο με has-text επιλογέας

PDF Γενιά με Playwright

Όπως PuppeteerSharp, Playwright μπορεί να δημιουργήσει PDFs. Το API είναι σχεδόν πανομοιότυπο:

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

Τα ίδια. PDF gotchas από το άρθρο PuppeteerSharp Ας κάνουμε αίτηση και εδώ.

Πότε να επιλέξετε το 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

Επιλέξτε Playwright όταν:

  • Χρειάζεσαι έλεγχο πολλαπλών περιηγητών
  • Θέλετε καλύτερα εργαλεία αποσφαλμάτωσης (trace viewer)
  • Δοκιμάζετε περίπλοκες σύγχρονες εφαρμογές ιστού
  • Χρειάζεσαι κινητή εξομοίωση.
  • Θέλετε πιο αξιόπιστες δοκιμές έξω από το κουτί

Επιλέξτε PuppeteerSharp όταν:

  • Το Chrome/Edge είναι μια χαρά.
  • Θέλεις απλούστερη ρύθμιση.
  • Είσαι ήδη εξοικειωμένος με τον Puppeteer.
  • Χρειάζεσαι λιγότερο πόρο.

Εκτέλεση σε CI/CD

Παράδειγμα ενεργειών GitHub

Το Playwright λειτουργεί απρόσκοπτα στους αγωγούς CI/CD. Ενέργειες GitHub ροή εργασίας:

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

Υποστήριξη Docker

Τρέξιμο τεστ Playwright σε ΝτόκερCity name (optional, probably does not need a translation) απαιτεί την εγκατάσταση εξαρτήσεων συστήματος:

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

Σύγκριση επιδόσεων

Από τις δοκιμές μου σε αυτό το blog:

□ Χαρακτηριστικό του PuppeteerSharp Playwright |---------|---------------|------------| | Ταχύτητα δοκιμής Chrome ~2.5s ~2.8s ~ | Πολλαπλός περιηγητήςName ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ | Αξιοπιστία αυτόματης αναμονής Ωραίο (εγχειρίδιο) Τέλεια (αυτόματη) | Μνήμη ανά περιηγητή {\pos (195,220) {\pos (192,220) } {\pos (192,220) } | Ρύθμιση πολυπλοκότητας Απλά Μεσαία | Εργαλεία αποσφαλμάτωσης ~ Chrome DevTools ~ Trace Viewer + DevTools ~ | Κινητή εξομοίωση Βασική βάση Εξαιρετική

Το Playwright είναι ελαφρώς πιο αργό και χρησιμοποιεί ελαφρώς περισσότερη μνήμη, αλλά η αξιοπιστία και τα οφέλη αποσφαλμάτωσης συνήθως αντισταθμίζουν αυτό.

Συχνές ΓκόταςCity name (optional, probably does not need a translation)

1. Αυστηρή λειτουργία

Οι εντοπιστές του Playwright είναι αυστηροί εξ ορισμού:

// 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. Εγκατάσταση περιηγητή

Μην ξεχάσεις να τρέξεις. playwright install Μετά την προσθήκη του πακέτου. Τα προγράμματα περιήγησης δεν περιλαμβάνονται στο πακέτο NuGet.

3. Πλαίσιο εναντίον σελίδας

Θυμηθείτε τα συμφραζόμενα απομονώνουν τις δοκιμές:

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

Το Safari (WebKit) μπορεί να συμπεριφέρεται διαφορετικά:

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

Συμπέρασμα

Το Playwright αντιπροσωπεύει την εξέλιξη της αυτοματοποίησης του browser για τους προγραμματιστές .NET. Αν χρειάζεστε υποστήριξη πολλαπλών browser, είναι η σαφής επιλογή. PuppeteerSharpCity name (optional, probably does not need a translation), αλλά τα οφέλη είναι σημαντικά:

  • Δοκιμή σε όλα τα κύρια προγράμματα περιήγησης με πανομοιότυπο κωδικό
  • Καλύτερη αξιοπιστία με αυτόματη αναμονή
  • Εξαιρετική αποσφαλμάτωση με οθόνη ιχνοστοιχείων
  • Εξομοίωση κινητού και δισκίου έξω από το κουτί
  • Πιο ανθεκτικό στο συγχρονισμό θέματα

Η πρότασή μου:

  • Νέα έργα που χρειάζονται δοκιμές cross-browser: Ξεκινήστε με Playwright
  • Σχέδια μόνο για το χρώμιο: PuppeteerSharp είναι απλούστερη
  • Υφιστάμενα έργα PuppeteerSharp: Μετατροπή μόνο αν χρειάζεστε multi-browser
  • Συγκρότημα σύγχρονων εφαρμογών ιστού: Playwright's tooling is week the overhead

Το να είμαι σε θέση να ξαναπαίξω ένα αποτυχημένο τεστ και να δω ακριβώς τι είδε ο περιηγητής σε κάθε βήμα είναι ανεκτίμητο.

Δώστε Playwright ένα go στο επόμενο πρότζεκτ σας - θα εκπλαγείτε ευχάριστα πόσο πιο εύκολο κάνει δοκιμές cross-browser.

Περαιτέρω Ανάγνωση

logo

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