Back to "Pruebas multi-navegador E2E con Playwright para .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

Pruebas multi-navegador E2E con Playwright para .NET

Thursday, 27 November 2025

Playwright es la solución oficial de Microsoft para la automatización de navegadores modernos, que ofrece soporte multi-navegador (Chrome, Firefox, Safari) con una sola API, depuración de trazas integrada, y emulación móvil. Esta guía cubre todo, desde la configuración hasta los patrones de prueba avanzados, comparándolo con PuppeteerSharp para ayudarle a elegir la herramienta correcta. TitiriteroSharp en su lugar - pero si necesita pruebas completas de cross-browser, Playwright es el camino a seguir.

Introducción

Si has leído mi artículo sobre TitiriteroSharp, usted sabrá que soy un fan de las modernas herramientas de prueba E2E que no te hacen querer rasgar el pelo hacia fuera . TitiriteroSharp es brillante para las pruebas sólo Chrome , pero lo que si usted necesita para probar a través de múltiples navegadores ? Ahí es donde Playwright Pasa.

Playwright es la respuesta de Microsoft al problema de automatización del navegador, y han aprendido de todo lo que vino antes. Es como el hermano más ambicioso de PuppeteerSharp - hace todo lo que Tippeteer hace, pero añade Firefox y Safari soporte, mejor auto-esperar, y una gran cantidad de herramientas de depuración que hacen que encontrar problemas sea un problema absoluto.

En este artículo, te mostraré cómo utilizar Playwright para .NET para probar a través de Chrome, Firefox y Safari, con ejemplos de código real y patrones prácticos que puede utilizar hoy en día.

¿Por qué dramaturgo sobre TitiriteroSharp?

No me malinterpretes TitiriteroSharp es excelente si usted sólo necesita Chrome. Pero aquí es cuando Playwright tiene sentido:

El problema del navegador múltiple

Sus usuarios no todos utilizan Chrome. Utilizan:

  • Cromo/Edge: ~65% de los usuarios (basado en cromo)
  • Safari: ~20% de los usuarios (especialmente móviles)
  • Firefox: ~5% de los usuarios (pero a menudo diferente representación)

Un error que sólo aparece en Safari puede perder el 20% de sus usuarios potenciales. Playwright le permite probar los tres con la misma API.

Mejor experiencia de desarrollador

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

Ventajas clave

  1. Soporte multi-navegador - Prueba Chrome, Firefox, Safari con código idéntico
  2. Mejor auto-esperar - Más confiable fuera de la caja, menos pruebas escalofriantes
  3. Visor de rastros - Registrar los rastros completos de las pruebas de depuración
  4. Codificador - Generar código de prueba interactuando con su sitio
  5. Intercepción de redes - Más poderoso que Titiritero Sharp
  6. Características modernas de la web - Mejor soporte para Shadow DOM, iframes, etc.

Configuración de Playwright para .NET

Instalar la Microsoft.Playwright Paquete NuGet:

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

A continuación, instale los navegadores (estas descargas Cromo, Firefox, y WebKit):

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

O en Linux/Mac:

playwright install

Nota: El primer navegador instalar descargas alrededor de 400 MB. Las actualizaciones posteriores son mucho más pequeñas.

Aquí está mi configuración de proyecto de prueba:

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

Estoy usando xUnit, pero el dramaturgo funciona igual de bien con NUnit o MSTest. De hecho, Playwright tiene un dedicado Integración de NUnit con ayudantes adicionales.

Creación de una clase de prueba básica

Similar a TitiriteroSharp, pero con soporte multi-navegador:

La estructura de clase

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
}

¿Por qué los contextos del navegador?

Note que creamos un Context antes de crear una PageEste es un concepto de dramaturgo que TitiriteroSharp no tiene:

  • Contexto = Sesión aislada del navegador (como el modo de incógnito)
  • Página = Una pestaña dentro de ese contexto

Los contextos le permiten:

  • Prueba con diferentes sesiones de usuario simultáneamente
  • Establecer diferentes cookies, permisos o localización por contexto
  • Pruebas de aislamiento completamente (incluso dentro de la misma instancia del navegador)

Métodos de ayuda

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

Localizadores - El arma secreta del dramaturgo

Nótese que Page.Locator() Esto es diferente de las llamadas de PuppeteerSharp. QuerySelector. Los localizadores son:

  • Perezoso - No consultan al DOM hasta que los usas.
  • Reintentar - Esperan y reintentan automáticamente las operaciones
  • Estricto - Error si varios elementos coinciden (prevención de pruebas escalofriantes)
// PuppeteerSharp way (manual waiting)
await Page.WaitForSelectorAsync("#button");
await Page.ClickAsync("#button");

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

Ensayo de múltiples navegadores

Aquí es donde brilla Playwright. Puedes ejecutar la misma prueba en todos los navegadores:

Usando la teoría de xUnit para pruebas de múltiples navegadores

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

Clases de prueba específicas para el navegador

O cree clases de prueba separadas para cada navegador:

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

Pruebas de escritura - El camino del dramaturgo

Veamos ejemplos reales que muestran las ventajas de Playwright:

Pruebas HTMX con espera automática

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

