E2E Test với ASP.NET Core, XUnit và Playwright

E2E Test với ASP.NET Core, XUnit và Playwright

Trong cộng đồng .NET, kiểm thử tích hợp (integration test) đã trở thành một thông lệ phổ biến nhờ vào những tiến bộ trong framework. ASP.NET Core có một bộ thử nghiệm tích hợp mạnh mẽ cho phép các nhà phát triển .NET chạy các phiên bản trong bộ nhớ của ứng dụng web của họ, với khả năng đưa ra các yêu cầu và xác minh các phản hồi.

Nói chung, bộ thử nghiệm là tốt, nhưng nó có giới hạn của nó, chủ yếu là các phản hồi là tải trọng theo nghĩa đen và không được thực thi trong ngữ cảnh của phiên trình duyệt. Trong bài viết này, chúng ta sẽ tìm hiểu cách sử dụng thư viện Playwright kết hợp với XUnit để kiểm tra các ứng dụng web của chúng ta như người dùng có thể.

Playwright là gì?

Playwright là một thư viện cho phép các nhà phát triển viết các bài kiểm tra đầu cuối (end-to-end test – e2e test) cho các ứng dụng web của họ. Các nhà phát triển sử dụng Playwright có thể tự động hóa các công cụ trình duyệt phổ biến Chromium, Firefox và Webkit trên tất cả các hệ điều hành hiện đại: Linux, macOS và Windows.

Với một loạt các công cụ web, các nhà phát triển có thể kiểm tra các trang HTML đơn giản đến các ứng dụng một trang phức tạp mà không có giới hạn. Playwright cũng có sự hỗ trợ tuyệt vời bất kể công nghệ của nhà phát triển lựa chọn là gì, với các API được viết cho JavaScript, Typescript, Python, Java và C#.

Các thử nghiệm cũng sẽ chạy trong quá trình thực hiện CI/CD, giúp đảm bảo các lỗi UX không tự hoạt động trong môi trường sản xuất (production).

Mặc dù mục đích chính của Playwright là dành cho các thử nghiệm đầu cuối (e2e test), nhưng các nhà phát triển cũng có thể sử dụng nó để thu thập dữ liệu từ các ứng dụng web hiện có. Hãy sử dụng kiến ​​thức này một cách tôn trọng và có trách nhiệm.

Cách sử dụng Playwright với ASP.NET Core và XUnit

Đầu tiên, chúng ta sẽ cần phải cài đặt các gói NuGet XUnit, Microsoft.NET.Test.SdkPlaywrightSharp.

<ItemGroup>
  <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
  <PackageReference Include="PlaywrightSharp" Version="0.192.0" />
  <PackageReference Include="xunit" Version="2.4.1" />
</ItemGroup>

Đáng buồn là chúng ta không thể sử dụng thư viện tích hợp là một phần của ASP.NET Core. Như đã đề cập trước đây, nó chỉ chạy trong bộ nhớ, có nghĩa là trình duyệt không thể điều hướng đến bất kỳ điểm cuối nào mà chúng ta hiển thị.

Vấn đề đòi hỏi một số khéo léo từ phía chúng ta. May mắn thay, chúng ta có thể tạo một ứng dụng ASP.NET Core bằng phương thức CreateHostBuilder thường thấy trong tệp Program.cs trong ứng dụng của chúng ta.

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

Chúng ta cũng sẽ sử dụng interface IClassFixture trong XUnit để giữ cho các phiên bản của trình duyệt ở mức tối thiểu. Playwright nhanh chóng nhưng yêu cầu một số thời gian thiết lập và khởi động mà chúng ta muốn tránh trong mọi thử nghiệm.

Chúng ta cũng sẽ chỉ định Chromium làm trình duyệt mà chúng ta sẽ sử dụng cho các thử nghiệm của mình. Hãy xem xét cốt lõi của các bài kiểm tra của chúng ta.

Lưu ý, bạn có thể cần đặt gốc nội dung (thư mục wwwroot) của ứng dụng web nếu bạn có nội dung CSS và javascript. Sử dụng webBuilder để gọi UseContentRoot với một đường dẫn.

using System;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using PlaywrightSharp;
using Xunit;

// ReSharper disable once ClassNeverInstantiated.Global
public class WebServerFixture : IAsyncLifetime, IDisposable
{
    private readonly IHost host;
    private IPlaywright Playwright { get; set; }
    public IBrowser Browser { get; private set; }
    public string BaseUrl { get; } = $"https://localhost:{GetRandomUnusedPort()}";

