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.Sdk PlaywrightSharp.

<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 IPlaywright IBrowser. 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 524ms và 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.

Nếu Comdy hữu ích và giúp bạn tiết kiệm thời gian làm việc

Bạn có thể vui lòng đưa Comdy vào whitelist của trình chặn quảng cáo ❤️ để hỗ trợ chúng tôi trong việc trả tiền cho dịch vụ lưu trữ web để duy trì hoạt động của trang web.

Unit TestXUnitASP.NET Core
Bài Viết Liên Quan:
Tạo DataAttribute tùy chỉnh cho Theory của xUnit để tải dữ liệu từ file JSON
Trung Nguyen 22/04/2021
Tạo DataAttribute tùy chỉnh cho Theory của xUnit để tải dữ liệu từ file JSON

Trong bài viết này hướng dẫn bạn cách tạo lớp DataAttribute tùy chỉnh để tải dữ liệu cho bài kiểm tra unit test viết bằng xUnit của bạn.

Tạo các bài kiểm tra unit test được tham số hóa trong xUnit với Theory, InlineData, ClassData và MemberData
Trung Nguyen 18/04/2021
Tạo các bài kiểm tra unit test được tham số hóa trong xUnit với Theory, InlineData, ClassData và MemberData

Trong bài viết này, tôi sẽ hướng dẫn bạn tạo các bài kiểm tra unit test được tham số hóa trong xUnit với Theory, InlineData, ClassData và MemberData

Sử dụng Theory và InlineData của xUnit để viết unit test
Trung Nguyen 18/04/2021
Sử dụng Theory và InlineData của xUnit để viết unit test

Trong bài này, chúng ta sẽ sử dụng thuộc tính [Theory] và [InlineData] của xUnit để nhanh chóng viết một loạt các bài kiểm tra unit test.

Unit test cho controller trong ASP.NET Core với xUnit và Moq
Trung Nguyen 18/04/2021
Unit test cho controller trong ASP.NET Core với xUnit và Moq

Trong bài viết này, chúng ta sẽ viết một số bài kiểm tra unit test cho Controller của ứng dụng ASP.NET sử dụng xUnit và Moq.