Pruebas de Interacciones Alpine.js

Manijas de reproducción Alpine.js reactividad y animaciones sin problemas:

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

Formularios de prueba con validación

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

Características avanzadas de los derechos de reproducción

Intercepción de red - más poderoso que el titiriteroSharp

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

Visor de rastros - Pruebas fallidas de depuración

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

A continuación, ver el rastro:

playwright show-trace traces/TestName.zip

Esto abre una interfaz de usuario mostrando:

  • Cada acción que su prueba tomó
  • Capturas de pantalla en cada paso
  • Solicitudes de redes
  • Registros de la consola
  • Snapshots DOM

Es absolutamente brillante para la depuración.

Capturas de pantalla sobre el fracaso

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

Emulación móvil

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

El dramaturgo viene con descriptores de dispositivos donde dice:

  • iPhone 13, 13 Pro, 12, 11, SE
  • iPad, iPad Pro
  • Samsung Galaxy, Pixel
  • Y muchos más

Probando el modo oscuro

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

dramaturgo vs titiriteroSharp - lado a lado

Aquí está la misma prueba en ambas bibliotecas para mostrar las diferencias:

Versión de TitiriteroSharp

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

Versión de reproducción

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

Aviso:

  • Sin manual WaitForSelectorAsync Necesario
  • No Task.Delay Necesario
  • Aserciones más limpias con Expect
  • Más legible con has-text selector

Generación PDF con derechos de reproducción

Como PuppeteerSharp, Playwright puede generar PDFs. La API es casi idéntica:

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

Lo mismo PDF gotchas del artículo de PuppeteerSharp aplicar aquí también.

Cuándo elegir el derecho de reproducción

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

Elija Playwright cuando:

  • Usted necesita pruebas multi-navegador
  • Quiere mejores herramientas de depuración (veedor de rastreo)
  • Estás probando aplicaciones web modernas y complejas
  • Necesitas emulación móvil
  • Usted quiere pruebas más confiables fuera de la caja

Elija PuppeteerSharp cuando:

  • Cromo/Edge sólo está bien
  • Quieres una configuración más sencilla.
  • Ya conoces a Puppeteer.
  • Necesitas un poco menos de recursos.

Correr en CI/CD

Ejemplo de acciones de GitHub

Playwright funciona perfectamente en tuberías CI/CD. Aquí está un completo Acciones de GitHub flujo de trabajo:

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

Soporte Docker

Ejecución de pruebas de reproducción en Docker requiere dependencias del sistema de instalación:

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

Comparación de los resultados

De mis pruebas en este blog:

Característica TitiriteroSharp dramaturgo |---------|---------------|------------| | Velocidad de ensayo cromada ~2.5s ~2.8s ~2.8s ~2.8s ~2.5s ~2.5s ~2.5s ~2.8s ~2.8s ~2.8s ~2.8s ~2.8s ~2.8s ~2.8s ~2.5s ~2.5s ~2.5s ~2.8s ~2.8s ~2.8s ~2.8s ~2.8s ~2.8s ~2.8s | Multi-navegador ✓ (adiciones ~1s por navegador) | Fiabilidad de la espera automática Bueno (manual) Excelente (automático) | Memoria por navegador ~150MB ~180MB | Complejidad de configuración # Simple # # Moderado # | Herramientas de depuración DevTools Chrome Visor de rastros + DevTools | Emulación móvil # Básico # # Excelente #

Playwright es un poco más lento y utiliza un poco más de memoria, pero los beneficios de fiabilidad y depuración generalmente superan esto.

Gotchas comunes

1. Modo estricto

Los localizadores de Playwright son estrictos por defecto:

// 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. Instalación del navegador

No te olvides de correr. playwright install después de añadir el paquete. Los navegadores no están incluidos en el paquete NuGet.

3. Contexto vs Página

Recuerde las pruebas de aislamiento de contextos:

// 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. Diferencias en el Webkit

Safari (WebKit) puede comportarse de manera diferente:

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

Conclusión

Playwright representa la evolución de la automatización del navegador para los desarrolladores .NET. Si usted necesita soporte multi-navegador, es la opción clara. Sí, es ligeramente más complejo que TitiriteroSharp, pero los beneficios son sustanciales:

  • Prueba en todos los navegadores principales con código idéntico
  • Mejor fiabilidad con espera automática
  • Excelente depuración con visor de trazas
  • Emulación móvil y tableta fuera de la caja
  • Más resistente a los problemas de cronometración

Mi recomendación:

  • Nuevos proyectos que requieren pruebas de cross-browser: Comience con el derecho de reproducción
  • Proyectos de sólo Chrome: TitiriteroSharp es más simple
  • Proyectos existentes de TitiriteroSharp: Migrar sólo si necesita multi-navegador
  • Aplicaciones web modernas complejas: La herramienta del dramaturgo vale la pena

El observador de trazas solo me ha ahorrado horas de depuración. Ser capaz de reproducir una prueba fallida y ver exactamente lo que el navegador vio en cada paso es invaluable.

Dale a Playwright una oportunidad en tu próximo proyecto - te sorprenderá gratamente lo fácil que es hacer pruebas de cross-browser.

Lectura adicional

logo

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