Automating Testing with NSwag, GitHub Actions, and Playwright

We have explored the basics of OpenAPI in ASP.NET here on this blog, in the past. You can review them here, and here, and here. Today, we’re starting a new series that builds on those previous posts. In this series, we’ll look at how we can use this technology to automatically generate clients for our .NET APIs (we’ll be generating a TypeScript client) and then use that generated client in our automated testing scenarios. We’ll be writing those tests using Playwright. We’ll need to have our API running in an environment of some sort to run playwright tests against it. Docker can server as a good candidate for doing so; we’ll get into that in a later installment. Today, let’s lay out some foundational pieces that will eventually allow us to build our API, generate a TypeScript client for that API, run our automated (Playwright) tests against them, all done automatically in our Continuous Integration (CI) environment in GitHub.

The Goal: A Seamless Dev Loop

The “Holy Grail” of API development is ensuring your client code is always in sync with your server code. By automating this, we eliminate the “I forgot to update the client” bugs. Our workflow will look like this:

  1. Build the .NET Web API.
  2. Generate an OpenAPI spec and a TypeScript client using NSwag.
  3. Test the live API using Playwright and the newly generated client.

Step 1: Configure NSwag for MSBuild

To run NSwag in CI, we’ll move away from NSwagStudio and use the NSwag.MSBuild package. This allows us to trigger code generation during the build process.

First, add the package to your Web API project:

dotnet add package NSwag.MSBuild

Next, create an nswag.json configuration file in your project root. You can export this from NSwagStudio or create it manually. Ensure the output path points to a location where your Playwright tests can find it (e.g., ./Tests/api-client.ts).

Step 2: Setting up the GitHub Action

Now, let’s define the automation in .github/workflows/ci.yml. We need a workflow that sets up both .NET and Node.js environments.

name: Build and Test

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4

    - name: Setup .NET
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: '8.0.x'

    - name: Setup Node.js
      uses: actions/setup-node@v4
      with:
        node-version: '20'

    - name: Install Dependencies
      run: |
        dotnet restore
        npm install

    - name: Build and Generate Client
      run: |
        dotnet build
        # This command triggers NSwag to generate the TypeScript client
        dotnet nswag run nswag.json /runtime:Net80

    - name: Install Playwright Browsers
      run: npx playwright install --with-deps

    - name: Run Integration Tests
      run: |
        # Start the API in the background
        dotnet run --project MyApi.csproj & 
        # Run Playwright tests
        npx playwright test

Step 3: Using the Generated Client in Playwright

This is where the magic happens. Because NSwag generated a typed TypeScript client, your Playwright tests can now interact with your API using full IntelliSense and type safety.

In your Playwright test file:

import { test, expect } from '@playwright/test';
import { WeatherClient } from './api-client'; // The generated file

test('should fetch weather forecast', async ({ request }) => {
  // Use the generated client!
  const client = new WeatherClient('http://localhost:5000', { fetch: request.fetch.bind(request) });
  
  const forecast = await client.getWeatherForecast();
  
  expect(forecast.length).toBeGreaterThan(0);
  expect(forecast[0].summary).toBeDefined();
});

Why This Matters

By coupling NSwag with Playwright in your CI pipeline, you gain several massive advantages:

  1. Type-Safe Testing: If you change a property name in your C# DTO, the TypeScript client will update. If that change breaks your tests, the TypeScript compiler (or your test runner) will catch it immediately in CI.
  2. Contract Verification: Your integration tests aren’t just checking logic; they are verifying that the API contract (OpenAPI spec) is actually being fulfilled by the running service.
  3. Zero Manual Effort: Every time you push code, your documentation, your client library, and your tests are refreshed and validated.

Conclusion

Automating your API lifecycle with NSwag and GitHub Actions transforms OpenAPI from a simple documentation tool into a foundational piece of your testing infrastructure. It bridges the gap between the backend and the frontend (or the test suite), ensuring that your “source of truth” is always the code itself.

We’ll flesh out these building blocks further in upcoming posts. Happy Coding!

Leave a Comment

Your email address will not be published. Required fields are marked *