    public WebServerFixture()
    {
        host = Program
            .CreateHostBuilder(null)
            .ConfigureWebHostDefaults(webBuilder => {
                webBuilder.UseStartup<Startup>();
                webBuilder.UseUrls(BaseUrl);
                // optional to set path to static file assets
                // webBuilder.UseContentRoot();
            })
            .ConfigureServices(configure => {
                // override any services
            })
            .Build();
    }

    public async Task InitializeAsync()
    {
        Playwright = await PlaywrightSharp.Playwright.CreateAsync();
        Browser = await Playwright.Chromium.LaunchAsync();
        await host.StartAsync();
    }

    public async Task DisposeAsync()
    {
        await host.StopAsync();
        host?.Dispose();
        Playwright?.Dispose();
    }

    public void Dispose()
    {
        host?.Dispose();
        Playwright?.Dispose();
    }

    private static int GetRandomUnusedPort()
    {
        var listener = new TcpListener(IPAddress.Any, 0);
        listener.Start();
        var port = ((IPEndPoint)listener.LocalEndpoint).Port;
        listener.Stop();
        return port;
    }
}

Lớp WebServerFixture giúp chúng ta tạo ra các đối tượng cho interface IPlaywrightIBrowser. Ngoài ra, trong phương thức khởi tạo tạo, chúng ta cần khởi tạo và thiết lập đối tượng IHost cho ứng dụng ASP.NET Core của chúng ta. Chúng ta cũng cần tìm một cổng mở ngẫu nhiên để nghe tiếp.

private static int GetRandomUnusedPort()
{
    var listener = new TcpListener(IPAddress.Any, 0);
    listener.Start();
    var port = ((IPEndPoint)listener.LocalEndpoint).Port;
    listener.Stop();
    return port;
}

Trước khi chúng ta bắt đầu viết các bài kiểm tra, hãy nói về chiến lược kiểm tra. Theo ý kiến ​​của tôi, tốt nhất là bổ sung cho các phần tử HTML với các thuộc tính duy nhất sẽ không thay đổi khi trải nghiệm UI/UX của chúng ta phát triển. Tôi đã thêm một thuộc tính pw-name vào các phần tử mô tả một tính năng trên trang.

<h1 class="display-4" pw-name="Page Title">Welcome</h1>

Bây giờ, chúng ta hãy viết một bài kiểm tra!

using System.Threading.Tasks;
using Xunit;

public class WebServerTests : IClassFixture<WebServerFixture>
{
    private readonly WebServerFixture fixture;

    public WebServerTests(WebServerFixture fixture)
    {
        this.fixture = fixture;
    }

    [Fact]
    public async Task Page_title_equals_Welcome()
    {
        var page = await fixture.Browser.NewPageAsync();
        await page.GoToAsync(fixture.BaseUrl);
        
        var actual = await page.GetTextContentAsync(
            Element.ByName("Page Title")
        );
        
        Assert.Equal("Welcome", actual);
    }
}

public static class Element
{
    public static string ByName(string name)
        => $"[pw-name='{name}']";
}

Có một nhược điểm đối với các thử nghiệm gỡ lỗi và đó là trong khi chúng ta tạm dừng trình gỡ lỗi trên một điểm ngắt, máy chủ của chúng ta không thể xử lý bất kỳ yêu cầu nào, điều này khiến việc thực hiện ứng dụng không thể thực hiện được nếu không có một vài dòng mã bổ sung.

var pause = true;
while (pause)
{
    Thread.Sleep(TimeSpan.FromSeconds(3));
}

Chúng ta sẽ cần sử dụng tính năng pause debuggerimmediate window để thay đổi giá trị của pauseđể tiếp tục thử nghiệm của chúng ta.

Chạy thử nghiệm, chúng ta thấy rằng quá trình thực thi diễn ra trong 524msvà thời gian đó bao gồm việc khởi động Playwright và ứng dụng web ASP.NET Core của chúng ta. Các thử nghiệm bổ sung trong bộ thử nghiệm của chúng ta sẽ được hưởng lợi từ đối tượng được chia sẻ của WebServerFixture.

Cách sử dụng Playwright với ASP.NET Core và XUnit

Phần kết luận

Playwright là một thư viện end-to-end test (e2e test) gọn gàng với các lợi ích dành cho các nhà phát triển ASP.NET Core. Playwright có các API bổ sung cho phép chúng ta chụp ảnh màn hình, thực hiện giao diện người dùng và hơn thế nữa.

Với bài kiểm tra cố định trong bài viết này, các nhà phát triển có thể viết các bài kiểm tra e2e test chạy bên trong đường ống CI/CD, giúp mọi người thêm tin tưởng rằng hành vi của ứng dụng là có chủ đích.

Tôi hy vọng bạn thấy bài viết này hữu ích và như mọi khi, cảm ơn bạn đã đọc.

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *