Have a single page application sitting on top of an ASP.NET Core project and want to run end-to-end tests on any pull request? In this post we’ll start with nothing and finish with end-to-end tests using Playwright and Github Actions.

Getting started

You can follow along here to build this functionality from nothing, or, you can see the completed source here. Starting from nothing, we’ll create a React project and an XUnit project to run our tests:

dotnet new react -o Web
dotnet new xunit -o Test

We need a couple more packages in the test project so:

cd Test
dotnet add package PlaywrightSharp --version 0.162.1
dotnet add package Shouldly --version 4.0.1

Playwright for .NET is a .NET library that lets us automate browsers and Shouldly is my favorite assertion library.

Now we’ll go back to the root install Tye locally. Tye will let us quickly bring up our Web project in a container. The tool manifest will let us run dotnet tool restore so we can reinstall Tye anywhere, for example in a Github Action.

cd..
dotnet new tool-manifest
dotnet tool install Microsoft.Tye --version "0.5.0-alpha.20555.1"

Tye will bring up our Web project on a random port, but we want it to be the same so the tests can always find it. Create a file called tye.yaml and stick this in it:

services:
- name: Web
  project: Web/Web.csproj
  bindings:
  - port: 5000

We can test the Web project by starting it with Tye and browsing to http://localhost:8000. Doing so shows us our Web project is running on http://localhost:5000.

dotnet tye run

Writing the test

In our test, we’ll browse to the Counter component, click it three times, and assert that the count has updated to the value 3. Find Counter.js and update this line…

<p aria-live="polite">Current count: <strong>{this.state.currentCount}</strong></p>

…to include an id we can use to select later:

<p aria-live="polite">Current count: <strong id='count'>{this.state.currentCount}</strong></p>

Now we can write our test. Change the contents of UnitTest1.cs to the following:

using System;
using System.Threading.Tasks;
using PlaywrightSharp;
using Shouldly;
using Xunit;

namespace Test
{
    public class UnitTest1
    {
        [Fact]
        public async Task Test1()
        {
            using var playwright = await Playwright.CreateAsync();
            await using var browser = await playwright.Chromium.LaunchAsync();
            var page = await browser.NewPageAsync();
            await page.GoToAsync("http://0.0.0.0:5000");
            await page.ClickAsync("text='Counter'");
            await page.ClickAsync("text='Increment'");
            await page.ClickAsync("text='Increment'");
            await page.ClickAsync("text='Increment'");
            var count = await page.GetTextContentAsync("#count");
            count.ShouldBe("3");
        }
    }
}

We should now be able to dotnet tye run Web from the root, bringing up our Web project, and in the Test folder we can run dotnet test. The result should be one passing test! If the test doesn’t pass, we can change our test to display the browser for us by using .LaunchAsync(headless: false). In the future we could control such behavior with environment variables as mentioned in this prior post.

Building the action

At this point we should push all our code to a git repository and we can start on the workflow that will be triggered on pull request. Add the following to .github/workflows/end2end.yml. I’ve commented the steps here to explain what’s happening.

name: End-to-end Tests

# only triggers on pull requests targeting the main branch
on:
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-16.04

    steps:
        # grab our code
      - name: Checkout git repository
        uses: actions/checkout@v2

        # ensure the correct version of .net core is installed
      - name: Setup .NET Core
        uses: actions/setup-dotnet@v1
        with:
          dotnet-version: 3.1.301

        # ensure the correct version of node is installed
      - name: Setup Node
        uses: actions/setup-node@v1
        with:
          node-version: 12

        # this step restores tye for us
      - name: Install dotnet tools
        run: dotnet tool restore
        working-directory: Web

        # builds our Web project into a docker container
      - name: Build
        run: dotnet tye build Web -v Debug

        # starting the container we just built in detached mode
      - name: Start container
        run: docker run -d -p 5000:80 web:1.0.0

        # executes tests against the running container
      - name: Run end-to-end tests
        run: dotnet test
        working-directory: Test

Conclusion

In a single Tye command we’ve restored our .NET and npm dependencies, built and published the Web project and copied the result into a container image. Once we start the container we can run our tests as part of our workflow where Playwright will ensure any needed Chrome dependencies are installed. You can see the all the source here and the result of the actions running here.